copy changes to NoteEditService.ts

This commit is contained in:
Hazelnoot 2025-04-01 10:29:58 -04:00
parent 0e3f2a90cf
commit ea0371de34

View file

@ -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<MiNote> {
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!,
text: note.text ?? '',
});
} else {
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<string>()];
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;