feat: 通知の受信設定を強化
This commit is contained in:
parent
4216a67462
commit
b9da1415a5
44 changed files with 676 additions and 503 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue