diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b1c256225d..b93080278d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,9 +24,6 @@ updates: aws-sdk: patterns: - "@aws-sdk/*" - bull-board: - patterns: - - "@bull-board/*" nestjs: patterns: - "@nestjs/*" diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml index 9fdbeab913..6608761c13 100644 --- a/.github/workflows/storybook.yml +++ b/.github/workflows/storybook.yml @@ -50,8 +50,8 @@ jobs: - run: pnpm i --frozen-lockfile - name: Check pnpm-lock.yaml run: git diff --exit-code pnpm-lock.yaml - - name: Build misskey-js - run: pnpm --filter misskey-js build + - name: Build dependent packages + run: pnpm -F misskey-js -F misskey-bubble-game -F misskey-reversi build - name: Build storybook run: pnpm --filter frontend build-storybook - name: Publish to Chromatic diff --git a/CHANGELOG.md b/CHANGELOG.md index 8282955538..e43e5aa6bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,23 +2,31 @@ ### General - Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように `#14177` -- +- セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。 + - Misskeyネイティブでダッシュボードを実装予定です ### Client - Feat: 設定の管理が強化されました - 自動でバックアップされるように - 任意の設定項目をデバイス間で同期できるように +- Feat: 画面を重ねて表示するオプションを実装(実験的) - Enhance: プラグインの管理が強化されました - インストール/アンインストール/設定の変更時にリロード不要になりました - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように - Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに - Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように - Enhance: テーマ設定画面のデザインを改善 +- Enhance: 投稿フォームの設定メニューを改良 + - 投稿フォームをリセットできるように + - 文字数カウントを復活 +- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように - Fix: テーマ切り替え時に一部の色が変わらない問題を修正 ### Server - Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正 - Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正 +- Fix: 連合無しモードでも外部から照会可能だった問題を修正 +- Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正 ## 2025.3.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c63f69cf1..a3aedfa9eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -273,7 +273,6 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド query?: Record; loginRequired?: boolean; hash?: string; - globalCacheKey?: string; children?: RouteDef[]; } ``` diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 4966007061..5e936da80c 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1013,6 +1013,7 @@ flip: "اقلب" lastNDays: "آخر {n} أيام" surrender: "ألغِ" postForm: "أنشئ ملاحظة" +information: "عن" _delivery: stop: "مُعلّق" _initialAccountSetting: diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 6aea5a1999..36d142bd0b 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -853,6 +853,7 @@ renotes: "রিনোট" sourceCode: "সোর্স কোড" flip: "উল্টান" postForm: "নোট লিখুন" +information: "আপনার সম্পর্কে" _delivery: stop: "স্থগিত করা হয়েছে" _type: diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 54e21946b2..23b958252a 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} està parlant sobre \"{word}\"" makeActive: "Activar" display: "Veure" copy: "Copiar" +copiedToClipboard: "Copiat al porta papers" metrics: "Mètriques" overview: "Visió General" logs: "Registres" @@ -1139,7 +1140,7 @@ channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista d thisChannelArchived: "Aquest Canal ha sigut arxivat." displayOfNote: "Mostrar notes" initialAccountSetting: "Configuració del perfil" -youFollowing: "Seguint" +youFollowing: "Segueixes " preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)" preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta." options: "Opcions" @@ -1190,7 +1191,7 @@ pastAnnouncements: "Informes passats" youHaveUnreadAnnouncements: "Tens informes per llegir." useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." replies: "Respostes" -renotes: "Impulsar" +renotes: "Impulsos" loadReplies: "Mostrar les respostes" loadConversation: "Mostrar la conversació " pinnedList: "Llista fixada" @@ -1326,7 +1327,19 @@ restore: "Restaurar " syncBetweenDevices: "Sincronització entre dispositius" preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu" preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?" +preferenceSyncConflictChoiceServer: "Valors de configuració del servidor" +preferenceSyncConflictChoiceDevice: "Punts d'ajustos del dispositiu " +preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronització " +paste: "Pegar" +emojiPalette: "Calaix d'emojis" postForm: "Formulari de publicació" +textCount: "Nombre de caràcters " +information: "Informació" +_emojiPalette: + palettes: "Calaixos d'emojis" + enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius" + paletteForMain: "Calaix d'emojis principal" + paletteForReaction: "Calaix d'emojis per reaccions" _settings: driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius." pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se." @@ -1344,6 +1357,9 @@ _settings: preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències." appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer." soundsBanner: "Configuració dels sons que reproduirà el client." + timelineAndNote: "Línia de temps i nota" + makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables" + makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions." _preferencesProfile: profileName: "Nom del perfil" profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu." @@ -2056,7 +2072,7 @@ _theme: hashtag: "Etiqueta" mention: "Menció" mentionMe: "Mencions (jo)" - renote: "Renotar" + renote: "Impulsar" modalBg: "Fons del modal" divider: "Divisor" scrollbarHandle: "Maneta de la barra de desplaçament" @@ -2498,7 +2514,7 @@ _notification: follow: "Segueix-me" mention: "Menció" reply: "Respostes" - renote: "Impulsar" + renote: "Impulsos" quote: "Citar" reaction: "Reaccions" pollEnded: "Enquesta terminada" @@ -2508,12 +2524,13 @@ _notification: achievementEarned: "Assoliment desbloquejat" exportCompleted: "Exportació completada" login: "Iniciar sessió" + createToken: "Creació de tokens d'accés " test: "Prova la notificació" app: "Notificacions d'aplicacions" _actions: followBack: "També et segueix" reply: "Respondre" - renote: "Impulsos" + renote: "Impulsar" _deck: alwaysShowMainColumn: "Mostrar sempre la columna principal" columnAlign: "Alinea les columnes" @@ -2657,10 +2674,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho." _plugin: title: "Vols instal·lar aquest afegit?" - metaTitle: "Informació de l'afegit " _theme: title: "Vols instal·lar aquest tema?" - metaTitle: "Informació del tema" _meta: base: "Paleta de colors base" _vendorInfo: diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index a509280862..11ad97c3a5 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -169,6 +169,9 @@ addAccount: "Přidat účet" reloadAccountsList: "Obnovit list účtů" loginFailed: "Přihlášení se nezdařilo." showOnRemote: "Více na původním profilu" +continueOnRemote: "Pokračujte na původní profil" +chooseServerOnMisskeyHub: "Vyberete si server z Misskey Hubu" +inputHostName: "Zadejte doménu" general: "Obecně" wallpaper: "Obrázek na pozadí" setWallpaper: "Nastavení obrázku na pozadí" @@ -193,6 +196,7 @@ perHour: "za hodinu" perDay: "za den" stopActivityDelivery: "Přestat zasílat aktivitu" blockThisInstance: "Blokovat tuto instanci" +silenceThisInstance: "Utišit tuto instanci" operations: "Operace" software: "Software" version: "Verze" @@ -1099,6 +1103,7 @@ flip: "Otočit" lastNDays: "Posledních {n} dnů" surrender: "Zrušit" postForm: "Formulář pro odeslání" +information: "Informace" _delivery: stop: "Suspendováno" _type: diff --git a/locales/de-DE.yml b/locales/de-DE.yml index bcb88a4acb..c6cc416638 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -49,7 +49,7 @@ pin: "An dein Profil anheften" unpin: "Von deinem Profil lösen" copyContent: "Inhalt kopieren" copyLink: "Link kopieren" -copyRemoteLink: "Renote-Link kopieren" +copyRemoteLink: "Remote-Link kopieren" copyLinkRenote: "Renote-Link kopieren" delete: "Löschen" deleteAndEdit: "Löschen und Bearbeiten" @@ -1309,6 +1309,7 @@ availableRoles: "Verfügbare Rollen" federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren." federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren." postForm: "Notizfenster" +information: "Über" _settings: webhook: "Webhook" _accountSettings: @@ -2524,10 +2525,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers." _plugin: title: "Möchtest du dieses Plugin installieren?" - metaTitle: "Plugininformation" _theme: title: "Möchten du dieses Farbschema installieren?" - metaTitle: "Farbschemainfo" _meta: base: "Farbschemavorlage" _vendorInfo: diff --git a/locales/el-GR.yml b/locales/el-GR.yml index be81a824c7..727565ae21 100644 --- a/locales/el-GR.yml +++ b/locales/el-GR.yml @@ -289,6 +289,7 @@ icon: "Εικονίδιο" replies: "Απάντηση" renotes: "Κοινοποίηση σημειώματος" postForm: "Φόρμα δημοσίευσης" +information: "Πληροφορίες" _email: _follow: title: "Έχετε ένα νέο ακόλουθο" diff --git a/locales/en-US.yml b/locales/en-US.yml index 1e7c4730d1..79805e65e1 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} said something about \"{word}\"" makeActive: "Activate" display: "Display" copy: "Copy" +copiedToClipboard: "Copied to clipboard" metrics: "Metrics" overview: "Overview" logs: "Logs" @@ -1316,14 +1317,29 @@ unmarkAsSensitiveConfirm: "Do you want to remove the sensitive designation for t preferences: "Preferences" accessibility: "Accessibility" preferencesProfile: "Preferences profile" -copyPreferenceId: "Copy the proference ID" +copyPreferenceId: "Copy the preference ID" resetToDefaultValue: "Revert to default" overrideByAccount: "Override by the account" untitled: "Untitled" noName: "No name" skip: "Skip" restore: "Restore" +syncBetweenDevices: "Sync between devices" +preferenceSyncConflictTitle: "The configured value exists on the server." +preferenceSyncConflictText: "The sync enabled settings will save their values to the server. However, there are existing values on the server. Which set of values would you like to overwrite?" +preferenceSyncConflictChoiceServer: "Configured value on server" +preferenceSyncConflictChoiceDevice: "Configured value on device" +preferenceSyncConflictChoiceCancel: "Cancel enabling sync" +paste: "Paste" +emojiPalette: "Emoji palette" postForm: "Posting form" +textCount: "Character count" +information: "About" +_emojiPalette: + palettes: "Palette" + enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices" + paletteForMain: "Main palette" + paletteForReaction: "Reaction palette" _settings: driveBanner: "You can manage and configure the drive, check usage, and configure file upload settings." pluginBanner: "You can extend client features with plugins. You can install plugins, configure and manage individually." @@ -1341,6 +1357,9 @@ _settings: preferencesBanner: "You can configure the overall behavior of the client according to your preferences." appearanceBanner: "You can configure the appearance and display settings for the client according to your preferences." soundsBanner: "You can configure the sound settings for playback in the client." + timelineAndNote: "Timeline and note" + makeEveryTextElementsSelectable: "Make all text elements selectable" + makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations." _preferencesProfile: profileName: "Profile name" profileNameDescription: "Set a name that identifies this device." @@ -2505,6 +2524,7 @@ _notification: achievementEarned: "Achievement unlocked" exportCompleted: "The export has been completed" login: "Sign In" + createToken: "Create access token" test: "Notification test" app: "Notifications from linked apps" _actions: @@ -2532,6 +2552,7 @@ _deck: useSimpleUiForNonRootPages: "Use simple UI for navigated pages" usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled" flexible: "Auto-adjust width" + enableSyncBetweenDevicesForProfiles: "Enable profile information sync between devices" _columns: main: "Main" widgets: "Widgets" @@ -2653,10 +2674,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation." _plugin: title: "Do you want to install this plugin?" - metaTitle: "Plugin information" _theme: title: "Do you want to install this theme?" - metaTitle: "Theme information" _meta: base: "Base color scheme" _vendorInfo: @@ -2857,8 +2876,8 @@ _selfXssPrevention: description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window." description3: "For more information, please refer to this. {link}" _followRequest: - recieved: "Received application" - sent: "Sent application" + recieved: "Received request" + sent: "Sent request" _remoteLookupErrors: _federationNotAllowed: title: "Unable to communicate with this server" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 4933b41ddb..cd96d348c3 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1300,6 +1300,7 @@ target: "Para" federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador." federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores" postForm: "Formulario" +information: "Información" _settings: webhook: "Webhook" _accountSettings: @@ -2521,10 +2522,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Asegúrate de que el distribuidor de este recurso es de confianza antes de proceder a la instalación." _plugin: title: "¿Quieres instalar este plugin?" - metaTitle: "Información del plugin" _theme: title: "¿Quieres instalar este tema?" - metaTitle: "Información del tema" _meta: base: "Esquema de color base" _vendorInfo: diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 6d3a3bb882..4bdcd89422 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1278,6 +1278,7 @@ lockdown: "Verrouiller" pleaseSelectAccount: "Sélectionner un compte" availableRoles: "Rôles disponibles" postForm: "Formulaire de publication" +information: "Informations" _abuseUserReport: forward: "Transférer" forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme." @@ -2295,10 +2296,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Veuillez confirmer que le distributeur est fiable avant l'installation." _plugin: title: "Voulez-vous installer cette extension ?" - metaTitle: "Informations sur l'extension" _theme: title: "Voulez-vous installer ce thème ?" - metaTitle: "Informations sur le thème" _meta: base: "Palette de couleurs de base" _vendorInfo: diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 62ddc35cad..9a291ca381 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1262,6 +1262,7 @@ modified: "Diubah" thereAreNChanges: "Ada {n} perubahan" prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna" postForm: "Buat catatan" +information: "Informasi" _settings: webhook: "Webhook" _abuseUserReport: @@ -2492,10 +2493,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan." _plugin: title: "Apakah kamu ingin memasang plugin ini?" - metaTitle: "Informasi plugin" _theme: title: "Apakah kamu ingin memasang tema ini?" - metaTitle: "Informasi tema" _meta: base: "Skema warna dasar" _vendorInfo: diff --git a/locales/index.d.ts b/locales/index.d.ts index c50a90c604..836108c646 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2814,6 +2814,10 @@ export interface Locale extends ILocale { * コピー */ "copy": string; + /** + * クリップボードにコピーされました + */ + "copiedToClipboard": string; /** * メトリクス */ @@ -5350,6 +5354,14 @@ export interface Locale extends ILocale { * 投稿フォーム */ "postForm": string; + /** + * 文字数 + */ + "textCount": string; + /** + * 情報 + */ + "information": string; "_emojiPalette": { /** * パレット @@ -5437,6 +5449,14 @@ export interface Locale extends ILocale { * タイムラインとノート */ "timelineAndNote": string; + /** + * 全てのテキスト要素を選択可能にする + */ + "makeEveryTextElementsSelectable": string; + /** + * 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。 + */ + "makeEveryTextElementsSelectable_description": string; }; "_preferencesProfile": { /** @@ -9781,6 +9801,10 @@ export interface Locale extends ILocale { * ログイン */ "login": string; + /** + * アクセストークンの作成 + */ + "createToken": string; /** * 通知のテスト */ @@ -10343,20 +10367,12 @@ export interface Locale extends ILocale { * このプラグインをインストールしますか? */ "title": string; - /** - * プラグイン情報 - */ - "metaTitle": string; }; "_theme": { /** * このテーマをインストールしますか? */ "title": string; - /** - * テーマ情報 - */ - "metaTitle": string; }; "_meta": { /** diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 6d69a07721..4d17d0bf7b 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\"" makeActive: "Attiva" display: "Visualizza" copy: "Copia" +copiedToClipboard: "Copiato negli appunti" metrics: "Statistiche" overview: "Anteprima" logs: "Log" @@ -973,7 +974,7 @@ check: "Verifica" driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo" driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato." requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore." -isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema" +isSystemAccount: "Si tratta di un profilo creato e gestito automaticamente dal sistema." typeToConfirm: "Digita {x} per continuare" deleteAccount: "Eliminazione profilo" document: "Documentazione" @@ -1323,7 +1324,22 @@ untitled: "Senza titolo" noName: "Senza nome" skip: "Salta" restore: "Ripristina" +syncBetweenDevices: "Sincronizzazione tra i dispositivi" +preferenceSyncConflictTitle: "Sul server esiste già il valore impostato" +preferenceSyncConflictText: "Le impostazione sincronizzata salverà il valore sul server. Però, bada che esiste già un valore sul server. Quale vorresti sovrascrivere?" +preferenceSyncConflictChoiceServer: "Valore del server" +preferenceSyncConflictChoiceDevice: "Valore del dispositivo" +preferenceSyncConflictChoiceCancel: "Annulla la sincronizzazione" +paste: "Incolla" +emojiPalette: "Tavolozza emoji" postForm: "Finestra di pubblicazione" +textCount: "Il numero di caratteri" +information: "Informazioni" +_emojiPalette: + palettes: "Tavolozza" + enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi" + paletteForMain: "Tavolozza principale" + paletteForReaction: "Tavolozza per reazioni" _settings: driveBanner: "Permette di gestire e configurare il Drive, controllare il consumo di spazio e configurare il caricamento dei file." pluginBanner: "Consentono di migliorare le funzionalità. Le estensioni si possono configurare e gestire singolarmente." @@ -1341,6 +1357,9 @@ _settings: preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo." appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze." soundsBanner: "Puoi personalizzare i suoni emessi dagli eventi sul tuo dispositivo." + timelineAndNote: "Note e Timeline" + makeEveryTextElementsSelectable: "Imposta ogni elemento come selezionabile" + makeEveryTextElementsSelectable_description: "Potrebbe ridurre l'usabilità in alcune situazioni." _preferencesProfile: profileName: "Nome del profilo" profileNameDescription: "Impostare il nome che indentifica questo dispositivo." @@ -2505,6 +2524,7 @@ _notification: achievementEarned: "Risultato raggiunto" exportCompleted: "Esportazione completata" login: "Accessi" + createToken: "Creare un token di accesso" test: "Notifiche di test" app: "Notifiche da applicazioni" _actions: @@ -2532,6 +2552,7 @@ _deck: useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice" usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima" flexible: "Larghezza flessibile" + enableSyncBetweenDevicesForProfiles: "Abilita la sincronizzazione delle informazioni profilo tra dispositivi" _columns: main: "Principale" widgets: "Riquadri" @@ -2653,10 +2674,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile." _plugin: title: "Vuoi davvero installare questo componente aggiuntivo?" - metaTitle: "Informazioni sul componente aggiuntivo" _theme: title: "Vuoi davvero installare questa variazione grafica?" - metaTitle: "Informazioni sulla variazione grafica" _meta: base: "Combinazione base di colori" _vendorInfo: diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 49e64adc3b..611aa5906a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -699,6 +699,7 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし makeActive: "アクティブにする" display: "表示" copy: "コピー" +copiedToClipboard: "クリップボードにコピーされました" metrics: "メトリクス" overview: "概要" logs: "ログ" @@ -1333,6 +1334,8 @@ preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル" paste: "ペースト" emojiPalette: "絵文字パレット" postForm: "投稿フォーム" +textCount: "文字数" +information: "情報" _emojiPalette: palettes: "パレット" @@ -1358,6 +1361,8 @@ _settings: appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。" soundsBanner: "クライアントで再生するサウンドの設定が行えます。" timelineAndNote: "タイムラインとノート" + makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする" + makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。" _preferencesProfile: profileName: "プロファイル名" @@ -2585,6 +2590,7 @@ _notification: achievementEarned: "実績の獲得" exportCompleted: "エクスポートが完了した" login: "ログイン" + createToken: "アクセストークンの作成" test: "通知のテスト" app: "連携アプリからの通知" @@ -2745,10 +2751,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。" _plugin: title: "このプラグインをインストールしますか?" - metaTitle: "プラグイン情報" _theme: title: "このテーマをインストールしますか?" - metaTitle: "テーマ情報" _meta: base: "基本のカラースキーム" _vendorInfo: diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index ae45379086..9c0bff4f95 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1312,6 +1312,7 @@ federationDisabled: "このサーバーは連合が無効化されてるで。 confirmOnReact: "ツッコむときに確認とる" reactAreYouSure: "\" {emoji} \" でツッコむ?" postForm: "投稿フォーム" +information: "情報" _settings: webhook: "Webhook" _accountSettings: @@ -2610,10 +2611,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。" _plugin: title: "このプラグイン、インストールする?" - metaTitle: "プラグイン情報" _theme: title: "このテーマインストールする?" - metaTitle: "テーマ情報" _meta: base: "" _vendorInfo: diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index ce83ef0e07..8ff11d35d9 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -655,6 +655,7 @@ replies: "답하기" renotes: "리노트" attach: "옇기" surrender: "아이예" +information: "정보" _delivery: stop: "고만 보내예" _type: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 798c371bc2..4a02b0c64a 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1330,6 +1330,7 @@ preferenceSyncConflictChoiceDevice: "장치 설정값" paste: "붙여넣기" emojiPalette: "이모지 팔레트" postForm: "글 입력란" +information: "정보" _emojiPalette: palettes: "팔레트" paletteForMain: "메인으로 사용할 팔레트" @@ -2650,10 +2651,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오." _plugin: title: "이 플러그인을 설치하시겠습니까?" - metaTitle: "플러그인 정보" _theme: title: "이 테마를 설치하시겠습니까?" - metaTitle: "테마 정보" _meta: base: "기본 컬러 스키마" _vendorInfo: diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index 06acda44be..52b5a28fb2 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -394,6 +394,7 @@ searchByGoogle: "ຄົ້ນຫາ" file: "ໄຟລ໌" replies: "ຕອບ​ກັບ" renotes: "Renote" +information: "ກ່ຽວກັບ" _delivery: stop: "ໂຈະ" _type: diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index 320b9d62ba..e2f6017f20 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -462,6 +462,7 @@ loggedInAsBot: "Momenteel als bot ingelogd" icon: "Avatar" replies: "Antwoord" renotes: "Herdelen" +information: "Over" _delivery: stop: "Opgeschort" _type: diff --git a/locales/no-NO.yml b/locales/no-NO.yml index c1b145cab4..7981360c41 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -463,6 +463,7 @@ icon: "Avatar" replies: "Svar" renotes: "Renote" surrender: "Avbryt" +information: "Informasjon" _delivery: stop: "Suspendert" _initialAccountSetting: diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index a4538ee7b2..21b715714d 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1045,6 +1045,7 @@ lastNDays: "W ciągu ostatnich {n} dni" surrender: "Odrzuć" gameRetry: "Spróbuj ponownie" postForm: "Formularz tworzenia wpisu" +information: "Informacje" _delivery: stop: "Zawieszono" _type: diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 4511ce662f..2f46eda784 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1302,6 +1302,7 @@ pleaseSelectAccount: "Selecione uma conta" availableRoles: "Cargos disponíveis" acknowledgeNotesAndEnable: "Ative após compreender as precauções." postForm: "Campo de postagem" +information: "Informações" _settings: webhook: "Webhook" _accountSettings: @@ -2598,10 +2599,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "Tenha certeza de que o distribuidor desse recurso é confiável antes da instalação." _plugin: title: "Deseja instalar esse plugin?" - metaTitle: "Informações do plugin" _theme: title: "Deseja instalar esse tema?" - metaTitle: "Informações do tema" _meta: base: "Paleta de cores base" _vendorInfo: diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 0d43e174a7..5d1459480d 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -646,6 +646,7 @@ show: "Arată" icon: "Avatar" replies: "Răspunde" renotes: "Re-notează" +information: "Despre" _delivery: stop: "Suspendat" _type: diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 2a200deac0..33a09d198a 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1182,6 +1182,7 @@ alwaysConfirmFollow: "Всегда подтверждать подписку" inquiry: "Связаться" messageToFollower: "Сообщение подписчикам" postForm: "Форма отправки" +information: "Описание" _settings: webhook: "Вебхук" _delivery: diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 7ae264081f..cc8ef9298a 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -918,6 +918,7 @@ sourceCode: "Zdrojový kód" flip: "Preklopiť" lastNDays: "Posledných {n} dní" postForm: "Napísať poznámku" +information: "Informácie" _delivery: stop: "Zmrazené" _type: diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 91413e3bc7..c740ab1c0c 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -562,6 +562,7 @@ inquiry: "Kontakt" tryAgain: "Försök igen senare" signinWithPasskey: "Logga in med nyckel" unknownWebAuthnKey: "Okänd nyckel" +information: "Om" _delivery: stop: "Suspenderad" _type: diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 939b0d7269..784e9049dd 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1293,6 +1293,7 @@ prohibitedWordsForNameOfUserDescription: "หากมีสตริงใด yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม" yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ" postForm: "แบบฟอร์มการโพสต์" +information: "เกี่ยวกับ" _settings: webhook: "Webhook" _abuseUserReport: @@ -2572,10 +2573,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง" _plugin: title: "ต้องการติดตั้งปลั๊กอินนี้ใช่ไหม?" - metaTitle: "ข้อมูลส่วนเสริม" _theme: title: "ต้องการติดตั้งธีมนี้ใช่ไหม?" - metaTitle: "ข้อมูลธีม" _meta: base: "โทนสีพื้นฐาน" _vendorInfo: diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 3e068127f5..a63e39eff4 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -910,6 +910,7 @@ sourceCode: "Вихідний код" flip: "Перевернути" lastNDays: "Останні {n} днів" postForm: "Створення нотатки" +information: "Інформація" _delivery: stop: "Призупинено" _type: diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index c70802e417..d5b7d4b770 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -841,6 +841,7 @@ icon: "Avatar" replies: "Javob berish" renotes: "Qayta qayd etish" flip: "Teskari" +information: "Haqida" _delivery: stop: "To'xtatilgan" _type: diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index d2107eaba4..8edcebcd08 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1120,6 +1120,7 @@ cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải lastNDays: "{n} ngày trước" surrender: "Từ chối" postForm: "Mẫu đăng" +information: "Giới thiệu" _delivery: stop: "Đã vô hiệu hóa" _type: diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index c437c7284d..ad3eebfebd 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 说了关于「{word}」的什么" makeActive: "启用" display: "显示" copy: "复制" +copiedToClipboard: "已复制到剪贴板" metrics: "指标" overview: "概览" logs: "日志" @@ -746,7 +747,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。 public: "公开" private: "私密" i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。" -manageAccessTokens: "管理 Access Tokens" +manageAccessTokens: "管理访问令牌" accountInfo: "账户信息" notesCount: "帖子数量" repliesCount: "回复数量" @@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "取消同步" paste: "粘贴" emojiPalette: "表情符号调色板" postForm: "投稿窗口" +textCount: "字数" +information: "关于" _emojiPalette: palettes: "调色板" enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步" @@ -1339,13 +1342,24 @@ _emojiPalette: paletteForReaction: "回应用调色板" _settings: driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。" + pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。" + notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。" api: "API" webhook: "Webhook" + serviceConnection: "连接服务" + serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。" + accountData: "账户数据" + accountDataBanner: "可在此导入或导出帐户数据的存档。" + muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。" + accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。" privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。" securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。" preferencesBanner: "可在此设置客户端的整体运作行为。" appearanceBanner: "可在此设置客户端的外观及显示方式。" soundsBanner: "可在此设置客户端播放的声音。" + timelineAndNote: "时间线和帖子" + makeEveryTextElementsSelectable: "使所有的文字均可选择" + makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。" _preferencesProfile: profileName: "配置名" profileNameDescription: "请指定用于识别此设备的名称" @@ -2510,6 +2524,7 @@ _notification: achievementEarned: "取得的成就" exportCompleted: "已完成导出" login: "登录" + createToken: "创建访问令牌" test: "测试通知" app: "关联应用的通知" _actions: @@ -2659,10 +2674,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "请在安装前确保来源可靠" _plugin: title: "要安装此插件吗?" - metaTitle: "插件信息" _theme: title: "要安装此主题吗?" - metaTitle: "主题信息" _meta: base: "基本配色方案" _vendorInfo: diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 6f27306702..0de267688c 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 說了一些關於「{word}」的話" makeActive: "啟用" display: "檢視" copy: "複製" +copiedToClipboard: "已複製到剪貼簿" metrics: "指標" overview: "概覽" logs: "日誌" @@ -1332,6 +1333,8 @@ preferenceSyncConflictChoiceCancel: "取消啟用同步" paste: "貼上" emojiPalette: "表情符號調色盤" postForm: "發文視窗" +textCount: "字數" +information: "關於" _emojiPalette: palettes: "調色盤" enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化" @@ -1355,6 +1358,8 @@ _settings: appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。" soundsBanner: "您可以調整用戶端播放的聲音設定。" timelineAndNote: "時間軸及貼文" + makeEveryTextElementsSelectable: "允許選取所有文字" + makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。" _preferencesProfile: profileName: "設定檔案名稱" profileNameDescription: "設定一個名稱來識別此裝置。" @@ -2519,6 +2524,7 @@ _notification: achievementEarned: "獲得成就" exportCompleted: "已完成匯出。" login: "登入" + createToken: "建立存取權杖" test: "通知測試" app: "應用程式通知" _actions: @@ -2668,10 +2674,8 @@ _externalResourceInstaller: checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。" _plugin: title: "要安裝此外掛嘛?" - metaTitle: "外掛資訊" _theme: title: "要安裝此佈景主題嗎?" - metaTitle: "佈景主題資訊" _meta: base: "基本配色方案" _vendorInfo: diff --git a/package.json b/package.json index 7b81c6a349..4768b90816 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.3.2-beta.1", + "version": "2025.3.2-beta.8", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/package.json b/packages/backend/package.json index cee5c7205b..bcaa6357ce 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -37,17 +37,17 @@ }, "optionalDependencies": { "@swc/core-android-arm64": "1.3.11", - "@swc/core-darwin-arm64": "1.10.16", - "@swc/core-darwin-x64": "1.10.16", + "@swc/core-darwin-arm64": "1.11.11", + "@swc/core-darwin-x64": "1.11.11", "@swc/core-freebsd-x64": "1.3.11", - "@swc/core-linux-arm-gnueabihf": "1.10.16", - "@swc/core-linux-arm64-gnu": "1.10.16", - "@swc/core-linux-arm64-musl": "1.10.16", - "@swc/core-linux-x64-gnu": "1.10.16", - "@swc/core-linux-x64-musl": "1.10.16", - "@swc/core-win32-arm64-msvc": "1.10.16", - "@swc/core-win32-ia32-msvc": "1.10.16", - "@swc/core-win32-x64-msvc": "1.10.16", + "@swc/core-linux-arm-gnueabihf": "1.11.11", + "@swc/core-linux-arm64-gnu": "1.11.11", + "@swc/core-linux-arm64-musl": "1.11.11", + "@swc/core-linux-x64-gnu": "1.11.11", + "@swc/core-linux-x64-musl": "1.11.11", + "@swc/core-win32-arm64-msvc": "1.11.11", + "@swc/core-win32-ia32-msvc": "1.11.11", + "@swc/core-win32-x64-msvc": "1.11.11", "@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs-node": "4.22.0", "bufferutil": "4.0.9", @@ -67,26 +67,23 @@ "utf-8-validate": "6.0.5" }, "dependencies": { - "@aws-sdk/client-s3": "3.749.0", - "@aws-sdk/lib-storage": "3.749.0", - "@bull-board/api": "6.7.7", - "@bull-board/fastify": "6.7.7", - "@bull-board/ui": "6.7.7", + "@aws-sdk/client-s3": "3.772.0", + "@aws-sdk/lib-storage": "3.772.0", "@discordapp/twemoji": "15.1.0", "@fastify/accepts": "5.0.2", "@fastify/cookie": "11.0.2", - "@fastify/cors": "10.0.2", + "@fastify/cors": "10.1.0", "@fastify/express": "4.0.2", "@fastify/http-proxy": "10.0.2", "@fastify/multipart": "9.0.3", - "@fastify/static": "8.1.0", + "@fastify/static": "8.1.1", "@fastify/view": "10.0.2", "@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/summaly": "5.2.0", - "@napi-rs/canvas": "0.1.67", - "@nestjs/common": "11.0.9", - "@nestjs/core": "11.0.9", - "@nestjs/testing": "11.0.9", + "@napi-rs/canvas": "0.1.68", + "@nestjs/common": "11.0.12", + "@nestjs/core": "11.0.12", + "@nestjs/testing": "11.0.12", "@peertube/http-signature": "1.7.0", "@sentry/node": "8.55.0", "@sentry/profiling-node": "8.55.0", @@ -94,7 +91,7 @@ "@sinonjs/fake-timers": "11.3.1", "@smithy/node-http-handler": "2.5.0", "@swc/cli": "0.6.0", - "@swc/core": "1.10.16", + "@swc/core": "1.11.11", "@twemoji/parser": "15.1.1", "accepts": "1.3.8", "ajv": "8.17.1", @@ -103,7 +100,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.3", - "bullmq": "5.41.1", + "bullmq": "5.44.1", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", "chalk": "5.4.1", @@ -125,7 +122,7 @@ "hpagent": "1.2.0", "htmlescape": "1.1.1", "http-link-header": "1.1.3", - "ioredis": "5.5.0", + "ioredis": "5.6.0", "ip-cidr": "4.0.2", "ipaddr.js": "2.2.0", "is-svg": "5.1.0", @@ -134,26 +131,26 @@ "json5": "2.2.3", "jsonld": "8.3.3", "jsrsasign": "11.1.0", - "juice": "11.0.0", - "meilisearch": "0.48.2", + "juice": "11.0.1", + "meilisearch": "0.49.0", "mfm-js": "0.24.0", "microformats-parser": "2.0.2", "mime-types": "2.1.35", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", "ms": "3.0.0-canary.1", - "nanoid": "5.1.0", + "nanoid": "5.1.5", "nested-property": "4.0.0", "node-fetch": "3.3.2", "nodemailer": "6.10.0", "nsfwjs": "4.2.0", - "oauth": "0.10.0", + "oauth": "0.10.2", "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", "otpauth": "9.3.6", "parse5": "7.2.1", - "pg": "8.13.3", + "pg": "8.14.1", "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -166,8 +163,8 @@ "reflect-metadata": "0.2.2", "rename": "1.0.4", "rss-parser": "3.13.0", - "rxjs": "7.8.1", - "sanitize-html": "2.14.0", + "rxjs": "7.8.2", + "sanitize-html": "2.15.0", "secure-json-parse": "3.0.2", "sharp": "0.33.5", "slacc": "0.0.10", @@ -176,14 +173,14 @@ "systeminformation": "5.25.11", "tinycolor2": "1.6.0", "tmp": "0.2.3", - "tsc-alias": "1.8.10", + "tsc-alias": "1.8.11", "tsconfig-paths": "4.2.0", - "typeorm": "0.3.20", - "typescript": "5.7.3", - "ulid": "2.3.0", + "typeorm": "0.3.21", + "typescript": "5.8.2", + "ulid": "2.4.0", "vary": "1.1.2", "web-push": "3.6.7", - "ws": "8.18.0", + "ws": "8.18.1", "xev": "3.0.2" }, "devDependencies": { @@ -207,7 +204,7 @@ "@types/jsrsasign": "10.5.15", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "22.13.4", + "@types/node": "22.13.10", "@types/nodemailer": "6.4.17", "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", @@ -226,9 +223,9 @@ "@types/tmp": "0.2.6", "@types/vary": "1.1.3", "@types/web-push": "3.6.4", - "@types/ws": "8.5.14", - "@typescript-eslint/eslint-plugin": "8.24.0", - "@typescript-eslint/parser": "8.24.0", + "@types/ws": "8.18.0", + "@typescript-eslint/eslint-plugin": "8.27.0", + "@typescript-eslint/parser": "8.27.0", "aws-sdk-client-mock": "4.1.0", "cross-env": "7.0.3", "eslint-plugin-import": "2.31.0", diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index f83dec67bf..e1a7801bb1 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -7,42 +7,16 @@ import { Injectable } from '@nestjs/common'; import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js'; -import { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js'; -import { Packed } from '@/misc/json-schema.js'; +import { type AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js'; +import { type Packed } from '@/misc/json-schema.js'; import { type WebhookEventTypes } from '@/models/Webhook.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { QueueService } from '@/core/QueueService.js'; import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; const oneDayMillis = 24 * 60 * 60 * 1000; -function generateAbuseReport(override?: Partial): AbuseReportPayload { - const result: MiAbuseUserReport = { - id: 'dummy-abuse-report1', - targetUserId: 'dummy-target-user', - targetUser: null, - reporterId: 'dummy-reporter-user', - reporter: null, - assigneeId: null, - assignee: null, - resolved: false, - forwarded: false, - comment: 'This is a dummy report for testing purposes.', - targetUserHost: null, - reporterHost: null, - resolvedAs: null, - moderationNote: 'foo', - ...override, - }; - - return { - ...result, - targetUser: result.targetUser ? toPackedUserLite(result.targetUser) : null, - reporter: result.reporter ? toPackedUserLite(result.reporter) : null, - assignee: result.assignee ? toPackedUserLite(result.assignee) : null, - }; -} - function generateDummyUser(override?: Partial): MiUser { return { id: 'dummy-user-1', @@ -134,124 +108,6 @@ function generateDummyNote(override?: Partial): MiNote { }; } -function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> { - return { - id: note.id, - createdAt: new Date().toISOString(), - deletedAt: null, - text: note.text, - cw: note.cw, - userId: note.userId, - user: toPackedUserLite(note.user ?? generateDummyUser()), - replyId: note.replyId, - renoteId: note.renoteId, - isHidden: false, - visibility: note.visibility, - mentions: note.mentions, - visibleUserIds: note.visibleUserIds, - fileIds: note.fileIds, - files: [], - tags: note.tags, - poll: null, - emojis: note.emojis, - channelId: note.channelId, - channel: note.channel, - localOnly: note.localOnly, - reactionAcceptance: note.reactionAcceptance, - reactionEmojis: {}, - reactions: {}, - reactionCount: 0, - renoteCount: note.renoteCount, - repliesCount: note.repliesCount, - uri: note.uri ?? undefined, - url: note.url ?? undefined, - reactionAndUserPairCache: note.reactionAndUserPairCache, - ...(detail ? { - clippedCount: note.clippedCount, - reply: note.reply ? toPackedNote(note.reply, false) : null, - renote: note.renote ? toPackedNote(note.renote, true) : null, - myReaction: null, - } : {}), - ...override, - }; -} - -function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> { - return { - id: user.id, - name: user.name, - username: user.username, - host: user.host, - avatarUrl: user.avatarUrl, - avatarBlurhash: user.avatarBlurhash, - avatarDecorations: user.avatarDecorations.map(it => ({ - id: it.id, - angle: it.angle, - flipH: it.flipH, - url: 'https://example.com/dummy-image001.png', - offsetX: it.offsetX, - offsetY: it.offsetY, - })), - isBot: user.isBot, - isCat: user.isCat, - emojis: user.emojis, - onlineStatus: 'active', - badgeRoles: [], - ...override, - }; -} - -function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> { - return { - ...toPackedUserLite(user), - url: null, - uri: null, - movedTo: null, - alsoKnownAs: [], - createdAt: new Date().toISOString(), - updatedAt: user.updatedAt?.toISOString() ?? null, - lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null, - bannerUrl: user.bannerUrl, - bannerBlurhash: user.bannerBlurhash, - isLocked: user.isLocked, - isSilenced: false, - isSuspended: user.isSuspended, - description: null, - location: null, - birthday: null, - lang: null, - fields: [], - verifiedLinks: [], - followersCount: user.followersCount, - followingCount: user.followingCount, - notesCount: user.notesCount, - pinnedNoteIds: [], - pinnedNotes: [], - pinnedPageId: null, - pinnedPage: null, - publicReactions: true, - followersVisibility: 'public', - followingVisibility: 'public', - twoFactorEnabled: false, - usePasswordLessLogin: false, - securityKeys: false, - roles: [], - memo: null, - moderationNote: undefined, - isFollowing: false, - isFollowed: false, - hasPendingFollowRequestFromYou: false, - hasPendingFollowRequestToYou: false, - isBlocking: false, - isBlocked: false, - isMuted: false, - isRenoteMuted: false, - notify: 'none', - withReplies: true, - ...override, - }; -} - const dummyUser1 = generateDummyUser(); const dummyUser2 = generateDummyUser({ id: 'dummy-user-2', @@ -284,6 +140,7 @@ export class WebhookTestService { }; constructor( + private customEmojiService: CustomEmojiService, private userWebhookService: UserWebhookService, private systemWebhookService: SystemWebhookService, private queueService: QueueService, @@ -354,31 +211,31 @@ export class WebhookTestService { switch (params.type) { case 'note': { - send('note', { note: toPackedNote(dummyNote1) }); + send('note', { note: await this.toPackedNote(dummyNote1) }); break; } case 'reply': { - send('reply', { note: toPackedNote(dummyReply1) }); + send('reply', { note: await this.toPackedNote(dummyReply1) }); break; } case 'renote': { - send('renote', { note: toPackedNote(dummyRenote1) }); + send('renote', { note: await this.toPackedNote(dummyRenote1) }); break; } case 'mention': { - send('mention', { note: toPackedNote(dummyMention1) }); + send('mention', { note: await this.toPackedNote(dummyMention1) }); break; } case 'follow': { - send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) }); + send('follow', { user: await this.toPackedUserDetailedNotMe(dummyUser1) }); break; } case 'followed': { - send('followed', { user: toPackedUserLite(dummyUser2) }); + send('followed', { user: await this.toPackedUserLite(dummyUser2) }); break; } case 'unfollow': { - send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) }); + send('unfollow', { user: await this.toPackedUserDetailedNotMe(dummyUser3) }); break; } // まだ実装されていない (#9485) @@ -427,7 +284,7 @@ export class WebhookTestService { switch (params.type) { case 'abuseReport': { - send('abuseReport', generateAbuseReport({ + send('abuseReport', await this.generateAbuseReport({ targetUserId: dummyUser1.id, targetUser: dummyUser1, reporterId: dummyUser2.id, @@ -436,7 +293,7 @@ export class WebhookTestService { break; } case 'abuseReportResolved': { - send('abuseReportResolved', generateAbuseReport({ + send('abuseReportResolved', await this.generateAbuseReport({ targetUserId: dummyUser1.id, targetUser: dummyUser1, reporterId: dummyUser2.id, @@ -448,7 +305,7 @@ export class WebhookTestService { break; } case 'userCreated': { - send('userCreated', toPackedUserLite(dummyUser1)); + send('userCreated', await this.toPackedUserLite(dummyUser1)); break; } case 'inactiveModeratorsWarning': { @@ -474,4 +331,153 @@ export class WebhookTestService { } } } + + @bindThis + private async generateAbuseReport(override?: Partial): Promise { + const result: MiAbuseUserReport = { + id: 'dummy-abuse-report1', + targetUserId: 'dummy-target-user', + targetUser: null, + reporterId: 'dummy-reporter-user', + reporter: null, + assigneeId: null, + assignee: null, + resolved: false, + forwarded: false, + comment: 'This is a dummy report for testing purposes.', + targetUserHost: null, + reporterHost: null, + resolvedAs: null, + moderationNote: 'foo', + ...override, + }; + + return { + ...result, + targetUser: result.targetUser ? await this.toPackedUserLite(result.targetUser) : null, + reporter: result.reporter ? await this.toPackedUserLite(result.reporter) : null, + assignee: result.assignee ? await this.toPackedUserLite(result.assignee) : null, + }; + } + + @bindThis + private async toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Promise> { + return { + id: note.id, + createdAt: new Date().toISOString(), + deletedAt: null, + text: note.text, + cw: note.cw, + userId: note.userId, + user: await this.toPackedUserLite(note.user ?? generateDummyUser()), + replyId: note.replyId, + renoteId: note.renoteId, + isHidden: false, + visibility: note.visibility, + mentions: note.mentions, + visibleUserIds: note.visibleUserIds, + fileIds: note.fileIds, + files: [], + tags: note.tags, + poll: null, + emojis: await this.customEmojiService.populateEmojis(note.emojis, note.userHost), + channelId: note.channelId, + channel: note.channel, + localOnly: note.localOnly, + reactionAcceptance: note.reactionAcceptance, + reactionEmojis: {}, + reactions: {}, + reactionCount: 0, + renoteCount: note.renoteCount, + repliesCount: note.repliesCount, + uri: note.uri ?? undefined, + url: note.url ?? undefined, + reactionAndUserPairCache: note.reactionAndUserPairCache, + ...(detail ? { + clippedCount: note.clippedCount, + reply: note.reply ? await this.toPackedNote(note.reply, false) : null, + renote: note.renote ? await this.toPackedNote(note.renote, true) : null, + myReaction: null, + } : {}), + ...override, + }; + } + + @bindThis + private async toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Promise> { + return { + id: user.id, + name: user.name, + username: user.username, + host: user.host, + avatarUrl: user.avatarUrl, + avatarBlurhash: user.avatarBlurhash, + avatarDecorations: user.avatarDecorations.map(it => ({ + id: it.id, + angle: it.angle, + flipH: it.flipH, + url: 'https://example.com/dummy-image001.png', + offsetX: it.offsetX, + offsetY: it.offsetY, + })), + isBot: user.isBot, + isCat: user.isCat, + emojis: await this.customEmojiService.populateEmojis(user.emojis, user.host), + onlineStatus: 'active', + badgeRoles: [], + ...override, + }; + } + + @bindThis + private async toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Promise> { + return { + ...await this.toPackedUserLite(user), + url: null, + uri: null, + movedTo: null, + alsoKnownAs: [], + createdAt: new Date().toISOString(), + updatedAt: user.updatedAt?.toISOString() ?? null, + lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null, + bannerUrl: user.bannerUrl, + bannerBlurhash: user.bannerBlurhash, + isLocked: user.isLocked, + isSilenced: false, + isSuspended: user.isSuspended, + description: null, + location: null, + birthday: null, + lang: null, + fields: [], + verifiedLinks: [], + followersCount: user.followersCount, + followingCount: user.followingCount, + notesCount: user.notesCount, + pinnedNoteIds: [], + pinnedNotes: [], + pinnedPageId: null, + pinnedPage: null, + publicReactions: true, + followersVisibility: 'public', + followingVisibility: 'public', + twoFactorEnabled: false, + usePasswordLessLogin: false, + securityKeys: false, + roles: [], + memo: null, + moderationNote: undefined, + isFollowing: false, + isFollowed: false, + hasPendingFollowRequestFromYou: false, + hasPendingFollowRequestToYou: false, + isBlocking: false, + isBlocked: false, + isMuted: false, + isRenoteMuted: false, + notify: 'none', + withReplies: true, + ...override, + }; + } } diff --git a/packages/backend/src/misc/FileWriterStream.ts b/packages/backend/src/misc/FileWriterStream.ts index 367a8eb560..27c67cb5df 100644 --- a/packages/backend/src/misc/FileWriterStream.ts +++ b/packages/backend/src/misc/FileWriterStream.ts @@ -4,6 +4,7 @@ */ import * as fs from 'node:fs/promises'; +import { WritableStream } from 'node:stream/web'; import type { PathLike } from 'node:fs'; /** diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index ac74d68c95..100fb69585 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -166,6 +166,7 @@ export interface Schema extends OfSchema { readonly maximum?: number; readonly minimum?: number; readonly pattern?: string; + readonly additionalProperties?: Schema | boolean; } type RequiredPropertyNames = { @@ -217,6 +218,13 @@ type ObjectSchemaTypeDef

= : p['anyOf'] extends ReadonlyArray ? never : // see CONTRIBUTING.md p['allOf'] extends ReadonlyArray ? UnionToIntersection> : + p['additionalProperties'] extends true ? Record : + p['additionalProperties'] extends Schema ? + p['additionalProperties'] extends infer AdditionalProperties ? + AdditionalProperties extends Schema ? + Record> : + never : + never : any; type ObjectSchemaType

= NullOrUndefined>; diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 725e1c8ba2..95fe0a2c6a 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -6,6 +6,7 @@ import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import { ZipReader } from 'slacc'; +import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; @@ -86,6 +87,7 @@ export class ImportCustomEmojisProcessorService { const emojiPath = outputPath + '/' + record.fileName; await this.emojisRepository.delete({ name: emojiInfo.name, + host: IsNull(), }); try { diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 20e985aaf2..48c80e5e61 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -13,7 +13,7 @@ import accepts from 'accepts'; import vary from 'vary'; import secureJson from 'secure-json-parse'; import { DI } from '@/di-symbols.js'; -import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js'; +import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js'; import * as url from '@/misc/prelude/url.js'; import type { Config } from '@/config.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; @@ -42,6 +42,9 @@ export class ActivityPubServerService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -102,6 +105,11 @@ export class ActivityPubServerService { @bindThis private inbox(request: FastifyRequest, reply: FastifyReply) { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + let signature; try { @@ -173,6 +181,11 @@ export class ActivityPubServerService { request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, reply: FastifyReply, ) { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const userId = request.params.user; const cursor = request.query.cursor; @@ -265,6 +278,11 @@ export class ActivityPubServerService { request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, reply: FastifyReply, ) { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const userId = request.params.user; const cursor = request.query.cursor; @@ -354,6 +372,11 @@ export class ActivityPubServerService { @bindThis private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const userId = request.params.user; const user = await this.usersRepository.findOneBy({ @@ -398,6 +421,11 @@ export class ActivityPubServerService { }>, reply: FastifyReply, ) { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const userId = request.params.user; const sinceId = request.query.since_id; @@ -482,6 +510,11 @@ export class ActivityPubServerService { @bindThis private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + if (user == null) { reply.code(404); return; @@ -564,6 +597,11 @@ export class ActivityPubServerService { fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { vary(reply.raw, 'Accept'); + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const note = await this.notesRepository.findOneBy({ id: request.params.note, visibility: In(['public', 'home']), @@ -594,6 +632,11 @@ export class ActivityPubServerService { fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => { vary(reply.raw, 'Accept'); + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const note = await this.notesRepository.findOneBy({ id: request.params.note, userHost: IsNull(), @@ -634,6 +677,11 @@ export class ActivityPubServerService { // publickey fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const userId = request.params.user; const user = await this.usersRepository.findOneBy({ @@ -661,6 +709,11 @@ export class ActivityPubServerService { fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { vary(reply.raw, 'Accept'); + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const userId = request.params.user; const user = await this.usersRepository.findOneBy({ @@ -674,6 +727,11 @@ export class ActivityPubServerService { fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { vary(reply.raw, 'Accept'); + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const acct = Acct.parse(request.params.acct); const user = await this.usersRepository.findOneBy({ @@ -688,6 +746,11 @@ export class ActivityPubServerService { // emoji fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const emoji = await this.emojisRepository.findOneBy({ host: IsNull(), name: request.params.emoji, @@ -705,6 +768,11 @@ export class ActivityPubServerService { // like fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like }); if (reaction == null) { @@ -726,6 +794,11 @@ export class ActivityPubServerService { // follow fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + // This may be used before the follow is completed, so we do not // check if the following exists. @@ -752,6 +825,11 @@ export class ActivityPubServerService { // follow fastify.get<{ Params: { followRequestId: string; } }>('/follows/:followRequestId', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + // This may be used before the follow is completed, so we do not // check if the following exists and only check if the follow request exists. diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts index 8e326da89a..d106be5bc8 100644 --- a/packages/backend/src/server/WellKnownServerService.ts +++ b/packages/backend/src/server/WellKnownServerService.ts @@ -8,7 +8,7 @@ import { IsNull } from 'typeorm'; import vary from 'vary'; import fastifyAccepts from '@fastify/accepts'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository } from '@/models/_.js'; +import type { MiMeta, UsersRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; import type { MiUser } from '@/models/User.js'; @@ -26,6 +26,9 @@ export class WellKnownServerService { @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private meta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -66,6 +69,11 @@ export class WellKnownServerService { }); fastify.get('/.well-known/host-meta', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + reply.header('Content-Type', xrd); return XRD({ element: 'Link', attributes: { rel: 'lrdd', @@ -75,6 +83,11 @@ export class WellKnownServerService { }); fastify.get('/.well-known/host-meta.json', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + reply.header('Content-Type', 'application/json'); return { links: [{ @@ -86,6 +99,11 @@ export class WellKnownServerService { }); fastify.get('/.well-known/nodeinfo', async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + return { links: this.nodeinfoServerService.getLinks() }; }); @@ -99,6 +117,11 @@ fastify.get('/.well-known/change-password', async (request, reply) => { */ fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => { + if (this.meta.federation === 'none') { + reply.code(403); + return; + } + const fromId = (id: MiUser['id']): FindOptionsWhere => ({ id, host: IsNull(), diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index 3a8cb19f01..32818003ad 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -6,7 +6,6 @@ import { Inject, Injectable } from '@nestjs/common'; import cors from '@fastify/cors'; import multipart from '@fastify/multipart'; -import fastifyCookie from '@fastify/cookie'; import { ModuleRef } from '@nestjs/core'; import { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { Config } from '@/config.js'; @@ -57,8 +56,6 @@ export class ApiServerService { }, }); - fastify.register(fastifyCookie, {}); - // Prevent cache fastify.addHook('onRequest', (request, reply, done) => { reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index f8b3843cac..927970e2e2 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -7,16 +7,12 @@ import { randomUUID } from 'node:crypto'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { Inject, Injectable } from '@nestjs/common'; -import { createBullBoard } from '@bull-board/api'; -import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js'; -import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify'; import ms from 'ms'; import sharp from 'sharp'; import pug from 'pug'; import { In, IsNull } from 'typeorm'; import fastifyStatic from '@fastify/static'; import fastifyView from '@fastify/view'; -import fastifyCookie from '@fastify/cookie'; import fastifyProxy from '@fastify/http-proxy'; import vary from 'vary'; import htmlSafeJsonStringify from 'htmlescape'; @@ -221,64 +217,6 @@ export class ClientServerService { @bindThis public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { - fastify.register(fastifyCookie, {}); - - //#region Bull Dashboard - const bullBoardPath = '/queue'; - - // Authenticate - fastify.addHook('onRequest', async (request, reply) => { - if (request.routeOptions.url == null) { - reply.code(404).send('Not found'); - return; - } - - // %71ueueとかでリクエストされたら困るため - const url = decodeURI(request.routeOptions.url); - if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) { - if (!url.startsWith(bullBoardPath + '/static/')) { - reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); - } - - const token = request.cookies.token; - if (token == null) { - reply.code(401).send('Login required'); - return; - } - const user = await this.usersRepository.findOneBy({ token }); - if (user == null) { - reply.code(403).send('No such user'); - return; - } - const isAdministrator = await this.roleService.isAdministrator(user); - if (!isAdministrator) { - reply.code(403).send('Access denied'); - return; - } - } - }); - - const bullBoardServerAdapter = new BullBoardFastifyAdapter(); - - createBullBoard({ - queues: [ - this.systemQueue, - this.endedPollNotificationQueue, - this.deliverQueue, - this.inboxQueue, - this.dbQueue, - this.relationshipQueue, - this.objectStorageQueue, - this.userWebhookDeliverQueue, - this.systemWebhookDeliverQueue, - ].map(q => new BullMQAdapter(q)), - serverAdapter: bullBoardServerAdapter, - }); - - bullBoardServerAdapter.setBasePath(bullBoardPath); - (fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath }); - //#endregion - fastify.register(fastifyView, { root: _dirname + '/views', engine: { diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index b85cebf724..740295bda8 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -6,7 +6,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; +import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; import type { SimpleGetResponse } from '../utils.js'; import type * as misskey from 'misskey-js'; @@ -156,20 +156,20 @@ describe('Webリソース', () => { describe(' has entry such ', () => { beforeEach(() => { - post(alice, { text: "**a**" }) + post(alice, { text: '**a**' }); }); test('MFMを含まない。', async () => { - const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text()); + const content = await simpleGet(path(alice.username), '*/*', undefined, res => res.text()); const _body: unknown = content.body; // JSONフィードのときは改めて文字列化する - const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string; + const body: string = typeof (_body) === 'object' ? JSON.stringify(_body) : _body as string; - if (body.includes("**a**")) { - throw new Error("MFM shouldn't be included"); + if (body.includes('**a**')) { + throw new Error('MFM shouldn\'t be included'); } }); - }) + }); }); describe.each([{ path: '/api/foo' }])('$path', ({ path }) => { @@ -180,24 +180,6 @@ describe('Webリソース', () => { })); }); - describe.each([{ path: '/queue' }])('$path', ({ path }) => { - test('はログインしないとGETできない。', async () => await notOk({ - path, - status: 401, - })); - - test('はadminでなければGETできない。', async () => await notOk({ - path, - cookie: cookie(bob), - status: 403, - })); - - test('はadminならGETできる。', async () => await ok({ - path, - cookie: cookie(alice), - })); - }); - describe.each([{ path: '/streaming' }])('$path', ({ path }) => { test('はGETできない。', async () => await notOk({ path, diff --git a/packages/backend/test/unit/WebhookTestService.ts b/packages/backend/test/unit/WebhookTestService.ts index 45bc932469..736aac40b4 100644 --- a/packages/backend/test/unit/WebhookTestService.ts +++ b/packages/backend/test/unit/WebhookTestService.ts @@ -14,6 +14,7 @@ import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersReposi import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { QueueService } from '@/core/QueueService.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; describe('WebhookTestService', () => { let app: TestingModule; @@ -56,6 +57,11 @@ describe('WebhookTestService', () => { providers: [ WebhookTestService, IdService, + { + provide: CustomEmojiService, useFactory: () => ({ + populateEmojis: jest.fn(), + }), + }, { provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn(), diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 26de19eaf1..7eecf8bb0d 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -35,7 +35,7 @@ export type SystemWebhookPayload = { createdAt: string; type: string; body: any; -} +}; const config = loadConfig(); export const port = config.port; @@ -45,10 +45,6 @@ export const host = new URL(config.url).host; export const WEBHOOK_HOST = 'http://localhost:15080'; export const WEBHOOK_PORT = 15080; -export const cookie = (me: UserToken): string => { - return `token=${me.token};`; -}; - export type ApiRequest = { endpoint: E, parameters: P, diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 21247e32ab..3bdd6fa52a 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -16,7 +16,7 @@ "@rollup/pluginutils": "5.1.4", "@tabler/icons-webfont": "3.31.0", "@twemoji/parser": "15.1.1", - "@vitejs/plugin-vue": "5.2.1", + "@vitejs/plugin-vue": "5.2.3", "@vue/compiler-sfc": "3.5.13", "astring": "1.9.0", "buraha": "0.0.1", @@ -25,16 +25,16 @@ "misskey-js": "workspace:*", "frontend-shared": "workspace:*", "punycode.js": "2.3.1", - "rollup": "4.34.9", - "sass": "1.85.1", - "shiki": "3.1.0", + "rollup": "4.36.0", + "sass": "1.86.0", + "shiki": "3.2.1", "tinycolor2": "1.6.0", "tsc-alias": "1.8.11", "tsconfig-paths": "4.2.0", "typescript": "5.8.2", "uuid": "11.1.0", "json5": "2.2.3", - "vite": "6.2.1", + "vite": "6.2.2", "vue": "3.5.13" }, "devDependencies": { @@ -42,26 +42,26 @@ "@testing-library/vue": "8.1.0", "@types/estree": "1.0.6", "@types/micromatch": "4.0.9", - "@types/node": "22.13.9", + "@types/node": "22.13.11", "@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/tinycolor2": "1.4.6", "@types/ws": "8.18.0", - "@typescript-eslint/eslint-plugin": "8.26.0", - "@typescript-eslint/parser": "8.26.0", - "@vitest/coverage-v8": "3.0.8", + "@typescript-eslint/eslint-plugin": "8.27.0", + "@typescript-eslint/parser": "8.27.0", + "@vitest/coverage-v8": "3.0.9", "@vue/runtime-core": "3.5.13", "acorn": "8.14.1", "cross-env": "7.0.3", "eslint-plugin-import": "2.31.0", "eslint-plugin-vue": "10.0.0", "fast-glob": "3.3.3", - "happy-dom": "17.3.0", + "happy-dom": "17.4.4", "intersection-observer": "0.12.2", "micromatch": "4.0.8", "msw": "2.7.3", "nodemon": "3.1.9", "prettier": "3.5.3", - "start-server-and-test": "2.0.10", + "start-server-and-test": "2.0.11", "vite-plugin-turbosnap": "1.0.3", "vue-component-type-helpers": "2.2.8", "vue-eslint-parser": "10.1.1", diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue index bf976c71ae..0bff048ce4 100644 --- a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue +++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue @@ -33,13 +33,11 @@ const canvasPromise = new Promise(resol Math.min(navigator.hardwareConcurrency - 1, 4), ); resolve(workers); - if (_DEV_) console.log('WebGL2 in worker is supported!'); } else { const canvas = document.createElement('canvas'); canvas.width = 64; canvas.height = 64; resolve(canvas); - if (_DEV_) console.log('WebGL2 in worker is not supported...'); } testWorker.terminate(); }); diff --git a/packages/frontend-embed/src/pages/not-found.vue b/packages/frontend-embed/src/pages/not-found.vue index bbb03b4e64..061254a39a 100644 --- a/packages/frontend-embed/src/pages/not-found.vue +++ b/packages/frontend-embed/src/pages/not-found.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index 5b32ec0fc8..5e89dfba12 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index d57300f647..0d37d973f0 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only

- - + + + + + +
+
@@ -25,19 +37,6 @@ SPDX-License-Identifier: AGPL-3.0-only -
- - - - - -
- diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index 051bfbfa68..3e07959458 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -4,66 +4,24 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -75,12 +33,10 @@ import XCommon from './_common_/common.vue'; import type { PageMetadata } from '@/page.js'; import * as os from '@/os.js'; import { instance } from '@/instance.js'; -import XSigninDialog from '@/components/MkSigninDialog.vue'; -import XSignupDialog from '@/components/MkSignupDialog.vue'; import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js'; import { i18n } from '@/i18n.js'; import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue'; -import { mainRouter } from '@/router/main.js'; +import { mainRouter } from '@/router.js'; import { DI } from '@/di.js'; const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index'); @@ -95,47 +51,19 @@ provideMetadataReceiver((metadataGetter) => { pageMetadata.value = info; if (pageMetadata.value) { if (isRoot.value && pageMetadata.value.title === instanceName) { - document.title = pageMetadata.value.title; + window.document.title = pageMetadata.value.title; } else { - document.title = `${pageMetadata.value.title} | ${instanceName}`; + window.document.title = `${pageMetadata.value.title} | ${instanceName}`; } } }); provideReactiveMetadata(pageMetadata); -const announcements = { - endpoint: 'announcements', - limit: 10, -}; - -const isTimelineAvailable = ref(instance.policies.ltlAvailable || instance.policies.gtlAvailable); - -const showMenu = ref(false); const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD); const narrow = ref(window.innerWidth < 1280); -const keymap = computed(() => { - return { - 's': () => { - mainRouter.push('/search'); - }, - }; -}); - -function signin() { - const { dispose } = os.popup(XSigninDialog, { - autoSet: true, - }, { - closed: () => dispose(), - }); -} - -function signup() { - const { dispose } = os.popup(XSignupDialog, { - autoSet: true, - }, { - closed: () => dispose(), - }); +function goHome() { + mainRouter.push('/'); } onMounted(() => { @@ -145,149 +73,64 @@ onMounted(() => { }, { passive: true }); } }); - -defineExpose({ - showMenu: showMenu, -}); - diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue index 99248abecf..3e4d452281 100644 --- a/packages/frontend/src/ui/zen.vue +++ b/packages/frontend/src/ui/zen.vue @@ -4,21 +4,23 @@ SPDX-License-Identifier: AGPL-3.0-only -->