Merge tag '2023.10.0' into merge-upstream

This commit is contained in:
riku6460
2023-10-10 21:22:31 +09:00
226 changed files with 5462 additions and 2914 deletions

View File

@@ -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();

View File

@@ -16,9 +16,8 @@ import Channel from '../channel.js';
class GlobalTimelineChannel extends Channel {
public readonly chName = 'globalTimeline';
public static shouldShare = true;
public static shouldShare = false;
public static requireCredential = false;
private withReplies: boolean;
private withRenotes: boolean;
private withFiles: boolean;
@@ -39,7 +38,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;
this.withFiles = params.withFiles ?? false;
@@ -49,6 +47,8 @@ class GlobalTimelineChannel extends Channel {
@bindThis
private async onNote(note: Packed<'Note'>) {
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
if (note.visibility !== 'public') return;
if (note.channelId != null) return;
@@ -69,7 +69,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;
@@ -87,13 +87,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);

View File

@@ -14,9 +14,8 @@ import Channel from '../channel.js';
class HomeTimelineChannel extends Channel {
public readonly chName = 'homeTimeline';
public static shouldShare = true;
public static shouldShare = false;
public static requireCredential = true;
private withReplies: boolean;
private withRenotes: boolean;
private withFiles: boolean;
@@ -32,7 +31,6 @@ class HomeTimelineChannel extends Channel {
@bindThis
public async init(params: any) {
this.withReplies = params.withReplies ?? false;
this.withRenotes = params.withRenotes ?? true;
this.withFiles = params.withFiles ?? false;
@@ -41,11 +39,13 @@ class HomeTimelineChannel extends Channel {
@bindThis
private async onNote(note: Packed<'Note'>) {
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
if (note.channelId) {
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
@@ -78,7 +78,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;
@@ -93,13 +93,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);

View File

@@ -16,9 +16,8 @@ import Channel from '../channel.js';
class HybridTimelineChannel extends Channel {
public readonly chName = 'hybridTimeline';
public static shouldShare = true;
public static shouldShare = false;
public static requireCredential = true;
private withReplies: boolean;
private withRenotes: boolean;
private withFiles: boolean;
@@ -39,9 +38,8 @@ 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;
this.withReplies = params.withFiles ?? false;
this.withFiles = params.withFiles ?? false;
// Subscribe events
this.subscriber.on('notesStream', this.onNote);
@@ -49,13 +47,15 @@ class HybridTimelineChannel extends Channel {
@bindThis
private async onNote(note: Packed<'Note'>) {
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
// チャンネルの投稿ではなく、自分自身の投稿 または
// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
// チャンネルの投稿ではなく、全体公開のローカルの投稿 または
// フォローしているチャンネルの投稿 の場合だけ
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;
@@ -90,7 +90,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;
@@ -105,13 +105,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);

View File

@@ -15,9 +15,8 @@ import Channel from '../channel.js';
class LocalTimelineChannel extends Channel {
public readonly chName = 'localTimeline';
public static shouldShare = true;
public static shouldShare = false;
public static requireCredential = false;
private withReplies: boolean;
private withRenotes: boolean;
private withFiles: boolean;
@@ -38,7 +37,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;
this.withFiles = params.withFiles ?? false;
@@ -48,6 +46,8 @@ class LocalTimelineChannel extends Channel {
@bindThis
private async onNote(note: Packed<'Note'>) {
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
if (note.user.host !== null) return;
if (note.visibility !== 'public') return;
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
@@ -69,7 +69,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;
@@ -84,13 +84,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);

View File

@@ -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,13 @@ class UserListChannel extends Channel {
public static shouldShare = false;
public static requireCredential = false;
private listId: string;
public listUsers: MiUser['id'][] = [];
private membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
private listUsersClock: NodeJS.Timeout;
private withFiles: boolean;
constructor(
private userListsRepository: UserListsRepository,
private userListJoiningsRepository: UserListJoiningsRepository,
private userListMembershipsRepository: UserListMembershipsRepository,
private noteEntityService: NoteEntityService,
id: string,
@@ -37,6 +38,7 @@ class UserListChannel extends Channel {
@bindThis
public async init(params: any) {
this.listId = params.listId as string;
this.withFiles = params.withFiles ?? false;
// Check existence and owner
const listExist = await this.userListsRepository.exist({
@@ -58,19 +60,27 @@ 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 (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) 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 +105,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 +141,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 +152,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,