diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-02-04 14:23:38 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-02-04 14:23:38 +0900 |
| commit | 9bde9edcf68430cb98bc08ff74f6804bd56ecf47 (patch) | |
| tree | e09781a1f26bae331968902a79253820e3a80e4e /packages/backend/src/core | |
| parent | Merge branch 'develop' (diff) | |
| parent | 13.3.2 (diff) | |
| download | misskey-9bde9edcf68430cb98bc08ff74f6804bd56ecf47.tar.gz misskey-9bde9edcf68430cb98bc08ff74f6804bd56ecf47.tar.bz2 misskey-9bde9edcf68430cb98bc08ff74f6804bd56ecf47.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages/backend/src/core')
| -rw-r--r-- | packages/backend/src/core/AntennaService.ts | 18 | ||||
| -rw-r--r-- | packages/backend/src/core/CreateNotificationService.ts | 6 | ||||
| -rw-r--r-- | packages/backend/src/core/CustomEmojiService.ts | 2 | ||||
| -rw-r--r-- | packages/backend/src/core/DeleteAccountService.ts | 4 | ||||
| -rw-r--r-- | packages/backend/src/core/NoteCreateService.ts | 10 | ||||
| -rw-r--r-- | packages/backend/src/core/NoteDeleteService.ts | 4 | ||||
| -rw-r--r-- | packages/backend/src/core/NoteReadService.ts | 18 | ||||
| -rw-r--r-- | packages/backend/src/core/PollService.ts | 20 | ||||
| -rw-r--r-- | packages/backend/src/core/ReactionService.ts | 17 | ||||
| -rw-r--r-- | packages/backend/src/core/UserBlockingService.ts | 83 | ||||
| -rw-r--r-- | packages/backend/src/core/UserFollowingService.ts | 60 | ||||
| -rw-r--r-- | packages/backend/src/core/UserListService.ts | 4 | ||||
| -rw-r--r-- | packages/backend/src/core/UserMutingService.ts | 2 | ||||
| -rw-r--r-- | packages/backend/src/core/entities/ChannelEntityService.ts | 2 | ||||
| -rw-r--r-- | packages/backend/src/core/entities/DriveFileEntityService.ts | 40 | ||||
| -rw-r--r-- | packages/backend/src/core/entities/UserEntityService.ts | 8 |
16 files changed, 173 insertions, 125 deletions
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 7db8c43ea5..a71327e947 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -10,10 +10,9 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { PushNotificationService } from '@/core/PushNotificationService.js'; import * as Acct from '@/misc/acct.js'; -import { Cache } from '@/misc/cache.js'; import type { Packed } from '@/misc/schema.js'; import { DI } from '@/di-symbols.js'; -import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; +import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { StreamMessages } from '@/server/api/stream/types.js'; @@ -23,7 +22,6 @@ import type { OnApplicationShutdown } from '@nestjs/common'; export class AntennaService implements OnApplicationShutdown { private antennasFetched: boolean; private antennas: Antenna[]; - private blockingCache: Cache<User['id'][]>; constructor( @Inject(DI.redisSubscriber) @@ -32,9 +30,6 @@ export class AntennaService implements OnApplicationShutdown { @Inject(DI.mutingsRepository) private mutingsRepository: MutingsRepository, - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -52,14 +47,13 @@ export class AntennaService implements OnApplicationShutdown { private utilityService: UtilityService, private idService: IdService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private pushNotificationService: PushNotificationService, private noteEntityService: NoteEntityService, private antennaEntityService: AntennaEntityService, ) { this.antennasFetched = false; this.antennas = []; - this.blockingCache = new Cache<User['id'][]>(1000 * 60 * 5); this.redisSubscriber.on('message', this.onRedisMessage); } @@ -109,7 +103,7 @@ export class AntennaService implements OnApplicationShutdown { read: read, }); - this.globalEventServie.publishAntennaStream(antenna.id, 'note', note); + this.globalEventService.publishAntennaStream(antenna.id, 'note', note); if (!read) { const mutings = await this.mutingsRepository.find({ @@ -139,7 +133,7 @@ export class AntennaService implements OnApplicationShutdown { setTimeout(async () => { const unread = await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false }); if (unread) { - this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna); + this.globalEventService.publishMainStream(antenna.userId, 'unreadAntenna', antenna); this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', { antenna: { id: antenna.id, name: antenna.name }, note: await this.noteEntityService.pack(note), @@ -155,10 +149,6 @@ export class AntennaService implements OnApplicationShutdown { public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }): Promise<boolean> { if (note.visibility === 'specified') return false; if (note.visibility === 'followers') return false; - - // アンテナ作成者がノート作成者にブロックされていたらスキップ - const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId))); - if (blockings.some(blocking => blocking === antenna.userId)) return false; if (!antenna.withReplies && note.replyId != null) return false; diff --git a/packages/backend/src/core/CreateNotificationService.ts b/packages/backend/src/core/CreateNotificationService.ts index f376b7b9cf..cd47844a75 100644 --- a/packages/backend/src/core/CreateNotificationService.ts +++ b/packages/backend/src/core/CreateNotificationService.ts @@ -26,7 +26,7 @@ export class CreateNotificationService { private notificationEntityService: NotificationEntityService, private idService: IdService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private pushNotificationService: PushNotificationService, ) { } @@ -60,7 +60,7 @@ export class CreateNotificationService { const packed = await this.notificationEntityService.pack(notification, {}); // Publish notification event - this.globalEventServie.publishMainStream(notifieeId, 'notification', packed); + this.globalEventService.publishMainStream(notifieeId, 'notification', packed); // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する setTimeout(async () => { @@ -77,7 +77,7 @@ export class CreateNotificationService { } //#endregion - this.globalEventServie.publishMainStream(notifieeId, 'unreadNotification', packed); + this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed); this.pushNotificationService.pushNotification(notifieeId, 'notification', packed); if (type === 'follow') this.emailNotificationFollow(notifieeId, await this.usersRepository.findOneByOrFail({ id: data.notifierId! })); diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 39814e1be6..63f0319442 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -120,7 +120,7 @@ export class CustomEmojiService { const url = isLocal ? emojiUrl : this.config.proxyRemoteFiles - ? `${this.config.url}/proxy/${encodeURIComponent((new URL(emojiUrl)).pathname)}?${query({ url: emojiUrl })}` + ? `${this.config.mediaProxy}/emoji.webp?${query({ url: emojiUrl })}` : emojiUrl; return url; diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts index 0ac12857c9..2acb5f2303 100644 --- a/packages/backend/src/core/DeleteAccountService.ts +++ b/packages/backend/src/core/DeleteAccountService.ts @@ -14,7 +14,7 @@ export class DeleteAccountService { private userSuspendService: UserSuspendService, private queueService: QueueService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, ) { } @@ -38,6 +38,6 @@ export class DeleteAccountService { }); // Terminate streaming - this.globalEventServie.publishUserEvent(user.id, 'terminate', {}); + this.globalEventService.publishUserEvent(user.id, 'terminate', {}); } } diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 3dc44a25fe..4a81f764dc 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -175,7 +175,7 @@ export class NoteCreateService { private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, private idService: IdService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private queueService: QueueService, private noteReadService: NoteReadService, private createNotificationService: CreateNotificationService, @@ -535,7 +535,7 @@ export class NoteCreateService { // Pack the note const noteObj = await this.noteEntityService.pack(note); - this.globalEventServie.publishNotesStream(noteObj); + this.globalEventService.publishNotesStream(noteObj); this.webhookService.getActiveWebhooks().then(webhooks => { webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note')); @@ -561,7 +561,7 @@ export class NoteCreateService { if (!threadMuted) { nm.push(data.reply.userId, 'reply'); - this.globalEventServie.publishMainStream(data.reply.userId, 'reply', noteObj); + this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply')); for (const webhook of webhooks) { @@ -584,7 +584,7 @@ export class NoteCreateService { // Publish event if ((user.id !== data.renote.userId) && data.renote.userHost === null) { - this.globalEventServie.publishMainStream(data.renote.userId, 'renote', noteObj); + this.globalEventService.publishMainStream(data.renote.userId, 'renote', noteObj); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote')); for (const webhook of webhooks) { @@ -684,7 +684,7 @@ export class NoteCreateService { detail: true, }); - this.globalEventServie.publishMainStream(u.id, 'mention', detailPackedNote); + this.globalEventService.publishMainStream(u.id, 'mention', detailPackedNote); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention')); for (const webhook of webhooks) { diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index b1f16b6e8a..4dad825097 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -34,7 +34,7 @@ export class NoteDeleteService { private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private relayService: RelayService, private federatedInstanceService: FederatedInstanceService, private apRendererService: ApRendererService, @@ -63,7 +63,7 @@ export class NoteDeleteService { } if (!quiet) { - this.globalEventServie.publishNoteStream(note.id, 'deleted', { + this.globalEventService.publishNoteStream(note.id, 'deleted', { deletedAt: deletedAt, }); diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index f4395725d6..84983d600e 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -40,7 +40,7 @@ export class NoteReadService { private userEntityService: UserEntityService, private idService: IdService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private notificationService: NotificationService, private antennaService: AntennaService, private pushNotificationService: PushNotificationService, @@ -87,13 +87,13 @@ export class NoteReadService { if (exist == null) return; if (params.isMentioned) { - this.globalEventServie.publishMainStream(userId, 'unreadMention', note.id); + this.globalEventService.publishMainStream(userId, 'unreadMention', note.id); } if (params.isSpecified) { - this.globalEventServie.publishMainStream(userId, 'unreadSpecifiedNote', note.id); + this.globalEventService.publishMainStream(userId, 'unreadSpecifiedNote', note.id); } if (note.channelId) { - this.globalEventServie.publishMainStream(userId, 'unreadChannel', note.id); + this.globalEventService.publishMainStream(userId, 'unreadChannel', note.id); } }, 2000); } @@ -155,7 +155,7 @@ export class NoteReadService { }).then(mentionsCount => { if (mentionsCount === 0) { // 全て既読になったイベントを発行 - this.globalEventServie.publishMainStream(userId, 'readAllUnreadMentions'); + this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions'); } }); @@ -165,7 +165,7 @@ export class NoteReadService { }).then(specifiedCount => { if (specifiedCount === 0) { // 全て既読になったイベントを発行 - this.globalEventServie.publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); + this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); } }); @@ -175,7 +175,7 @@ export class NoteReadService { }).then(channelNoteCount => { if (channelNoteCount === 0) { // 全て既読になったイベントを発行 - this.globalEventServie.publishMainStream(userId, 'readAllChannels'); + this.globalEventService.publishMainStream(userId, 'readAllChannels'); } }); @@ -200,14 +200,14 @@ export class NoteReadService { }); if (count === 0) { - this.globalEventServie.publishMainStream(userId, 'readAntenna', antenna); + this.globalEventService.publishMainStream(userId, 'readAntenna', antenna); this.pushNotificationService.pushNotification(userId, 'readAntenna', { antennaId: antenna.id }); } } this.userEntityService.getHasUnreadAntenna(userId).then(unread => { if (!unread) { - this.globalEventServie.publishMainStream(userId, 'readAllAntennas'); + this.globalEventService.publishMainStream(userId, 'readAllAntennas'); this.pushNotificationService.pushNotification(userId, 'readAllAntennas', undefined); } }); diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index abc598ab76..042dcb3e67 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -1,17 +1,17 @@ import { Inject, Injectable } from '@nestjs/common'; import { Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { NotesRepository, UsersRepository, BlockingsRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; +import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import type { Note } from '@/models/entities/Note.js'; import { RelayService } from '@/core/RelayService.js'; import type { CacheableUser } from '@/models/entities/User.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; -import { CreateNotificationService } from '@/core/CreateNotificationService.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'; @Injectable() export class PollService { @@ -28,14 +28,11 @@ export class PollService { @Inject(DI.pollVotesRepository) private pollVotesRepository: PollVotesRepository, - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - private userEntityService: UserEntityService, private idService: IdService, private relayService: RelayService, - private globalEventServie: GlobalEventService, - private createNotificationService: CreateNotificationService, + private globalEventService: GlobalEventService, + private userBlockingService: UserBlockingService, private apRendererService: ApRendererService, private apDeliverManagerService: ApDeliverManagerService, ) { @@ -52,11 +49,8 @@ export class PollService { // Check blocking if (note.userId !== user.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: note.userId, - blockeeId: user.id, - }); - if (block) { + const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); + if (blocked) { throw new Error('blocked'); } } @@ -88,7 +82,7 @@ export class PollService { const index = choice + 1; // In SQL, array index is 1 based await this.pollsRepository.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`); - this.globalEventServie.publishNoteStream(note.id, 'pollVoted', { + this.globalEventService.publishNoteStream(note.id, 'pollVoted', { choice: choice, userId: user.id, }); diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 0c1c3d0a3b..3806590059 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -18,7 +18,8 @@ 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 './UtilityService.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { UserBlockingService } from '@/core/UserBlockingService.js'; const legacies: Record<string, string> = { 'like': '👍', @@ -73,8 +74,9 @@ export class ReactionService { private metaService: MetaService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, + private userBlockingService: UserBlockingService, private idService: IdService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private apRendererService: ApRendererService, private apDeliverManagerService: ApDeliverManagerService, private createNotificationService: CreateNotificationService, @@ -86,11 +88,8 @@ export class ReactionService { public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, reaction?: string) { // Check blocking if (note.userId !== user.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: note.userId, - blockeeId: user.id, - }); - if (block) { + const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); + if (blocked) { throw new IdentifiableError('e70412a4-7197-4726-8e74-f3e0deb92aa7'); } } @@ -157,7 +156,7 @@ export class ReactionService { select: ['name', 'host', 'originalUrl', 'publicUrl'], }); - this.globalEventServie.publishNoteStream(note.id, 'reacted', { + this.globalEventService.publishNoteStream(note.id, 'reacted', { reaction: decodedReaction.reaction, emoji: emoji != null ? { name: emoji.host ? `${emoji.name}@${emoji.host}` : `${emoji.name}@.`, @@ -229,7 +228,7 @@ export class ReactionService { if (!user.isBot) this.notesRepository.decrement({ id: note.id }, 'score', 1); - this.globalEventServie.publishNoteStream(note.id, 'unreacted', { + this.globalEventService.publishNoteStream(note.id, 'unreacted', { reaction: this.decodeReaction(exist.reaction).reaction, userId: user.id, }); diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts index c923700427..d734328669 100644 --- a/packages/backend/src/core/UserBlockingService.ts +++ b/packages/backend/src/core/UserBlockingService.ts @@ -1,5 +1,6 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; +import Redis from 'ioredis'; import { IdService } from '@/core/IdService.js'; import type { CacheableUser, User } from '@/models/entities/User.js'; import type { Blocking } from '@/models/entities/Blocking.js'; @@ -7,7 +8,6 @@ import { QueueService } from '@/core/QueueService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import { DI } from '@/di-symbols.js'; -import logger from '@/logger.js'; import type { UsersRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/index.js'; import Logger from '@/logger.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -15,12 +15,20 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { WebhookService } from '@/core/WebhookService.js'; import { bindThis } from '@/decorators.js'; +import { Cache } from '@/misc/cache.js'; +import { StreamMessages } from '@/server/api/stream/types.js'; @Injectable() -export class UserBlockingService { +export class UserBlockingService implements OnApplicationShutdown { private logger: Logger; + // キーがユーザーIDで、値がそのユーザーがブロックしているユーザーのIDのリストなキャッシュ + private blockingsByUserIdCache: Cache<User['id'][]>; + constructor( + @Inject(DI.redisSubscriber) + private redisSubscriber: Redis.Redis, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -42,13 +50,44 @@ export class UserBlockingService { private userEntityService: UserEntityService, private idService: IdService, private queueService: QueueService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private webhookService: WebhookService, private apRendererService: ApRendererService, private perUserFollowingChart: PerUserFollowingChart, private loggerService: LoggerService, ) { this.logger = this.loggerService.getLogger('user-block'); + + this.blockingsByUserIdCache = new Cache<User['id'][]>(Infinity); + + this.redisSubscriber.on('message', this.onMessage); + } + + @bindThis + private async onMessage(_: string, data: string): Promise<void> { + const obj = JSON.parse(data); + + if (obj.channel === 'internal') { + const { type, body } = obj.message as StreamMessages['internal']['payload']; + switch (type) { + case 'blockingCreated': { + const cached = this.blockingsByUserIdCache.get(body.blockerId); + if (cached) { + this.blockingsByUserIdCache.set(body.blockerId, [...cached, ...[body.blockeeId]]); + } + break; + } + case 'blockingDeleted': { + const cached = this.blockingsByUserIdCache.get(body.blockerId); + if (cached) { + this.blockingsByUserIdCache.set(body.blockerId, cached.filter(x => x !== body.blockeeId)); + } + break; + } + default: + break; + } + } } @bindThis @@ -72,6 +111,11 @@ export class UserBlockingService { await this.blockingsRepository.insert(blocking); + this.globalEventService.publishInternalEvent('blockingCreated', { + blockerId: blocker.id, + blockeeId: blockee.id, + }); + if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) { const content = this.apRendererService.renderActivity(this.apRendererService.renderBlock(blocking)); this.queueService.deliver(blocker, content, blockee.inbox); @@ -97,15 +141,15 @@ export class UserBlockingService { if (this.userEntityService.isLocalUser(followee)) { this.userEntityService.pack(followee, followee, { detail: true, - }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); + }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } if (this.userEntityService.isLocalUser(follower)) { this.userEntityService.pack(followee, follower, { detail: true, }).then(async packed => { - this.globalEventServie.publishUserEvent(follower.id, 'unfollow', packed); - this.globalEventServie.publishMainStream(follower.id, 'unfollow', packed); + this.globalEventService.publishUserEvent(follower.id, 'unfollow', packed); + this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); for (const webhook of webhooks) { @@ -152,8 +196,8 @@ export class UserBlockingService { this.userEntityService.pack(followee, follower, { detail: true, }).then(async packed => { - this.globalEventServie.publishUserEvent(follower.id, 'unfollow', packed); - this.globalEventServie.publishMainStream(follower.id, 'unfollow', packed); + this.globalEventService.publishUserEvent(follower.id, 'unfollow', packed); + this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); for (const webhook of webhooks) { @@ -210,10 +254,31 @@ export class UserBlockingService { await this.blockingsRepository.delete(blocking.id); + this.globalEventService.publishInternalEvent('blockingDeleted', { + blockerId: blocker.id, + blockeeId: blockee.id, + }); + // deliver if remote bloking if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) { const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(this.apRendererService.renderBlock(blocking), blocker)); this.queueService.deliver(blocker, content, blockee.inbox); } } + + @bindThis + public async checkBlocked(blockerId: User['id'], blockeeId: User['id']): Promise<boolean> { + const blockedUserIds = await this.blockingsByUserIdCache.fetch(blockerId, () => this.blockingsRepository.find({ + where: { + blockerId, + }, + select: ['blockeeId'], + }).then(records => records.map(record => record.blockeeId))); + return blockedUserIds.includes(blockeeId); + } + + @bindThis + public onApplicationShutdown(signal?: string | undefined) { + this.redisSubscriber.off('message', this.onMessage); + } } diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index f1ce311cea..18a2ef5c05 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -12,10 +12,11 @@ import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { WebhookService } from '@/core/WebhookService.js'; import { CreateNotificationService } from '@/core/CreateNotificationService.js'; import { DI } from '@/di-symbols.js'; -import type { BlockingsRepository, FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; +import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js'; 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 Logger from '../logger.js'; const logger = new Logger('following/create'); @@ -48,21 +49,18 @@ export class UserFollowingService { @Inject(DI.followRequestsRepository) private followRequestsRepository: FollowRequestsRepository, - @Inject(DI.blockingsRepository) - private blockingsRepository: BlockingsRepository, - @Inject(DI.instancesRepository) private instancesRepository: InstancesRepository, private userEntityService: UserEntityService, + private userBlockingService: UserBlockingService, private idService: IdService, private queueService: QueueService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private createNotificationService: CreateNotificationService, private federatedInstanceService: FederatedInstanceService, private webhookService: WebhookService, private apRendererService: ApRendererService, - private globalEventService: GlobalEventService, private perUserFollowingChart: PerUserFollowingChart, private instanceChart: InstanceChart, ) { @@ -77,26 +75,20 @@ export class UserFollowingService { // check blocking const [blocking, blocked] = await Promise.all([ - this.blockingsRepository.findOneBy({ - blockerId: follower.id, - blockeeId: followee.id, - }), - this.blockingsRepository.findOneBy({ - blockerId: followee.id, - blockeeId: follower.id, - }), + this.userBlockingService.checkBlocked(follower.id, followee.id), + this.userBlockingService.checkBlocked(followee.id, follower.id), ]); if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocked) { - // リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。 + // リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。 const content = this.apRendererService.renderActivity(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee, requestId), followee)); this.queueService.deliver(followee, content, follower.inbox); return; } else if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocking) { - // リモートフォローを受けてブロックされているはずの場合だったら、ブロック解除しておく。 - await this.blockingsRepository.delete(blocking.id); + // リモートフォローを受けてブロックされているはずの場合だったら、ブロック解除しておく。 + await this.userBlockingService.unblock(follower, followee); } else { - // それ以外は単純に例外 + // それ以外は単純に例外 if (blocking != null) throw new IdentifiableError('710e8fb0-b8c3-4922-be49-d5d93d8e6a6e', 'blocking'); if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); } @@ -227,8 +219,8 @@ export class UserFollowingService { this.userEntityService.pack(followee.id, follower, { detail: true, }).then(async packed => { - this.globalEventServie.publishUserEvent(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>); - this.globalEventServie.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>); + this.globalEventService.publishUserEvent(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>); + this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow')); for (const webhook of webhooks) { @@ -242,7 +234,7 @@ export class UserFollowingService { // Publish followed event if (this.userEntityService.isLocalUser(followee)) { this.userEntityService.pack(follower.id, followee).then(async packed => { - this.globalEventServie.publishMainStream(followee.id, 'followed', packed); + this.globalEventService.publishMainStream(followee.id, 'followed', packed); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === followee.id && x.on.includes('followed')); for (const webhook of webhooks) { @@ -288,8 +280,8 @@ export class UserFollowingService { this.userEntityService.pack(followee.id, follower, { detail: true, }).then(async packed => { - this.globalEventServie.publishUserEvent(follower.id, 'unfollow', packed); - this.globalEventServie.publishMainStream(follower.id, 'unfollow', packed); + this.globalEventService.publishUserEvent(follower.id, 'unfollow', packed); + this.globalEventService.publishMainStream(follower.id, 'unfollow', packed); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); for (const webhook of webhooks) { @@ -357,14 +349,8 @@ export class UserFollowingService { // check blocking const [blocking, blocked] = await Promise.all([ - this.blockingsRepository.findOneBy({ - blockerId: follower.id, - blockeeId: followee.id, - }), - this.blockingsRepository.findOneBy({ - blockerId: followee.id, - blockeeId: follower.id, - }), + this.userBlockingService.checkBlocked(follower.id, followee.id), + this.userBlockingService.checkBlocked(followee.id, follower.id), ]); if (blocking != null) throw new Error('blocking'); @@ -388,11 +374,11 @@ export class UserFollowingService { // Publish receiveRequest event if (this.userEntityService.isLocalUser(followee)) { - this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventServie.publishMainStream(followee.id, 'receiveFollowRequest', packed)); + this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed)); this.userEntityService.pack(followee.id, followee, { detail: true, - }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); + }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); // 通知を作成 this.createNotificationService.createNotification(followee.id, 'receiveFollowRequest', { @@ -440,7 +426,7 @@ export class UserFollowingService { this.userEntityService.pack(followee.id, followee, { detail: true, - }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); + }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } @bindThis @@ -468,7 +454,7 @@ export class UserFollowingService { this.userEntityService.pack(followee.id, followee, { detail: true, - }).then(packed => this.globalEventServie.publishMainStream(followee.id, 'meUpdated', packed)); + }).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed)); } @bindThis @@ -583,8 +569,8 @@ export class UserFollowingService { detail: true, }); - this.globalEventServie.publishUserEvent(follower.id, 'unfollow', packedFollowee); - this.globalEventServie.publishMainStream(follower.id, 'unfollow', packedFollowee); + this.globalEventService.publishUserEvent(follower.id, 'unfollow', packedFollowee); + this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee); const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('unfollow')); for (const webhook of webhooks) { diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index fc48738307..c174394999 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -25,7 +25,7 @@ export class UserListService { private idService: IdService, private userFollowingService: UserFollowingService, private roleService: RoleService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, private proxyAccountService: ProxyAccountService, ) { } @@ -46,7 +46,7 @@ export class UserListService { userListId: list.id, } as UserListJoining); - this.globalEventServie.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target)); + this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target)); // このインスタンス内にこのリモートユーザーをフォローしているユーザーがいなくても投稿を受け取るためにダミーのユーザーがフォローしたということにする if (this.userEntityService.isRemoteUser(target)) { diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts index 3029d02c00..e98f11709f 100644 --- a/packages/backend/src/core/UserMutingService.ts +++ b/packages/backend/src/core/UserMutingService.ts @@ -18,7 +18,7 @@ export class UserMutingService { private idService: IdService, private queueService: QueueService, - private globalEventServie: GlobalEventService, + private globalEventService: GlobalEventService, ) { } diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 5e2f019a12..6ce590aa96 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -54,7 +54,7 @@ export class ChannelEntityService { name: channel.name, description: channel.description, userId: channel.userId, - bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner, false) : null, + bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null, usersCount: channel.usersCount, notesCount: channel.notesCount, diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 7f54cfdeac..efc196f74a 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -71,27 +71,41 @@ export class DriveFileEntityService { } @bindThis - public getPublicUrl(file: DriveFile, thumbnail = false): string | null { + public getPublicUrl(file: DriveFile, mode? : 'static' | 'avatar'): string | null { // static = thumbnail + const proxiedUrl = (url: string) => appendQuery( + `${this.config.mediaProxy}/${mode ?? 'image'}.webp`, + query({ + url, + ...(mode ? { [mode]: '1' } : {}), + }) + ); + // リモートかつメディアプロキシ - if (file.uri != null && file.userHost != null && this.config.mediaProxy != null) { - return appendQuery(this.config.mediaProxy, query({ - url: file.uri, - thumbnail: thumbnail ? '1' : undefined, - })); + if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { + return proxiedUrl(file.uri); } // リモートかつ期限切れはローカルプロキシを試みる if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) { - const key = thumbnail ? file.thumbnailAccessKey : file.webpublicAccessKey; + const key = mode === 'static' ? file.thumbnailAccessKey : file.webpublicAccessKey; if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外 - return `${this.config.url}/files/${key}`; + const url = `${this.config.url}/files/${key}`; + if (mode === 'avatar') return proxiedUrl(url); + return url; } } const isImage = file.type && ['image/png', 'image/apng', 'image/gif', 'image/jpeg', 'image/webp', 'image/avif', 'image/svg+xml'].includes(file.type); - return thumbnail ? (file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null)) : (file.webpublicUrl ?? file.url); + if (mode === 'static') { + return file.thumbnailUrl ?? (isImage ? (file.webpublicUrl ?? file.url) : null); + } + + const url = file.webpublicUrl ?? file.url; + + if (mode === 'avatar') return proxiedUrl(url); + return url; } @bindThis @@ -166,8 +180,8 @@ export class DriveFileEntityService { isSensitive: file.isSensitive, blurhash: file.blurhash, properties: opts.self ? file.properties : this.getPublicProperties(file), - url: opts.self ? file.url : this.getPublicUrl(file, false), - thumbnailUrl: this.getPublicUrl(file, true), + url: opts.self ? file.url : this.getPublicUrl(file), + thumbnailUrl: this.getPublicUrl(file, 'static'), comment: file.comment, folderId: file.folderId, folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, { @@ -201,8 +215,8 @@ export class DriveFileEntityService { isSensitive: file.isSensitive, blurhash: file.blurhash, properties: opts.self ? file.properties : this.getPublicProperties(file), - url: opts.self ? file.url : this.getPublicUrl(file, false), - thumbnailUrl: this.getPublicUrl(file, true), + url: opts.self ? file.url : this.getPublicUrl(file), + thumbnailUrl: this.getPublicUrl(file, 'static'), comment: file.comment, folderId: file.folderId, folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, { diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index aaa80033b3..ff42c07359 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -314,10 +314,10 @@ export class UserEntityService implements OnModuleInit { @bindThis public async getAvatarUrl(user: User): Promise<string> { if (user.avatar) { - return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id); + return this.driveFileEntityService.getPublicUrl(user.avatar, 'avatar') ?? this.getIdenticonUrl(user.id); } else if (user.avatarId) { const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId }); - return this.driveFileEntityService.getPublicUrl(avatar, true) ?? this.getIdenticonUrl(user.id); + return this.driveFileEntityService.getPublicUrl(avatar, 'avatar') ?? this.getIdenticonUrl(user.id); } else { return this.getIdenticonUrl(user.id); } @@ -326,7 +326,7 @@ export class UserEntityService implements OnModuleInit { @bindThis public getAvatarUrlSync(user: User): string { if (user.avatar) { - return this.driveFileEntityService.getPublicUrl(user.avatar, true) ?? this.getIdenticonUrl(user.id); + return this.driveFileEntityService.getPublicUrl(user.avatar, 'avatar') ?? this.getIdenticonUrl(user.id); } else { return this.getIdenticonUrl(user.id); } @@ -422,7 +422,7 @@ export class UserEntityService implements OnModuleInit { createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null, - bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner, false) : null, + bannerUrl: user.banner ? this.driveFileEntityService.getPublicUrl(user.banner) : null, bannerBlurhash: user.banner?.blurhash ?? null, isLocked: user.isLocked, isSilenced: this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), |