summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/NoteEditService.ts
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-10-18 21:14:49 +0000
committerdakkar <dakkar@thenautilus.net>2024-10-18 21:14:49 +0000
commitba17776b1961376b5369aa775e8910a227cc02ec (patch)
tree0d0579dd911a6c9b74ebc84195cacf10eafe0918 /packages/backend/src/core/NoteEditService.ts
parentmerge: Allow logged in users to refresh polls (!686) (diff)
parentMerge branch 'develop' into feature/2024.9.0 (diff)
downloadsharkey-ba17776b1961376b5369aa775e8910a227cc02ec.tar.gz
sharkey-ba17776b1961376b5369aa775e8910a227cc02ec.tar.bz2
sharkey-ba17776b1961376b5369aa775e8910a227cc02ec.zip
merge: version 2024.9.0 (!675)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/675 Approved-by: Julia <julia@insertdomain.name> Approved-by: Marie <github@yuugi.dev>
Diffstat (limited to 'packages/backend/src/core/NoteEditService.ts')
-rw-r--r--packages/backend/src/core/NoteEditService.ts88
1 files changed, 43 insertions, 45 deletions
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index 58114a0e0c..df45595da9 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -8,13 +8,12 @@ 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 RE2 from 're2';
import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { MiNote } from '@/models/Note.js';
-import type { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository, PollsRepository } from '@/models/_.js';
+import type { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository, PollsRepository } from '@/models/_.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiApp } from '@/models/App.js';
import { concat } from '@/misc/prelude/array.js';
@@ -42,7 +41,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { bindThis } from '@/decorators.js';
import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { RoleService } from '@/core/RoleService.js';
-import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { UtilityService } from '@/core/UtilityService.js';
@@ -53,6 +51,8 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { LatestNoteService } from '@/core/LatestNoteService.js';
+import { CollapsedQueue } from '@/misc/collapsed-queue.js';
+import { NoteCreateService } from '@/core/NoteCreateService.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited';
@@ -146,11 +146,15 @@ type Option = {
@Injectable()
export class NoteEditService implements OnApplicationShutdown {
#shutdownController = new AbortController();
+ private updateNotesCountQueue: CollapsedQueue<MiNote['id'], number>;
constructor(
@Inject(DI.config)
private config: Config,
+ @Inject(DI.meta)
+ private meta: MiMeta,
+
@Inject(DI.db)
private db: DataSource,
@@ -208,7 +212,6 @@ export class NoteEditService implements OnApplicationShutdown {
private apDeliverManagerService: ApDeliverManagerService,
private apRendererService: ApRendererService,
private roleService: RoleService,
- private metaService: MetaService,
private searchService: SearchService,
private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart,
@@ -216,7 +219,10 @@ export class NoteEditService implements OnApplicationShutdown {
private userBlockingService: UserBlockingService,
private cacheService: CacheService,
private latestNoteService: LatestNoteService,
- ) { }
+ private noteCreateService: NoteCreateService,
+ ) {
+ this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount);
+ }
@bindThis
public async edit(user: {
@@ -277,10 +283,8 @@ export class NoteEditService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true;
if (data.updatedAt == null) data.updatedAt = new Date();
- const meta = await this.metaService.fetch();
-
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)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
@@ -288,17 +292,17 @@ export class NoteEditService implements OnApplicationShutdown {
}
}
- const hasProhibitedWords = await this.checkProhibitedWordsContain({
+ const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({
cw: data.cw,
text: data.text,
pollChoices: data.poll?.choices,
- }, meta.prohibitedWords);
+ }, this.meta.prohibitedWords);
if (hasProhibitedWords) {
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) {
data.visibility = 'home';
@@ -407,7 +411,7 @@ export class NoteEditService implements OnApplicationShutdown {
}
// 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);
@@ -512,6 +516,7 @@ export class NoteEditService implements OnApplicationShutdown {
renoteUserId: data.renote ? data.renote.userId : null,
renoteUserHost: data.renote ? data.renote.userHost : null,
userHost: user.host,
+ reactionAndUserPairCache: oldnote.reactionAndUserPairCache,
});
if (data.uri != null) note.uri = data.uri;
@@ -581,8 +586,8 @@ export class NoteEditService implements OnApplicationShutdown {
// Register host
if (this.userEntityService.isRemoteUser(user)) {
this.federatedInstanceService.fetch(user.host).then(async i => {
- this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
- if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+ this.updateNotesCountQueue.enqueue(i.id, 1);
+ if (this.meta.enableChartsForFederatedInstances) {
this.instanceChart.updateNote(i.host, note, true);
}
});
@@ -871,15 +876,14 @@ export class NoteEditService implements OnApplicationShutdown {
@bindThis
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
- const meta = await this.metaService.fetch();
- if (!meta.enableFanoutTimeline) return;
+ if (!this.meta.enableFanoutTimeline) return;
const r = this.redisForTimelines.pipeline();
if (note.channelId) {
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({
where: {
@@ -889,9 +893,9 @@ export class NoteEditService implements OnApplicationShutdown {
});
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) {
- 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 {
@@ -929,9 +933,9 @@ export class NoteEditService implements OnApplicationShutdown {
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) {
- 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);
}
}
@@ -948,25 +952,25 @@ export class NoteEditService implements OnApplicationShutdown {
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) {
- 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
if (note.userHost == null) {
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) {
- 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)) {
- 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) {
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
@@ -975,9 +979,9 @@ export class NoteEditService implements OnApplicationShutdown {
}
}
} 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) {
- 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) {
@@ -1036,30 +1040,24 @@ export class NoteEditService implements OnApplicationShutdown {
}
}
- public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
- if (prohibitedWords == null) {
- prohibitedWords = (await this.metaService.fetch()).prohibitedWords;
- }
-
- if (
- this.utilityService.isKeyWordIncluded(
- this.utilityService.concatNoteContentsForKeyWordCheck(content),
- prohibitedWords,
- )
- ) {
- return true;
- }
+ @bindThis
+ private collapseNotesCount(oldValue: number, newValue: number) {
+ return oldValue + newValue;
+ }
- return false;
+ @bindThis
+ private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) {
+ await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy);
}
@bindThis
- public dispose(): void {
+ public async dispose(): Promise<void> {
this.#shutdownController.abort();
+ await this.updateNotesCountQueue.performAllNow();
}
@bindThis
- public onApplicationShutdown(signal?: string | undefined): void {
- this.dispose();
+ public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
+ await this.dispose();
}
}