fix(backend): RBTの修正 (#14621)

* fix(backend): 絵文字の変換処理が不十分なのを修正

* enhance: リアクションバッファリングが無効になったら即bakeするように

* attempt to fix test

* fix
This commit is contained in:
かっこかり 2024-09-24 18:29:02 +09:00 committed by GitHub
parent 1d8bfe4f1c
commit 6a1a2bef43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 73 additions and 40 deletions

View file

@ -126,8 +126,8 @@ const $meta: Provider = {
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'metaUpdated': {
for (const key in body) {
(meta as any)[key] = (body as any)[key];
for (const key in body.after) {
(meta as any)[key] = (body.after as any)[key];
}
meta.proxyAccount = null; // joinなカラムは通常取ってこないので
break;

View file

@ -241,7 +241,7 @@ export interface InternalEventTypes {
avatarDecorationCreated: MiAvatarDecoration;
avatarDecorationDeleted: MiAvatarDecoration;
avatarDecorationUpdated: MiAvatarDecoration;
metaUpdated: MiMeta;
metaUpdated: { before?: MiMeta; after: MiMeta; };
followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
updateUserProfile: MiUserProfile;

View file

@ -52,7 +52,7 @@ export class MetaService implements OnApplicationShutdown {
switch (type) {
case 'metaUpdated': {
this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
...body,
...(body.after),
proxyAccount: null, // joinなカラムは通常取ってこないので
};
break;
@ -141,7 +141,7 @@ export class MetaService implements OnApplicationShutdown {
});
}
this.globalEventService.publishInternalEvent('metaUpdated', updated);
this.globalEventService.publishInternalEvent('metaUpdated', { before, after: updated });
return updated;
}

View file

@ -337,10 +337,22 @@ export class ReactionService {
//#endregion
}
/**
* -
* - `@.` `decodeReaction()`
*/
@bindThis
public convertLegacyReaction(reaction: string): string {
reaction = this.decodeReaction(reaction).reaction;
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
return reaction;
}
// TODO: 廃止
/**
*
* 0
* -
* - `@.` `decodeReaction()`
* - 0
*/
@bindThis
public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
@ -353,10 +365,7 @@ export class ReactionService {
return count > 0;
})
.map(([reaction, count]) => {
// unchecked indexed access
const convertedReaction = legacies[reaction] as string | undefined;
const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
const key = this.convertLegacyReaction(reaction);
return [key, count] as const;
})
@ -411,11 +420,4 @@ export class ReactionService {
host: undefined,
};
}
@bindThis
public convertLegacyReaction(reaction: string): string {
reaction = this.decodeReaction(reaction).reaction;
if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
return reaction;
}
}

View file

@ -11,22 +11,48 @@ import { bindThis } from '@/decorators.js';
import type { MiUser, NotesRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas';
const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
@Injectable()
export class ReactionsBufferingService {
export class ReactionsBufferingService implements OnApplicationShutdown {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,
@Inject(DI.redisForReactions)
private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
) {
this.redisForSub.on('message', this.onMessage);
}
@bindThis
private async onMessage(_: string, data: string) {
const obj = JSON.parse(data);
if (obj.channel === 'internal') {
const { type, body } = obj.message as GlobalEvents['internal']['payload'];
switch (type) {
case 'metaUpdated': {
// リアクションバッファリングが有効→無効になったら即bake
if (body.before != null && body.before.enableReactionsBuffering && !body.after.enableReactionsBuffering) {
this.bake();
}
break;
}
default:
break;
}
}
}
@bindThis
@ -159,4 +185,27 @@ export class ReactionsBufferingService {
.execute();
}
}
@bindThis
public mergeReactions(src: MiNote['reactions'], delta: Record<string, number>): MiNote['reactions'] {
const reactions = { ...src };
for (const [name, count] of Object.entries(delta)) {
if (reactions[name] != null) {
reactions[name] += count;
} else {
reactions[name] = count;
}
}
return reactions;
}
@bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
}
@bindThis
public onApplicationShutdown(signal?: string | undefined): void {
this.dispose();
}
}

View file

@ -16,25 +16,12 @@ import { bindThis } from '@/decorators.js';
import { DebounceLoader } from '@/misc/loader.js';
import { IdService } from '@/core/IdService.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import { MetaService } from '@/core/MetaService.js';
import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { ReactionService } from '../ReactionService.js';
import type { UserEntityService } from './UserEntityService.js';
import type { DriveFileEntityService } from './DriveFileEntityService.js';
function mergeReactions(src: Record<string, number>, delta: Record<string, number>) {
const reactions = { ...src };
for (const [name, count] of Object.entries(delta)) {
if (reactions[name] != null) {
reactions[name] += count;
} else {
reactions[name] = count;
}
}
return reactions;
}
@Injectable()
export class NoteEntityService implements OnModuleInit {
private userEntityService: UserEntityService;
@ -329,12 +316,7 @@ export class NoteEntityService implements OnModuleInit {
: this.meta.enableReactionsBuffering
? await this.reactionsBufferingService.get(note.id)
: { deltas: {}, pairs: [] };
const reactions = mergeReactions(this.reactionService.convertLegacyReactions(note.reactions), bufferedReactions.deltas ?? {});
for (const [name, count] of Object.entries(reactions)) {
if (count <= 0) {
delete reactions[name];
}
}
const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {}));
const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
@ -451,7 +433,7 @@ export class NoteEntityService implements OnModuleInit {
for (const note of notes) {
if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
const reactionsCount = Object.values(mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
if (reactionsCount === 0) {
myReactionsMap.set(note.renote.id, null);
} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) {
@ -467,7 +449,7 @@ export class NoteEntityService implements OnModuleInit {
}
} else {
if (note.id < oldId) {
const reactionsCount = Object.values(mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
if (reactionsCount === 0) {
myReactionsMap.set(note.id, null);
} else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) {