summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/ReactionService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/core/ReactionService.ts')
-rw-r--r--packages/backend/src/core/ReactionService.ts93
1 files changed, 50 insertions, 43 deletions
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 17ff168786..0179b0680f 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -4,9 +4,8 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
import { DI } from '@/di-symbols.js';
-import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, NoteThreadMutingsRepository } from '@/models/_.js';
+import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, NoteThreadMutingsRepository, MiMeta } from '@/models/_.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import type { MiRemoteUser, MiUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
@@ -21,7 +20,6 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
-import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js';
import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
@@ -30,9 +28,10 @@ import { RoleService } from '@/core/RoleService.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
+import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
+import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
const FALLBACK = '\u2764';
-const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
const legacies: Record<string, string> = {
'like': '👍',
@@ -71,8 +70,8 @@ const decodeCustomEmojiRegexp = /^:([\p{Letter}\p{Number}\p{Mark}_+-]+)(?:@([\w.
@Injectable()
export class ReactionService {
constructor(
- @Inject(DI.redis)
- private redisClient: Redis.Redis,
+ @Inject(DI.meta)
+ private meta: MiMeta,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@@ -90,12 +89,12 @@ export class ReactionService {
private emojisRepository: EmojisRepository,
private utilityService: UtilityService,
- private metaService: MetaService,
private customEmojiService: CustomEmojiService,
private roleService: RoleService,
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private userBlockingService: UserBlockingService,
+ private reactionsBufferingService: ReactionsBufferingService,
private idService: IdService,
private featuredService: FeaturedService,
private globalEventService: GlobalEventService,
@@ -108,8 +107,6 @@ export class ReactionService {
@bindThis
public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) {
- const meta = await this.metaService.fetch();
-
// Check blocking
if (note.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
@@ -155,7 +152,7 @@ export class ReactionService {
}
// for media silenced host, custom emoji reactions are not allowed
- if (reacterHost != null && this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, reacterHost)) {
+ if (reacterHost != null && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, reacterHost)) {
reaction = FALLBACK;
}
} else {
@@ -177,7 +174,6 @@ export class ReactionService {
reaction,
};
- // Create reaction
try {
await this.noteReactionsRepository.insert(record);
} catch (e) {
@@ -201,16 +197,20 @@ export class ReactionService {
}
// Increment reactions count
- const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
- await this.notesRepository.createQueryBuilder().update()
- .set({
- reactions: () => sql,
- ...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
- reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
- } : {}),
- })
- .where('id = :id', { id: note.id })
- .execute();
+ if (this.meta.enableReactionsBuffering) {
+ await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache);
+ } else {
+ const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
+ await this.notesRepository.createQueryBuilder().update()
+ .set({
+ reactions: () => sql,
+ ...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
+ reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
+ } : {}),
+ })
+ .where('id = :id', { id: note.id })
+ .execute();
+ }
// 30%の確率、セルフではない、3日以内に投稿されたノートの場合ハイライト用ランキング更新
if (
@@ -230,7 +230,7 @@ export class ReactionService {
}
}
- if (meta.enableChartsForRemoteUser || (user.host == null)) {
+ if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
this.perUserReactionsChart.update(user, note);
}
@@ -317,14 +317,18 @@ export class ReactionService {
}
// Decrement reactions count
- const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
- await this.notesRepository.createQueryBuilder().update()
- .set({
- reactions: () => sql,
- reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
- })
- .where('id = :id', { id: note.id })
- .execute();
+ if (this.meta.enableReactionsBuffering) {
+ await this.reactionsBufferingService.delete(note.id, user.id, exist.reaction);
+ } else {
+ const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
+ await this.notesRepository.createQueryBuilder().update()
+ .set({
+ reactions: () => sql,
+ reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
+ })
+ .where('id = :id', { id: note.id })
+ .execute();
+ }
this.globalEventService.publishNoteStream(note.id, 'unreacted', {
reaction: this.decodeReaction(exist.reaction).reaction,
@@ -346,8 +350,21 @@ export class ReactionService {
}
/**
- * 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、
- * データベース上には存在する「0個のリアクションがついている」という情報を削除する。
+ * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する
+ * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果)
+ */
+ @bindThis
+ public convertLegacyReaction(reaction: string): string {
+ reaction = this.decodeReaction(reaction).reaction;
+ if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
+ return reaction;
+ }
+
+ // TODO: 廃止
+ /**
+ * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する
+ * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果)
+ * - データベース上には存在する「0個のリアクションがついている」という情報を削除する
*/
@bindThis
public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
@@ -360,10 +377,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;
})
@@ -418,11 +432,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;
- }
}