Merge 05156ca854 into 65e51463c8
This commit is contained in:
commit
e5989a53ca
|
|
@ -17,6 +17,7 @@ import { DebounceLoader } from '@/misc/loader.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
import { shouldHideNoteByTime } from '@/misc/should-hide-note-by-time.js';
|
||||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||||
import type { ReactionService } from '../ReactionService.js';
|
import type { ReactionService } from '../ReactionService.js';
|
||||||
|
|
@ -101,6 +102,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
//private reactionService: ReactionService,
|
//private reactionService: ReactionService,
|
||||||
//private reactionsBufferingService: ReactionsBufferingService,
|
//private reactionsBufferingService: ReactionsBufferingService,
|
||||||
//private idService: IdService,
|
//private idService: IdService,
|
||||||
|
private cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -376,7 +378,46 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
: this.meta.enableReactionsBuffering
|
: this.meta.enableReactionsBuffering
|
||||||
? await this.reactionsBufferingService.get(note.id)
|
? await this.reactionsBufferingService.get(note.id)
|
||||||
: { deltas: {}, pairs: [] };
|
: { deltas: {}, pairs: [] };
|
||||||
const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {}));
|
|
||||||
|
let reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {}));
|
||||||
|
if (meId) {
|
||||||
|
// ログインユーザーがいる場合のみ、ブロック/ミュートユーザーを除外して集計し直す
|
||||||
|
// 1. ブロック・ミュートリストを取得
|
||||||
|
const [mutedIds, blockedIds] = await Promise.all([
|
||||||
|
this.cacheService.userMutingsCache.fetch(meId),
|
||||||
|
this.cacheService.userBlockingCache.fetch(meId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 2. DBとバッファから、フィルタリングに必要な全ユーザー/リアクションペアを取得
|
||||||
|
// DBからの全リアクションレコードを取得
|
||||||
|
const dbReactions = await this.noteReactionsRepository.findBy({ noteId: note.id });
|
||||||
|
|
||||||
|
// バッファリングされたペアを追加
|
||||||
|
const bufferedPairs = bufferedReactions.pairs ?? []; // pairs: ([MiUser['id'], string])[]
|
||||||
|
|
||||||
|
// 3. フィルタリングして再集計
|
||||||
|
const filteredReactions: Record<string, number> = {};
|
||||||
|
|
||||||
|
// 3a. DBからのリアクションをフィルタリング
|
||||||
|
for (const reaction of dbReactions) {
|
||||||
|
const isBlockedOrMuted = blockedIds.has(reaction.userId) || mutedIds.has(reaction.userId);
|
||||||
|
if (!isBlockedOrMuted) {
|
||||||
|
const reactionName = this.reactionService.convertLegacyReaction(reaction.reaction);
|
||||||
|
filteredReactions[reactionName] = (filteredReactions[reactionName] || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3b. バッファからのリアクションをフィルタリング
|
||||||
|
for (const [userId, reactionName] of bufferedPairs) {
|
||||||
|
const isBlockedOrMuted = blockedIds.has(userId) || mutedIds.has(userId);
|
||||||
|
if (!isBlockedOrMuted) {
|
||||||
|
const normalizedReaction = this.reactionService.convertLegacyReaction(reactionName);
|
||||||
|
filteredReactions[normalizedReaction] = (filteredReactions[normalizedReaction] || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reactions = filteredReactions;
|
||||||
|
}
|
||||||
|
|
||||||
const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
|
const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
|
||||||
|
|
||||||
|
|
@ -600,7 +641,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async fetchDiffs(noteIds: MiNote['id'][]) {
|
public async fetchDiffs(noteIds: MiNote['id'][], meId: MiUser['id'] | null) {
|
||||||
if (noteIds.length === 0) return [];
|
if (noteIds.length === 0) return [];
|
||||||
|
|
||||||
const notes = await this.notesRepository.find({
|
const notes = await this.notesRepository.find({
|
||||||
|
|
@ -617,12 +658,43 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
|
|
||||||
const bufferedReactionsMap = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(noteIds) : null;
|
const bufferedReactionsMap = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(noteIds) : null;
|
||||||
|
|
||||||
const packings = notes.map(note => {
|
const packings = notes.map(async note => {
|
||||||
const bufferedReactions = bufferedReactionsMap?.get(note.id);
|
const bufferedReactions = bufferedReactionsMap?.get(note.id);
|
||||||
//const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
|
//const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
|
||||||
|
|
||||||
const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.deltas ?? {}));
|
let reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.deltas ?? {}));
|
||||||
|
|
||||||
|
if (meId) {
|
||||||
|
const [mutedIds, blockedIds] = await Promise.all([
|
||||||
|
this.cacheService.userMutingsCache.fetch(meId),
|
||||||
|
this.cacheService.userBlockingCache.fetch(meId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 2. DBとバッファから、フィルタリングに必要な全ユーザー/リアクションペアを取得
|
||||||
|
const dbReactions = await this.noteReactionsRepository.findBy({ noteId: note.id });
|
||||||
|
const bufferedPairs = bufferedReactions?.pairs ?? [];
|
||||||
|
|
||||||
|
const filteredReactions: Record<string, number> = {};
|
||||||
|
|
||||||
|
// 3a. DBからのリアクションをフィルタリング
|
||||||
|
for (const reaction of dbReactions) {
|
||||||
|
const isBlockedOrMuted = blockedIds.has(reaction.userId) || mutedIds.has(reaction.userId);
|
||||||
|
if (!isBlockedOrMuted) {
|
||||||
|
const reactionName = this.reactionService.convertLegacyReaction(reaction.reaction);
|
||||||
|
filteredReactions[reactionName] = (filteredReactions[reactionName] || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3b. バッファからのリアクションをフィルタリング
|
||||||
|
for (const [userId, reactionName] of bufferedPairs) {
|
||||||
|
const isBlockedOrMuted = blockedIds.has(userId) || mutedIds.has(userId);
|
||||||
|
if (!isBlockedOrMuted) {
|
||||||
|
const normalizedReaction = this.reactionService.convertLegacyReaction(reactionName);
|
||||||
|
filteredReactions[normalizedReaction] = (filteredReactions[normalizedReaction] || 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reactions = filteredReactions;
|
||||||
|
}
|
||||||
const reactionEmojiNames = Object.keys(reactions)
|
const reactionEmojiNames = Object.keys(reactions)
|
||||||
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
|
||||||
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ import type { NoteReactionsRepository } from '@/models/_.js';
|
||||||
import type { Packed } from '@/misc/json-schema.js';
|
import type { Packed } from '@/misc/json-schema.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import type { OnModuleInit } from '@nestjs/common';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { } from '@/models/Blocking.js';
|
|
||||||
import type { MiUser } from '@/models/User.js';
|
import type { MiUser } from '@/models/User.js';
|
||||||
import type { MiNoteReaction } from '@/models/NoteReaction.js';
|
import type { MiNoteReaction } from '@/models/NoteReaction.js';
|
||||||
|
import type { OnModuleInit } from '@nestjs/common';
|
||||||
|
import type { } from '@/models/Blocking.js';
|
||||||
import type { ReactionService } from '../ReactionService.js';
|
import type { ReactionService } from '../ReactionService.js';
|
||||||
import type { UserEntityService } from './UserEntityService.js';
|
import type { UserEntityService } from './UserEntityService.js';
|
||||||
import type { NoteEntityService } from './NoteEntityService.js';
|
import type { NoteEntityService } from './NoteEntityService.js';
|
||||||
|
|
@ -24,6 +25,7 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
private noteEntityService: NoteEntityService;
|
private noteEntityService: NoteEntityService;
|
||||||
private reactionService: ReactionService;
|
private reactionService: ReactionService;
|
||||||
private idService: IdService;
|
private idService: IdService;
|
||||||
|
private cacheService: CacheService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private moduleRef: ModuleRef,
|
private moduleRef: ModuleRef,
|
||||||
|
|
@ -35,6 +37,7 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
//private noteEntityService: NoteEntityService,
|
//private noteEntityService: NoteEntityService,
|
||||||
//private reactionService: ReactionService,
|
//private reactionService: ReactionService,
|
||||||
//private idService: IdService,
|
//private idService: IdService,
|
||||||
|
//private cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +46,7 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
||||||
this.reactionService = this.moduleRef.get('ReactionService');
|
this.reactionService = this.moduleRef.get('ReactionService');
|
||||||
this.idService = this.moduleRef.get('IdService');
|
this.idService = this.moduleRef.get('IdService');
|
||||||
|
this.cacheService = this.moduleRef.get('CacheService');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
@ -75,10 +79,30 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
): Promise<Packed<'NoteReaction'>[]> {
|
): Promise<Packed<'NoteReaction'>[]> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({
|
||||||
}, options);
|
}, options);
|
||||||
const _users = reactions.map(({ user, userId }) => user ?? userId);
|
const meId = me ? me.id : null;
|
||||||
|
|
||||||
|
// ログインユーザーがいる場合のみ、ブロック・ミュートリストを取得
|
||||||
|
let muted: Set<string> | null = null;
|
||||||
|
let blocked: Set<string> | null = null;
|
||||||
|
let newReactions: MiNoteReaction[] = reactions;
|
||||||
|
|
||||||
|
if (meId) {
|
||||||
|
[blocked, muted] = await Promise.all([
|
||||||
|
this.cacheService.userBlockingCache.fetch(meId), // 自分がブロックしたユーザー
|
||||||
|
this.cacheService.userMutingsCache.fetch(meId), // 自分がミュートしたユーザー
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filteredReactions = reactions.filter(reaction => {
|
||||||
|
const isBlockedOrMuted = blocked!.has(reaction.userId) || muted!.has(reaction.userId);
|
||||||
|
return !isBlockedOrMuted;
|
||||||
|
});
|
||||||
|
|
||||||
|
newReactions = filteredReactions;
|
||||||
|
}
|
||||||
|
const _users = newReactions.map(({ user, userId }) => user ?? userId);
|
||||||
const _userMap = await this.userEntityService.packMany(_users, me)
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
.then(users => new Map(users.map(u => [u.id, u])));
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
return Promise.all(newReactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
@ -94,6 +118,22 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src });
|
const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src });
|
||||||
|
const meId = me ? me.id : null;
|
||||||
|
|
||||||
|
// ログインユーザーがいる場合のみ、ブロック・ミュートリストを取得
|
||||||
|
let muted: Set<string> | null = null;
|
||||||
|
let blocked: Set<string> | null = null;
|
||||||
|
|
||||||
|
if (meId) {
|
||||||
|
[blocked, muted] = await Promise.all([
|
||||||
|
this.cacheService.userBlockingCache.fetch(meId), // 自分がブロックしたユーザー
|
||||||
|
this.cacheService.userMutingsCache.fetch(meId), // 自分がミュートしたユーザー
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (reaction.userId && (blocked?.has(reaction.userId) || muted?.has(reaction.userId))) {
|
||||||
|
return {} as any; // ミュート・ブロックされている場合は空オブジェクトを返す
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: reaction.id,
|
id: reaction.id,
|
||||||
|
|
@ -110,11 +150,24 @@ export class NoteReactionEntityService implements OnModuleInit {
|
||||||
me?: { id: MiUser['id'] } | null | undefined,
|
me?: { id: MiUser['id'] } | null | undefined,
|
||||||
options?: object,
|
options?: object,
|
||||||
): Promise<Packed<'NoteReactionWithNote'>[]> {
|
): Promise<Packed<'NoteReactionWithNote'>[]> {
|
||||||
const opts = Object.assign({
|
const opts = Object.assign({}, options);
|
||||||
}, options);
|
|
||||||
const _users = reactions.map(({ user, userId }) => user ?? userId);
|
// キャッシュからミュート・ブロック情報を取得
|
||||||
|
const blocked = me ? await this.cacheService.userBlockedCache.fetch(me.id) : null;
|
||||||
|
const muted = me ? await this.cacheService.userMutingsCache.fetch(me.id) : null;
|
||||||
|
|
||||||
|
// ミュート・ブロックされたユーザーのリアクションを除外
|
||||||
|
const filteredReactions = reactions.filter(reaction => {
|
||||||
|
if (!me) return true;
|
||||||
|
return !(blocked?.has(reaction.userId) || muted?.has(reaction.userId));
|
||||||
|
});
|
||||||
|
|
||||||
|
const _users = filteredReactions.map(({ user, userId }) => user ?? userId);
|
||||||
const _userMap = await this.userEntityService.packMany(_users, me)
|
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||||
.then(users => new Map(users.map(u => [u.id, u])));
|
.then(users => new Map(users.map(u => [u.id, u])));
|
||||||
return Promise.all(reactions.map(reaction => this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) })));
|
|
||||||
|
return Promise.all(filteredReactions.map(reaction =>
|
||||||
|
this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private noteEntityService: NoteEntityService,
|
private noteEntityService: NoteEntityService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
return await this.noteEntityService.fetchDiffs(ps.noteIds);
|
return await this.noteEntityService.fetchDiffs(ps.noteIds, me?.id ?? null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,16 @@ export function useNoteCapture(props: {
|
||||||
function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }): void {
|
function onReacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }): void {
|
||||||
let normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:');
|
let normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:');
|
||||||
normalizedName = normalizedName.match('\u200d') ? normalizedName : normalizedName.replace(/\ufe0f/g, '');
|
normalizedName = normalizedName.match('\u200d') ? normalizedName : normalizedName.replace(/\ufe0f/g, '');
|
||||||
|
const blockedIds: Set<string> = ($i as any)?.blockedIds ?? new Set();
|
||||||
|
const mutedIds: Set<string> = ($i as any)?.mutedIds ?? new Set();
|
||||||
|
const isBlocked = blockedIds.has(ctx.userId);
|
||||||
|
const isMuted = mutedIds.has(ctx.userId);
|
||||||
|
|
||||||
|
if (isBlocked || isMuted) {
|
||||||
|
// ブロック/ミュートユーザーからのリアクションは集計に含めず、処理を終了
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (reactionUserMap.has(ctx.userId) && reactionUserMap.get(ctx.userId) === normalizedName) return;
|
if (reactionUserMap.has(ctx.userId) && reactionUserMap.get(ctx.userId) === normalizedName) return;
|
||||||
reactionUserMap.set(ctx.userId, normalizedName);
|
reactionUserMap.set(ctx.userId, normalizedName);
|
||||||
|
|
||||||
|
|
@ -247,6 +257,15 @@ export function useNoteCapture(props: {
|
||||||
function onUnreacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }): void {
|
function onUnreacted(ctx: { userId: Misskey.entities.User['id']; reaction: string; emoji?: { name: string; url: string; } | null; }): void {
|
||||||
let normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:');
|
let normalizedName = ctx.reaction.replace(/^:(\w+):$/, ':$1@.:');
|
||||||
normalizedName = normalizedName.match('\u200d') ? normalizedName : normalizedName.replace(/\ufe0f/g, '');
|
normalizedName = normalizedName.match('\u200d') ? normalizedName : normalizedName.replace(/\ufe0f/g, '');
|
||||||
|
const blockedIds: Set<string> = ($i as any)?.blockedIds ?? new Set();
|
||||||
|
const mutedIds: Set<string> = ($i as any)?.mutedIds ?? new Set();
|
||||||
|
const isBlocked = blockedIds.has(ctx.userId);
|
||||||
|
const isMuted = mutedIds.has(ctx.userId);
|
||||||
|
|
||||||
|
if (isBlocked || isMuted) {
|
||||||
|
// ブロック/ミュートユーザーによるリアクション削除は無視する
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 確実に一度リアクションされて取り消されている場合のみ処理をとめる(APIで初回読み込み→Streamでアップデート等の場合、reactionUserMapに情報がないため)
|
// 確実に一度リアクションされて取り消されている場合のみ処理をとめる(APIで初回読み込み→Streamでアップデート等の場合、reactionUserMapに情報がないため)
|
||||||
if (reactionUserMap.has(ctx.userId) && reactionUserMap.get(ctx.userId) === noReaction) return;
|
if (reactionUserMap.has(ctx.userId) && reactionUserMap.get(ctx.userId) === noReaction) return;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue