summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/GlobalEventService.ts
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-09-29 11:29:54 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-09-29 11:29:54 +0900
commitb9da1415a50f6036350c6453d4a681b58bf17d1e (patch)
tree06614d5be85f027890a969548ca816e6a36c8c78 /packages/backend/src/core/GlobalEventService.ts
parent.js (diff)
downloadsharkey-b9da1415a50f6036350c6453d4a681b58bf17d1e.tar.gz
sharkey-b9da1415a50f6036350c6453d4a681b58bf17d1e.tar.bz2
sharkey-b9da1415a50f6036350c6453d4a681b58bf17d1e.zip
feat: 通知の受信設定を強化
Diffstat (limited to 'packages/backend/src/core/GlobalEventService.ts')
-rw-r--r--packages/backend/src/core/GlobalEventService.ts271
1 files changed, 249 insertions, 22 deletions
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 4bc4f54c21..b74fbbe584 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -5,27 +5,254 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
+import type { MiChannel } from '@/models/Channel.js';
import type { MiUser } from '@/models/User.js';
+import type { MiUserProfile } from '@/models/UserProfile.js';
import type { MiNote } from '@/models/Note.js';
-import type { MiUserList } from '@/models/UserList.js';
import type { MiAntenna } from '@/models/Antenna.js';
-import type {
- StreamChannels,
- AdminStreamTypes,
- AntennaStreamTypes,
- BroadcastTypes,
- DriveStreamTypes,
- InternalStreamTypes,
- MainStreamTypes,
- NoteStreamTypes,
- UserListStreamTypes,
- RoleTimelineStreamTypes,
-} from '@/server/api/stream/types.js';
+import type { MiDriveFile } from '@/models/DriveFile.js';
+import type { MiDriveFolder } from '@/models/DriveFolder.js';
+import type { MiUserList } from '@/models/UserList.js';
+import type { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
+import type { MiSignin } from '@/models/Signin.js';
+import type { MiPage } from '@/models/Page.js';
+import type { MiWebhook } from '@/models/Webhook.js';
+import type { MiMeta } from '@/models/Meta.js';
+import { MiRole, MiRoleAssignment } from '@/models/_.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
-import { MiRole } from '@/models/_.js';
+import { Serialized } from '@/types.js';
+import type Emitter from 'strict-event-emitter-types';
+import type { EventEmitter } from 'events';
+
+//#region Stream type-body definitions
+export interface BroadcastTypes {
+ emojiAdded: {
+ emoji: Packed<'EmojiDetailed'>;
+ };
+ emojiUpdated: {
+ emojis: Packed<'EmojiDetailed'>[];
+ };
+ emojiDeleted: {
+ emojis: {
+ id?: string;
+ name: string;
+ [other: string]: any;
+ }[];
+ };
+ announcementCreated: {
+ announcement: Packed<'Announcement'>;
+ };
+}
+
+export interface MainEventTypes {
+ notification: Packed<'Notification'>;
+ mention: Packed<'Note'>;
+ reply: Packed<'Note'>;
+ renote: Packed<'Note'>;
+ follow: Packed<'UserDetailedNotMe'>;
+ followed: Packed<'User'>;
+ unfollow: Packed<'User'>;
+ meUpdated: Packed<'User'>;
+ pageEvent: {
+ pageId: MiPage['id'];
+ event: string;
+ var: any;
+ userId: MiUser['id'];
+ user: Packed<'User'>;
+ };
+ urlUploadFinished: {
+ marker?: string | null;
+ file: Packed<'DriveFile'>;
+ };
+ readAllNotifications: undefined;
+ unreadNotification: Packed<'Notification'>;
+ unreadMention: MiNote['id'];
+ readAllUnreadMentions: undefined;
+ unreadSpecifiedNote: MiNote['id'];
+ readAllUnreadSpecifiedNotes: undefined;
+ readAllAntennas: undefined;
+ unreadAntenna: MiAntenna;
+ readAllAnnouncements: undefined;
+ myTokenRegenerated: undefined;
+ signin: MiSignin;
+ registryUpdated: {
+ scope?: string[];
+ key: string;
+ value: any | null;
+ };
+ driveFileCreated: Packed<'DriveFile'>;
+ readAntenna: MiAntenna;
+ receiveFollowRequest: Packed<'User'>;
+ announcementCreated: {
+ announcement: Packed<'Announcement'>;
+ };
+}
+
+export interface DriveEventTypes {
+ fileCreated: Packed<'DriveFile'>;
+ fileDeleted: MiDriveFile['id'];
+ fileUpdated: Packed<'DriveFile'>;
+ folderCreated: Packed<'DriveFolder'>;
+ folderDeleted: MiDriveFolder['id'];
+ folderUpdated: Packed<'DriveFolder'>;
+}
+
+export interface NoteEventTypes {
+ pollVoted: {
+ choice: number;
+ userId: MiUser['id'];
+ };
+ deleted: {
+ deletedAt: Date;
+ };
+ updated: {
+ cw: string | null;
+ text: string;
+ };
+ reacted: {
+ reaction: string;
+ emoji?: {
+ name: string;
+ url: string;
+ } | null;
+ userId: MiUser['id'];
+ };
+ unreacted: {
+ reaction: string;
+ userId: MiUser['id'];
+ };
+}
+type NoteStreamEventTypes = {
+ [key in keyof NoteEventTypes]: {
+ id: MiNote['id'];
+ body: NoteEventTypes[key];
+ };
+};
+
+export interface UserListEventTypes {
+ userAdded: Packed<'User'>;
+ userRemoved: Packed<'User'>;
+}
+
+export interface AntennaEventTypes {
+ note: MiNote;
+}
+
+export interface RoleTimelineEventTypes {
+ note: Packed<'Note'>;
+}
+
+export interface AdminEventTypes {
+ newAbuseUserReport: {
+ id: MiAbuseUserReport['id'];
+ targetUserId: MiUser['id'],
+ reporterId: MiUser['id'],
+ comment: string;
+ };
+}
+//#endregion
+
+// 辞書(interface or type)から{ type, body }ユニオンを定義
+// https://stackoverflow.com/questions/49311989/can-i-infer-the-type-of-a-value-using-extends-keyof-type
+// VS Codeの展開を防止するためにEvents型を定義
+type Events<T extends object> = { [K in keyof T]: { type: K; body: T[K]; } };
+type EventUnionFromDictionary<
+ T extends object,
+ U = Events<T>
+> = U[keyof U];
+
+type SerializedAll<T> = {
+ [K in keyof T]: Serialized<T[K]>;
+};
+
+export interface InternalEventTypes {
+ userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
+ userTokenRegenerated: { id: MiUser['id']; oldToken: string; newToken: string; };
+ remoteUserUpdated: { id: MiUser['id']; };
+ follow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
+ unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
+ blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
+ blockingDeleted: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
+ policiesUpdated: MiRole['policies'];
+ roleCreated: MiRole;
+ roleDeleted: MiRole;
+ roleUpdated: MiRole;
+ userRoleAssigned: MiRoleAssignment;
+ userRoleUnassigned: MiRoleAssignment;
+ webhookCreated: MiWebhook;
+ webhookDeleted: MiWebhook;
+ webhookUpdated: MiWebhook;
+ antennaCreated: MiAntenna;
+ antennaDeleted: MiAntenna;
+ antennaUpdated: MiAntenna;
+ metaUpdated: MiMeta;
+ followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
+ unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
+ updateUserProfile: MiUserProfile;
+ mute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
+ unmute: { muterId: MiUser['id']; muteeId: MiUser['id']; };
+ userListMemberAdded: { userListId: MiUserList['id']; memberId: MiUser['id']; };
+ userListMemberRemoved: { userListId: MiUserList['id']; memberId: MiUser['id']; };
+}
+
+// name/messages(spec) pairs dictionary
+export type GlobalEvents = {
+ internal: {
+ name: 'internal';
+ payload: EventUnionFromDictionary<SerializedAll<InternalEventTypes>>;
+ };
+ broadcast: {
+ name: 'broadcast';
+ payload: EventUnionFromDictionary<SerializedAll<BroadcastTypes>>;
+ };
+ main: {
+ name: `mainStream:${MiUser['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<MainEventTypes>>;
+ };
+ drive: {
+ name: `driveStream:${MiUser['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<DriveEventTypes>>;
+ };
+ note: {
+ name: `noteStream:${MiNote['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<NoteStreamEventTypes>>;
+ };
+ userList: {
+ name: `userListStream:${MiUserList['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<UserListEventTypes>>;
+ };
+ roleTimeline: {
+ name: `roleTimelineStream:${MiRole['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<RoleTimelineEventTypes>>;
+ };
+ antenna: {
+ name: `antennaStream:${MiAntenna['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<AntennaEventTypes>>;
+ };
+ admin: {
+ name: `adminStream:${MiUser['id']}`;
+ payload: EventUnionFromDictionary<SerializedAll<AdminEventTypes>>;
+ };
+ notes: {
+ name: 'notesStream';
+ payload: Serialized<Packed<'Note'>>;
+ };
+};
+
+// API event definitions
+// ストリームごとのEmitterの辞書を用意
+type EventEmitterDictionary = { [x in keyof GlobalEvents]: Emitter.default<EventEmitter, { [y in GlobalEvents[x]['name']]: (e: GlobalEvents[x]['payload']) => void }> };
+// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
+type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
+// Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする
+export type StreamEventEmitter = UnionToIntersection<EventEmitterDictionary[keyof GlobalEvents]>;
+// { [y in name]: (e: spec) => void }をまとめてその交差型をEmitterにかけるとts(2590)にひっかかる
+
+// provide stream channels union
+export type StreamChannels = GlobalEvents[keyof GlobalEvents]['name'];
@Injectable()
export class GlobalEventService {
@@ -51,7 +278,7 @@ export class GlobalEventService {
}
@bindThis
- public publishInternalEvent<K extends keyof InternalStreamTypes>(type: K, value?: InternalStreamTypes[K]): void {
+ public publishInternalEvent<K extends keyof InternalEventTypes>(type: K, value?: InternalEventTypes[K]): void {
this.publish('internal', type, typeof value === 'undefined' ? null : value);
}
@@ -61,17 +288,17 @@ export class GlobalEventService {
}
@bindThis
- public publishMainStream<K extends keyof MainStreamTypes>(userId: MiUser['id'], type: K, value?: MainStreamTypes[K]): void {
+ public publishMainStream<K extends keyof MainEventTypes>(userId: MiUser['id'], type: K, value?: MainEventTypes[K]): void {
this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishDriveStream<K extends keyof DriveStreamTypes>(userId: MiUser['id'], type: K, value?: DriveStreamTypes[K]): void {
+ public publishDriveStream<K extends keyof DriveEventTypes>(userId: MiUser['id'], type: K, value?: DriveEventTypes[K]): void {
this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishNoteStream<K extends keyof NoteStreamTypes>(noteId: MiNote['id'], type: K, value?: NoteStreamTypes[K]): void {
+ public publishNoteStream<K extends keyof NoteEventTypes>(noteId: MiNote['id'], type: K, value?: NoteEventTypes[K]): void {
this.publish(`noteStream:${noteId}`, type, {
id: noteId,
body: value,
@@ -79,17 +306,17 @@ export class GlobalEventService {
}
@bindThis
- public publishUserListStream<K extends keyof UserListStreamTypes>(listId: MiUserList['id'], type: K, value?: UserListStreamTypes[K]): void {
+ public publishUserListStream<K extends keyof UserListEventTypes>(listId: MiUserList['id'], type: K, value?: UserListEventTypes[K]): void {
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishAntennaStream<K extends keyof AntennaStreamTypes>(antennaId: MiAntenna['id'], type: K, value?: AntennaStreamTypes[K]): void {
+ public publishAntennaStream<K extends keyof AntennaEventTypes>(antennaId: MiAntenna['id'], type: K, value?: AntennaEventTypes[K]): void {
this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
- public publishRoleTimelineStream<K extends keyof RoleTimelineStreamTypes>(roleId: MiRole['id'], type: K, value?: RoleTimelineStreamTypes[K]): void {
+ public publishRoleTimelineStream<K extends keyof RoleTimelineEventTypes>(roleId: MiRole['id'], type: K, value?: RoleTimelineEventTypes[K]): void {
this.publish(`roleTimelineStream:${roleId}`, type, typeof value === 'undefined' ? null : value);
}
@@ -99,7 +326,7 @@ export class GlobalEventService {
}
@bindThis
- public publishAdminStream<K extends keyof AdminStreamTypes>(userId: MiUser['id'], type: K, value?: AdminStreamTypes[K]): void {
+ public publishAdminStream<K extends keyof AdminEventTypes>(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
}