summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/stream
diff options
context:
space:
mode:
authorMar0xy <marie@kaifa.ch>2023-12-04 02:10:51 +0100
committerMar0xy <marie@kaifa.ch>2023-12-04 02:10:51 +0100
commit2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310 (patch)
treeaa9801e261ed978d553cfc8cd80fee524d6496a6 /packages/backend/src/server/api/stream
parentupd: add additional check to visibility selector for boost (diff)
downloadsharkey-2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310.tar.gz
sharkey-2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310.tar.bz2
sharkey-2f99c7e9dc2e5e3ca06c9672a6ab4887eb094310.zip
add: Bubble timeline
Closes transfem-org/Sharkey#154
Diffstat (limited to 'packages/backend/src/server/api/stream')
-rw-r--r--packages/backend/src/server/api/stream/ChannelsService.ts3
-rw-r--r--packages/backend/src/server/api/stream/channels/bubble-timeline.ts124
2 files changed, 127 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts
index 8fd106c10c..f9f2f15aff 100644
--- a/packages/backend/src/server/api/stream/ChannelsService.ts
+++ b/packages/backend/src/server/api/stream/ChannelsService.ts
@@ -8,6 +8,7 @@ import { bindThis } from '@/decorators.js';
import { HybridTimelineChannelService } from './channels/hybrid-timeline.js';
import { LocalTimelineChannelService } from './channels/local-timeline.js';
import { HomeTimelineChannelService } from './channels/home-timeline.js';
+import { BubbleTimelineChannelService } from './channels/bubble-timeline.js';
import { GlobalTimelineChannelService } from './channels/global-timeline.js';
import { MainChannelService } from './channels/main.js';
import { ChannelChannelService } from './channels/channel.js';
@@ -28,6 +29,7 @@ export class ChannelsService {
private localTimelineChannelService: LocalTimelineChannelService,
private hybridTimelineChannelService: HybridTimelineChannelService,
private globalTimelineChannelService: GlobalTimelineChannelService,
+ private bubbleTimelineChannelService: BubbleTimelineChannelService,
private userListChannelService: UserListChannelService,
private hashtagChannelService: HashtagChannelService,
private roleTimelineChannelService: RoleTimelineChannelService,
@@ -48,6 +50,7 @@ export class ChannelsService {
case 'localTimeline': return this.localTimelineChannelService;
case 'hybridTimeline': return this.hybridTimelineChannelService;
case 'globalTimeline': return this.globalTimelineChannelService;
+ case 'bubbleTimeline': return this.bubbleTimelineChannelService;
case 'userList': return this.userListChannelService;
case 'hashtag': return this.hashtagChannelService;
case 'roleTimeline': return this.roleTimelineChannelService;
diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
new file mode 100644
index 0000000000..74d5c3ea4e
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
@@ -0,0 +1,124 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { checkWordMute } from '@/misc/check-word-mute.js';
+import { isInstanceMuted } from '@/misc/is-instance-muted.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import type { Packed } from '@/misc/json-schema.js';
+import { MetaService } from '@/core/MetaService.js';
+import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { bindThis } from '@/decorators.js';
+import { RoleService } from '@/core/RoleService.js';
+import type { MiMeta } from '@/models/Meta.js';
+import Channel from '../channel.js';
+
+class BubbleTimelineChannel extends Channel {
+ public readonly chName = 'bubbleTimeline';
+ public static shouldShare = false;
+ public static requireCredential = false;
+ private withRenotes: boolean;
+ private withFiles: boolean;
+ private withBots: boolean;
+ private instance: MiMeta;
+
+ constructor(
+ private metaService: MetaService,
+ private roleService: RoleService,
+ private noteEntityService: NoteEntityService,
+
+ id: string,
+ connection: Channel['connection'],
+ ) {
+ super(id, connection);
+ //this.onNote = this.onNote.bind(this);
+ }
+
+ @bindThis
+ public async init(params: any) {
+ const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
+ if (!policies.btlAvailable) return;
+
+ this.withRenotes = params.withRenotes ?? true;
+ this.withFiles = params.withFiles ?? false;
+ this.withBots = params.withBots ?? true;
+ this.instance = await this.metaService.fetch();
+
+ // Subscribe events
+ this.subscriber.on('notesStream', this.onNote);
+ }
+
+ @bindThis
+ private async onNote(note: Packed<'Note'>) {
+ if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
+ if (!this.withBots && note.user.isBot) return;
+
+ if (!(note.user.host != null && this.instance.bubbleInstances.includes(note.user.host) && note.visibility === 'public' )) return;
+
+ if (note.channelId != null) return;
+
+ // 関係ない返信は除外
+ if (note.reply && !this.following[note.userId]?.withReplies) {
+ const reply = note.reply;
+ // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
+ if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
+ }
+
+ if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
+
+ if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
+
+ // Ignore notes from instances the user has muted
+ if (isInstanceMuted(note, new Set<string>(this.userProfile?.mutedInstances ?? []))) return;
+
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
+
+ if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
+
+ if (this.user && note.renoteId && !note.text) {
+ if (note.renote && Object.keys(note.renote.reactions).length > 0) {
+ const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id);
+ note.renote.myReaction = myRenoteReaction;
+ }
+ }
+
+ this.connection.cacheNote(note);
+
+ this.send('note', note);
+ }
+
+ @bindThis
+ public dispose() {
+ // Unsubscribe events
+ this.subscriber.off('notesStream', this.onNote);
+ }
+}
+
+@Injectable()
+export class BubbleTimelineChannelService {
+ public readonly shouldShare = BubbleTimelineChannel.shouldShare;
+ public readonly requireCredential = BubbleTimelineChannel.requireCredential;
+
+ constructor(
+ private metaService: MetaService,
+ private roleService: RoleService,
+ private noteEntityService: NoteEntityService,
+ ) {
+ }
+
+ @bindThis
+ public create(id: string, connection: Channel['connection']): BubbleTimelineChannel {
+ return new BubbleTimelineChannel(
+ this.metaService,
+ this.roleService,
+ this.noteEntityService,
+ id,
+ connection,
+ );
+ }
+}