copy changes from NoteCreateService to NoteEditService

This commit is contained in:
dakkar 2024-10-09 15:40:00 +01:00
parent b4c53687b4
commit 1a656e8eb5

View file

@ -8,7 +8,6 @@ import * as mfm from '@transfem-org/sfm-js';
import { DataSource, In, IsNull, LessThan } from 'typeorm'; import { DataSource, In, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import RE2 from 're2';
import { extractMentions } from '@/misc/extract-mentions.js'; import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js'; import { extractHashtags } from '@/misc/extract-hashtags.js';
@ -42,7 +41,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js'; import { SearchService } from '@/core/SearchService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
@ -52,6 +50,8 @@ import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js'; import { trackPromise } from '@/misc/promise-tracker.js';
import { isUserRelated } from '@/misc/is-user-related.js'; import { isUserRelated } from '@/misc/is-user-related.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited';
@ -145,11 +145,15 @@ type Option = {
@Injectable() @Injectable()
export class NoteEditService implements OnApplicationShutdown { export class NoteEditService implements OnApplicationShutdown {
#shutdownController = new AbortController(); #shutdownController = new AbortController();
private updateNotesCountQueue: CollapsedQueue<MiNote['id'], number>;
constructor( constructor(
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@ -207,14 +211,16 @@ export class NoteEditService implements OnApplicationShutdown {
private apDeliverManagerService: ApDeliverManagerService, private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private roleService: RoleService, private roleService: RoleService,
private metaService: MetaService,
private searchService: SearchService, private searchService: SearchService,
private activeUsersChart: ActiveUsersChart, private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
private utilityService: UtilityService, private utilityService: UtilityService,
private userBlockingService: UserBlockingService, private userBlockingService: UserBlockingService,
private cacheService: CacheService, private cacheService: CacheService,
) { } private noteCreateService: NoteCreateService,
) {
this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount);
}
@bindThis @bindThis
public async edit(user: { public async edit(user: {
@ -275,10 +281,8 @@ export class NoteEditService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true; if (data.channel != null) data.localOnly = true;
if (data.updatedAt == null) data.updatedAt = new Date(); if (data.updatedAt == null) data.updatedAt = new Date();
const meta = await this.metaService.fetch();
if (data.visibility === 'public' && data.channel == null) { if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = meta.sensitiveWords; const sensitiveWords = this.meta.sensitiveWords;
if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) { if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home'; data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
@ -286,17 +290,17 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
const hasProhibitedWords = await this.checkProhibitedWordsContain({ const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({
cw: data.cw, cw: data.cw,
text: data.text, text: data.text,
pollChoices: data.poll?.choices, pollChoices: data.poll?.choices,
}, meta.prohibitedWords); }, this.meta.prohibitedWords);
if (hasProhibitedWords) { if (hasProhibitedWords) {
throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words'); throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
} }
const inSilencedInstance = this.utilityService.isSilencedHost((meta).silencedHosts, user.host); const inSilencedInstance = this.utilityService.isSilencedHost(this.meta.silencedHosts, user.host);
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
data.visibility = 'home'; data.visibility = 'home';
@ -405,7 +409,7 @@ export class NoteEditService implements OnApplicationShutdown {
} }
// if the host is media-silenced, custom emojis are not allowed // if the host is media-silenced, custom emojis are not allowed
if (this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, user.host)) emojis = []; 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);
@ -579,8 +583,8 @@ export class NoteEditService implements OnApplicationShutdown {
// Register host // Register host
if (this.userEntityService.isRemoteUser(user)) { if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetch(user.host).then(async i => { this.federatedInstanceService.fetch(user.host).then(async i => {
this.instancesRepository.increment({ id: i.id }, 'notesCount', 1); this.updateNotesCountQueue.enqueue(i.id, 1);
if ((await this.metaService.fetch()).enableChartsForFederatedInstances) { if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, true); this.instanceChart.updateNote(i.host, note, true);
} }
}); });
@ -866,15 +870,14 @@ export class NoteEditService implements OnApplicationShutdown {
@bindThis @bindThis
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
const meta = await this.metaService.fetch(); if (!this.meta.enableFanoutTimeline) return;
if (!meta.enableFanoutTimeline) return;
const r = this.redisForTimelines.pipeline(); const r = this.redisForTimelines.pipeline();
if (note.channelId) { if (note.channelId) {
this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r); this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
const channelFollowings = await this.channelFollowingsRepository.find({ const channelFollowings = await this.channelFollowingsRepository.find({
where: { where: {
@ -884,9 +887,9 @@ export class NoteEditService implements OnApplicationShutdown {
}); });
for (const channelFollowing of channelFollowings) { for (const channelFollowing of channelFollowings) {
this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
} else { } else {
@ -924,9 +927,9 @@ export class NoteEditService implements OnApplicationShutdown {
if (!following.withReplies) continue; if (!following.withReplies) continue;
} }
this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
@ -943,25 +946,25 @@ export class NoteEditService implements OnApplicationShutdown {
if (!userListMembership.withReplies) continue; if (!userListMembership.withReplies) continue;
} }
this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r); this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax / 2, r);
} }
} }
// 自分自身のHTL // 自分自身のHTL
if (note.userHost == null) { if (note.userHost == null) {
if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r); this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
} }
} }
} }
// 自分自身以外への返信 // 自分自身以外への返信
if (isReply(note)) { if (isReply(note)) {
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
if (note.visibility === 'public' && note.userHost == null) { if (note.visibility === 'public' && note.userHost == null) {
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r); this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
@ -970,9 +973,9 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
} else { } else {
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
if (note.fileIds.length > 0) { if (note.fileIds.length > 0) {
this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r); this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax / 2 : this.meta.perRemoteUserUserTimelineCacheMax / 2, r);
} }
if (note.visibility === 'public' && note.userHost == null) { if (note.visibility === 'public' && note.userHost == null) {
@ -1031,30 +1034,24 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) { @bindThis
if (prohibitedWords == null) { private collapseNotesCount(oldValue: number, newValue: number) {
prohibitedWords = (await this.metaService.fetch()).prohibitedWords; return oldValue + newValue;
}
if (
this.utilityService.isKeyWordIncluded(
this.utilityService.concatNoteContentsForKeyWordCheck(content),
prohibitedWords,
)
) {
return true;
}
return false;
} }
@bindThis @bindThis
public dispose(): void { private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) {
await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy);
}
@bindThis
public async dispose(): Promise<void> {
this.#shutdownController.abort(); this.#shutdownController.abort();
await this.updateNotesCountQueue.performAllNow();
} }
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined): void { public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
this.dispose(); await this.dispose();
} }
} }