diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index ce6e6a9f03..7722beefc9 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -8,6 +8,7 @@ import * as mfm from '@transfem-org/sfm-js'; import { DataSource, In, IsNull, LessThan } from 'typeorm'; import * as Redis from 'ioredis'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; +import { UnrecoverableError } from 'bullmq'; import { extractMentions } from '@/misc/extract-mentions.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; @@ -36,7 +37,6 @@ 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 { NoteReadService } from '@/core/NoteReadService.js'; import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; @@ -203,7 +203,6 @@ export class NoteEditService implements OnApplicationShutdown { private globalEventService: GlobalEventService, private queueService: QueueService, private fanoutTimelineService: FanoutTimelineService, - private noteReadService: NoteReadService, private notificationService: NotificationService, private relayService: RelayService, private federatedInstanceService: FederatedInstanceService, @@ -233,7 +232,7 @@ export class NoteEditService implements OnApplicationShutdown { noindex: MiUser['noindex']; }, editid: MiNote['id'], data: Option, silent = false): Promise { if (!editid) { - throw new Error('fail'); + throw new UnrecoverableError('edit failed: missing editid'); } const oldnote = await this.notesRepository.findOneBy({ @@ -241,11 +240,11 @@ export class NoteEditService implements OnApplicationShutdown { }); if (oldnote == null) { - throw new Error('no such note'); + throw new UnrecoverableError('edit failed: missing oldnote'); } if (oldnote.userId !== user.id) { - throw new Error('not the author'); + throw new UnrecoverableError(`edit failed for ${oldnote.id}: user is not the note author`); } // we never want to change the replyId, so fetch the original "parent" @@ -276,12 +275,12 @@ export class NoteEditService implements OnApplicationShutdown { data.channel = await this.channelsRepository.findOneBy({ id: data.reply.channelId }); } + if (data.updatedAt == null) data.updatedAt = new Date(); if (data.visibility == null) data.visibility = 'public'; if (data.localOnly == null) data.localOnly = false; if (data.channel != null) data.visibility = 'public'; if (data.channel != null) data.visibleUsers = []; if (data.channel != null) data.localOnly = true; - if (data.updatedAt == null) data.updatedAt = new Date(); if (data.visibility === 'public' && data.channel == null) { const sensitiveWords = this.meta.sensitiveWords; @@ -310,7 +309,7 @@ export class NoteEditService implements OnApplicationShutdown { if (this.isRenote(data)) { if (data.renote.id === oldnote.id) { - throw new Error('A note can\'t renote itself'); + throw new UnrecoverableError(`edit failed for ${oldnote.id}: cannot renote itself`); } switch (data.renote.visibility) { @@ -406,10 +405,10 @@ export class NoteEditService implements OnApplicationShutdown { // Parse MFM if needed if (!tags || !emojis || !mentionedUsers) { - const tokens = data.text ? mfm.parse(data.text)! : []; - const cwTokens = data.cw ? mfm.parse(data.cw)! : []; + const tokens = data.text ? mfm.parse(data.text) : []; + const cwTokens = data.cw ? mfm.parse(data.cw) : []; const choiceTokens = data.poll && data.poll.choices - ? concat(data.poll.choices.map(choice => mfm.parse(choice)!)) + ? concat(data.poll.choices.map(choice => mfm.parse(choice))) : []; const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens); @@ -424,7 +423,7 @@ export class NoteEditService implements OnApplicationShutdown { // if the host is media-silenced, custom emojis are not allowed if (this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) emojis = []; - tags = tags.filter(tag => Array.from(tag ?? '').length <= 128).splice(0, 32); + tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32); if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) { mentionedUsers.push(await this.usersRepository.findOneByOrFail({ id: data.reply!.userId })); @@ -467,10 +466,8 @@ export class NoteEditService implements OnApplicationShutdown { update.hasPoll = !!data.poll; } - // technically we should check if the two sets of files are - // different, or if their descriptions have changed. In practice - // this is good enough. - const filesChanged = oldnote.fileIds?.length || data.files?.length; + // TODO deep-compare files + const filesChanged = oldnote.fileIds.length || data.files?.length; const poll = await this.pollsRepository.findOneBy({ noteId: oldnote.id }); @@ -564,7 +561,7 @@ export class NoteEditService implements OnApplicationShutdown { noteVisibility: note.visibility, userId: user.id, userHost: user.host, - channelId: data.channel ? data.channel.id : null, + channelId: data.channel?.id ?? null, }); if (!oldnote.hasPoll) { @@ -628,44 +625,12 @@ export class NoteEditService implements OnApplicationShutdown { if (!silent) { if (this.userEntityService.isLocalUser(user)) this.activeUsersChart.write(user); - // 未読通知を作成 - if (data.visibility === 'specified') { - if (data.visibleUsers == null) throw new Error('invalid param'); - - for (const u of data.visibleUsers) { - // ローカルユーザーのみ - if (!this.userEntityService.isLocalUser(u)) continue; - - this.noteReadService.insertNoteUnread(u.id, note, { - isSpecified: true, - isMentioned: false, - }); - } - } else { - for (const u of mentionedUsers) { - // ローカルユーザーのみ - if (!this.userEntityService.isLocalUser(u)) continue; - - this.noteReadService.insertNoteUnread(u.id, note, { - isSpecified: false, - isMentioned: true, - }); - } - } - // Pack the note const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true }); - if (data.poll != null) { - this.globalEventService.publishNoteStream(note.id, 'updated', { - cw: note.cw, - text: note.text!, - }); - } else { - this.globalEventService.publishNoteStream(note.id, 'updated', { - cw: note.cw, - text: note.text!, - }); - } + this.globalEventService.publishNoteStream(note.id, 'updated', { + cw: note.cw, + text: note.text ?? '', + }); this.roleService.addNoteToRoleTimeline(noteObj); @@ -673,8 +638,6 @@ export class NoteEditService implements OnApplicationShutdown { const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note); - //await this.createMentionedEvents(mentionedUsers, note, nm); - // If has in reply to note if (data.reply) { // 通知 @@ -697,7 +660,6 @@ export class NoteEditService implements OnApplicationShutdown { if (!isThreadMuted && !muted) { nm.push(data.reply.userId, 'edited'); this.globalEventService.publishMainStream(data.reply.userId, 'edited', noteObj); - this.webhookService.enqueueUserWebhook(data.reply.userId, 'reply', { note: noteObj }); } } @@ -803,42 +765,6 @@ export class NoteEditService implements OnApplicationShutdown { (note.files != null && note.files.length > 0); } - // TODO why is this unused? - @bindThis - private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) { - // FIXME only users the note is visible to should receive a notification, same as when a note is created - for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { - const isThreadMuted = await this.noteThreadMutingsRepository.exists({ - where: { - userId: u.id, - threadId: note.threadId ?? note.id, - }, - }); - - const [ - userIdsWhoMeMuting, - ] = u.id ? await Promise.all([ - this.cacheService.userMutingsCache.fetch(u.id), - ]) : [new Set()]; - - const muted = isUserRelated(note, userIdsWhoMeMuting); - - if (isThreadMuted || muted) { - continue; - } - - const detailPackedNote = await this.noteEntityService.pack(note, u, { - detail: true, - }); - - this.globalEventService.publishMainStream(u.id, 'edited', detailPackedNote); - this.webhookService.enqueueUserWebhook(u.id, 'edited', { note: detailPackedNote }); - - // Create notification - nm.push(u.id, 'edited'); - } - } - @bindThis private async renderNoteOrRenoteActivity(data: Option, note: MiNote, user: MiUser) { if (data.localOnly) return null;