diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 39682e154..5cb17a44d 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -103,6 +103,7 @@ redis: # port: 7700 # apiKey: '' # ssl: true +# index: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index ef8373c4d..c17939596 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -103,6 +103,7 @@ redis: # port: 7700 # apiKey: '' # ssl: true +# index: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 1d761ae75..824a046dc 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -103,6 +103,7 @@ redis: # port: 7700 # apiKey: '' # ssl: true +# index: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/.github/reviewer-lottery.yml b/.github/reviewer-lottery.yml index fd2fb1913..c88e1342d 100644 --- a/.github/reviewer-lottery.yml +++ b/.github/reviewer-lottery.yml @@ -6,5 +6,4 @@ groups: - syuilo - acid-chicken - EbiseLutica - - rinsuki - tamaina diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index eb6ace27d..b04f4260c 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 submodules: true - name: Checkout HEAD - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request_target' run: git checkout ${{ github.head_ref }} - name: Install pnpm uses: pnpm/action-setup@v2 @@ -41,12 +41,12 @@ jobs: - name: Build storybook run: pnpm --filter frontend build-storybook - name: Publish to Chromatic - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' + if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/master' run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static env: CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Publish to Chromatic - if: github.event_name != 'pull_request' && github.ref != 'refs/heads/master' + if: github.event_name != 'pull_request_target' && github.ref != 'refs/heads/master' id: chromatic_push run: | DIFF="${{ github.event.before }} HEAD" @@ -57,11 +57,15 @@ jobs: if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then echo "skip=true" >> $GITHUB_OUTPUT fi - pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER") + if pnpm --filter frontend chromatic -d storybook-static $(echo "$CHROMATIC_PARAMETER"); then + echo "success=true" >> $GITHUB_OUTPUT + else + echo "success=false" >> $GITHUB_OUTPUT + fi env: CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} - name: Publish to Chromatic - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request_target' id: chromatic_pull_request run: | DIFF="${{ github.base_ref }} HEAD" @@ -75,9 +79,21 @@ jobs: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER") env: CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + - name: Notify that Chromatic detects changes + uses: actions/github-script@v6.4.0 + if: github.event_name != 'pull_request_target' && steps.chromatic_push.outputs.success == 'false' + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + github.rest.repos.createCommitComment({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: context.sha, + body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).' + }) - name: Notify that Chromatic will skip testing uses: actions/github-script@v6.4.0 - if: github.event_name == 'pull_request' && steps.chromatic_pull_request.outputs.skip == 'true' + if: github.event_name == 'pull_request_target' && steps.chromatic_pull_request.outputs.skip == 'true' with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 50e097c68..8594c42d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,30 @@ --> +## 13.12.2 + +## NOTE +Meilisearchの設定に`index`が必要になりました。値はMisskeyサーバーのホスト名にすることをお勧めします(アルファベット、ハイフン、アンダーバーのみ使用可能)。例: `misskey-io` +過去に作成された`notes`インデックスは、`---notes`にリネームが必要です。例: `misskey-io---notes` + +### General +- 投稿したコンテンツのAIによる学習を軽減するオプションを追加 + +### Client +- ユーザーを指定してのノート検索が可能に +- アカウント初期設定ウィザードにプライバシー設定を追加 +- リテンション率チャートに折れ線グラフを追加 +- Fix: ブラーエフェクトを有効にしている状態で高負荷になる問題を修正 +- Fix: Pageにおいて画像ブロックに画像を設定できない問題を修正 +- Fix: カラーバーがリプライには表示されないのを修正 +- Fix: チャンネル内の検索ボックスが挙動不審な問題を修正 +- Fix: リテンションチャートのレンダリングを修正 +- Fix: リアクションエフェクトのレンダリングの問題を修正 + +### Server +- センシティブワードの登録にAnd、正規表現が使用できるようになりました。 +- Fix: ひとつのMeilisearchサーバーを複数のMisskeyサーバーで使えない問題を修正 + ## 13.12.1 ### Client diff --git a/chart/files/default.yml b/chart/files/default.yml index 342c6091f..e62032abf 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -124,6 +124,7 @@ redis: # port: 7700 # apiKey: '' # ssl: true +# index: '' # ┌───────────────┐ #───┘ ID generation └─────────────────────────────────────────── diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js index 73f6e7a0f..2515c14ad 100644 --- a/cypress/e2e/basic.cy.js +++ b/cypress/e2e/basic.cy.js @@ -171,6 +171,10 @@ describe('After user signed in', () => { cy.get('[data-cy-user-setup-continue]').click(); + // プライバシー設定 + + cy.get('[data-cy-user-setup-continue]').click(); + // フォローはスキップ cy.get('[data-cy-user-setup-continue]').click(); diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 9a5936660..bfca086f5 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -19,6 +19,7 @@ noNotes: "لم يُعثر على أية ملاحظات" noNotifications: "ليس هناك أية اشعارات" instance: "مثيل الخادم" settings: "الاعدادات" +notificationSettings: "إعدادات الإشعارات" basicSettings: "الاعدادات الأساسية" otherSettings: "إعدادات أخرى" openInWindow: "افتح في نافذة جديدة" @@ -127,6 +128,7 @@ unblockConfirm: "أمتأكد من إلغاء حجب هذا الحساب؟" suspendConfirm: "أمتأكد من تعليق الحساب؟" unsuspendConfirm: "أمتأكد من إلغاء تعليق؟" selectList: "اختر قائمة" +selectChannel: "اختر قناة" selectAntenna: "اختر هوائيًا" selectWidget: "اختر ودجة" editWidgets: "عدّل الودجات" @@ -250,6 +252,9 @@ noMoreHistory: "لا يوجد المزيد من التاريخ" startMessaging: "ابدأ محادثة" nUsersRead: "قرأه {n}" agreeTo: "اوافق على {0}" +agree: "أقبل" +basicNotesBeforeCreateAccount: "ملاحظات مهمة" +termsOfService: "شروط الخدمة" start: "البداية" home: "الرئيسي" remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد." @@ -379,6 +384,8 @@ about: "عن" aboutMisskey: "عن Misskey" administrator: "المدير" token: "الرمز المميز" +2fa: "الاستيثاق بعاملَيْن" +totp: "تطبيق استيثاق" moderator: "مشرِف" moderation: "الإشراف" nUsersMentioned: "{n} مستخدمين أُشير إليهم" @@ -506,6 +513,7 @@ userSuspended: "عُلق هذا المستخدم." userSilenced: "كُتم هذا المستخدم." yourAccountSuspendedTitle: "هذا الحساب معلق" yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد." +accountDeleted: "حُذف الحساب" menu: "القائمة" divider: "فاصل" addItem: "إضافة عنصر" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index d678fadd4..843470cf4 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -990,6 +990,7 @@ rolesAssignedToMe: "Mir zugewiesene Rollen" resetPasswordConfirm: "Wirklich Passwort zurücksetzen?" sensitiveWords: "Sensible Wörter" sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden." +sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden." notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar." license: "Lizenz" unfavoriteConfirm: "Wirklich aus Favoriten entfernen?" @@ -1038,10 +1039,16 @@ thisChannelArchived: "Dieser Kanal wurde archiviert." displayOfNote: "Anzeige von Notizen" initialAccountSetting: "Kontoeinrichtung" youFollowing: "Gefolgt" +preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive AI/KI) ablehnen" +preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich." +options: "Optionen" +specifyUser: "Spezifischer Benutzer" _initialAccountSetting: accountCreated: "Dein Konto wurde erfolgreich erstellt!" + letsStartAccountSetup: "Lass uns nun dein Konto einrichten." letsFillYourProfile: "Lass uns zuerst dein Profil einrichten." profileSetting: "Profileinstellungen" + privacySetting: "Privatsphäreneinstellungen" theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern." youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei." followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen." diff --git a/locales/en-US.yml b/locales/en-US.yml index ea91bcc0e..3ea2313b2 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -990,6 +990,7 @@ rolesAssignedToMe: "Roles assigned to me" resetPasswordConfirm: "Really reset your password?" sensitiveWords: "Sensitive words" sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks." +sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression." notesSearchNotAvailable: "Note search is unavailable." license: "License" unfavoriteConfirm: "Really remove from favorites?" @@ -1036,20 +1037,26 @@ channelArchiveConfirmTitle: "Really archive {name}?" channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore." thisChannelArchived: "This channel has been archived." displayOfNote: "Note display" -initialAccountSetting: "Profile configuration" +initialAccountSetting: "Profile setup" youFollowing: "Followed" +preventAiLearning: "Reject usage in Machine Learning (Generative AI)" +preventAiLearningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (Predictive / Generative AI) data sets. This is achieved by adding a \"noai\" HTML-Response flag to the respective content. A complete prevention can however not be achieved through this flag, as it may simply be ignored." +options: "Options" +specifyUser: "Specific user" _initialAccountSetting: accountCreated: "Your account was successfully created!" + letsStartAccountSetup: "For starters, let's set up your profile." letsFillYourProfile: "First, let's set up your profile." profileSetting: "Profile settings" + privacySetting: "Privacy settings" theseSettingsCanEditLater: "You can always change these settings later." youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later." followUsers: "Try following some users that interest you to build up your timeline." pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device." - initialAccountSettingCompleted: "Profile configuration complete!" + initialAccountSettingCompleted: "Profile setup complete!" haveFun: "Enjoy {name}!" ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}." - skipAreYouSure: "Really skip profile configuration?" + skipAreYouSure: "Really skip profile setup?" _serverRules: description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended." _accountMigration: @@ -1321,7 +1328,7 @@ _role: isConditionalRole: "This is a conditional role." isPublic: "Public role" descriptionOfIsPublic: "Anyone will be able to view a list of users assigned to this role. In addition, this role will be displayed in the profiles of assigned users." - options: "Role options" + options: "Options" policies: "Policies" baseRole: "Role template" useBaseValue: "Use role template value" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 42d52e5a2..b043ecf3c 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -993,6 +993,7 @@ accountMigration: "Migración de cuenta" accountMoved: "Este usuario se ha mudado a una nueva cuenta:" horizontal: "Horizontal" youFollowing: "Siguiendo" +options: "Opción" _accountMigration: moveFrom: "Trasladar de otra cuenta a ésta" moveFromLabel: "Cuenta desde la que se realiza el traslado:" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index e98ee9ff9..d8ac41c92 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -452,6 +452,7 @@ native: "Natif" disableDrawer: "Les menus ne s'affichent pas dans le tiroir" noHistory: "Pas d'historique" signinHistory: "Historique de connexion" +enableAdvancedMfm: "Activer la MFM avancée" doing: "En cours..." category: "Catégorie" tags: "Étiquettes" @@ -846,6 +847,7 @@ rateLimitExceeded: "Limite de taux dépassée" cropImage: "Recadrer l'image" cropImageAsk: "Voulez-vous recadrer cette image ?" cropYes: "Rogner" +cropNo: "Utiliser en l'état" file: "Fichiers" recentNHours: "Dernières {n} heures" recentNDays: "Derniers {n} jours" @@ -912,6 +914,7 @@ color: "Couleur" manageCustomEmojis: "Gestion des émojis personnalisés" preset: "Préréglage" selectFromPresets: "Sélectionner à partir des préréglages" +thisPostMayBeAnnoyingCancel: "Annuler" license: "Licence" video: "Vidéo" videos: "Vidéos" @@ -925,6 +928,7 @@ leftBottom: "En bas à gauche" rightBottom: "En bas à droite" vertical: "Vertical" horizontal: "Latéral" +serverRules: "Règles du serveur" youFollowing: "Abonné·e" _achievements: _types: @@ -934,10 +938,13 @@ _achievements: _notes100000: title: "ALL YOUR NOTE ARE BELONG TO US" _login3: + title: "Débutant Ⅰ" description: "Se connecter pour un total de 3 jours" _login7: + title: "Débutant Ⅱ" description: "Se connecter pour un total de 7 jours" _login15: + title: "Débutant Ⅲ" description: "Se connecter pour un total de 15 jours" _login30: description: "Se connecter pour un total de 30 jours" @@ -945,6 +952,22 @@ _achievements: description: "Se connecter pour un total de 60 jours" _login100: description: "Se connecter pour un total de 100 jours" + _login200: + description: "Se connecter pour un total de 200 jours" + _login300: + description: "Se connecter pour un total de 300 jours" + _login400: + description: "Se connecter pour un total de 400 jours" + _login500: + description: "Se connecter pour un total de 500 jours" + _login600: + description: "Se connecter pour un total de 600 jours" + _login700: + description: "Se connecter pour un total de 700 jours" + _login800: + description: "Se connecter pour un total de 800 jours" + _login900: + description: "Se connecter pour un total de 900 jours" _login1000: flavor: "Merci d'utiliser Misskey !" _markedAsCat: @@ -954,8 +977,12 @@ _achievements: title: "Beaucoup d'amis" _followers10: title: "Abonnez-moi !" + _iLoveMisskey: + title: "J’adore Misskey" _viewInstanceChart: title: "Analyste" + _loggedInOnBirthday: + title: "Joyeux Anniversaire !" _loggedInOnNewYearsDay: title: "Bonne année !" _role: diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 5159775fc..df42697cc 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -955,6 +955,7 @@ disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi disableFederationOk: "Matikan federasi" horizontal: "Horisontal" youFollowing: "Mengikuti" +options: "Opsi peran" _achievements: earnedAt: "Terbuka pada" _types: diff --git a/locales/it-IT.yml b/locales/it-IT.yml index cacd91cac..3c1a26e85 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1020,6 +1020,7 @@ pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, auto pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto." continue: "Continua" youFollowing: "Seguiti" +options: "Opzioni del ruolo" _serverRules: description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio." _accountMigration: diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8bbf9459f..0b7108fe6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -689,7 +689,7 @@ no: "いいえ" driveFilesCount: "ドライブのファイル数" driveUsage: "ドライブ使用量" noCrawle: "クローラーによるインデックスを拒否" -noCrawleDescription: "検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要請します。" +noCrawleDescription: "外部の検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要求します。" lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にする" loadRawImages: "添付画像のサムネイルをオリジナル画質にする" @@ -832,6 +832,8 @@ breakFollow: "フォロワーを解除" breakFollowConfirm: "フォロワー解除しますか?" itsOn: "オンになっています" itsOff: "オフになっています" +on: "オン" +off: "オフ" emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする" unread: "未読" filter: "フィルタ" @@ -990,6 +992,7 @@ rolesAssignedToMe: "自分に割り当てられたロール" resetPasswordConfirm: "パスワードリセットしますか?" sensitiveWords: "センシティブワード" sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。" +sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。" notesSearchNotAvailable: "ノート検索は利用できません。" license: "ライセンス" unfavoriteConfirm: "お気に入り解除しますか?" @@ -1038,12 +1041,17 @@ thisChannelArchived: "このチャンネルはアーカイブされています displayOfNote: "ノートの表示" initialAccountSetting: "初期設定" youFollowing: "フォロー中" +preventAiLearning: "生成AIによる学習を拒否" +preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。" +options: "オプション" +specifyUser: "ユーザー指定" _initialAccountSetting: accountCreated: "アカウントの作成が完了しました!" letsStartAccountSetup: "アカウントの初期設定を行いましょう。" letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。" profileSetting: "プロフィール設定" + privacySetting: "プライバシー設定" theseSettingsCanEditLater: "これらの設定は後から変更できます。" youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。" followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 512cce145..d09f75155 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -689,7 +689,7 @@ no: "あかん" driveFilesCount: "ドライブのファイル数" driveUsage: "ドライブ使用量やで" noCrawle: "クローラーによるインデックスを拒否するで" -noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せんように頼むで。邪魔すんねんやったら帰って〜。" +noCrawleDescription: "検索エンジンにあんたのユーザーページ、ノート、Pagesとかのコンテンツを登録(インデックス)せぇへんように頼むで。" lockedAccountInfo: "フォローを承認制にしとっても、ノートの公開範囲を「フォロワー」にせぇへん限り、誰でもあんたのノートを見れるで。" alwaysMarkSensitive: "デフォルトでメディアを閲覧注意にするで" loadRawImages: "添付画像のサムネイルをオリジナル画質にするで" @@ -990,6 +990,7 @@ rolesAssignedToMe: "自分に割り当てられたロール" resetPasswordConfirm: "パスワード作り直すんでええな?" sensitiveWords: "けったいな単語" sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。" +sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。" notesSearchNotAvailable: "ノート検索は使われへんで。" license: "ライセンス" unfavoriteConfirm: "ほんまに気に入らんの?" @@ -1038,10 +1039,16 @@ thisChannelArchived: "このチャンネル、アーカイブされとるで。" displayOfNote: "ノートの表示" initialAccountSetting: "初期設定" youFollowing: "フォロー中やで" +preventAiLearning: "生成AIの学習に使わんといて" +preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したノートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。" +options: "オプション" +specifyUser: "ユーザー指定" _initialAccountSetting: accountCreated: "アカウント作り終わったで。" + letsStartAccountSetup: "アカウントの初期設定をしよか。" letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。" profileSetting: "プロフィール設定" + privacySetting: "プライバシー設定" theseSettingsCanEditLater: "この設定はあとから変えれるで。" youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。" followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。" @@ -1060,11 +1067,12 @@ _accountMigration: moveTo: "このアカウントをさらのアカウントに引っ越すで" moveToLabel: "引っ越し先のアカウント:" moveCannotBeUndone: "アカウントを移行すると、取り消すことはできへんくなります。" - moveAccountDescription: "この操作は戻されへんで。まず引っ越し先のアカウントでこのアカウントへのエイリアスが作れたか確認してきなはれや。エイリアスができてたら、引っ越し先のアカウントをこんな風に入力してくれへんか?:@person@instance.com" + moveAccountDescription: "おニューのアカウントに移行すんで。\n ・フォロワーがおニューの方を勝手にフォローすんで。\n ・このアカウントからのフォローはまるまる全部解除されんで。\n ・このアカウントでノート作れへんようになるで。\n\nフォロワーの移行は勝手にこっちでやっとくけど、フォローの移行は自分でしてや。移行前にこのアカウントでフォローエクスポートして、移行したあとすぐにおニューのところでインポートしてくれな。\nリストとかミュート、あとブロックもおんなじや。自分で移行してな。\n\n(この説明はこのサーバー、つまりMisskey v13.12.0から後の仕様や。Mastodonとか他のActivityPubソフトやとちょっと挙動が違うこともあんで。)" moveAccountHowTo: "アカウントの引っ越しには、まず引っ越し先のアカウントで自分のアカウントに対しエイリアスを作成しなはれや。\nエイリアス作成した後、引っ越し先のアカウントを次のように入力してくれへんか?:@username@server.example.com" startMigration: "引っ越しする" migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃ~んと確認しーや?" movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。" + postMigrationNote: "このアカウントからのフォロー解除は移行操作から丸一日経ったら実行されんで。\nこのアカウントのフォロー・フォロワー数はどっちも0や。フォローの解除はされへんから、あんたのフォロワーはこのアカウントのフォロワー向けの投稿をこの後も見れるで。" movedTo: "引っ越し先のアカウント:" _achievements: earnedAt: "貰った日ぃ" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index ef690d260..39574d332 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1038,6 +1038,7 @@ thisChannelArchived: "이 채널은 아카이브되었습니다." displayOfNote: "노트 표시" initialAccountSetting: "초기 설정" youFollowing: "팔로잉" +options: "옵션" _initialAccountSetting: accountCreated: "계정 생성이 완료되었습니다!" letsStartAccountSetup: "계정의 초기 설정을 진행합니다." @@ -1240,6 +1241,7 @@ _achievements: title: "잠깐 쉬어" description: "클라이언트를 시작하고 30분이 경과하였습니다" _client60min: + title: "No \"Miss\" in Misskey" description: "클라이언트를 시작하고 60분이 경과하였습니다" _noteDeletedWithin1min: title: "있었는데요 없었습니다" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index f7f0d442d..36c29295f 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -278,6 +278,7 @@ video: "Video" videos: "Videoer" continue: "Fortsett" youFollowing: "Følger" +options: "Alternativ" _initialAccountSetting: theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." _achievements: diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 55d4b7059..a123ad726 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1006,6 +1006,7 @@ videos: "Видео" dataSaver: "Экономия трафика" horizontal: "Сбоку" youFollowing: "Подписки" +options: "Настройки ролей" _achievements: earnedAt: "Разблокировано в" _types: diff --git a/locales/th-TH.yml b/locales/th-TH.yml index d1f87dbfd..22f110eba 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1031,6 +1031,7 @@ preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร" createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้" youFollowing: "ติดตามแล้ว" +options: "ตัวเลือกบทบาท" _serverRules: description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ" _accountMigration: diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 1fe82185b..638cc1cf8 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1030,6 +1030,13 @@ continue: "继续" preservedUsernames: "保留的用户名" createNoteFromTheFile: "从文件创建帖子" youFollowing: "正在关注" +options: "选项" +_initialAccountSetting: + accountCreated: "账户创建完成了!" + letsStartAccountSetup: "来进行帐户的初始设置吧。" + letsFillYourProfile: "首先,来设定你的个人档案吧!" + profileSetting: "个人资料设置" + theseSettingsCanEditLater: "也可以在稍后修改这里的设置。" _serverRules: description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。" _accountMigration: @@ -1038,8 +1045,11 @@ _accountMigration: moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com" moveTo: "把这个账户迁移到新的账户" moveToLabel: "迁移后的账户" + moveCannotBeUndone: "一旦迁移账户,就无法撤销。" moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com" + startMigration: "迁移" migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。" + movedAndCannotBeUndone: "该账户已被迁移。\n迁移操作无法撤销。" movedTo: "迁移后的账户" _achievements: earnedAt: "达成时间" @@ -1572,6 +1582,9 @@ _time: minute: "分" hour: "小时" day: "日" +_timelineTutorial: + step3_1: "将想说的话发出去了吗?" + step3_2: "太棒了!现在你可以在你的时间线中看到刚刚发布的帖子了。" _2fa: alreadyRegistered: "此设备已被注册" registerTOTP: "开始设置认证应用" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 4073b9629..d2b42313a 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -990,6 +990,7 @@ rolesAssignedToMe: "指派給自己的角色" resetPasswordConfirm: "重設密碼?" sensitiveWords: "敏感詞" sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。" +sensitiveWordsDescription2: "用空格分隔關鍵詞構成AND格式,用斜線包圍關鍵字構成正規表達式。" notesSearchNotAvailable: "無法使用搜尋貼文功能。" license: "授權" unfavoriteConfirm: "要取消收錄我的最愛嗎?" @@ -1036,7 +1037,24 @@ channelArchiveConfirmTitle: "要封存{name}嗎?" channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。" thisChannelArchived: "這個頻道已被封存。" displayOfNote: "顯示貼文" -youFollowing: "關注中" +initialAccountSetting: "初始設定" +youFollowing: "追隨中" +preventAiLearning: "拒絕接受產生式AI的學習" +preventAiLearningDescription: "要求外部的文章產生AI或圖像產生AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的,但不能完全防止AI的學習,因為這要看該AI是否遵守這個要求。" +options: "選項" +_initialAccountSetting: + accountCreated: "帳戶已建立完成!" + letsStartAccountSetup: "來進行帳戶的初始設定吧。" + letsFillYourProfile: "首先,來設定您的個人檔案吧。" + profileSetting: "個人檔案設定" + theseSettingsCanEditLater: "這裡的設定可以在之後變更。" + youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。" + followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。" + pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。" + initialAccountSettingCompleted: "初始設定完成了!" + haveFun: "盡情享受{name}吧!" + ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。" + skipAreYouSure: "要略過初始設定嗎?" _serverRules: description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。" _accountMigration: @@ -1464,7 +1482,7 @@ _channel: removeBanner: "移除橫幅圖像" featured: "熱門貼文" owned: "管理中" - following: "關注中" + following: "追隨中" usersCount: "有{n}人參與" notesCount: "有{n}個貼文" nameAndDescription: "名稱與說明" @@ -1586,6 +1604,16 @@ _time: minute: "分鐘" hour: "小時" day: "日" +_timelineTutorial: + title: "Misskey的使用方法" + step1_1: "這個畫面是「時間軸」。發布到{name}的「貼文」按照時間順序顯示。" + step1_2: "時間軸有多種類型,例如在「首頁時間軸」中流動的是您追蹤的人的貼文;而在「本地時間軸」流動的是{name}全體的貼文。" + step2_1: "試試看,發布個貼文吧!按畫面上鉛筆圖示的按鈕開啟表格。" + step2_2: "初次貼文的內容,建議包括自我介紹以及「開始使用{name}」。" + step3_1: "貼文發出去了嗎?" + step3_2: "如果你的貼文出現在時間軸上,就代表發文成功。" + step4_1: "可以對貼文標記「反應」。" + step4_2: "點擊貼文的「+」圖示,即可選擇喜好的表情符號來標記反應。" _2fa: alreadyRegistered: "此設備已經被註冊過了" registerTOTP: "開始設定驗證應用程式" diff --git a/package.json b/package.json index 28e1cdcf1..a21d7ab9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "13.12.1", + "version": "13.12.2", "codename": "nasubi", "repository": { "type": "git", @@ -56,11 +56,11 @@ "devDependencies": { "@types/gulp": "4.0.10", "@types/gulp-rename": "2.0.1", - "@typescript-eslint/eslint-plugin": "5.59.2", - "@typescript-eslint/parser": "5.59.2", + "@typescript-eslint/eslint-plugin": "5.59.5", + "@typescript-eslint/parser": "5.59.5", "cross-env": "7.0.3", - "cypress": "12.11.0", - "eslint": "8.39.0", + "cypress": "12.12.0", + "eslint": "8.40.0", "start-server-and-test": "2.0.0" }, "optionalDependencies": { diff --git a/packages/backend/migration/1683682889948-prevent-ai-larning.js b/packages/backend/migration/1683682889948-prevent-ai-larning.js new file mode 100644 index 000000000..9d1a19c10 --- /dev/null +++ b/packages/backend/migration/1683682889948-prevent-ai-larning.js @@ -0,0 +1,11 @@ +export class PreventAiLarning1683682889948 { + name = 'PreventAiLarning1683682889948' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ADD "preventAiLarning" boolean NOT NULL DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "preventAiLarning"`); + } +} diff --git a/packages/backend/migration/1683683083083-public-reactions-default-true.js b/packages/backend/migration/1683683083083-public-reactions-default-true.js new file mode 100644 index 000000000..195ea02a5 --- /dev/null +++ b/packages/backend/migration/1683683083083-public-reactions-default-true.js @@ -0,0 +1,11 @@ +export class PublicReactionsDefaultTrue1683683083083 { + name = 'PublicReactionsDefaultTrue1683683083083' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "publicReactions" SET DEFAULT false`); + } +} diff --git a/packages/backend/migration/1683789676867-fix-typo.js b/packages/backend/migration/1683789676867-fix-typo.js new file mode 100644 index 000000000..c0dbbf005 --- /dev/null +++ b/packages/backend/migration/1683789676867-fix-typo.js @@ -0,0 +1,11 @@ +export class FixTypo1683789676867 { + name = 'FixTypo1683789676867' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLarning" TO "preventAiLearning"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" RENAME COLUMN "preventAiLearning" TO "preventAiLarning"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index e0ece2bfe..4bab4a734 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -58,7 +58,7 @@ "@fastify/accepts": "4.1.0", "@fastify/cookie": "8.3.0", "@fastify/cors": "8.2.1", - "@fastify/http-proxy": "9.0.0", + "@fastify/http-proxy": "9.1.0", "@fastify/multipart": "7.6.0", "@fastify/static": "6.10.1", "@fastify/view": "7.4.1", @@ -89,11 +89,11 @@ "escape-regexp": "0.0.1", "fastify": "4.17.0", "feed": "4.2.2", - "file-type": "18.3.0", + "file-type": "18.4.0", "fluent-ffmpeg": "2.1.2", "form-data": "4.0.0", "got": "12.6.0", - "happy-dom": "9.10.2", + "happy-dom": "9.16.0", "hpagent": "1.2.0", "ioredis": "5.3.2", "ip-cidr": "3.1.0", @@ -110,11 +110,11 @@ "ms": "3.0.0-canary.1", "nested-property": "4.0.0", "node-fetch": "3.3.1", - "nodemailer": "6.9.1", + "nodemailer": "6.9.2", "nsfwjs": "2.4.2", "oauth": "0.10.0", "os-utils": "0.0.14", - "otpauth": "9.1.1", + "otpauth": "9.1.2", "parse5": "7.1.2", "pg": "8.10.0", "private-ip": "3.0.0", @@ -149,7 +149,7 @@ "tsc-alias": "1.8.6", "tsconfig-paths": "4.2.0", "twemoji-parser": "14.0.0", - "typeorm": "0.3.15", + "typeorm": "0.3.16", "typescript": "5.0.4", "ulid": "2.3.0", "unzipper": "0.10.11", @@ -178,7 +178,7 @@ "@types/jsonld": "1.5.8", "@types/jsrsasign": "10.5.8", "@types/mime-types": "2.1.1", - "@types/node": "18.16.3", + "@types/node": "20.1.3", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.7", "@types/oauth": "0.9.1", @@ -191,7 +191,7 @@ "@types/redis": "4.0.11", "@types/rename": "1.0.4", "@types/sanitize-html": "2.9.0", - "@types/semver": "7.3.13", + "@types/semver": "7.5.0", "@types/sharp": "0.32.0", "@types/sinonjs__fake-timers": "8.1.2", "@types/tinycolor2": "1.4.3", @@ -202,11 +202,11 @@ "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.59.2", - "@typescript-eslint/parser": "5.59.2", - "aws-sdk-client-mock": "^2.1.1", + "@typescript-eslint/eslint-plugin": "5.59.5", + "@typescript-eslint/parser": "5.59.5", + "aws-sdk-client-mock": "2.1.1", "cross-env": "7.0.3", - "eslint": "8.39.0", + "eslint": "8.40.0", "eslint-plugin-import": "2.27.5", "execa": "6.1.0", "jest": "29.5.0", diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts index 45ded5495..3995545d7 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 b41fb603b..c6e107538 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 364976e4a..977c9052c 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 d4905a5f8..1d7394777 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>; export type EndedPollNotificationQueue = Bull.Queue; export type DeliverQueue = Bull.Queue; export type InboxQueue = Bull.Queue; -export type DbQueue = Bull.Queue>; +export type DbQueue = Bull.Queue; export type RelationshipQueue = Bull.Queue; -export type ObjectStorageQueue = Bull.Queue; +export type ObjectStorageQueue = Bull.Queue; export type WebhookDeliverQueue = Bull.Queue; 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 { + 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 e68fde088..9502afcc9 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 453c1473d..7f61e1d6f 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 60c1c55de..236ee8f98 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; @@ -147,6 +147,11 @@ export class UserProfile { }) public noCrawle: boolean; + @Column('boolean', { + default: true, + }) + public preventAiLearning: boolean; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 529c1303d..f9a20ac39 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 df8ac3a30..000000000 --- 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 865e47c3f..000000000 --- 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 3d4cc7732..e1c6b93d9 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 706110f6f..dc025f988 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 736b4fa80..000000000 --- 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 7fb0da4b1..000000000 --- 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 42229c8f2..f49d2a096 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 { 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 6c66300bb..74be00a8b 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 { 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 50b23a068..f780280c1 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 4c692bf59..74dc62f1e 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 5166855ea..5594fcdfb 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 ca0663a48..10f2d269b 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 65696ea13..badfcccd6 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 4219e76a5..ddffc361c 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 119993fdb..f4c83aa89 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) diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index 9c851a5dd..d2eb8f01d 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -541,6 +541,61 @@ describe('Note', () => { assert.strictEqual(res.status, 400); }); + + test('センシティブな投稿はhomeになる (単語指定)', async () => { + const sensitive = await api('admin/update-meta', { + sensitiveWords: [ + "test", + ] + }, alice); + + assert.strictEqual(sensitive.status, 204); + + await new Promise(x => setTimeout(x, 2)); + + const note1 = await api('/notes/create', { + text: 'hogetesthuge', + }, alice); + + assert.strictEqual(note1.status, 200); + assert.strictEqual(note1.body.createdNote.visibility, 'home'); + + }); + + test('センシティブな投稿はhomeになる (正規表現)', async () => { + const sensitive = await api('admin/update-meta', { + sensitiveWords: [ + "/Test/i", + ] + }, alice); + + assert.strictEqual(sensitive.status, 204); + + const note2 = await api('/notes/create', { + text: 'hogetesthuge', + }, alice); + + assert.strictEqual(note2.status, 200); + assert.strictEqual(note2.body.createdNote.visibility, 'home'); + }); + + test('センシティブな投稿はhomeになる (スペースアンド)', async () => { + const sensitive = await api('admin/update-meta', { + sensitiveWords: [ + "Test hoge" + ] + }, alice); + + assert.strictEqual(sensitive.status, 204); + + const note2 = await api('/notes/create', { + text: 'hogeTesthuge', + }, alice); + + assert.strictEqual(note2.status, 200); + assert.strictEqual(note2.body.createdNote.visibility, 'home'); + + }); }); describe('notes/delete', () => { diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 51537dda1..a7f8210c8 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -145,6 +145,7 @@ describe('ユーザー', () => { carefulBot: user.carefulBot, autoAcceptFollowed: user.autoAcceptFollowed, noCrawle: user.noCrawle, + preventAiLearning: user.preventAiLearning, isExplorable: user.isExplorable, isDeleted: user.isDeleted, hideOnlineStatus: user.hideOnlineStatus, @@ -370,7 +371,7 @@ describe('ユーザー', () => { assert.deepStrictEqual(response.pinnedNotes, []); assert.strictEqual(response.pinnedPageId, null); assert.strictEqual(response.pinnedPage, null); - assert.strictEqual(response.publicReactions, false); + assert.strictEqual(response.publicReactions, true); assert.strictEqual(response.ffVisibility, 'public'); assert.strictEqual(response.twoFactorEnabled, false); assert.strictEqual(response.usePasswordLessLogin, false); @@ -390,6 +391,7 @@ describe('ユーザー', () => { assert.strictEqual(response.carefulBot, false); assert.strictEqual(response.autoAcceptFollowed, true); assert.strictEqual(response.noCrawle, false); + assert.strictEqual(response.preventAiLearning, true); assert.strictEqual(response.isExplorable, true); assert.strictEqual(response.isDeleted, false); assert.strictEqual(response.hideOnlineStatus, false); @@ -462,6 +464,8 @@ describe('ユーザー', () => { { parameters: (): object => ({ autoAcceptFollowed: false }) }, { parameters: (): object => ({ noCrawle: true }) }, { parameters: (): object => ({ noCrawle: false }) }, + { parameters: (): object => ({ preventAiLearning: false }) }, + { parameters: (): object => ({ preventAiLearning: true }) }, { parameters: (): object => ({ isBot: true }) }, { parameters: (): object => ({ isBot: false }) }, { parameters: (): object => ({ isCat: true }) }, diff --git a/packages/frontend/.storybook/changes.ts b/packages/frontend/.storybook/changes.ts index 755bec686..fc0f0c286 100644 --- a/packages/frontend/.storybook/changes.ts +++ b/packages/frontend/.storybook/changes.ts @@ -45,7 +45,7 @@ fs.readFile( micromatch(Array.from(modules), [ '../../assets/**', '../../fluent-emojis/**', - '../../locales/**', + '../../locales/ja-JP.yml', '../../misskey-assets/**', 'assets/**', 'public/**', diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 7646e152f..5b4004d8e 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -17,13 +17,13 @@ "@discordapp/twemoji": "14.1.2", "@rollup/plugin-alias": "5.0.0", "@rollup/plugin-json": "6.0.0", - "@rollup/plugin-replace": "^5.0.2", + "@rollup/plugin-replace": "5.0.2", "@rollup/pluginutils": "5.0.2", "@syuilo/aiscript": "0.13.2", "@tabler/icons-webfont": "2.17.0", - "@vitejs/plugin-vue": "4.2.1", - "@vue-macros/reactivity-transform": "^0.3.5", - "@vue/compiler-sfc": "3.2.47", + "@vitejs/plugin-vue": "4.2.2", + "@vue-macros/reactivity-transform": "0.3.6", + "@vue/compiler-sfc": "3.3.1", "autosize": "5.0.2", "blurhash": "2.0.5", "broadcast-channel": "4.20.2", @@ -34,14 +34,14 @@ "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "6.17.3", - "compare-versions": "5.0.1", + "chromatic": "6.17.4", + "compare-versions": "5.0.3", "cropperjs": "2.0.0-beta.2", "date-fns": "2.30.0", "escape-regexp": "0.0.1", "eventemitter3": "5.0.1", "gsap": "3.11.5", - "idb-keyval": "6.2.0", + "idb-keyval": "6.2.1", "insert-text-at-cursor": "0.3.0", "is-file-animated": "1.0.2", "json5": "2.2.3", @@ -53,7 +53,7 @@ "punycode": "2.3.0", "querystring": "0.2.1", "rndstr": "1.0.0", - "rollup": "3.21.3", + "rollup": "3.21.6", "s-age": "1.1.2", "sanitize-html": "2.10.0", "sass": "1.62.1", @@ -70,40 +70,40 @@ "typescript": "5.0.4", "uuid": "9.0.0", "vanilla-tilt": "1.8.0", - "vite": "4.3.4", - "vue": "3.2.47", + "vite": "4.3.5", + "vue": "3.3.1", "vue-plyr": "7.0.0", "vue-prism-editor": "2.0.0-alpha.2", "vuedraggable": "next" }, "devDependencies": { - "@storybook/addon-actions": "7.0.7", - "@storybook/addon-essentials": "7.0.7", - "@storybook/addon-interactions": "7.0.7", - "@storybook/addon-links": "7.0.7", - "@storybook/addon-storysource": "7.0.7", - "@storybook/addons": "7.0.7", - "@storybook/blocks": "7.0.7", - "@storybook/core-events": "7.0.7", + "@storybook/addon-actions": "7.0.10", + "@storybook/addon-essentials": "7.0.10", + "@storybook/addon-interactions": "7.0.10", + "@storybook/addon-links": "7.0.10", + "@storybook/addon-storysource": "7.0.10", + "@storybook/addons": "7.0.10", + "@storybook/blocks": "7.0.10", + "@storybook/core-events": "7.0.10", "@storybook/jest": "0.1.0", - "@storybook/manager-api": "7.0.7", - "@storybook/preview-api": "7.0.7", - "@storybook/react": "7.0.7", - "@storybook/react-vite": "7.0.7", + "@storybook/manager-api": "7.0.10", + "@storybook/preview-api": "7.0.10", + "@storybook/react": "7.0.10", + "@storybook/react-vite": "7.0.10", "@storybook/testing-library": "0.1.0", - "@storybook/theming": "7.0.7", - "@storybook/types": "7.0.7", - "@storybook/vue3": "7.0.7", - "@storybook/vue3-vite": "7.0.7", + "@storybook/theming": "7.0.10", + "@storybook/types": "7.0.10", + "@storybook/vue3": "7.0.10", + "@storybook/vue3-vite": "7.0.10", "@testing-library/jest-dom": "5.16.5", "@testing-library/vue": "7.0.0", "@types/escape-regexp": "0.0.1", "@types/estree": "1.0.1", "@types/gulp": "4.0.10", - "@types/gulp-rename": "2.0.1", - "@types/matter-js": "0.18.2", + "@types/gulp-rename": "2.0.2", + "@types/matter-js": "0.18.3", "@types/micromatch": "4.0.2", - "@types/node": "18.16.3", + "@types/node": "20.1.3", "@types/punycode": "2.1.0", "@types/sanitize-html": "2.9.0", "@types/seedrandom": "3.0.5", @@ -113,19 +113,19 @@ "@types/uuid": "9.0.1", "@types/websocket": "1.0.5", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.59.2", - "@typescript-eslint/parser": "5.59.2", - "@vitest/coverage-c8": "0.30.1", - "@vue/runtime-core": "3.2.47", + "@typescript-eslint/eslint-plugin": "5.59.5", + "@typescript-eslint/parser": "5.59.5", + "@vitest/coverage-c8": "0.31.0", + "@vue/runtime-core": "3.3.1", "astring": "1.8.4", "chokidar-cli": "3.0.0", "cross-env": "7.0.3", - "cypress": "12.11.0", - "eslint": "8.39.0", + "cypress": "12.12.0", + "eslint": "8.40.0", "eslint-plugin-import": "2.27.5", - "eslint-plugin-vue": "9.11.0", + "eslint-plugin-vue": "9.12.0", "fast-glob": "3.2.12", - "happy-dom": "9.10.2", + "happy-dom": "9.16.0", "micromatch": "3.1.10", "msw": "1.2.1", "msw-storybook-addon": "1.8.0", @@ -133,13 +133,13 @@ "react": "18.2.0", "react-dom": "18.2.0", "start-server-and-test": "2.0.0", - "storybook": "7.0.7", + "storybook": "7.0.10", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "summaly": "github:misskey-dev/summaly", "vite-plugin-turbosnap": "1.0.2", - "vitest": "0.30.1", + "vitest": "0.31.0", "vitest-fetch-mock": "0.2.2", - "vue-eslint-parser": "9.1.1", - "vue-tsc": "1.6.3" + "vue-eslint-parser": "9.2.1", + "vue-tsc": "1.6.4" } } diff --git a/packages/frontend/src/components/MkCheckbox.vue b/packages/frontend/src/components/MkCheckbox.vue deleted file mode 100644 index a8e24dd83..000000000 --- a/packages/frontend/src/components/MkCheckbox.vue +++ /dev/null @@ -1,144 +0,0 @@ - - - - - diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 0f87fef6b..6fcd8f781 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -52,9 +52,12 @@ -
+
+
+ +
@@ -86,6 +89,7 @@ import { i18n } from '@/i18n'; import MkHeatmap from '@/components/MkHeatmap.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; +import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue'; import { initChart } from '@/scripts/init-chart'; initChart(); @@ -202,7 +206,12 @@ onMounted(() => { margin-bottom: 16px; } -.retention { +.retentionHeatmap { + padding: 16px; + margin-bottom: 16px; +} + +.retentionLine { padding: 16px; margin-bottom: 16px; } diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index ad7dc4da1..63c55b904 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -1,15 +1,15 @@