diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts index 793bbeecb1..5afeb1131b 100644 --- a/packages/backend/src/core/HashtagService.ts +++ b/packages/backend/src/core/HashtagService.ts @@ -7,11 +7,11 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type { MiUser } from '@/models/User.js'; +import { isLocalUser, isRemoteUser } from '@/models/User.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { IdService } from '@/core/IdService.js'; import type { MiHashtag } from '@/models/Hashtag.js'; import type { HashtagsRepository, MiMeta } from '@/models/_.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -28,7 +28,6 @@ export class HashtagService { @Inject(DI.hashtagsRepository) private hashtagsRepository: HashtagsRepository, - private userEntityService: UserEntityService, private featuredService: FeaturedService, private idService: IdService, private utilityService: UtilityService, @@ -78,19 +77,19 @@ export class HashtagService { set.attachedUsersCount = () => '"attachedUsersCount" + 1'; } // 自分が(ローカル内で)初めてこのタグを使ったなら - if (this.userEntityService.isLocalUser(user) && !index.attachedLocalUserIds.some(id => id === user.id)) { + if (isLocalUser(user) && !index.attachedLocalUserIds.some(id => id === user.id)) { set.attachedLocalUserIds = () => `array_append("attachedLocalUserIds", '${user.id}')`; set.attachedLocalUsersCount = () => '"attachedLocalUsersCount" + 1'; } // 自分が(リモートで)初めてこのタグを使ったなら - if (this.userEntityService.isRemoteUser(user) && !index.attachedRemoteUserIds.some(id => id === user.id)) { + if (isRemoteUser(user) && !index.attachedRemoteUserIds.some(id => id === user.id)) { set.attachedRemoteUserIds = () => `array_append("attachedRemoteUserIds", '${user.id}')`; set.attachedRemoteUsersCount = () => '"attachedRemoteUsersCount" + 1'; } } else { set.attachedUserIds = () => `array_remove("attachedUserIds", '${user.id}')`; set.attachedUsersCount = () => '"attachedUsersCount" - 1'; - if (this.userEntityService.isLocalUser(user)) { + if (isLocalUser(user)) { set.attachedLocalUserIds = () => `array_remove("attachedLocalUserIds", '${user.id}')`; set.attachedLocalUsersCount = () => '"attachedLocalUsersCount" - 1'; } else { @@ -105,12 +104,12 @@ export class HashtagService { set.mentionedUsersCount = () => '"mentionedUsersCount" + 1'; } // 自分が(ローカル内で)初めてこのタグを使ったなら - if (this.userEntityService.isLocalUser(user) && !index.mentionedLocalUserIds.some(id => id === user.id)) { + if (isLocalUser(user) && !index.mentionedLocalUserIds.some(id => id === user.id)) { set.mentionedLocalUserIds = () => `array_append("mentionedLocalUserIds", '${user.id}')`; set.mentionedLocalUsersCount = () => '"mentionedLocalUsersCount" + 1'; } // 自分が(リモートで)初めてこのタグを使ったなら - if (this.userEntityService.isRemoteUser(user) && !index.mentionedRemoteUserIds.some(id => id === user.id)) { + if (isRemoteUser(user) && !index.mentionedRemoteUserIds.some(id => id === user.id)) { set.mentionedRemoteUserIds = () => `array_append("mentionedRemoteUserIds", '${user.id}')`; set.mentionedRemoteUsersCount = () => '"mentionedRemoteUsersCount" + 1'; } @@ -133,10 +132,10 @@ export class HashtagService { mentionedRemoteUsersCount: 0, attachedUserIds: [user.id], attachedUsersCount: 1, - attachedLocalUserIds: this.userEntityService.isLocalUser(user) ? [user.id] : [], - attachedLocalUsersCount: this.userEntityService.isLocalUser(user) ? 1 : 0, - attachedRemoteUserIds: this.userEntityService.isRemoteUser(user) ? [user.id] : [], - attachedRemoteUsersCount: this.userEntityService.isRemoteUser(user) ? 1 : 0, + attachedLocalUserIds: isLocalUser(user) ? [user.id] : [], + attachedLocalUsersCount: isLocalUser(user) ? 1 : 0, + attachedRemoteUserIds: isRemoteUser(user) ? [user.id] : [], + attachedRemoteUsersCount: isRemoteUser(user) ? 1 : 0, } as MiHashtag); } else { this.hashtagsRepository.insert({ @@ -144,10 +143,10 @@ export class HashtagService { name: tag, mentionedUserIds: [user.id], mentionedUsersCount: 1, - mentionedLocalUserIds: this.userEntityService.isLocalUser(user) ? [user.id] : [], - mentionedLocalUsersCount: this.userEntityService.isLocalUser(user) ? 1 : 0, - mentionedRemoteUserIds: this.userEntityService.isRemoteUser(user) ? [user.id] : [], - mentionedRemoteUsersCount: this.userEntityService.isRemoteUser(user) ? 1 : 0, + mentionedLocalUserIds: isLocalUser(user) ? [user.id] : [], + mentionedLocalUsersCount: isLocalUser(user) ? 1 : 0, + mentionedRemoteUserIds: isRemoteUser(user) ? [user.id] : [], + mentionedRemoteUsersCount: isRemoteUser(user) ? 1 : 0, attachedUserIds: [], attachedUsersCount: 0, attachedLocalUserIds: [], diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 4bcf2b8e08..a18aaebebd 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -19,6 +19,7 @@ import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; import { IdService } from '@/core/IdService.js'; import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; +import { isLocalUser, isRemoteUser } from '@/models/User.js'; import type { IPoll } from '@/models/Poll.js'; import { MiPoll } from '@/models/Poll.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; @@ -39,7 +40,6 @@ import { HashtagService } from '@/core/HashtagService.js'; import { AntennaService } from '@/core/AntennaService.js'; import { QueueService } from '@/core/QueueService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; @@ -525,7 +525,7 @@ export class NoteCreateService implements OnApplicationShutdown { if (mentionedUsers.length > 0) { insert.mentions = mentionedUsers.map(u => u.id); const profiles = await this.userProfilesRepository.findBy({ userId: In(insert.mentions) }); - insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u)).map(u => { + insert.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => isRemoteUser(u)).map(u => { const profile = profiles.find(p => p.userId === u.id); const url = profile != null ? profile.url : null; return { @@ -591,7 +591,7 @@ export class NoteCreateService implements OnApplicationShutdown { // Register host if (this.meta.enableStatsForFederatedInstances) { - if (this.userEntityService.isRemoteUser(user)) { + if (isRemoteUser(user)) { this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { if (!this.isRenote(note) || this.isQuote(note)) { this.updateNotesCountQueue.enqueue(i.id, 1); @@ -676,7 +676,7 @@ export class NoteCreateService implements OnApplicationShutdown { } if (!silent) { - if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user); + if (this.isLocalUser(user)) this.activeUsersChart.write(user); // Pack the note const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true }); @@ -752,26 +752,26 @@ export class NoteCreateService implements OnApplicationShutdown { nm.notify(); //#region AP deliver - if (!data.localOnly && this.userEntityService.isLocalUser(user)) { + if (!data.localOnly && isLocalUser(user)) { trackTask(async () => { const noteActivity = await this.apRendererService.renderNoteOrRenoteActivity(note, user, { renote: data.renote }); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); // メンションされたリモートユーザーに配送 - for (const u of mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u))) { + for (const u of mentionedUsers.filter(u => isRemoteUser(u))) { dm.addDirectRecipe(u as MiRemoteUser); } // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 if (data.reply && data.reply.userHost !== null) { const u = await this.usersRepository.findOneBy({ id: data.reply.userId }); - if (u && this.userEntityService.isRemoteUser(u)) dm.addDirectRecipe(u); + if (u && isRemoteUser(u)) dm.addDirectRecipe(u); } // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 if (data.renote && data.renote.userHost !== null) { const u = await this.usersRepository.findOneBy({ id: data.renote.userId }); - if (u && this.userEntityService.isRemoteUser(u)) dm.addDirectRecipe(u); + if (u && isRemoteUser(u)) dm.addDirectRecipe(u); } // フォロワーに配送 @@ -814,27 +814,20 @@ export class NoteCreateService implements OnApplicationShutdown { if (!user.noindex) this.index(note); } - @bindThis - public isPureRenote(note: Option): note is PureRenoteOption { - return this.isRenote(note) && !this.isQuote(note); - } + /** + * @deprecated Use the exported function instead + */ + readonly isPureRenote = isPureRenote; - @bindThis - private isRenote(note: Option): note is Option & { renote: MiNote } { - return note.renote != null; - } + /** + * @deprecated Use the exported function instead + */ + readonly isRenote = isRenote; - @bindThis - private isQuote(note: Option & { renote: MiNote }): note is Option & { renote: MiNote } & ( - { text: string } | { cw: string } | { reply: MiNote } | { poll: IPoll } | { files: MiDriveFile[] } - ) { - // NOTE: SYNC WITH misc/is-quote.ts - return note.text != null || - note.reply != null || - note.cw != null || - note.poll != null || - (note.files != null && note.files.length > 0); - } + /** + * @deprecated Use the exported function instead + */ + readonly isQuote = isQuote; @bindThis private async incRenoteCount(renote: MiNote, user: MiUser) { @@ -874,7 +867,7 @@ export class NoteCreateService implements OnApplicationShutdown { ]); // Only create mention events for local users, and users for whom the note is visible - for (const u of mentionedUsers.filter(u => (note.visibility !== 'specified' || note.visibleUserIds.some(x => x === u.id)) && this.userEntityService.isLocalUser(u))) { + for (const u of mentionedUsers.filter(u => (note.visibility !== 'specified' || note.visibleUserIds.some(x => x === u.id)) && isLocalUser(u))) { const threadId = note.threadId ?? note.id; const isThreadMuted = threadMutings.get(u.id)?.has(threadId); @@ -1157,3 +1150,22 @@ export class NoteCreateService implements OnApplicationShutdown { } } } + +export function isPureRenote(note: Option): note is PureRenoteOption { + return isRenote(note) && !isQuote(note); +} + +export function isRenote(note: Option): note is Option & { renote: MiNote } { + return note.renote != null; +} + +export function isQuote(note: Option & { renote: MiNote }): note is Option & { renote: MiNote } & ( + { text: string } | { cw: string } | { reply: MiNote } | { poll: IPoll } | { files: MiDriveFile[] } +) { + // NOTE: SYNC WITH misc/is-quote.ts + return note.text != null || + note.reply != null || + note.cw != null || + note.poll != null || + (note.files != null && note.files.length > 0); +} diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 9ce8cb6731..69811e6197 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -6,6 +6,7 @@ import { Brackets, In, IsNull, Not } from 'typeorm'; import { Injectable, Inject } from '@nestjs/common'; import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; +import { isLocalUser, isRemoteUser } from '@/models/User.js'; import { MiNote, IMentionedRemoteUsers } from '@/models/Note.js'; import type { InstancesRepository, MiMeta, NotesRepository, UsersRepository } from '@/models/_.js'; import { RelayService } from '@/core/RelayService.js'; @@ -18,14 +19,13 @@ import InstanceChart from '@/core/chart/charts/instance.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; import { LatestNoteService } from '@/core/LatestNoteService.js'; import { ApLogService } from '@/core/ApLogService.js'; -import Logger from '@/logger.js'; +import type Logger from '@/logger.js'; import { LoggerService } from './LoggerService.js'; @Injectable() @@ -48,7 +48,6 @@ export class NoteDeleteService { @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, - private userEntityService: UserEntityService, private globalEventService: GlobalEventService, private relayService: RelayService, private federatedInstanceService: FederatedInstanceService, @@ -92,7 +91,7 @@ export class NoteDeleteService { }); //#region ローカルの投稿なら削除アクティビティを配送 - if (this.userEntityService.isLocalUser(user) && !note.localOnly) { + if (isLocalUser(user) && !note.localOnly) { let renote: MiNote | null = null; // if deleted note is renote @@ -113,7 +112,7 @@ export class NoteDeleteService { const federatedLocalCascadingNotes = (cascadingNotes).filter(note => !note.localOnly && note.userHost == null); // filter out local-only notes for (const cascadingNote of federatedLocalCascadingNotes) { if (!cascadingNote.user) continue; - if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue; + if (!isLocalUser(cascadingNote.user)) continue; const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); this.deliverToConcerned(cascadingNote.user, cascadingNote, content); } @@ -132,7 +131,7 @@ export class NoteDeleteService { } if (this.meta.enableStatsForFederatedInstances) { - if (this.userEntityService.isRemoteUser(user)) { + if (isRemoteUser(user)) { this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { if (note.renoteId && note.text || !note.renoteId) { this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index e9b404d084..8857e022c3 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -20,6 +20,7 @@ import type { MiApp } from '@/models/App.js'; import { concat } from '@/misc/prelude/array.js'; import { IdService } from '@/core/IdService.js'; import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js'; +import { isRemoteUser, isLocalUser } from '@/models/User.js'; import { MiPoll, type IPoll } from '@/models/Poll.js'; import type { MiChannel } from '@/models/Channel.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; @@ -34,7 +35,6 @@ import { NotificationService } from '@/core/NotificationService.js'; import { UserWebhookService } from '@/core/UserWebhookService.js'; import { QueueService } from '@/core/QueueService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; @@ -200,7 +200,6 @@ export class NoteEditService implements OnApplicationShutdown { @Inject(DI.pollsRepository) private pollsRepository: PollsRepository, - private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private idService: IdService, private globalEventService: GlobalEventService, @@ -545,7 +544,7 @@ export class NoteEditService implements OnApplicationShutdown { if (mentionedUsers.length > 0) { note.mentions = mentionedUsers.map(u => u.id); const profiles = await this.userProfilesRepository.findBy({ userId: In(note.mentions) }); - note.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u)).map(u => { + note.mentionedRemoteUsers = JSON.stringify(mentionedUsers.filter(u => isRemoteUser(u)).map(u => { const profile = profiles.find(p => p.userId === u.id); const url = profile != null ? profile.url : null; return { @@ -608,7 +607,7 @@ export class NoteEditService implements OnApplicationShutdown { }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { // Register host if (this.meta.enableStatsForFederatedInstances) { - if (this.userEntityService.isRemoteUser(user)) { + if (isRemoteUser(user)) { this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { if (note.renote && note.text || !note.renote) { this.updateNotesCountQueue.enqueue(i.id, 1); @@ -638,7 +637,7 @@ export class NoteEditService implements OnApplicationShutdown { } if (!silent) { - if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user); + if (isLocalUser(user)) this.activeUsersChart.write(user); // Pack the note const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true }); @@ -680,26 +679,26 @@ export class NoteEditService implements OnApplicationShutdown { nm.notify(); //#region AP deliver - if (!data.localOnly && this.userEntityService.isLocalUser(user)) { + if (!data.localOnly && isLocalUser(user)) { trackTask(async () => { const noteActivity = await this.apRendererService.renderNoteOrRenoteActivity(note, user, { renote: data.renote }); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); // メンションされたリモートユーザーに配送 - for (const u of mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u))) { + for (const u of mentionedUsers.filter(u => isRemoteUser(u))) { dm.addDirectRecipe(u as MiRemoteUser); } // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 if (data.reply && data.reply.userHost !== null) { const u = await this.usersRepository.findOneBy({ id: data.reply.userId }); - if (u && this.userEntityService.isRemoteUser(u)) dm.addDirectRecipe(u); + if (u && isRemoteUser(u)) dm.addDirectRecipe(u); } // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 if (this.isRenote(data) && data.renote.userHost !== null) { const u = await this.usersRepository.findOneBy({ id: data.renote.userId }); - if (u && this.userEntityService.isRemoteUser(u)) dm.addDirectRecipe(u); + if (u && isRemoteUser(u)) dm.addDirectRecipe(u); } // フォロワーに配送 diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 33262a4804..e93cc7ba4c 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -11,10 +11,10 @@ import { RelayService } from '@/core/RelayService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; import { bindThis } from '@/decorators.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; +import { isLocalUser } from '@/models/User.js'; @Injectable() export class PollService { @@ -31,7 +31,6 @@ export class PollService { @Inject(DI.pollVotesRepository) private pollVotesRepository: PollVotesRepository, - private userEntityService: UserEntityService, private idService: IdService, private relayService: RelayService, private globalEventService: GlobalEventService, @@ -96,7 +95,7 @@ export class PollService { const user = await this.usersRepository.findOneBy({ id: note.userId }); if (user == null) throw new Error('note not found'); - if (this.userEntityService.isLocalUser(user)) { + if (isLocalUser(user)) { const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, user, false), user)); await this.apDeliverManagerService.deliverToFollowers(user, content); await this.relayService.deliverToRelays(user, content); diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 758c2184e1..4abc953356 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -3,11 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, NoteThreadMutingsRepository, MiMeta } from '@/models/_.js'; +import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, MiMeta } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MiRemoteUser, MiUser } from '@/models/User.js'; +import { isLocalUser, isRemoteUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; import { IdService } from '@/core/IdService.js'; import { MiNoteReaction } from '@/models/NoteReaction.js'; @@ -17,8 +19,6 @@ import { NotificationService } from '@/core/NotificationService.js'; import PerUserReactionsChart from '@/core/chart/charts/per-user-reactions.js'; import { emojiRegex } from '@/misc/emoji-regex.js'; import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { bindThis } from '@/decorators.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -71,8 +71,12 @@ const isCustomEmojiRegexp = /^:([\p{Letter}\p{Number}\p{Mark}_+-]+)(?:@\.)?:$/u; const decodeCustomEmojiRegexp = /^:([\p{Letter}\p{Number}\p{Mark}_+-]+)(?:@([\w.-]+))?:$/u; @Injectable() -export class ReactionService { +export class ReactionService implements OnModuleInit { + private roleService: RoleService; + constructor( + private readonly moduleRef: ModuleRef, + @Inject(DI.meta) private meta: MiMeta, @@ -85,9 +89,6 @@ export class ReactionService { @Inject(DI.noteReactionsRepository) private noteReactionsRepository: NoteReactionsRepository, - @Inject(DI.noteThreadMutingsRepository) - private noteThreadMutingsRepository: NoteThreadMutingsRepository, - @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, @@ -96,9 +97,6 @@ export class ReactionService { private utilityService: UtilityService, private customEmojiService: CustomEmojiService, - private roleService: RoleService, - private userEntityService: UserEntityService, - private noteEntityService: NoteEntityService, private userBlockingService: UserBlockingService, private reactionsBufferingService: ReactionsBufferingService, private idService: IdService, @@ -113,6 +111,11 @@ export class ReactionService { ) { } + @bindThis + onModuleInit() { + this.roleService = this.moduleRef.get('RoleService'); + } + @bindThis public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) { // Check blocking @@ -280,7 +283,7 @@ export class ReactionService { } //#region 配信 - if (this.userEntityService.isLocalUser(user) && !note.localOnly) { + if (isLocalUser(user) && !note.localOnly) { const content = this.apRendererService.addContext(await this.apRendererService.renderLike(record, note)); const dm = this.apDeliverManagerService.createDeliverManager(user, content); if (note.userHost !== null) { @@ -292,7 +295,7 @@ export class ReactionService { dm.addFollowersRecipe(); } else if (note.visibility === 'specified') { const visibleUsers = await Promise.all(note.visibleUserIds.map(id => this.usersRepository.findOneBy({ id }))); - for (const u of visibleUsers.filter(u => u && this.userEntityService.isRemoteUser(u))) { + for (const u of visibleUsers.filter(u => u && isRemoteUser(u))) { dm.addDirectRecipe(u as MiRemoteUser); } } @@ -343,7 +346,7 @@ export class ReactionService { }); //#region 配信 - if (this.userEntityService.isLocalUser(user) && !note.localOnly) { + if (isLocalUser(user) && !note.localOnly) { const content = this.apRendererService.addContext(this.apRendererService.renderUndo(await this.apRendererService.renderLike(exist, note), user)); const dm = this.apDeliverManagerService.createDeliverManager(user, content); if (note.userHost !== null) { @@ -357,86 +360,102 @@ export class ReactionService { } /** - * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する - * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果) + * @deprecated Use the exported function instead */ - @bindThis - public convertLegacyReaction(reaction: string): string { - reaction = this.decodeReaction(reaction).reaction; - if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; - return reaction; - } + readonly convertLegacyReaction = convertLegacyReaction; - // TODO: 廃止 /** - * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する - * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果) - * - データベース上には存在する「0個のリアクションがついている」という情報を削除する + * @deprecated Use the exported function instead */ - @bindThis - public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] { - return Object.entries(reactions) - .filter(([, count]) => { - // `ReactionService.prototype.delete`ではリアクション削除時に、 - // `MiNote['reactions']`のエントリの値をデクリメントしているが、 - // デクリメントしているだけなのでエントリ自体は0を値として持つ形で残り続ける。 - // そのため、この処理がなければ、「0個のリアクションがついている」ということになってしまう。 - return count > 0; - }) - .map(([reaction, count]) => { - const key = this.convertLegacyReaction(reaction); + readonly convertLegacyReactions = convertLegacyReactions; - return [key, count] as const; - }) - .reduce((acc, [key, count]) => { - // unchecked indexed access - const prevCount = acc[key] as number | undefined; + /** + * @deprecated Use the exported function instead + */ + readonly normalize = normalize; - acc[key] = (prevCount ?? 0) + count; + /** + * @deprecated Use the exported function instead + */ + readonly decodeReaction = decodeReaction; +} - return acc; - }, {}); +/** + * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する + * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果) + */ +export function convertLegacyReaction(reaction: string): string { + reaction = decodeReaction(reaction).reaction; + if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; + return reaction; +} + +// TODO: 廃止 +/** + * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する + * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果) + * - データベース上には存在する「0個のリアクションがついている」という情報を削除する + */ +export function convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] { + return Object.entries(reactions) + .filter(([, count]) => { + // `ReactionService.prototype.delete`ではリアクション削除時に、 + // `MiNote['reactions']`のエントリの値をデクリメントしているが、 + // デクリメントしているだけなのでエントリ自体は0を値として持つ形で残り続ける。 + // そのため、この処理がなければ、「0個のリアクションがついている」ということになってしまう。 + return count > 0; + }) + .map(([reaction, count]) => { + const key = convertLegacyReaction(reaction); + + return [key, count] as const; + }) + .reduce((acc, [key, count]) => { + // unchecked indexed access + const prevCount = acc[key] as number | undefined; + + acc[key] = (prevCount ?? 0) + count; + + return acc; + }, {}); +} + +export function normalize(reaction: string | null): string { + if (reaction == null) return FALLBACK; + + // 文字列タイプのリアクションを絵文字に変換 + if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; + + // Unicode絵文字 + const match = emojiRegex.exec(reaction); + if (match) { + // 合字を含む1つの絵文字 + const unicode = match[0]; + + // 異体字セレクタ除去 + return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, ''); } - @bindThis - public normalize(reaction: string | null): string { - if (reaction == null) return FALLBACK; + return FALLBACK; +} - // 文字列タイプのリアクションを絵文字に変換 - if (Object.keys(legacies).includes(reaction)) return legacies[reaction]; +export function decodeReaction(str: string): DecodedReaction { + const custom = str.match(decodeCustomEmojiRegexp); - // Unicode絵文字 - const match = emojiRegex.exec(reaction); - if (match) { - // 合字を含む1つの絵文字 - const unicode = match[0]; - - // 異体字セレクタ除去 - return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, ''); - } - - return FALLBACK; - } - - @bindThis - public decodeReaction(str: string): DecodedReaction { - const custom = str.match(decodeCustomEmojiRegexp); - - if (custom) { - const name = custom[1]; - const host = custom[2] ?? null; - - return { - reaction: `:${name}@${host ?? '.'}:`, // ローカル分は@以降を省略するのではなく.にする - name, - host, - }; - } + if (custom) { + const name = custom[1]; + const host = custom[2] ?? null; return { - reaction: str, - name: undefined, - host: undefined, + reaction: `:${name}@${host ?? '.'}:`, // ローカル分は@以降を省略するのではなく.にする + name, + host, }; } + + return { + reaction: str, + name: undefined, + host: undefined, + }; } diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index e7f46e8b29..a55d96086f 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -16,11 +16,10 @@ import type { import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; -import { CacheService } from '@/core/CacheService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { IdService } from '@/core/IdService.js'; -import { NotificationService } from '@/core/NotificationService.js'; +import type { NotificationService } from '@/core/NotificationService.js'; import { Serialized } from '@/types.js'; import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js'; import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; @@ -40,7 +39,6 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { @Inject(DI.reversiGamesRepository) private reversiGamesRepository: ReversiGamesRepository, - private cacheService: CacheService, private userEntityService: UserEntityService, private globalEventService: GlobalEventService, private reversiGameEntityService: ReversiGameEntityService, @@ -50,7 +48,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { @bindThis async onModuleInit() { - this.notificationService = this.moduleRef.get(NotificationService.name); + this.notificationService = this.moduleRef.get('NotificationService'); } @bindThis diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 6d22ca59e9..63506eb034 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -22,7 +22,7 @@ import type { FollowingsRepository, FollowRequestsRepository, InstancesRepositor import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { bindThis } from '@/decorators.js'; -import { UserBlockingService } from '@/core/UserBlockingService.js'; +import type { UserBlockingService } from '@/core/UserBlockingService.js'; import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import { AccountMoveService } from '@/core/AccountMoveService.js'; diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 3ca73db3dc..2b0d69b261 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import type { NoteReactionsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; -import { IdService } from '@/core/IdService.js'; +import type { IdService } from '@/core/IdService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index d170c49f31..0e45807ad7 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -15,8 +15,8 @@ import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; import { FilterUnionByProperty, groupedNotificationTypes } from '@/types.js'; import { CacheService } from '@/core/CacheService.js'; -import { RoleEntityService } from './RoleEntityService.js'; -import { ChatEntityService } from './ChatEntityService.js'; +import type { RoleEntityService } from './RoleEntityService.js'; +import type { ChatEntityService } from './ChatEntityService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 19b41c41ed..ce3b52011c 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -44,16 +44,16 @@ import type { UsersRepository, } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; -import { RolePolicies, RoleService } from '@/core/RoleService.js'; -import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; -import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; -import { IdService } from '@/core/IdService.js'; +import { isSystemAccount } from '@/misc/is-system-account.js'; +import type { RolePolicies, RoleService } from '@/core/RoleService.js'; +import type { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; +import type { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; +import type { IdService } from '@/core/IdService.js'; import type { AnnouncementService } from '@/core/AnnouncementService.js'; import type { CustomEmojiService } from '@/core/CustomEmojiService.js'; -import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; -import { ChatService } from '@/core/ChatService.js'; -import { isSystemAccount } from '@/misc/is-system-account.js'; -import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import type { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import type { ChatService } from '@/core/ChatService.js'; +import type { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import type { CacheService } from '@/core/CacheService.js'; import { getCallerId } from '@/misc/attach-caller-id.js'; import type { OnModuleInit } from '@nestjs/common'; @@ -182,7 +182,9 @@ export class UserEntityService implements OnModuleInit { public validateListenBrainz = ajv.compile(listenbrainzSchema); //#endregion + /** @deprecated use export from MiUser */ public isLocalUser = isLocalUser; + /** @deprecated use export from MiUser */ public isRemoteUser = isRemoteUser; @bindThis diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 4f6cd68ecf..e73de15241 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -21,6 +21,7 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { QueueService } from '@/core/QueueService.js'; import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js'; +import { isLocalUser } from '@/models/User.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; import type { MiUserPublickey } from '@/models/UserPublickey.js'; import type { MiFollowing } from '@/models/Following.js'; @@ -28,7 +29,6 @@ import { countIf } from '@/misc/prelude/array.js'; import type { MiNote } from '@/models/Note.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; @@ -81,7 +81,6 @@ export class ActivityPubServerService { private followRequestsRepository: FollowRequestsRepository, private utilityService: UtilityService, - private userEntityService: UserEntityService, private apRendererService: ApRendererService, private apDbResolverService: ApDbResolverService, private queueService: QueueService, @@ -976,7 +975,7 @@ export class ActivityPubServerService { const keypair = await this.userKeypairService.getUserKeypair(user.id); - if (this.userEntityService.isLocalUser(user)) { + if (isLocalUser(user)) { this.setResponseType(request, reply); return (this.apRendererService.addContext(this.apRendererService.renderKey(user, keypair))); } else { diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 806b203b51..d10a7b3d39 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -13,7 +13,6 @@ import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; import { MiLocalUser } from '@/models/User.js'; -import { CacheService } from '@/core/CacheService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -83,7 +82,6 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private activeUsersChart: ActiveUsersChart, - private readonly cacheService: CacheService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index ac6dfe8da6..c9a4f15fcc 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -10,7 +10,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; -import { CacheService } from '@/core/CacheService.js'; import { QueryService } from '@/core/QueryService.js'; import { ApiError } from '@/server/api/error.js'; import { RoleService } from '@/core/RoleService.js'; @@ -67,7 +66,6 @@ export default class extends Endpoint { // eslint- @Inject(DI.notesRepository) private notesRepository: NotesRepository, - private cacheService: CacheService, private noteEntityService: NoteEntityService, private featuredService: FeaturedService, private queryService: QueryService, diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index 601c5e7a48..816cd771be 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -12,7 +12,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; -import { CacheService } from '@/core/CacheService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -68,7 +67,6 @@ export default class extends Endpoint { // eslint- private queryService: QueryService, private roleService: RoleService, private activeUsersChart: ActiveUsersChart, - private readonly cacheService: CacheService, ) { super(meta, paramDef, async (ps, me) => { const policies = await this.roleService.getUserPolicies(me ? me.id : null); diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index 7ad1b0bf6e..c22d3fa5ab 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -12,7 +12,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { IdService } from '@/core/IdService.js'; -import { CacheService } from '@/core/CacheService.js'; import { FanoutTimelineName } from '@/core/FanoutTimelineService.js'; import { QueryService } from '@/core/QueryService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; @@ -90,7 +89,6 @@ export default class extends Endpoint { // eslint- private roleService: RoleService, private activeUsersChart: ActiveUsersChart, private idService: IdService, - private cacheService: CacheService, private queryService: QueryService, private userFollowingService: UserFollowingService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 73725f9af2..6b820f9fe9 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -15,7 +15,6 @@ import { IdService } from '@/core/IdService.js'; import { QueryService } from '@/core/QueryService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; -import { CacheService } from '@/core/CacheService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -84,7 +83,6 @@ export default class extends Endpoint { // eslint- private idService: IdService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private queryService: QueryService, - private readonly cacheService: CacheService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 27ba7399a7..826d342221 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -12,7 +12,6 @@ import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; -import { CacheService } from '@/core/CacheService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { MiLocalUser } from '@/models/User.js'; import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js'; @@ -71,7 +70,6 @@ export default class extends Endpoint { // eslint- private noteEntityService: NoteEntityService, private activeUsersChart: ActiveUsersChart, private idService: IdService, - private cacheService: CacheService, private fanoutTimelineEndpointService: FanoutTimelineEndpointService, private userFollowingService: UserFollowingService, private queryService: QueryService,