feat: improve tl performance (#11946)
* wip * wip * wip * wip * wip * wip * Update NoteCreateService.ts * wip * wip * wip * wip * Update NoteCreateService.ts * wip * Update NoteCreateService.ts * wip * Update user-notes.ts * wip * wip * wip * Update NoteCreateService.ts * wip * Update timeline.ts * Update timeline.ts * Update timeline.ts * Update timeline.ts * Update timeline.ts * wip * Update timelines.ts * Update timelines.ts * Update timelines.ts * wip * wip * wip * Update timelines.ts * Update misskey-js.api.md * Update timelines.ts * Update timelines.ts * wip * wip * wip * Update timelines.ts * wip * Update timelines.ts * wip * test * Update activitypub.ts * refactor: UserListJoining -> UserListMembership * Update NoteCreateService.ts * wip
This commit is contained in:
@@ -11,7 +11,7 @@ import type { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import type { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { MiUserProfile } from '@/models/_.js';
|
||||
import { MiFollowing, MiUserProfile } from '@/models/_.js';
|
||||
import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { ChannelsService } from './ChannelsService.js';
|
||||
import type { EventEmitter } from 'events';
|
||||
@@ -30,7 +30,7 @@ export default class Connection {
|
||||
private subscribingNotes: any = {};
|
||||
private cachedNotes: Packed<'Note'>[] = [];
|
||||
public userProfile: MiUserProfile | null = null;
|
||||
public following: Set<string> = new Set();
|
||||
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
|
||||
public followingChannels: Set<string> = new Set();
|
||||
public userIdsWhoMeMuting: Set<string> = new Set();
|
||||
public userIdsWhoBlockingMe: Set<string> = new Set();
|
||||
|
@@ -18,7 +18,6 @@ class GlobalTimelineChannel extends Channel {
|
||||
public readonly chName = 'globalTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = false;
|
||||
private withReplies: boolean;
|
||||
private withRenotes: boolean;
|
||||
|
||||
constructor(
|
||||
@@ -38,7 +37,6 @@ class GlobalTimelineChannel extends Channel {
|
||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||
if (!policies.gtlAvailable) return;
|
||||
|
||||
this.withReplies = params.withReplies ?? false;
|
||||
this.withRenotes = params.withRenotes ?? true;
|
||||
|
||||
// Subscribe events
|
||||
@@ -64,7 +62,7 @@ class GlobalTimelineChannel extends Channel {
|
||||
}
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
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;
|
||||
@@ -82,13 +80,6 @@ class GlobalTimelineChannel extends Channel {
|
||||
|
||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||
|
||||
this.connection.cacheNote(note);
|
||||
|
||||
this.send('note', note);
|
||||
|
@@ -16,7 +16,6 @@ class HomeTimelineChannel extends Channel {
|
||||
public readonly chName = 'homeTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
private withReplies: boolean;
|
||||
private withRenotes: boolean;
|
||||
|
||||
constructor(
|
||||
@@ -31,7 +30,6 @@ class HomeTimelineChannel extends Channel {
|
||||
|
||||
@bindThis
|
||||
public async init(params: any) {
|
||||
this.withReplies = params.withReplies ?? false;
|
||||
this.withRenotes = params.withRenotes ?? true;
|
||||
|
||||
this.subscriber.on('notesStream', this.onNote);
|
||||
@@ -43,7 +41,7 @@ class HomeTimelineChannel extends Channel {
|
||||
if (!this.followingChannels.has(note.channelId)) return;
|
||||
} else {
|
||||
// その投稿のユーザーをフォローしていなかったら弾く
|
||||
if ((this.user!.id !== note.userId) && !this.following.has(note.userId)) return;
|
||||
if ((this.user!.id !== note.userId) && !Object.hasOwn(this.following, note.userId)) return;
|
||||
}
|
||||
|
||||
// Ignore notes from instances the user has muted
|
||||
@@ -73,7 +71,7 @@ class HomeTimelineChannel extends Channel {
|
||||
}
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
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;
|
||||
@@ -88,13 +86,6 @@ class HomeTimelineChannel extends Channel {
|
||||
|
||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (await checkWordMute(note, this.user, this.userProfile!.mutedWords)) return;
|
||||
|
||||
this.connection.cacheNote(note);
|
||||
|
||||
this.send('note', note);
|
||||
|
@@ -18,7 +18,6 @@ class HybridTimelineChannel extends Channel {
|
||||
public readonly chName = 'hybridTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = true;
|
||||
private withReplies: boolean;
|
||||
private withRenotes: boolean;
|
||||
|
||||
constructor(
|
||||
@@ -38,7 +37,6 @@ class HybridTimelineChannel extends Channel {
|
||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||
if (!policies.ltlAvailable) return;
|
||||
|
||||
this.withReplies = params.withReplies ?? false;
|
||||
this.withRenotes = params.withRenotes ?? true;
|
||||
|
||||
// Subscribe events
|
||||
@@ -53,7 +51,7 @@ class HybridTimelineChannel extends Channel {
|
||||
// フォローしているチャンネルの投稿 の場合だけ
|
||||
if (!(
|
||||
(note.channelId == null && this.user!.id === note.userId) ||
|
||||
(note.channelId == null && this.following.has(note.userId)) ||
|
||||
(note.channelId == null && Object.hasOwn(this.following, note.userId)) ||
|
||||
(note.channelId == null && (note.user.host == null && note.visibility === 'public')) ||
|
||||
(note.channelId != null && this.followingChannels.has(note.channelId))
|
||||
)) return;
|
||||
@@ -85,7 +83,7 @@ class HybridTimelineChannel extends Channel {
|
||||
if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.withReplies) {
|
||||
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;
|
||||
@@ -100,13 +98,6 @@ class HybridTimelineChannel extends Channel {
|
||||
|
||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||
|
||||
this.connection.cacheNote(note);
|
||||
|
||||
this.send('note', note);
|
||||
|
@@ -17,7 +17,6 @@ class LocalTimelineChannel extends Channel {
|
||||
public readonly chName = 'localTimeline';
|
||||
public static shouldShare = true;
|
||||
public static requireCredential = false;
|
||||
private withReplies: boolean;
|
||||
private withRenotes: boolean;
|
||||
|
||||
constructor(
|
||||
@@ -37,7 +36,6 @@ class LocalTimelineChannel extends Channel {
|
||||
const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
|
||||
if (!policies.ltlAvailable) return;
|
||||
|
||||
this.withReplies = params.withReplies ?? false;
|
||||
this.withRenotes = params.withRenotes ?? true;
|
||||
|
||||
// Subscribe events
|
||||
@@ -64,7 +62,7 @@ class LocalTimelineChannel extends Channel {
|
||||
}
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && this.user && !this.withReplies) {
|
||||
if (note.reply && this.user && !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;
|
||||
@@ -79,13 +77,6 @@ class LocalTimelineChannel extends Channel {
|
||||
|
||||
if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
|
||||
|
||||
// 流れてきたNoteがミュートすべきNoteだったら無視する
|
||||
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
|
||||
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
|
||||
// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
|
||||
// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
|
||||
if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
|
||||
|
||||
this.connection.cacheNote(note);
|
||||
|
||||
this.send('note', note);
|
||||
|
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js';
|
||||
import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
@@ -18,12 +18,12 @@ class UserListChannel extends Channel {
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = false;
|
||||
private listId: string;
|
||||
public listUsers: MiUser['id'][] = [];
|
||||
public membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
|
||||
private listUsersClock: NodeJS.Timeout;
|
||||
|
||||
constructor(
|
||||
private userListsRepository: UserListsRepository,
|
||||
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||
private userListMembershipsRepository: UserListMembershipsRepository,
|
||||
private noteEntityService: NoteEntityService,
|
||||
|
||||
id: string,
|
||||
@@ -58,19 +58,25 @@ class UserListChannel extends Channel {
|
||||
|
||||
@bindThis
|
||||
private async updateListUsers() {
|
||||
const users = await this.userListJoiningsRepository.find({
|
||||
const memberships = await this.userListMembershipsRepository.find({
|
||||
where: {
|
||||
userListId: this.listId,
|
||||
},
|
||||
select: ['userId'],
|
||||
});
|
||||
|
||||
this.listUsers = users.map(x => x.userId);
|
||||
const membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
|
||||
for (const membership of memberships) {
|
||||
membershipsMap[membership.userId] = {
|
||||
withReplies: membership.withReplies,
|
||||
};
|
||||
}
|
||||
this.membershipsMap = membershipsMap;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onNote(note: Packed<'Note'>) {
|
||||
if (!this.listUsers.includes(note.userId)) return;
|
||||
if (!Object.hasOwn(this.membershipsMap, note.userId)) return;
|
||||
|
||||
if (['followers', 'specified'].includes(note.visibility)) {
|
||||
note = await this.noteEntityService.pack(note.id, this.user, {
|
||||
@@ -95,6 +101,13 @@ class UserListChannel extends Channel {
|
||||
}
|
||||
}
|
||||
|
||||
// 関係ない返信は除外
|
||||
if (note.reply && !this.membershipsMap[note.userId]?.withReplies) {
|
||||
const reply = note.reply;
|
||||
// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
|
||||
if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
|
||||
}
|
||||
|
||||
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
|
||||
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
|
||||
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
|
||||
@@ -124,8 +137,8 @@ export class UserListChannelService {
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userListJoiningsRepository)
|
||||
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||
@Inject(DI.userListMembershipsRepository)
|
||||
private userListMembershipsRepository: UserListMembershipsRepository,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
) {
|
||||
@@ -135,7 +148,7 @@ export class UserListChannelService {
|
||||
public create(id: string, connection: Channel['connection']): UserListChannel {
|
||||
return new UserListChannel(
|
||||
this.userListsRepository,
|
||||
this.userListJoiningsRepository,
|
||||
this.userListMembershipsRepository,
|
||||
this.noteEntityService,
|
||||
id,
|
||||
connection,
|
||||
|
Reference in New Issue
Block a user