mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-03 23:14:13 +00:00 
			
		
		
		
	fix(backend): RBTの修正 (#14621)
* fix(backend): 絵文字の変換処理が不十分なのを修正 * enhance: リアクションバッファリングが無効になったら即bakeするように * attempt to fix test * fix
This commit is contained in:
		
							parent
							
								
									1d8bfe4f1c
								
							
						
					
					
						commit
						6a1a2bef43
					
				
					 6 changed files with 73 additions and 40 deletions
				
			
		| 
						 | 
					@ -126,8 +126,8 @@ const $meta: Provider = {
 | 
				
			||||||
				const { type, body } = obj.message as GlobalEvents['internal']['payload'];
 | 
									const { type, body } = obj.message as GlobalEvents['internal']['payload'];
 | 
				
			||||||
				switch (type) {
 | 
									switch (type) {
 | 
				
			||||||
					case 'metaUpdated': {
 | 
										case 'metaUpdated': {
 | 
				
			||||||
						for (const key in body) {
 | 
											for (const key in body.after) {
 | 
				
			||||||
							(meta as any)[key] = (body as any)[key];
 | 
												(meta as any)[key] = (body.after as any)[key];
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
						meta.proxyAccount = null; // joinなカラムは通常取ってこないので
 | 
											meta.proxyAccount = null; // joinなカラムは通常取ってこないので
 | 
				
			||||||
						break;
 | 
											break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -241,7 +241,7 @@ export interface InternalEventTypes {
 | 
				
			||||||
	avatarDecorationCreated: MiAvatarDecoration;
 | 
						avatarDecorationCreated: MiAvatarDecoration;
 | 
				
			||||||
	avatarDecorationDeleted: MiAvatarDecoration;
 | 
						avatarDecorationDeleted: MiAvatarDecoration;
 | 
				
			||||||
	avatarDecorationUpdated: MiAvatarDecoration;
 | 
						avatarDecorationUpdated: MiAvatarDecoration;
 | 
				
			||||||
	metaUpdated: MiMeta;
 | 
						metaUpdated: { before?: MiMeta; after: MiMeta; };
 | 
				
			||||||
	followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
 | 
						followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
 | 
				
			||||||
	unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
 | 
						unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
 | 
				
			||||||
	updateUserProfile: MiUserProfile;
 | 
						updateUserProfile: MiUserProfile;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@ export class MetaService implements OnApplicationShutdown {
 | 
				
			||||||
			switch (type) {
 | 
								switch (type) {
 | 
				
			||||||
				case 'metaUpdated': {
 | 
									case 'metaUpdated': {
 | 
				
			||||||
					this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 | 
										this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 | 
				
			||||||
						...body,
 | 
											...(body.after),
 | 
				
			||||||
						proxyAccount: null, // joinなカラムは通常取ってこないので
 | 
											proxyAccount: null, // joinなカラムは通常取ってこないので
 | 
				
			||||||
					};
 | 
										};
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
| 
						 | 
					@ -141,7 +141,7 @@ export class MetaService implements OnApplicationShutdown {
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.globalEventService.publishInternalEvent('metaUpdated', updated);
 | 
							this.globalEventService.publishInternalEvent('metaUpdated', { before, after: updated });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return updated;
 | 
							return updated;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -337,10 +337,22 @@ export class ReactionService {
 | 
				
			||||||
		//#endregion
 | 
							//#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: 廃止
 | 
						// TODO: 廃止
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、
 | 
						 * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する
 | 
				
			||||||
	 * データベース上には存在する「0個のリアクションがついている」という情報を削除する。
 | 
						 * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果)
 | 
				
			||||||
 | 
						 * - データベース上には存在する「0個のリアクションがついている」という情報を削除する
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@bindThis
 | 
						@bindThis
 | 
				
			||||||
	public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
 | 
						public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
 | 
				
			||||||
| 
						 | 
					@ -353,10 +365,7 @@ export class ReactionService {
 | 
				
			||||||
				return count > 0;
 | 
									return count > 0;
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
			.map(([reaction, count]) => {
 | 
								.map(([reaction, count]) => {
 | 
				
			||||||
				// unchecked indexed access
 | 
									const key = this.convertLegacyReaction(reaction);
 | 
				
			||||||
				const convertedReaction = legacies[reaction] as string | undefined;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return [key, count] as const;
 | 
									return [key, count] as const;
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
| 
						 | 
					@ -411,11 +420,4 @@ export class ReactionService {
 | 
				
			||||||
			host: undefined,
 | 
								host: undefined,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@bindThis
 | 
					 | 
				
			||||||
	public convertLegacyReaction(reaction: string): string {
 | 
					 | 
				
			||||||
		reaction = this.decodeReaction(reaction).reaction;
 | 
					 | 
				
			||||||
		if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
 | 
					 | 
				
			||||||
		return reaction;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,22 +11,48 @@ import { bindThis } from '@/decorators.js';
 | 
				
			||||||
import type { MiUser, NotesRepository } from '@/models/_.js';
 | 
					import type { MiUser, NotesRepository } from '@/models/_.js';
 | 
				
			||||||
import type { Config } from '@/config.js';
 | 
					import type { Config } from '@/config.js';
 | 
				
			||||||
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.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_DELTA_PREFIX = 'reactionsBufferDeltas';
 | 
				
			||||||
const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
 | 
					const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class ReactionsBufferingService {
 | 
					export class ReactionsBufferingService implements OnApplicationShutdown {
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
		@Inject(DI.config)
 | 
							@Inject(DI.config)
 | 
				
			||||||
		private config: Config,
 | 
							private config: Config,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Inject(DI.redisForSub)
 | 
				
			||||||
 | 
							private redisForSub: Redis.Redis,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@Inject(DI.redisForReactions)
 | 
							@Inject(DI.redisForReactions)
 | 
				
			||||||
		private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
 | 
							private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@Inject(DI.notesRepository)
 | 
							@Inject(DI.notesRepository)
 | 
				
			||||||
		private notesRepository: 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
 | 
						@bindThis
 | 
				
			||||||
| 
						 | 
					@ -159,4 +185,27 @@ export class ReactionsBufferingService {
 | 
				
			||||||
				.execute();
 | 
									.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();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,25 +16,12 @@ import { bindThis } from '@/decorators.js';
 | 
				
			||||||
import { DebounceLoader } from '@/misc/loader.js';
 | 
					import { DebounceLoader } from '@/misc/loader.js';
 | 
				
			||||||
import { IdService } from '@/core/IdService.js';
 | 
					import { IdService } from '@/core/IdService.js';
 | 
				
			||||||
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
 | 
					import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
 | 
				
			||||||
import { MetaService } from '@/core/MetaService.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';
 | 
				
			||||||
import type { UserEntityService } from './UserEntityService.js';
 | 
					import type { UserEntityService } from './UserEntityService.js';
 | 
				
			||||||
import type { DriveFileEntityService } from './DriveFileEntityService.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()
 | 
					@Injectable()
 | 
				
			||||||
export class NoteEntityService implements OnModuleInit {
 | 
					export class NoteEntityService implements OnModuleInit {
 | 
				
			||||||
	private userEntityService: UserEntityService;
 | 
						private userEntityService: UserEntityService;
 | 
				
			||||||
| 
						 | 
					@ -329,12 +316,7 @@ 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 = mergeReactions(this.reactionService.convertLegacyReactions(note.reactions), bufferedReactions.deltas ?? {});
 | 
							const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {}));
 | 
				
			||||||
		for (const [name, count] of Object.entries(reactions)) {
 | 
					 | 
				
			||||||
			if (count <= 0) {
 | 
					 | 
				
			||||||
				delete reactions[name];
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
 | 
							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) {
 | 
								for (const note of notes) {
 | 
				
			||||||
				if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
 | 
									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) {
 | 
										if (reactionsCount === 0) {
 | 
				
			||||||
						myReactionsMap.set(note.renote.id, null);
 | 
											myReactionsMap.set(note.renote.id, null);
 | 
				
			||||||
					} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) {
 | 
										} 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 {
 | 
									} else {
 | 
				
			||||||
					if (note.id < oldId) {
 | 
										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) {
 | 
											if (reactionsCount === 0) {
 | 
				
			||||||
							myReactionsMap.set(note.id, null);
 | 
												myReactionsMap.set(note.id, null);
 | 
				
			||||||
						} else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) {
 | 
											} else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue