summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-05-12 12:41:53 +0900
committerGitHub <noreply@github.com>2023-05-12 12:41:53 +0900
commitde6348e8a00a72c2410a8907d59a9bd29142a200 (patch)
treef163329a67ac5c6dc1c33845b4fc131a8c59a39b /packages/backend/src
parentMerge pull request #10814 from misskey-dev/develop (diff)
parentfix(frontend): fix retention rate heatmap rendering (diff)
downloadmisskey-de6348e8a00a72c2410a8907d59a9bd29142a200.tar.gz
misskey-de6348e8a00a72c2410a8907d59a9bd29142a200.tar.bz2
misskey-de6348e8a00a72c2410a8907d59a9bd29142a200.zip
Merge pull request #10833 from misskey-dev/develop
* refactor(frontend): use css modules * feat: 投稿したコンテンツのAIによる学習を軽減するオプションを追加 Resolve #10819 * enhance(backend): publicReactionsをデフォルトtrueに * 念のためnoimageaiもつける * add X-Robots-Tag: noai * Update ja-JP.yml * fix(frontend): ブラーエフェクトを有効にしている状態で高負荷になる問題を修正 * enhance(backend): graceful shutdown for job queue and refactor * fix(backend): テスト時は一部のサービスを停止 * fix test * New Crowdin updates (#10815) * New translations ja-JP.yml (English) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Korean) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (Chinese Traditional) * refactor * bump * refactor(frontend): use css module * refactor(frontend): use css module * delete unused component * センシティブワードを正規表現、CWにも適用するように (#10688) * cwにセンシティブが効いてない * CWが無いときにTextを見るように * 比較演算子間違えた * とりあえずチェック * 正規表現対応 * /test/giにも対応 * matchでしなくてもいいのでは感 * レビュー修正 * Update packages/backend/src/core/NoteCreateService.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * Update packages/backend/src/core/NoteCreateService.ts Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * 修正 * wipかも * wordsでスペース区切りのものできたかも * なんか動いたかも * test作成 * 文言の修正 * 修正 * note参照 --------- Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> * Update CHANGELOG.md * New Crowdin updates (#10823) * New translations ja-JP.yml (English) * New translations ja-JP.yml (German) * ci: fix typo * fix(frontend): より明確な説明にしたのとtypo修正 * fix typo * fix(frontend): カラーバーがリプライには表示されないのを修正 * fix(frontend): チャンネル内の検索ボックスが挙動不審な問題を修正 Fix #10793 * enhance(backend): ノートのハッシュタグもMeilisearchに突っ込むように 今後ハッシュタグ検索とか実装するときのため * feat(frontend): ユーザー指定ノート検索 * fix(frontend): fix retention chart rendering * Update about-misskey.vue * meta: Remove @rinsuki from reviewer-lottery (#10830) * New Crowdin updates (#10824) * New translations ja-JP.yml (English) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (German) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (French) * New translations ja-JP.yml (German) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Japanese, Kansai) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (Spanish) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Korean) * New translations ja-JP.yml (Norwegian) * New translations ja-JP.yml (Russian) * New translations ja-JP.yml (Chinese Simplified) * New translations ja-JP.yml (Indonesian) * New translations ja-JP.yml (Thai) * enhance(frontend): アカウント初期設定ウィザードにプライバシー設定を追加 * Update CHANGELOG.md * fix(backend): ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正 * fix MkUserSetupDialog.Privacy.vue * ci: skip non-Japanese locale on TurboSnap * ci: notify on changes for push events * ci: fix missing branch * Update basic.cy.js * [ci skip] New Crowdin updates (#10834) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Arabic) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Chinese Simplified) * New translations ja-JP.yml (Japanese, Kansai) * New translations ja-JP.yml (Arabic) * :art: * :art: * enhance(frontend): add retention line chart * update deps * refactor * fix(frontend): Pageにおいて画像ブロックに画像を設定できない問題を修正 Fix #10837 --------- Co-authored-by: nenohi <kimutipartylove@gmail.com> Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com> Co-authored-by: rinsuki <428rinsuki+git@gmail.com>
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/boot/common.ts10
-rw-r--r--packages/backend/src/config.ts1
-rw-r--r--packages/backend/src/core/NoteCreateService.ts29
-rw-r--r--packages/backend/src/core/QueueModule.ts41
-rw-r--r--packages/backend/src/core/SearchService.ts4
-rw-r--r--packages/backend/src/core/entities/UserEntityService.ts1
-rw-r--r--packages/backend/src/models/entities/UserProfile.ts7
-rw-r--r--packages/backend/src/models/json-schema/user.ts6
-rw-r--r--packages/backend/src/queue/DbQueueProcessorsService.ts69
-rw-r--r--packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts25
-rw-r--r--packages/backend/src/queue/QueueProcessorModule.ts8
-rw-r--r--packages/backend/src/queue/QueueProcessorService.ts108
-rw-r--r--packages/backend/src/queue/RelationshipQueueProcessorsService.ts26
-rw-r--r--packages/backend/src/queue/SystemQueueProcessorsService.ts37
-rw-r--r--packages/backend/src/server/api/endpoints/admin/show-user.ts1
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts4
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts26
-rw-r--r--packages/backend/src/server/web/views/clip.pug3
-rw-r--r--packages/backend/src/server/web/views/flash.pug3
-rw-r--r--packages/backend/src/server/web/views/gallery-post.pug3
-rw-r--r--packages/backend/src/server/web/views/note.pug3
-rw-r--r--packages/backend/src/server/web/views/page.pug3
-rw-r--r--packages/backend/src/server/web/views/user.pug3
23 files changed, 225 insertions, 196 deletions
diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts
index 45ded5495c..3995545d7f 100644
--- a/packages/backend/src/boot/common.ts
+++ b/packages/backend/src/boot/common.ts
@@ -18,10 +18,12 @@ export async function server() {
const serverService = app.get(ServerService);
await serverService.launch();
- app.get(ChartManagementService).start();
- app.get(JanitorService).start();
- app.get(QueueStatsService).start();
- app.get(ServerStatsService).start();
+ if (process.env.NODE_ENV !== 'test') {
+ app.get(ChartManagementService).start();
+ app.get(JanitorService).start();
+ app.get(QueueStatsService).start();
+ app.get(ServerStatsService).start();
+ }
return app;
}
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index b41fb603bb..c6e1075389 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -62,6 +62,7 @@ export type Source = {
port: string;
apiKey: string;
ssl?: boolean;
+ index: string;
};
proxy?: string;
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 364976e4a7..977c9052c0 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -3,6 +3,7 @@ import * as mfm from 'mfm-js';
import { In, DataSource } 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';
@@ -238,7 +239,8 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true;
if (data.visibility === 'public' && data.channel == null) {
- if ((data.text != null) && (await this.metaService.fetch()).sensitiveWords.some(w => data.text!.includes(w))) {
+ const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
+ if (this.isSensitive(data, sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home';
@@ -670,6 +672,31 @@ export class NoteCreateService implements OnApplicationShutdown {
// Register to search database
this.index(note);
}
+
+ @bindThis
+ private isSensitive(note: Option, sensitiveWord: string[]): boolean {
+ if (sensitiveWord.length > 0) {
+ const text = note.cw ?? note.text ?? '';
+ if (text === '') return false;
+ const matched = sensitiveWord.some(filter => {
+ // represents RegExp
+ const regexp = filter.match(/^\/(.+)\/(.*)$/);
+ // This should never happen due to input sanitisation.
+ if (!regexp) {
+ const words = filter.split(' ');
+ return words.every(keyword => text.includes(keyword));
+ }
+ try {
+ return new RE2(regexp[1], regexp[2]).test(text);
+ } catch (err) {
+ // This should never happen due to input sanitisation.
+ return false;
+ }
+ });
+ if (matched) return true;
+ }
+ return false;
+ }
@bindThis
private incRenoteCount(renote: Note) {
diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts
index d4905a5f88..1d73947776 100644
--- a/packages/backend/src/core/QueueModule.ts
+++ b/packages/backend/src/core/QueueModule.ts
@@ -1,4 +1,5 @@
-import { Module } from '@nestjs/common';
+import { setTimeout } from 'node:timers/promises';
+import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
import Bull from 'bull';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@@ -41,9 +42,9 @@ export type SystemQueue = Bull.Queue<Record<string, unknown>>;
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
export type DeliverQueue = Bull.Queue<DeliverJobData>;
export type InboxQueue = Bull.Queue<InboxJobData>;
-export type DbQueue = Bull.Queue<DbJobData<keyof DbJobMap>>;
+export type DbQueue = Bull.Queue;
export type RelationshipQueue = Bull.Queue<RelationshipJobData>;
-export type ObjectStorageQueue = Bull.Queue<ObjectStorageJobData>;
+export type ObjectStorageQueue = Bull.Queue;
export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
const $system: Provider = {
@@ -118,4 +119,36 @@ const $webhookDeliver: Provider = {
$webhookDeliver,
],
})
-export class QueueModule {}
+export class QueueModule implements OnApplicationShutdown {
+ constructor(
+ @Inject('queue:system') public systemQueue: SystemQueue,
+ @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
+ @Inject('queue:deliver') public deliverQueue: DeliverQueue,
+ @Inject('queue:inbox') public inboxQueue: InboxQueue,
+ @Inject('queue:db') public dbQueue: DbQueue,
+ @Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
+ @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
+ @Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
+ ) {}
+
+ async onApplicationShutdown(signal: string): Promise<void> {
+ if (process.env.NODE_ENV === 'test') {
+ // XXX:
+ // Shutting down the existing connections causes errors on Jest as
+ // Misskey has asynchronous postgres/redis connections that are not
+ // awaited.
+ // Let's wait for some random time for them to finish.
+ await setTimeout(5000);
+ }
+ await Promise.all([
+ this.systemQueue.close(),
+ this.endedPollNotificationQueue.close(),
+ this.deliverQueue.close(),
+ this.inboxQueue.close(),
+ this.dbQueue.close(),
+ this.relationshipQueue.close(),
+ this.objectStorageQueue.close(),
+ this.webhookDeliverQueue.close(),
+ ]);
+ }
+}
diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts
index e68fde088d..9502afcc9b 100644
--- a/packages/backend/src/core/SearchService.ts
+++ b/packages/backend/src/core/SearchService.ts
@@ -68,7 +68,7 @@ export class SearchService {
private idService: IdService,
) {
if (meilisearch) {
- this.meilisearchNoteIndex = meilisearch.index('notes');
+ this.meilisearchNoteIndex = meilisearch.index(`${config.meilisearch!.index}---notes`);
this.meilisearchNoteIndex.updateSettings({
searchableAttributes: [
'text',
@@ -82,6 +82,7 @@ export class SearchService {
'userId',
'userHost',
'channelId',
+ 'tags',
],
typoTolerance: {
enabled: false,
@@ -107,6 +108,7 @@ export class SearchService {
channelId: note.channelId,
cw: note.cw,
text: note.text,
+ tags: note.tags,
}], {
primaryKey: 'id',
});
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 453c1473dd..7f61e1d6f3 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -445,6 +445,7 @@ export class UserEntityService implements OnModuleInit {
carefulBot: profile!.carefulBot,
autoAcceptFollowed: profile!.autoAcceptFollowed,
noCrawle: profile!.noCrawle,
+ preventAiLearning: profile!.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
hideOnlineStatus: user.hideOnlineStatus,
diff --git a/packages/backend/src/models/entities/UserProfile.ts b/packages/backend/src/models/entities/UserProfile.ts
index 60c1c55de5..236ee8f988 100644
--- a/packages/backend/src/models/entities/UserProfile.ts
+++ b/packages/backend/src/models/entities/UserProfile.ts
@@ -76,7 +76,7 @@ export class UserProfile {
public emailNotificationTypes: string[];
@Column('boolean', {
- default: false,
+ default: true,
})
public publicReactions: boolean;
@@ -148,6 +148,11 @@ export class UserProfile {
public noCrawle: boolean;
@Column('boolean', {
+ default: true,
+ })
+ public preventAiLearning: boolean;
+
+ @Column('boolean', {
default: false,
})
public alwaysMarkNsfw: boolean;
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 529c1303d1..f9a20ac398 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -302,7 +302,11 @@ export const packedMeDetailedOnlySchema = {
},
noCrawle: {
type: 'boolean',
- nullable: true, optional: false,
+ nullable: false, optional: false,
+ },
+ preventAiLearning: {
+ type: 'boolean',
+ nullable: false, optional: false,
},
isExplorable: {
type: 'boolean',
diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts
deleted file mode 100644
index df8ac3a301..0000000000
--- a/packages/backend/src/queue/DbQueueProcessorsService.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
-import { bindThis } from '@/decorators.js';
-import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
-import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
-import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
-import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
-import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
-import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
-import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
-import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
-import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
-import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
-import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
-import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
-import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
-import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
-import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
-import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
-import type Bull from 'bull';
-
-@Injectable()
-export class DbQueueProcessorsService {
- constructor(
- @Inject(DI.config)
- private config: Config,
-
- private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
- private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
- private exportNotesProcessorService: ExportNotesProcessorService,
- private exportFavoritesProcessorService: ExportFavoritesProcessorService,
- private exportFollowingProcessorService: ExportFollowingProcessorService,
- private exportMutingProcessorService: ExportMutingProcessorService,
- private exportBlockingProcessorService: ExportBlockingProcessorService,
- private exportUserListsProcessorService: ExportUserListsProcessorService,
- private exportAntennasProcessorService: ExportAntennasProcessorService,
- private importFollowingProcessorService: ImportFollowingProcessorService,
- private importMutingProcessorService: ImportMutingProcessorService,
- private importBlockingProcessorService: ImportBlockingProcessorService,
- private importUserListsProcessorService: ImportUserListsProcessorService,
- private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
- private importAntennasProcessorService: ImportAntennasProcessorService,
- private deleteAccountProcessorService: DeleteAccountProcessorService,
- ) {
- }
-
- @bindThis
- public start(q: Bull.Queue): void {
- q.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
- q.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
- q.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
- q.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
- q.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
- q.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
- q.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
- q.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
- q.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
- q.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
- q.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
- q.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
- q.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
- q.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
- q.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
- q.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
- q.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
- q.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
- }
-}
diff --git a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts b/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts
deleted file mode 100644
index 865e47c3f8..0000000000
--- a/packages/backend/src/queue/ObjectStorageQueueProcessorsService.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
-import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
-import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
-import type Bull from 'bull';
-import { bindThis } from '@/decorators.js';
-
-@Injectable()
-export class ObjectStorageQueueProcessorsService {
- constructor(
- @Inject(DI.config)
- private config: Config,
-
- private deleteFileProcessorService: DeleteFileProcessorService,
- private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
- ) {
- }
-
- @bindThis
- public start(q: Bull.Queue): void {
- q.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
- q.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
- }
-}
diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts
index 3d4cc77321..e1c6b93d9b 100644
--- a/packages/backend/src/queue/QueueProcessorModule.ts
+++ b/packages/backend/src/queue/QueueProcessorModule.ts
@@ -3,14 +3,10 @@ import { CoreModule } from '@/core/CoreModule.js';
import { GlobalModule } from '@/GlobalModule.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { QueueProcessorService } from './QueueProcessorService.js';
-import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
-import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
-import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
import { InboxProcessorService } from './processors/InboxProcessorService.js';
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
-import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
import { CleanProcessorService } from './processors/CleanProcessorService.js';
@@ -68,10 +64,6 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
DeleteFileProcessorService,
CleanRemoteFilesProcessorService,
RelationshipProcessorService,
- SystemQueueProcessorsService,
- ObjectStorageQueueProcessorsService,
- DbQueueProcessorsService,
- RelationshipQueueProcessorsService,
WebhookDeliverProcessorService,
EndedPollNotificationProcessorService,
DeliverProcessorService,
diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts
index 706110f6fc..dc025f9889 100644
--- a/packages/backend/src/queue/QueueProcessorService.ts
+++ b/packages/backend/src/queue/QueueProcessorService.ts
@@ -5,15 +5,36 @@ import type Logger from '@/logger.js';
import { QueueService } from '@/core/QueueService.js';
import { bindThis } from '@/decorators.js';
import { getJobInfo } from './get-job-info.js';
-import { SystemQueueProcessorsService } from './SystemQueueProcessorsService.js';
-import { ObjectStorageQueueProcessorsService } from './ObjectStorageQueueProcessorsService.js';
-import { DbQueueProcessorsService } from './DbQueueProcessorsService.js';
import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
import { InboxProcessorService } from './processors/InboxProcessorService.js';
+import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js';
+import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
+import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
+import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
+import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
+import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
+import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
+import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
+import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
+import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
+import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
+import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
+import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
+import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
+import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
+import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
+import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
+import { DeleteFileProcessorService } from './processors/DeleteFileProcessorService.js';
+import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
+import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
+import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
+import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
+import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
+import { CleanProcessorService } from './processors/CleanProcessorService.js';
+import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import { QueueLoggerService } from './QueueLoggerService.js';
-import { RelationshipQueueProcessorsService } from './RelationshipQueueProcessorsService.js';
@Injectable()
export class QueueProcessorService {
@@ -25,14 +46,35 @@ export class QueueProcessorService {
private queueLoggerService: QueueLoggerService,
private queueService: QueueService,
- private systemQueueProcessorsService: SystemQueueProcessorsService,
- private objectStorageQueueProcessorsService: ObjectStorageQueueProcessorsService,
- private dbQueueProcessorsService: DbQueueProcessorsService,
- private relationshipQueueProcessorsService: RelationshipQueueProcessorsService,
private webhookDeliverProcessorService: WebhookDeliverProcessorService,
private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
private deliverProcessorService: DeliverProcessorService,
private inboxProcessorService: InboxProcessorService,
+ private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService,
+ private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
+ private exportNotesProcessorService: ExportNotesProcessorService,
+ private exportFavoritesProcessorService: ExportFavoritesProcessorService,
+ private exportFollowingProcessorService: ExportFollowingProcessorService,
+ private exportMutingProcessorService: ExportMutingProcessorService,
+ private exportBlockingProcessorService: ExportBlockingProcessorService,
+ private exportUserListsProcessorService: ExportUserListsProcessorService,
+ private exportAntennasProcessorService: ExportAntennasProcessorService,
+ private importFollowingProcessorService: ImportFollowingProcessorService,
+ private importMutingProcessorService: ImportMutingProcessorService,
+ private importBlockingProcessorService: ImportBlockingProcessorService,
+ private importUserListsProcessorService: ImportUserListsProcessorService,
+ private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
+ private importAntennasProcessorService: ImportAntennasProcessorService,
+ private deleteAccountProcessorService: DeleteAccountProcessorService,
+ private deleteFileProcessorService: DeleteFileProcessorService,
+ private cleanRemoteFilesProcessorService: CleanRemoteFilesProcessorService,
+ private relationshipProcessorService: RelationshipProcessorService,
+ private tickChartsProcessorService: TickChartsProcessorService,
+ private resyncChartsProcessorService: ResyncChartsProcessorService,
+ private cleanChartsProcessorService: CleanChartsProcessorService,
+ private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
+ private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
+ private cleanProcessorService: CleanProcessorService,
) {
this.logger = this.queueLoggerService.logger;
}
@@ -119,14 +161,6 @@ export class QueueProcessorService {
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
- this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
- this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
- this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
- this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
- this.dbQueueProcessorsService.start(this.queueService.dbQueue);
- this.relationshipQueueProcessorsService.start(this.queueService.relationshipQueue);
- this.objectStorageQueueProcessorsService.start(this.queueService.objectStorageQueue);
-
this.queueService.systemQueue.add('tickCharts', {
}, {
repeat: { cron: '55 * * * *' },
@@ -163,6 +197,46 @@ export class QueueProcessorService {
removeOnComplete: true,
});
- this.systemQueueProcessorsService.start(this.queueService.systemQueue);
+ this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
+ this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
+ this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
+ this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
+
+ this.queueService.dbQueue.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
+ this.queueService.dbQueue.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
+ this.queueService.dbQueue.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
+ this.queueService.dbQueue.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
+ this.queueService.dbQueue.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
+ this.queueService.dbQueue.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
+ this.queueService.dbQueue.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
+ this.queueService.dbQueue.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
+ this.queueService.dbQueue.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
+ this.queueService.dbQueue.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
+ this.queueService.dbQueue.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
+
+ this.queueService.objectStorageQueue.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
+ this.queueService.objectStorageQueue.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
+
+ {
+ const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
+ this.queueService.relationshipQueue.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
+ this.queueService.relationshipQueue.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
+ this.queueService.relationshipQueue.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
+ this.queueService.relationshipQueue.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
+ }
+
+ this.queueService.systemQueue.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
+ this.queueService.systemQueue.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
+ this.queueService.systemQueue.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
+ this.queueService.systemQueue.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
+ this.queueService.systemQueue.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
+ this.queueService.systemQueue.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
}
}
diff --git a/packages/backend/src/queue/RelationshipQueueProcessorsService.ts b/packages/backend/src/queue/RelationshipQueueProcessorsService.ts
deleted file mode 100644
index 736b4fa80d..0000000000
--- a/packages/backend/src/queue/RelationshipQueueProcessorsService.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Inject, Injectable } from '@nestjs/common';
-import { bindThis } from '@/decorators.js';
-import { RelationshipProcessorService } from './processors/RelationshipProcessorService.js';
-import type Bull from 'bull';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
-
-@Injectable()
-export class RelationshipQueueProcessorsService {
- constructor(
- @Inject(DI.config)
- private config: Config,
-
- private relationshipProcessorService: RelationshipProcessorService,
- ) {
- }
-
- @bindThis
- public start(q: Bull.Queue): void {
- const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
- q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
- q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
- q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
- q.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
- }
-}
diff --git a/packages/backend/src/queue/SystemQueueProcessorsService.ts b/packages/backend/src/queue/SystemQueueProcessorsService.ts
deleted file mode 100644
index 7fb0da4b10..0000000000
--- a/packages/backend/src/queue/SystemQueueProcessorsService.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Inject, Injectable } from '@nestjs/common';
-import { DI } from '@/di-symbols.js';
-import type { Config } from '@/config.js';
-import { bindThis } from '@/decorators.js';
-import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
-import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
-import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
-import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
-import { CleanProcessorService } from './processors/CleanProcessorService.js';
-import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
-import type Bull from 'bull';
-
-@Injectable()
-export class SystemQueueProcessorsService {
- constructor(
- @Inject(DI.config)
- private config: Config,
-
- private tickChartsProcessorService: TickChartsProcessorService,
- private resyncChartsProcessorService: ResyncChartsProcessorService,
- private cleanChartsProcessorService: CleanChartsProcessorService,
- private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
- private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
- private cleanProcessorService: CleanProcessorService,
- ) {
- }
-
- @bindThis
- public start(q: Bull.Queue): void {
- q.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
- q.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
- q.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
- q.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
- q.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
- q.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
- }
-}
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index 42229c8f23..f49d2a0966 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -68,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
emailVerified: profile.emailVerified,
autoAcceptFollowed: profile.autoAcceptFollowed,
noCrawle: profile.noCrawle,
+ preventAiLearning: profile.preventAiLearning,
alwaysMarkNsfw: profile.alwaysMarkNsfw,
autoSensitive: profile.autoSensitive,
carefulBot: profile.carefulBot,
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index 6c66300bb7..74be00a8b8 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -98,7 +98,7 @@ export const meta = {
message: 'This feature is restricted by your role.',
code: 'RESTRICTED_BY_ROLE',
id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
- }
+ },
},
res: {
@@ -138,6 +138,7 @@ export const paramDef = {
carefulBot: { type: 'boolean' },
autoAcceptFollowed: { type: 'boolean' },
noCrawle: { type: 'boolean' },
+ preventAiLearning: { type: 'boolean' },
isBot: { type: 'boolean' },
isCat: { type: 'boolean' },
showTimelineReplies: { type: 'boolean' },
@@ -242,6 +243,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
+ if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning;
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 50b23a0682..f780280c1f 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -35,8 +35,8 @@ import { RoleService } from '@/core/RoleService.js';
import manifest from './manifest.json' assert { type: 'json' };
import { FeedService } from './FeedService.js';
import { UrlPreviewService } from './UrlPreviewService.js';
-import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
import { ClientLoggerService } from './ClientLoggerService.js';
+import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@@ -423,6 +423,10 @@ export class ClientServerService {
: [];
reply.header('Cache-Control', 'public, max-age=15');
+ if (profile.preventAiLearning) {
+ reply.header('X-Robots-Tag', 'noimageai');
+ reply.header('X-Robots-Tag', 'noai');
+ }
return await reply.view('user', {
user, profile, me,
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
@@ -467,6 +471,10 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
+ if (profile.preventAiLearning) {
+ reply.header('X-Robots-Tag', 'noimageai');
+ reply.header('X-Robots-Tag', 'noai');
+ }
return await reply.view('note', {
note: _note,
profile,
@@ -506,6 +514,10 @@ export class ClientServerService {
} else {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
}
+ if (profile.preventAiLearning) {
+ reply.header('X-Robots-Tag', 'noimageai');
+ reply.header('X-Robots-Tag', 'noai');
+ }
return await reply.view('page', {
page: _page,
profile,
@@ -530,6 +542,10 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
+ if (profile.preventAiLearning) {
+ reply.header('X-Robots-Tag', 'noimageai');
+ reply.header('X-Robots-Tag', 'noai');
+ }
return await reply.view('flash', {
flash: _flash,
profile,
@@ -554,6 +570,10 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
+ if (profile.preventAiLearning) {
+ reply.header('X-Robots-Tag', 'noimageai');
+ reply.header('X-Robots-Tag', 'noai');
+ }
return await reply.view('clip', {
clip: _clip,
profile,
@@ -576,6 +596,10 @@ export class ClientServerService {
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
const meta = await this.metaService.fetch();
reply.header('Cache-Control', 'public, max-age=15');
+ if (profile.preventAiLearning) {
+ reply.header('X-Robots-Tag', 'noimageai');
+ reply.header('X-Robots-Tag', 'noai');
+ }
return await reply.view('gallery-post', {
post: _post,
profile,
diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug
index 4c692bf59b..74dc62f1e7 100644
--- a/packages/backend/src/server/web/views/clip.pug
+++ b/packages/backend/src/server/web/views/clip.pug
@@ -21,6 +21,9 @@ block og
block meta
if profile.noCrawle
meta(name='robots' content='noindex')
+ if profile.preventAiLearning
+ meta(name='robots' content='noimageai')
+ meta(name='robots' content='noai')
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
diff --git a/packages/backend/src/server/web/views/flash.pug b/packages/backend/src/server/web/views/flash.pug
index 5166855ea2..5594fcdfbf 100644
--- a/packages/backend/src/server/web/views/flash.pug
+++ b/packages/backend/src/server/web/views/flash.pug
@@ -21,6 +21,9 @@ block og
block meta
if profile.noCrawle
meta(name='robots' content='noindex')
+ if profile.preventAiLearning
+ meta(name='robots' content='noimageai')
+ meta(name='robots' content='noai')
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
diff --git a/packages/backend/src/server/web/views/gallery-post.pug b/packages/backend/src/server/web/views/gallery-post.pug
index ca0663a481..10f2d269bc 100644
--- a/packages/backend/src/server/web/views/gallery-post.pug
+++ b/packages/backend/src/server/web/views/gallery-post.pug
@@ -21,6 +21,9 @@ block og
block meta
if user.host || profile.noCrawle
meta(name='robots' content='noindex')
+ if profile.preventAiLearning
+ meta(name='robots' content='noimageai')
+ meta(name='robots' content='noai')
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index 65696ea138..badfcccd61 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -22,6 +22,9 @@ block og
block meta
if user.host || isRenote || profile.noCrawle
meta(name='robots' content='noindex')
+ if profile.preventAiLearning
+ meta(name='robots' content='noimageai')
+ meta(name='robots' content='noai')
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug
index 4219e76a52..ddffc361c8 100644
--- a/packages/backend/src/server/web/views/page.pug
+++ b/packages/backend/src/server/web/views/page.pug
@@ -21,6 +21,9 @@ block og
block meta
if profile.noCrawle
meta(name='robots' content='noindex')
+ if profile.preventAiLearning
+ meta(name='robots' content='noimageai')
+ meta(name='robots' content='noai')
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)
diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug
index 119993fdb5..f4c83aa89d 100644
--- a/packages/backend/src/server/web/views/user.pug
+++ b/packages/backend/src/server/web/views/user.pug
@@ -20,6 +20,9 @@ block og
block meta
if user.host || profile.noCrawle
meta(name='robots' content='noindex')
+ if profile.preventAiLearning
+ meta(name='robots' content='noimageai')
+ meta(name='robots' content='noai')
meta(name='misskey:user-username' content=user.username)
meta(name='misskey:user-id' content=user.id)