import { MastodonStream } from "../channel.js"; import { isUserRelated } from "../../../../../misc/is-user-related.js"; import { isInstanceMuted } from "../../../../../misc/is-instance-muted.js"; import { NoteConverter } from "../../converters/note.js"; import { NotificationConverter } from "../../converters/notification.js"; import { AnnouncementConverter } from "../../converters/announcement.js"; import isQuote from "../../../../../misc/is-quote.js"; export class MastodonStreamUser extends MastodonStream { static shouldShare = true; static requireCredential = true; static requiredScopes = [ 'read:statuses', 'read:notifications' ]; notificationsOnly; constructor(connection, name){ super(connection, name); this.notificationsOnly = name === "user:notification"; this.onNote = this.onNote.bind(this); this.onNoteEvent = this.onNoteEvent.bind(this); this.onUserEvent = this.onUserEvent.bind(this); this.onBroadcastEvent = this.onBroadcastEvent.bind(this); } get user() { return this.connection.user; } async init() { this.subscriber.on(`mainStream:${this.user.id}`, this.onUserEvent); if (!this.notificationsOnly) { this.subscriber.on("notesStream", this.onNote); this.subscriber.on("noteUpdatesStream", this.onNoteEvent); this.subscriber.on("broadcast", this.onBroadcastEvent); } } async onNote(note) { if (!await this.shouldProcessNote(note)) return; const encoded = await NoteConverter.encodeEvent(note, this.user, 'home'); this.connection.send(this.chName, "update", encoded); } async onNoteEvent(data) { const note = data.body; if (!await this.shouldProcessNote(note)) return; switch(data.type){ case "updated": const encoded = await NoteConverter.encodeEvent(note, this.user, 'home'); this.connection.send(this.chName, "status.update", encoded); break; case "deleted": this.connection.send(this.chName, "delete", note.id); break; default: break; } } async onUserEvent(data) { switch(data.type){ case "notification": const encoded = await NotificationConverter.encodeEvent(data.body.id, this.user, 'notifications'); if (encoded) this.connection.send(this.chName, "notification", encoded); break; default: break; } } async onBroadcastEvent(data) { switch(data.type){ case "announcementAdded": // This shouldn't be necessary but is for some reason data.body.createdAt = new Date(data.body.createdAt); this.connection.send(this.chName, "announcement", await AnnouncementConverter.encode(data.body, false)); break; case "announcementDeleted": this.connection.send(this.chName, "announcement.delete", data.body); break; default: break; } } async shouldProcessNote(note) { if (note.visibility === "hidden") return false; if (note.userId === this.user.id) return true; if (note.visibility === "specified") return note.visibleUserIds?.includes(this.user.id); if (note.channelId) return false; if (this.user.id !== note.userId && !this.following.has(note.userId)) return false; if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return false; if (isUserRelated(note, this.muting)) return false; if (isUserRelated(note, this.blocking)) return false; if (isUserRelated(note, this.hidden)) return false; if (note.renoteId !== null && !isQuote(note) && this.renoteMuting.has(note.userId)) return false; return true; } dispose() { this.subscriber.off(`mainStream:${this.user.id}`, this.onUserEvent); if (!this.notificationsOnly) { this.subscriber.off("notesStream", this.onNote); this.subscriber.off("noteUpdatesStream", this.onNoteEvent); this.subscriber.off("broadcast", this.onBroadcastEvent); } } }