diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml index 3be8f095f..5cffbd81b 100644 --- a/.github/workflows/api-misskey-js.yml +++ b/.github/workflows/api-misskey-js.yml @@ -14,7 +14,7 @@ jobs: - run: corepack enable - name: Setup Node.js - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml index 9dc812061..d604f9b16 100644 --- a/.github/workflows/get-api-diff.yml +++ b/.github/workflows/get-api-diff.yml @@ -37,7 +37,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5096e54af..d6832278e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: with: version: 8 run_install: false - - uses: actions/setup-node@v4.0.0 + - uses: actions/setup-node@v4.0.1 with: node-version-file: '.node-version' cache: 'pnpm' @@ -46,7 +46,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v4.0.0 + - uses: actions/setup-node@v4.0.1 with: node-version-file: '.node-version' cache: 'pnpm' @@ -72,7 +72,7 @@ jobs: with: version: 7 run_install: false - - uses: actions/setup-node@v4.0.0 + - uses: actions/setup-node@v4.0.1 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml index 6e8327ca0..9681cbec5 100644 --- a/.github/workflows/test-backend.yml +++ b/.github/workflows/test-backend.yml @@ -17,7 +17,7 @@ jobs: services: postgres: - image: postgres:13 + image: postgres:15 ports: - 54312:5432 env: @@ -38,7 +38,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml index e5c461e6d..83740bf15 100644 --- a/.github/workflows/test-frontend.yml +++ b/.github/workflows/test-frontend.yml @@ -25,7 +25,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -56,7 +56,7 @@ jobs: services: postgres: - image: postgres:13 + image: postgres:15 ports: - 54312:5432 env: @@ -83,7 +83,7 @@ jobs: version: 7 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml index 76e170b3e..055152f32 100644 --- a/.github/workflows/test-misskey-js.yml +++ b/.github/workflows/test-misskey-js.yml @@ -26,7 +26,7 @@ jobs: - run: corepack enable - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml index 694fa1a8f..9e02c0d8f 100644 --- a/.github/workflows/test-production.yml +++ b/.github/workflows/test-production.yml @@ -28,7 +28,7 @@ jobs: version: 8 run_install: false - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/CHANGELOG.md b/CHANGELOG.md index 7251fd221..2c21684e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,18 +5,18 @@ - ### Client -- Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正 -- Fix: MFMでルビの中のテキストがnyaizeされない問題を修正 +- ### Server - --> -## 2023.x.x (unreleased) +## 2023.12.0 ### Note - Node.js 20.10.0が最小要件になりました +- 絵文字の追加辞書を既にインストールしている場合は、お手数ですが再インストールのほどお願いします - 絵文字ピッカーにピン留め表示する絵文字設定が「リアクション用」と「絵文字入力用」に分かれました。以前の設定は「リアクション用」として使用されます。 **影響:** @@ -31,9 +31,12 @@ - Feat: メールアドレスの認証にverifymail.ioを使えるように (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/971ba07a44550f68d2ba31c62066db2d43a0caed) - Feat: モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能を追加 (cherry-pick from https://github.com/TeamNijimiss/misskey/commit/e0eb5a752f6e5616d6312bb7c9790302f9dbff83) - Feat: TL上からノートが見えなくなるワードミュートであるハードミュートを追加 +- Enhance: 公開ロールにアサインされたときに通知が作成されるように - Enhance: アイコンデコレーションを複数設定できるように - Enhance: アイコンデコレーションの位置を微調整できるように - Enhance: つながりの公開範囲をフォロー/フォロワーで個別に設定可能に #12072 +- Enhance: ローカリゼーションの更新 +- Enhance: 依存関係の更新 - Fix: MFM `$[unixtime ]` に不正な値を入力した際に発生する各種エラーを修正 ### Client @@ -59,12 +62,16 @@ - Enhance: ユーザー名、プロフィール、お知らせ、ページの編集画面でMFMや絵文字のオートコンプリートが使用できるように - Enhance: プロフィール、お知らせの編集画面でMFMのプレビューを表示できるように - Enhance: 絵文字の詳細ページに記載される情報を追加 +- Enhance: リアクションの表示幅制限を設定可能に - Enhance: Unicode 15.0のサポート - Enhance: コードブロックのハイライト機能を利用するには言語を明示的に指定させるように - MFMでコードブロックを利用する際に意図しないハイライトが起こらないようになりました - 逆に、MFMでコードハイライトを利用したい際は言語を明示的に指定する必要があります (例: ` ```js ` → Javascript, ` ```ais ` → AiScript) - Enhance: 絵文字などのオートコンプリートでShift+Tabを押すと前の候補を選択できるように +- Enhance: チャンネルに新規の投稿がある場合にバッジを表示させる +- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加 +- Enhance: 設定したタグをトレンドに表示させないようにする項目を管理画面で設定できるように - Fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 - Fix: コードエディタが正しく表示されない問題を修正 @@ -80,10 +87,16 @@ - Fix: 投票のみ/画像のみの引用RNが、通知欄でただのRNとして判定されるバグを修正 - Fix: CWをつけて引用RNしても、普通のRNとして扱われてしまうバグを修正しました。 - Fix: 「画像が1枚のみのメディアリストの高さ」を「デフォルト」以外に設定していると、CWの中などに添付された画像が見られないバグを修正 +- Fix: DeepL TranslationのPro accountトグルスイッチが表示されていなかったのを修正 +- Fix: twitterの埋め込みカード内リンクからリンク先を開けない問題を修正 +- Fix: WebKitブラウザー上でも「デバイスの画面を常にオンにする」機能が効くように +- Fix: ページ一覧ページの表示がモバイル環境において崩れているのを修正 +- Fix: MFMでルビの中のテキストがnyaizeされない問題を修正 ### Server - Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように - Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように +- Enhance: カスタム絵文字のインポート時の動作を改善 - Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303 - Fix: ロールタイムラインが保存されない問題を修正 - Fix: api.jsonの生成ロジックを改善 #12402 @@ -98,6 +111,7 @@ - Fix: 「みつける」が年越し時に壊れる問題を修正 - Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正 - Fix: モデレーションログがモデレーターは閲覧できないように修正 +- Fix: ハッシュタグのトレンド除外設定が即時に効果を持つように修正 - Fix: HTTP Digestヘッダのアルゴリズム部分に大文字の"SHA-256"しか使えない - Fix: 管理者用APIのアクセス権限が適切に設定されていない問題を修正 @@ -119,7 +133,6 @@ - 例: `$[unixtime 1701356400]` - Enhance: プラグインでエラーが発生した場合のハンドリングを強化 - Enhance: 細かなUIのブラッシュアップ -- Enhance: サウンド設定に「サウンドを出力しない」と「Misskeyがアクティブな時のみサウンドを出力する」を追加 - Fix: 効果音が再生されるとデバイスで再生している動画や音声が停止する問題を修正 #12339 - Fix: デッキに表示されたチャンネルの表示先チャンネルを切り替えた際、即座に反映されない問題を修正 #12236 - Fix: プラグインでノートの表示を書き換えられない問題を修正 @@ -147,7 +160,7 @@ ### General - Feat: アイコンデコレーション機能 - サーバーで用意された画像をアイコンに重ねることができます - - 画像のテンプレートはこちらです: https://misskey-hub.net/avatar-decoration-template.png + - 画像のテンプレートはこちらです: https://misskey-hub.net/brand-assets/ - 最大でも黄色いエリア内にデコレーションを収めることを推奨します。 - 画像は512x512pxを推奨します。 - Feat: チャンネル設定にリノート/引用リノートの可否を設定できる項目を追加 @@ -164,7 +177,7 @@ ### Client - Feat: プラグイン・テーマを外部サイトから直接インストールできるようになりました - 外部サイトでの実装が必要です。詳細は Misskey Hub をご覧ください - https://misskey-hub.net/docs/advanced/publish-on-your-website.html + https://misskey-hub.net/docs/for-developers/publish-on-your-website/ - Feat: 通知をグルーピングして表示するオプション(オプトアウト) - Feat: Misskeyの基本的なチュートリアルを実装 - Feat: スワイプしてタイムラインを再読込できるように diff --git a/README.md b/README.md index ab4388c2e..6fa804f1f 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ --- - + find an instance - + create an instance @@ -51,7 +51,7 @@ With Misskey's built in drive, you get cloud storage right in your social media, ## Documentation -Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it. +Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/docs/), some of the links and graphics above also lead to specific portions of it. ## Sponsors diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml index d5dd14f59..3c7383780 100644 --- a/chart/templates/Deployment.yml +++ b/chart/templates/Deployment.yml @@ -27,7 +27,7 @@ spec: ports: - containerPort: 3000 - name: postgres - image: postgres:14-alpine + image: postgres:15-alpine env: - name: POSTGRES_USER value: "example-misskey-user" @@ -38,7 +38,7 @@ spec: ports: - containerPort: 5432 - name: redis - image: redis:alpine + image: redis:7-alpine ports: - containerPort: 6379 volumes: diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index b4fa799ad..727e473cf 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -121,6 +121,12 @@ sensitive: "NSFW" add: "Afegir" reaction: "Reaccions" reactions: "Reaccions" +emojiPicker: "Selecció d'emojis" +pinnedEmojisForReactionSettingDescription: "Selecciona l'emoji amb el qual reaccionar" +pinnedEmojisSettingDescription: "Selecciona l'emoji amb el qual reaccionar" +emojiPickerDisplay: "Visualitza el selector d'emojis" +overwriteFromPinnedEmojisForReaction: "Reemplaça els emojis de la reacció" +overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats" reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir." rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes" attachCancel: "Eliminar el fitxer adjunt" @@ -213,6 +219,9 @@ clearQueueConfirmText: "Les notes no lliurades que quedin a la cua no es federar clearCachedFiles: "Esborra la memòria cau" clearCachedFilesConfirm: "Segur que voleu eliminar tots els fitxers de la memòria cau?" blockedInstances: "Instàncies bloquejades" +blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols bloquejar separades per un salt de pàgina. Les instàncies llistades no podran comunicar-se amb aquesta instància." +silencedInstances: "Instàncies silenciades" +silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols silenciar. Tots els comptes de les instàncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instàncies bloquejades." muteAndBlock: "Silencia i bloca" mutedUsers: "Usuaris silenciats" blockedUsers: "Usuaris bloquejats" @@ -227,9 +236,12 @@ preview: "Vista prèvia" default: "Per defecte" defaultValueIs: "Per defecte: {value}" noCustomEmojis: "Cap emoji personalitzat" +noJobs: "No hi ha feines" federating: "Federant" blocked: "Bloquejat" suspended: "Suspés" +all: "tot" +subscribing: "Subscrit a" publishing: "S'està publicant" notResponding: "Sense resposta" instanceFollowing: "Seguits del servidor" @@ -254,11 +266,31 @@ removed: "Eliminat" removeAreYouSure: "Segur que voleu retirar «{x}»?" deleteAreYouSure: "Segur que voleu retirar «{x}»?" resetAreYouSure: "Segur que voleu restablir-ho?" +areYouSure: "Està segur?" saved: "S'ha desat" messaging: "Xat" upload: "Puja" +keepOriginalUploading: "Guarda la imatge original" +keepOriginalUploadingDescription: "Guarda la imatge pujada com hi és. Si està apagat, una versió per a la visualització a la xarxa serà generada quan sigui pujada." +fromDrive: "Des de la unitat" +fromUrl: "Des d'un enllaç" +uploadFromUrl: "Carrega des d'un enllaç" +uploadFromUrlDescription: "Enllaç del fitxer que vols carregar" +uploadFromUrlRequested: "Càrrega sol·licitada" +uploadFromUrlMayTakeTime: "La càrrega des de l'enllaç pot prendre un temps" +explore: "Explora" +messageRead: "Vist" +noMoreHistory: "No hi resta més per veure" +startMessaging: "Començar a xatejar" +nUsersRead: "Vist per {n}" +agreeTo: "Accepto que {0}" +agree: "Hi estic d'acord" +agreeBelow: "Hi estic d'acord amb el següent" +basicNotesBeforeCreateAccount: "Notes importants" +termsOfService: "Condicions d'ús" start: "Comença" home: "Inici" +remoteUserCaution: "Ja que aquest usuari resideix a una instància remota, la informació mostrada es podria trobar incompleta." activity: "Activitat" images: "Imatges" image: "Imatges" @@ -274,16 +306,34 @@ dark: "Fosc" lightThemes: "Temes clars" darkThemes: "Temes foscos" syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu" +drive: "Unitat" +fileName: "Nom del Fitxer" +selectFile: "Selecciona fitxers" +selectFiles: "Selecciona fitxers" +selectFolder: "Selecció de carpeta" +selectFolders: "Selecció de carpeta" renameFile: "Canvia el nom del fitxer" folderName: "Nom de la carpeta" createFolder: "Crea una carpeta" renameFolder: "Canvia el nom de la carpeta" deleteFolder: "Elimina la carpeta" +folder: "Carpeta " addFile: "Afegeix un fitxer" +emptyDrive: "La teva unitat és buida" emptyFolder: "La carpeta està buida" unableToDelete: "No es pot eliminar" +inputNewFileName: "Introduïu el nom de fitxer nou" +inputNewDescription: "Inserta una nova llegenda" +inputNewFolderName: "Introduïu el nom de la carpeta nova" +circularReferenceFolder: "La carpeta destinatària és una subcarpeta de la carpeta a la qual la desitges moure" +hasChildFilesOrFolders: "No és possible esborrar aquesta carpeta ja que no és buida" copyUrl: "Copia l'URL" rename: "Canvia el nom" +avatar: "Icona" +banner: "Bàner" +displayOfSensitiveMedia: "Visualització de contingut sensible" +whenServerDisconnected: "Quan es perdi la connexió al servidor" +disconnectedFromServer: "Desconnectat pel servidor" reload: "Actualitza" doNothing: "Ignora" accept: "Accepta" @@ -353,33 +403,132 @@ notFound: "No s'ha trobat" markAsReadAllUnreadNotes: "Marca-ho tot com a llegit" help: "Ajuda" invites: "Convida" +title: "Títol" +text: "Text" +enable: "Habilita" next: "Següent" +retype: "Torneu a introduir-la" noteOf: "Publicació de: {user}" +quoteAttached: "Frase adjunta" +quoteQuestion: "Vols annexar-la com a cita?" +noMessagesYet: "Encara no hi ha missatges" +newMessageExists: "Has rebut un nou missatge" +onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge" +signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar" invitations: "Convida" +invitationCode: "Codi d'invitació" +checking: "Comprovació en curs..." +available: "Disponible" +unavailable: "No és disponible" +usernameInvalidFormat: "Pots fer servir lletres (majúscules i minúscules), números i barres baixes (\"_\")" +tooShort: "Massa curt" +tooLong: "Massa llarg" +weakPassword: "Contrasenya insegura" +normalPassword: "Bona contrasenya" +strongPassword: "Contrasenya segura" +passwordMatched: "Correcte!" +passwordNotMatched: "No coincideix" +signinWith: "Inicia sessió amb amb {x}" +signinFailed: "Autenticació sense èxit. Intenta-ho un altre cop utilitzant la contrasenya i el nom correctes." +or: "O" +language: "Idioma" +uiLanguage: "Idioma de l'interfície" +aboutX: "Respecte a {x}" +emojiStyle: "Estil d'emoji" +native: "Nadiu" +disableDrawer: "No mostrar els menús en calaixos" +showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor" +noHistory: "No hi ha un registre previ" +signinHistory: "Historial d'autenticacions" +enableAdvancedMfm: "Habilitar l'MFM avançat" +enableAnimatedMfm: "Habilitar l'MFM amb moviment" +doing: "Processant..." +category: "Categoria" tags: "Etiquetes" docSource: "Font del document" createAccount: "Crea un compte" existingAccount: "Compte existent" regenerate: "Regenera" fontSize: "Mida del text" +mediaListWithOneImageAppearance: "Altura de la llista de fitxers amb una única imatge" +limitTo: "Limita a {x}" noFollowRequests: "No tens sol·licituds de seguiment" +openImageInNewTab: "Obre imatges a una nova pestanya" dashboard: "Panell de control" local: "Local" remote: "Remot" total: "Total" +weekOverWeekChanges: "Canvis l'última setmana" +dayOverDayChanges: "Canvis ahir" appearance: "Aparença" clientSettings: "Configuració del client" accountSettings: "Configuració del compte" +promotion: "Promocionat" +promote: "Promoure" +numberOfDays: "Nombre de dies" hideThisNote: "Amaga la publicació" showFeaturedNotesInTimeline: "Mostra publicacions destacades en la línia de temps" +objectStorage: "Emmagatzematge d'objectes\n" +useObjectStorage: "Utilitzar l'emmagatzematge d'objectes" +objectStorageBaseUrl: "Base d'enllaç" +objectStorageBaseUrlDesc: "Prefix d'enllaç utilitzat per a fer referencia als fitxers. Especifica l'enllaç del teu CDN o Proxy si n'estàs utilitzant qualsevol, en cas contrari, especifica l'enllaç al que es pot accedir públicament segons la guia de servei que vosté utilitza.\nPer l'ús d'S3 utilitza 'https://.s3.amazonaws.com' I per a GCS o serveis equivalents utilitza 'https://storage.googleapis.com/'." newNoteRecived: "Hi ha publicacions noves" installedDate: "Data d'instal·lació" state: "Estat" sort: "Ordena" ascendingOrder: "Ascendent" descendingOrder: "Descendent" +removeAllFollowing: "Deixar de seguir tots els usuaris seguits" +removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix." +userSuspended: "Aquest usuari ha sigut suspès" +userSilenced: "Aquest usuari està sent silenciat" +yourAccountSuspendedTitle: "Aquest compte és suspès" +yourAccountSuspendedDescription: "Aquest compte ha sigut suspès a causa de la violació de les condicions d'ús o similars. Contacta l'administrador si en vol saber més. Si us plau, no en faci un altre compte." +tokenRevoked: "Codi de seguretat no vàlid" +tokenRevokedDescription: "La petició més recent ha estat denegada perquè contenia un codi de seguretat no vàlid. Actualitza la pàgina i torna-ho a provar." +accountDeleted: "Compte eliminat amb èxit" +accountDeletedDescription: "Aquest compte ha sigut eliminat" +menu: "Menú" +divider: "Divisor" +addItem: "Afegir element" +rearrange: "Torna a ordenar" +relays: "Relés" +addRelay: "Afegeix relés" +inboxUrl: "Enllaç de la safata d'entrada" +addedRelays: "Relés afegits" +serviceworkerInfo: "És obligatòria l'activació per a obtenir notificacions push" deletedNote: "Publicacions eliminades" invisibleNote: "Publicacions amagades" +enableInfiniteScroll: "Carrega més automàticament\n" +visibility: "Visibilitat" +poll: "Enquesta" +useCw: "Amaga el contingut" +enablePlayer: "Obre el reproductor de vídeo" +disablePlayer: "Tanca el reproductor de vídeo" +expandTweet: "Expandir post" +themeEditor: "Editor de temes" +description: "Descripció" +describeFile: "Afegir subtitulació" +enterFileDescription: "Afegeix un títol" +author: "Autor" +leaveConfirm: "Hi ha canvis sense guardar. Els vols descartar?" +manage: "Administració" +plugins: "Extensions" +preferencesBackups: "Configuracions de les Còpies de seguretat" +deck: "Escriptori" +undeck: "Tanca l'escriptori" +useBlurEffectForModal: "Utilitzar l'efecte de difuminació a modals" +useFullReactionPicker: "Utilitza el cercador de reaccions d'escala sencera" +width: "Amplada" +height: "Alçària" +large: "Gran" +medium: "Mitjà" +small: "Petit" +generateAccessToken: "Genera codi d'accés" +permission: "Permisos" +enableAll: "Habilita tot" +disableAll: "Deshabilita tot" +tokenRequested: "Donar accés al compte" smtpHost: "Amfitrió" smtpUser: "Nom d'usuari" smtpPass: "Contrasenya" @@ -389,12 +538,17 @@ clearCache: "Esborra la memòria cau" showingPastTimeline: "Estàs veient una línia de temps antiga" info: "Informació" user: "Usuaris" +administration: "Administració" +middle: "Mitjà" global: "Global" searchByGoogle: "Cercar" file: "Fitxers" +icon: "Icona" replies: "Respondre" renotes: "Impulsa" _role: + _priority: + middle: "Mitjà" _options: antennaMax: "Nombre màxim d'antenes" _email: @@ -403,9 +557,11 @@ _email: _instanceMute: instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat." _theme: + description: "Descripció" keys: mention: "Menció" renote: "Renotar" + divider: "Divisor" _sfx: note: "Notes" notification: "Notificacions" @@ -447,6 +603,8 @@ _timelines: local: "Local" social: "Social" global: "Global" +_play: + summary: "Descripció" _pages: contents: "Contingut" blocks: diff --git a/locales/en-US.yml b/locales/en-US.yml index 65fe07b6d..0701bc371 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -543,7 +543,7 @@ showInPage: "Show in page" popout: "Pop-out" volume: "Volume" masterVolume: "Master volume" -notUseSound: "No sounds output." +notUseSound: "Disable sound" useSoundOnlyWhenActive: "Output sounds only if Misskey is active." details: "Details" chooseEmoji: "Select an emoji" @@ -1167,6 +1167,7 @@ cwNotationRequired: "If \"Hide content\" is enabled, a description must be provi doReaction: "Add reaction" code: "Code" reloadRequiredToApplySettings: "Reloading is required to apply the settings." +decorate: "Decorate" _announcement: forExistingUsers: "Existing users only" forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it." @@ -1256,7 +1257,7 @@ _initialTutorial: sensitiveSucceeded: "When attaching files, please set sensitivities in accordance with the server guidelines." doItToContinue: "Mark the attachment file as sensitive to proceed." _done: - title: "The tutorial is complete! 🎉" + title: "You've completed the tutorial! 🎉" description: "The functions introduced here are just a small part. For a more detailed understanding of using Misskey, please refer to {link}." _timelineDescription: home: "In the Home timeline, you can see notes from accounts you follow." @@ -2154,6 +2155,7 @@ _notification: pollEnded: "Poll results have become available" newNote: "New note" unreadAntennaNote: "Antenna {name}" + roleAssigned: "Role given" emptyPushNotificationMessage: "Push notifications have been updated" achievementEarned: "Achievement unlocked" testNotification: "Test notification" @@ -2175,6 +2177,7 @@ _notification: pollEnded: "Polls ending" receiveFollowRequest: "Received follow requests" followRequestAccepted: "Accepted follow requests" + roleAssigned: "Role given" achievementEarned: "Achievement unlocked" app: "Notifications from linked apps" _actions: diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 8acbc7d7a..e12b50861 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1899,6 +1899,7 @@ _notification: yourFollowRequestAccepted: "Votre demande d’abonnement a été accepté" pollEnded: "Les résultats du sondage sont disponibles" unreadAntennaNote: "Antenne {name}" + roleAssigned: "Rôle attribué" emptyPushNotificationMessage: "Les notifications push ont été mises à jour" achievementEarned: "Accomplissement" testNotification: "Tester la notification" diff --git a/locales/index.d.ts b/locales/index.d.ts index 25a16d4a4..fd96fd762 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -2325,6 +2325,7 @@ export interface Locale { "pollEnded": string; "newNote": string; "unreadAntennaNote": string; + "roleAssigned": string; "emptyPushNotificationMessage": string; "achievementEarned": string; "testNotification": string; @@ -2346,6 +2347,7 @@ export interface Locale { "pollEnded": string; "receiveFollowRequest": string; "followRequestAccepted": string; + "roleAssigned": string; "achievementEarned": string; "app": string; }; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 308b7ae67..2c29bd20d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2227,6 +2227,7 @@ _notification: pollEnded: "アンケートの結果が出ました" newNote: "新しい投稿" unreadAntennaNote: "アンテナ {name}" + roleAssigned: "ロールが付与されました" emptyPushNotificationMessage: "プッシュ通知の更新をしました" achievementEarned: "実績を獲得" testNotification: "通知テスト" @@ -2249,6 +2250,7 @@ _notification: pollEnded: "アンケートが終了" receiveFollowRequest: "フォロー申請を受け取った" followRequestAccepted: "フォローが受理された" + roleAssigned: "ロールが付与された" achievementEarned: "実績の獲得" app: "連携アプリからの通知" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index ae612baec..d8efa7f04 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -881,6 +881,8 @@ makeReactionsPublicDescription: "나의 리액션을 누구나 볼 수 있게 classic: "클래식" muteThread: "글타래 뮤트" unmuteThread: "글타래 뮤트 해제" +followingVisibility: "팔로우의 공개 범위" +followersVisibility: "팔로워의 공개 범위" continueThread: "글타래 더 보기" deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? " incorrectPassword: "비밀번호가 올바르지 않습니다." @@ -1178,6 +1180,7 @@ reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 remainingN: "나머지: {n}" overwriteContentConfirm: "현재 내용을 덮어쓰기 합니다. 계속 진행하시겠습니까?" seasonalScreenEffect: "철에 맞는 화면으로 꾸미기" +decorate: "장식하기" _announcement: forExistingUsers: "기존 유저에게만 알림" forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다." @@ -1837,8 +1840,8 @@ _soundSettings: driveFileWarn: "드라이브에 있는 파일을 선택하세요." driveFileTypeWarn: "이 파일은 지원되지 않습니다." driveFileTypeWarnDescription: "오디오 파일을 선택하세요." - driveFileDurationWarn: "오디오가 너무 길어요." - driveFileDurationWarnDescription: "길은 오디오를 사용하시는 경우 미스키 사용에 지장이 갈 수도 있습니다. 그래도 괜찮습니까?" + driveFileDurationWarn: "오디오가 너무 깁니다" + driveFileDurationWarnDescription: "긴 오디오로 설정할 경우 미스키 사용에 지장이 갈 수도 있습니다. 그래도 괜찮습니까?" _ago: future: "미래" justNow: "방금 전" @@ -2168,6 +2171,7 @@ _notification: pollEnded: "투표 결과가 발표되었습니다" newNote: "새 게시물" unreadAntennaNote: "안테나 {name}" + roleAssigned: "역할이 부여 되었습니다." emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다" achievementEarned: "도전 과제를 달성했습니다" testNotification: "알림 테스트" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 8fbf03638..d05691d42 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1217,7 +1217,7 @@ _initialTutorial: skipAreYouSure: "結束教學模式?" _landing: title: "歡迎使用本教學課程" - description: "在這裡您可以查看Misskey的基本使用方法和功能。" + description: "在這裡您可以查看 Misskey 的基本使用方法和功能。" _note: title: "什麼是貼文?" description: "在Misskey上發布的內容稱為「貼文」。貼文在時間軸上按時間順序排列,並即時更新。" @@ -2171,6 +2171,7 @@ _notification: pollEnded: "問卷調查已產生結果" newNote: "新的貼文" unreadAntennaNote: "天線 {name}" + roleAssigned: "已授予角色" emptyPushNotificationMessage: "推送通知已更新" achievementEarned: "獲得成就" testNotification: "通知測試" diff --git a/package.json b/package.json index 6120c0709..d39b800a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2023.12.0-beta.5", + "version": "2023.12.0", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/src/core/FeaturedService.ts b/packages/backend/src/core/FeaturedService.ts index d970ffa43..595383c82 100644 --- a/packages/backend/src/core/FeaturedService.ts +++ b/packages/backend/src/core/FeaturedService.ts @@ -77,6 +77,17 @@ export class FeaturedService { return Array.from(ranking.keys()); } + @bindThis + private async removeFromRanking(name: string, windowRange: number, element: string): Promise { + const currentWindow = this.getCurrentWindow(windowRange); + const previousWindow = currentWindow - 1; + + const redisPipeline = this.redisClient.pipeline(); + redisPipeline.zrem(`${name}:${currentWindow}`, element); + redisPipeline.zrem(`${name}:${previousWindow}`, element); + await redisPipeline.exec(); + } + @bindThis public updateGlobalNotesRanking(noteId: MiNote['id'], score = 1): Promise { return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score); @@ -126,4 +137,9 @@ export class FeaturedService { public getHashtagsRanking(threshold: number): Promise { return this.getRankingOf('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, threshold); } + + @bindThis + public removeHashtagsFromRanking(hashtag: string): Promise { + return this.removeFromRanking('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, hashtag); + } } diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts index 508544dc0..80e802096 100644 --- a/packages/backend/src/core/MetaService.ts +++ b/packages/backend/src/core/MetaService.ts @@ -11,6 +11,7 @@ import { MiMeta } from '@/models/Meta.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; +import { FeaturedService } from '@/core/FeaturedService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; @Injectable() @@ -25,6 +26,7 @@ export class MetaService implements OnApplicationShutdown { @Inject(DI.db) private db: DataSource, + private featuredService: FeaturedService, private globalEventService: GlobalEventService, ) { //this.onMessage = this.onMessage.bind(this); @@ -95,6 +97,8 @@ export class MetaService implements OnApplicationShutdown { @bindThis public async update(data: Partial): Promise { + let before: MiMeta | undefined; + const updated = await this.db.transaction(async transactionalEntityManager => { const metas = await transactionalEntityManager.find(MiMeta, { order: { @@ -102,10 +106,10 @@ export class MetaService implements OnApplicationShutdown { }, }); - const meta = metas[0]; + before = metas[0]; - if (meta) { - await transactionalEntityManager.update(MiMeta, meta.id, data); + if (before) { + await transactionalEntityManager.update(MiMeta, before.id, data); const metas = await transactionalEntityManager.find(MiMeta, { order: { @@ -119,6 +123,21 @@ export class MetaService implements OnApplicationShutdown { } }); + if (data.hiddenTags) { + process.nextTick(() => { + const hiddenTags = new Set(data.hiddenTags); + if (before) { + for (const previousHiddenTag of before.hiddenTags) { + hiddenTags.delete(previousHiddenTag); + } + } + + for (const hiddenTag of hiddenTags) { + this.featuredService.removeHashtagsFromRanking(hiddenTag); + } + }); + } + this.globalEventService.publishInternalEvent('metaUpdated', updated); return updated; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 9fe965b13..54493612b 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -293,7 +293,7 @@ export class NoteCreateService implements OnApplicationShutdown { } // Check blocking - if (data.renote && this.isQuote(data)) { + if (this.isQuote(data)) { if (data.renote.userHost === null) { if (data.renote.userId !== user.id) { const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id); @@ -730,8 +730,9 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private isQuote(note: Option): boolean { - return !!note.text || !!note.cw || !!note.files || !!note.poll; + private isQuote(note: Option): note is Option & { renote: MiNote } { + // sync with misc/is-quote.ts + return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll); } @bindThis @@ -799,7 +800,7 @@ export class NoteCreateService implements OnApplicationShutdown { private async renderNoteOrRenoteActivity(data: Option, note: MiNote) { if (data.localOnly) return null; - const content = data.renote && this.isQuote(data) + const content = data.renote && !this.isQuote(data) ? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note) : this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 4de719d6a..d354faa7c 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -6,7 +6,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { In } from 'typeorm'; -import type { MiRole, MiRoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js'; +import { ModuleRef } from '@nestjs/core'; +import type { + MiRole, + MiRoleAssignment, + RoleAssignmentsRepository, + RolesRepository, + UsersRepository, +} from '@/models/_.js'; import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js'; import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; @@ -16,12 +23,13 @@ import { CacheService } from '@/core/CacheService.js'; import type { RoleCondFormulaValue } from '@/models/Role.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; -import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { IdService } from '@/core/IdService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import type { Packed } from '@/misc/json-schema.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; -import type { OnApplicationShutdown } from '@nestjs/common'; +import { NotificationService } from '@/core/NotificationService.js'; +import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; export type RolePolicies = { gtlAvailable: boolean; @@ -78,14 +86,17 @@ export const DEFAULT_POLICIES: RolePolicies = { }; @Injectable() -export class RoleService implements OnApplicationShutdown { +export class RoleService implements OnApplicationShutdown, OnModuleInit { private rolesCache: MemorySingleCache; private roleAssignmentByUserIdCache: MemoryKVCache; + private notificationService: NotificationService; public static AlreadyAssignedError = class extends Error {}; public static NotAssignedError = class extends Error {}; constructor( + private moduleRef: ModuleRef, + @Inject(DI.redis) private redisClient: Redis.Redis, @@ -120,6 +131,10 @@ export class RoleService implements OnApplicationShutdown { this.redisForSub.on('message', this.onMessage); } + async onModuleInit() { + this.notificationService = this.moduleRef.get(NotificationService.name); + } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); @@ -427,6 +442,12 @@ export class RoleService implements OnApplicationShutdown { this.globalEventService.publishInternalEvent('userRoleAssigned', created); + if (role.isPublic) { + this.notificationService.createNotification(userId, 'roleAssigned', { + roleId: roleId, + }); + } + if (moderator) { const user = await this.usersRepository.findOneByOrFail({ id: userId }); this.moderationLogService.log(moderator, 'assignRole', { diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 702c731fc..b6e4e1e88 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -3,30 +3,34 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; +import { Inject, Injectable, OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; import * as Redis from 'ioredis'; +import { ModuleRef } from '@nestjs/core'; import type { UserListMembershipsRepository } from '@/models/_.js'; import type { MiUser } from '@/models/User.js'; import type { MiUserList } from '@/models/UserList.js'; import type { MiUserListMembership } from '@/models/UserListMembership.js'; import { IdService } from '@/core/IdService.js'; +import type { GlobalEvents } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { ProxyAccountService } from '@/core/ProxyAccountService.js'; import { bindThis } from '@/decorators.js'; -import { RoleService } from '@/core/RoleService.js'; import { QueueService } from '@/core/QueueService.js'; import { RedisKVCache } from '@/misc/cache.js'; -import type { GlobalEvents } from '@/core/GlobalEventService.js'; +import { RoleService } from '@/core/RoleService.js'; @Injectable() -export class UserListService implements OnApplicationShutdown { +export class UserListService implements OnApplicationShutdown, OnModuleInit { public static TooManyUsersError = class extends Error {}; public membersCache: RedisKVCache>; + private roleService: RoleService; constructor( + private moduleRef: ModuleRef, + @Inject(DI.redis) private redisClient: Redis.Redis, @@ -38,7 +42,6 @@ export class UserListService implements OnApplicationShutdown { private userEntityService: UserEntityService, private idService: IdService, - private roleService: RoleService, private globalEventService: GlobalEventService, private proxyAccountService: ProxyAccountService, private queueService: QueueService, @@ -54,6 +57,10 @@ export class UserListService implements OnApplicationShutdown { this.redisForSub.on('message', this.onMessage); } + async onModuleInit() { + this.roleService = this.moduleRef.get(RoleService.name); + } + @bindThis private async onMessage(_: string, data: string): Promise { const obj = JSON.parse(data); diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts index e723ea5a5..704081ed0 100644 --- a/packages/backend/src/core/entities/NotificationEntityService.ts +++ b/packages/backend/src/core/entities/NotificationEntityService.ts @@ -15,8 +15,8 @@ import type { Packed } from '@/misc/json-schema.js'; import { bindThis } from '@/decorators.js'; import { isNotNull } from '@/misc/is-not-null.js'; import { FilterUnionByProperty, notificationTypes } from '@/types.js'; +import { RoleEntityService } from './RoleEntityService.js'; import type { OnModuleInit } from '@nestjs/common'; -import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js'; @@ -27,7 +27,7 @@ const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 're export class NotificationEntityService implements OnModuleInit { private userEntityService: UserEntityService; private noteEntityService: NoteEntityService; - private customEmojiService: CustomEmojiService; + private roleEntityService: RoleEntityService; constructor( private moduleRef: ModuleRef, @@ -43,14 +43,13 @@ export class NotificationEntityService implements OnModuleInit { //private userEntityService: UserEntityService, //private noteEntityService: NoteEntityService, - //private customEmojiService: CustomEmojiService, ) { } onModuleInit() { this.userEntityService = this.moduleRef.get('UserEntityService'); this.noteEntityService = this.moduleRef.get('NoteEntityService'); - this.customEmojiService = this.moduleRef.get('CustomEmojiService'); + this.roleEntityService = this.moduleRef.get('RoleEntityService'); } @bindThis @@ -81,6 +80,7 @@ export class NotificationEntityService implements OnModuleInit { detail: false, }) ) : undefined; + const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined; return await awaitAll({ id: notification.id, @@ -92,6 +92,9 @@ export class NotificationEntityService implements OnModuleInit { ...(notification.type === 'reaction' ? { reaction: notification.reaction, } : {}), + ...(notification.type === 'roleAssigned' ? { + role: role, + } : {}), ...(notification.type === 'achievementEarned' ? { achievement: notification.achievement, } : {}), @@ -216,6 +219,8 @@ export class NotificationEntityService implements OnModuleInit { }); } + const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined; + return await awaitAll({ id: notification.id, createdAt: new Date(notification.createdAt).toISOString(), @@ -226,6 +231,9 @@ export class NotificationEntityService implements OnModuleInit { ...(notification.type === 'reaction' ? { reaction: notification.reaction, } : {}), + ...(notification.type === 'roleAssigned' ? { + role: role, + } : {}), ...(notification.type === 'achievementEarned' ? { achievement: notification.achievement, } : {}), diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts index 059f6a4b5..db72d1d57 100644 --- a/packages/backend/src/misc/is-quote.ts +++ b/packages/backend/src/misc/is-quote.ts @@ -7,5 +7,6 @@ import type { MiNote } from '@/models/Note.js'; // eslint-disable-next-line import/no-default-export export default function(note: MiNote): boolean { + // sync with NoteCreateService.isQuote return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0)); } diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index 49f35b9b7..176978d35 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -38,6 +38,7 @@ import { packedFlashSchema } from '@/models/json-schema/flash.js'; import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js'; import { packedSigninSchema } from '@/models/json-schema/signin.js'; import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js'; +import { packedAdSchema } from '@/models/json-schema/ad.js'; export const refs = { UserLite: packedUserLiteSchema, @@ -49,6 +50,7 @@ export const refs = { User: packedUserSchema, UserList: packedUserListSchema, + Ad: packedAdSchema, Announcement: packedAnnouncementSchema, App: packedAppSchema, Note: packedNoteSchema, diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 1d5fc124e..3bc2edaa0 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -3,11 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { notificationTypes } from '@/types.js'; import { MiUser } from './User.js'; import { MiNote } from './Note.js'; -import { MiFollowRequest } from './FollowRequest.js'; import { MiAccessToken } from './AccessToken.js'; +import { MiRole } from './Role.js'; export type MiNotification = { type: 'note'; @@ -68,6 +67,11 @@ export type MiNotification = { id: string; createdAt: string; notifierId: MiUser['id']; +} | { + type: 'roleAssigned'; + id: string; + createdAt: string; + roleId: MiRole['id']; } | { type: 'achievementEarned'; id: string; diff --git a/packages/backend/src/models/json-schema/ad.ts b/packages/backend/src/models/json-schema/ad.ts new file mode 100644 index 000000000..649ffcd4d --- /dev/null +++ b/packages/backend/src/models/json-schema/ad.ts @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const packedAdSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, + nullable: false, + format: 'id', + example: 'xxxxxxxxxx', + }, + expiresAt: { + type: 'string', + optional: false, + nullable: false, + format: 'date-time', + }, + startsAt: { + type: 'string', + optional: false, + nullable: false, + format: 'date-time', + }, + place: { + type: 'string', + optional: false, + nullable: false, + }, + priority: { + type: 'string', + optional: false, + nullable: false, + }, + ratio: { + type: 'number', + optional: false, + nullable: false, + }, + url: { + type: 'string', + optional: false, + nullable: false, + }, + imageUrl: { + type: 'string', + optional: false, + nullable: false, + }, + memo: { + type: 'string', + optional: false, + nullable: false, + }, + dayOfWeek: { + type: 'integer', + optional: false, + nullable: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 1b86b1bf1..6a0d43b1a 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -554,9 +554,7 @@ export const packedMeDetailedOnlySchema = { mention: notificationRecieveConfig, reaction: notificationRecieveConfig, pollEnded: notificationRecieveConfig, - achievementEarned: notificationRecieveConfig, receiveFollowRequest: notificationRecieveConfig, - followRequestAccepted: notificationRecieveConfig, }, }, emailNotificationTypes: { diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index ee081ccaa..b872dd65f 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -153,8 +153,8 @@ export class QueueProcessorService implements OnApplicationShutdown { this.systemQueueWorker .on('active', (job) => systemLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) - .on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) })) + .on('failed', (job, err) => systemLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => systemLogger.error(`error ${err.stack}`, { e: renderError(err) })) .on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`)); //#endregion @@ -191,8 +191,8 @@ export class QueueProcessorService implements OnApplicationShutdown { this.dbQueueWorker .on('active', (job) => dbLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) - .on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) })) + .on('failed', (job, err) => dbLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => dbLogger.error(`error ${err.stack}`, { e: renderError(err) })) .on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`)); //#endregion @@ -215,8 +215,8 @@ export class QueueProcessorService implements OnApplicationShutdown { this.deliverQueueWorker .on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) - .on('error', (err: Error) => deliverLogger.error(`error ${err}`, { e: renderError(err) })) + .on('failed', (job, err) => deliverLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) + .on('error', (err: Error) => deliverLogger.error(`error ${err.stack}`, { e: renderError(err) })) .on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`)); //#endregion @@ -239,8 +239,8 @@ export class QueueProcessorService implements OnApplicationShutdown { this.inboxQueueWorker .on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`)) .on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`)) - .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) })) - .on('error', (err: Error) => inboxLogger.error(`error ${err}`, { e: renderError(err) })) + .on('failed', (job, err) => inboxLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => inboxLogger.error(`error ${err.stack}`, { e: renderError(err) })) .on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`)); //#endregion @@ -263,8 +263,8 @@ export class QueueProcessorService implements OnApplicationShutdown { this.webhookDeliverQueueWorker .on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`)) .on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`)) - .on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) - .on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) })) + .on('failed', (job, err) => webhookLogger.warn(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`)) + .on('error', (err: Error) => webhookLogger.error(`error ${err.stack}`, { e: renderError(err) })) .on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`)); //#endregion @@ -292,8 +292,8 @@ export class QueueProcessorService implements OnApplicationShutdown { this.relationshipQueueWorker .on('active', (job) => relationshipLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) - .on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) })) + .on('failed', (job, err) => relationshipLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => relationshipLogger.error(`error ${err.stack}`, { e: renderError(err) })) .on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`)); //#endregion @@ -315,8 +315,8 @@ export class QueueProcessorService implements OnApplicationShutdown { this.objectStorageQueueWorker .on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`)) .on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`)) - .on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) - .on('error', (err: Error) => objectStorageLogger.error(`error ${err}`, { e: renderError(err) })) + .on('failed', (job, err) => objectStorageLogger.warn(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) })) + .on('error', (err: Error) => objectStorageLogger.error(`error ${err.stack}`, { e: renderError(err) })) .on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`)); //#endregion diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts index 7dc9ca830..bc292fd53 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts @@ -25,6 +25,11 @@ export const meta = { id: 'cb865949-8af5-4062-a88c-ef55e8786d1d', }, }, + res: { + type: 'object', + optional: false, nullable: false, + ref: 'User', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts index cbe9727c4..087ae4bef 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts @@ -17,6 +17,12 @@ export const meta = { requireCredential: true, requireModerator: true, + res: { + type: 'object', + optional: false, + nullable: false, + ref: 'Ad', + }, } as const; export const paramDef = { @@ -63,7 +69,18 @@ export default class extends Endpoint { // eslint- ad: ad, }); - return ad; + return { + id: ad.id, + expiresAt: ad.expiresAt.toISOString(), + startsAt: ad.startsAt.toISOString(), + dayOfWeek: ad.dayOfWeek, + url: ad.url, + imageUrl: ad.imageUrl, + priority: ad.priority, + ratio: ad.ratio, + place: ad.place, + memo: ad.memo, + }; }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts index 3bda9fcb0..12528917d 100644 --- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts @@ -16,6 +16,17 @@ export const meta = { requireCredential: true, requireModerator: true, + res: { + type: 'array', + optional: false, + nullable: false, + items: { + type: 'object', + optional: false, + nullable: false, + ref: 'Ad', + }, + }, } as const; export const paramDef = { @@ -46,7 +57,18 @@ export default class extends Endpoint { // eslint- } const ads = await query.limit(ps.limit).getMany(); - return ads; + return ads.map(ad => ({ + id: ad.id, + expiresAt: ad.expiresAt.toISOString(), + startsAt: ad.startsAt.toISOString(), + dayOfWeek: ad.dayOfWeek, + url: ad.url, + imageUrl: ad.imageUrl, + memo: ad.memo, + place: ad.place, + priority: ad.priority, + ratio: ad.ratio, + })); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 360926594..76ff1c6b9 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -31,6 +31,8 @@ export const meta = { id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975', }, }, + + ref: 'EmojiDetailed', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts index 2de85f655..b81d9857d 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts @@ -15,6 +15,16 @@ export const meta = { kind: 'read:admin', tags: ['admin'], + res: { + type: 'array', + items: { + type: 'object', + properties: { + tablename: { type: 'string' }, + indexname: { type: 'string' }, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts index 6a404c0c7..76c32f2a9 100644 --- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts +++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts @@ -16,6 +16,25 @@ export const meta = { requireCredential: true, requireModerator: true, + res: { + type: 'array', + optional: false, + nullable: false, + items: { + type: 'object', + optional: false, + nullable: false, + properties: { + ip: { type: 'string' }, + createdAt: { + type: 'string', + optional: false, + nullable: false, + format: 'date-time', + }, + }, + }, + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts index 53145a32d..6a0f7f998 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts @@ -28,6 +28,20 @@ export const meta = { id: '224eff5e-2488-4b18-b3e7-f50d94421648', }, }, + + res: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'string', format: 'misskey:id' }, + createdAt: { type: 'string', format: 'date-time' }, + user: { ref: 'UserDetailed' }, + expiresAt: { type: 'string', format: 'date-time', nullable: true }, + }, + required: ['id', 'createdAt', 'user'], + }, + } } as const; export const paramDef = { @@ -80,7 +94,7 @@ export default class extends Endpoint { // eslint- id: assign.id, createdAt: this.idService.parse(assign.id).date.toISOString(), user: await this.userEntityService.pack(assign.user!, me, { detail: true }), - expiresAt: assign.expiresAt, + expiresAt: assign.expiresAt?.toISOString() ?? null, }))); }); } diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts index cecaded20..66ac8f664 100644 --- a/packages/backend/src/server/api/endpoints/endpoint.ts +++ b/packages/backend/src/server/api/endpoints/endpoint.ts @@ -11,6 +11,23 @@ export const meta = { requireCredential: false, tags: ['meta'], + + res: { + type: 'object', + nullable: true, + properties: { + params: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + type: { type: 'string' }, + }, + }, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts index e3ffea7b7..6548142d4 100644 --- a/packages/backend/src/server/api/endpoints/federation/stats.ts +++ b/packages/backend/src/server/api/endpoints/federation/stats.ts @@ -18,6 +18,92 @@ export const meta = { allowGet: true, cacheSec: 60 * 60, + + res: { + type: 'object', + optional: false, + nullable: false, + properties: { + topSubInstances: { + type: 'array', + optional: false, + nullable: false, + items: { + properties: { + id: { type: 'string' }, + firstRetrievedAt: { type: 'string' }, + host: { type: 'string' }, + usersCount: { type: 'number' }, + notesCount: { type: 'number' }, + followingCount: { type: 'number' }, + followersCount: { type: 'number' }, + isNotResponding: { type: 'boolean' }, + isSuspended: { type: 'boolean' }, + isBlocked: { type: 'boolean' }, + softwareName: { type: 'string' }, + softwareVersion: { type: 'string' }, + openRegistrations: { type: 'boolean' }, + name: { type: 'string' }, + description: { type: 'string' }, + maintainerName: { type: 'string' }, + maintainerEmail: { type: 'string' }, + isSilenced: { type: 'boolean' }, + iconUrl: { type: 'string' }, + faviconUrl: { type: 'string' }, + themeColor: { type: 'string' }, + infoUpdatedAt: { + type: 'string', + nullable: true, + }, + latestRequestReceivedAt: { + type: 'string', + nullable: true, + }, + } + }, + }, + otherFollowersCount: { type: 'number' }, + topPubInstances: { + type: 'array', + optional: false, + nullable: false, + items: { + properties: { + id: { type: 'string' }, + firstRetrievedAt: { type: 'string' }, + host: { type: 'string' }, + usersCount: { type: 'number' }, + notesCount: { type: 'number' }, + followingCount: { type: 'number' }, + followersCount: { type: 'number' }, + isNotResponding: { type: 'boolean' }, + isSuspended: { type: 'boolean' }, + isBlocked: { type: 'boolean' }, + softwareName: { type: 'string' }, + softwareVersion: { type: 'string' }, + openRegistrations: { type: 'boolean' }, + name: { type: 'string' }, + description: { type: 'string' }, + maintainerName: { type: 'string' }, + maintainerEmail: { type: 'string' }, + isSilenced: { type: 'boolean' }, + iconUrl: { type: 'string' }, + faviconUrl: { type: 'string' }, + themeColor: { type: 'string' }, + infoUpdatedAt: { + type: 'string', + nullable: true, + }, + latestRequestReceivedAt: { + type: 'string', + nullable: true, + }, + } + }, + }, + otherFollowingCount: { type: 'number' }, + }, + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/fetch-external-resources.ts b/packages/backend/src/server/api/endpoints/fetch-external-resources.ts index d7b46cc66..6391a2f58 100644 --- a/packages/backend/src/server/api/endpoints/fetch-external-resources.ts +++ b/packages/backend/src/server/api/endpoints/fetch-external-resources.ts @@ -32,6 +32,18 @@ export const meta = { id: '693ba8ba-b486-40df-a174-72f8279b56a4', }, }, + + res: { + type: 'object', + properties: { + type: { + type: 'string', + }, + data: { + type: 'string', + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts index 37859d833..b2dee83fe 100644 --- a/packages/backend/src/server/api/endpoints/fetch-rss.ts +++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts @@ -16,6 +16,18 @@ export const meta = { requireCredential: false, allowGet: true, cacheSec: 60 * 3, + + res: { + type: 'object', + properties: { + items: { + type: 'array', + items: { + type: 'object', + }, + } + } + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts index 4fa65ac9a..674f32373 100644 --- a/packages/backend/src/server/api/endpoints/flash/create.ts +++ b/packages/backend/src/server/api/endpoints/flash/create.ts @@ -27,6 +27,12 @@ export const meta = { errors: { }, + + res: { + type: 'object', + optional: false, nullable: false, + ref: 'Flash', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index 8a61168f2..737d637b7 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -16,6 +16,16 @@ export const meta = { requireCredential: false, allowGet: true, cacheSec: 60 * 1, + res: { + type: 'object', + optional: false, nullable: false, + properties: { + count: { + type: 'number', + nullable: false, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 6d530aba3..a7be47fd0 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -32,6 +32,16 @@ export const meta = { id: '798d6847-b1ed-4f9c-b1f9-163c42655995', }, }, + + res: { + type: 'object', + nullable: false, + optional: false, + properties: { + id: { type: 'string' }, + name: { type: 'string' }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index c39005f2d..0fac96d58 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -36,6 +36,140 @@ export const meta = { id: 'bf32b864-449b-47b8-974e-f9a5468546f1', }, }, + + res: { + type: 'object', + nullable: false, + optional: false, + properties: { + rp: { + type: 'object', + properties: { + id: { + type: 'string', + nullable: true, + }, + }, + }, + user: { + type: 'object', + properties: { + id: { + type: 'string', + }, + name: { + type: 'string', + }, + displayName: { + type: 'string', + }, + }, + }, + challenge: { + type: 'string', + }, + pubKeyCredParams: { + type: 'array', + items: { + type: 'object', + properties: { + type: { + type: 'string', + }, + alg: { + type: 'number', + }, + }, + }, + }, + timeout: { + type: 'number', + nullable: true, + }, + excludeCredentials: { + type: 'array', + nullable: true, + items: { + type: 'object', + properties: { + id: { + type: 'string', + }, + type: { + type: 'string', + }, + transports: { + type: 'array', + items: { + type: 'string', + enum: [ + "ble", + "cable", + "hybrid", + "internal", + "nfc", + "smart-card", + "usb", + ], + }, + }, + }, + }, + }, + authenticatorSelection: { + type: 'object', + nullable: true, + properties: { + authenticatorAttachment: { + type: 'string', + enum: [ + "cross-platform", + "platform", + ], + }, + requireResidentKey: { + type: 'boolean', + }, + userVerification: { + type: 'string', + enum: [ + "discouraged", + "preferred", + "required", + ], + }, + }, + }, + attestation: { + type: 'string', + nullable: true, + enum: [ + "direct", + "enterprise", + "indirect", + "none", + ], + }, + extensions: { + type: 'object', + nullable: true, + properties: { + appid: { + type: 'string', + nullable: true, + }, + credProps: { + type: 'boolean', + nullable: true, + }, + hmacCreateSecret: { + type: 'boolean', + nullable: true, + }, + }, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index b358c812e..cc083cbf7 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -26,6 +26,19 @@ export const meta = { id: '78d6c839-20c9-4c66-b90a-fc0542168b48', }, }, + + res: { + type: 'object', + nullable: false, + optional: false, + properties: { + qr: { type: 'string' }, + url: { type: 'string' }, + secret: { type: 'string' }, + label: { type: 'string' }, + issuer: { type: 'string' }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 09f6540a7..ef89f9318 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -13,6 +13,37 @@ export const meta = { requireCredential: true, secure: true, + + res: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'misskey:id', + }, + name: { + type: 'string', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + lastUsedAt: { + type: 'string', + format: 'date-time', + }, + permission: { + type: 'array', + uniqueItems: true, + items: { + type: 'string' + }, + } + }, + }, + }, } as const; export const paramDef = { @@ -50,7 +81,7 @@ export default class extends Endpoint { // eslint- id: token.id, name: token.name ?? token.app?.name, createdAt: this.idService.parse(token.id).date.toISOString(), - lastUsedAt: token.lastUsedAt, + lastUsedAt: token.lastUsedAt?.toISOString(), permission: token.permission, }))); }); diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts index 32061c2aa..a0ed371fb 100644 --- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -14,6 +14,36 @@ export const meta = { requireCredential: true, secure: true, + + res: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'misskey:id', + }, + name: { + type: 'string', + }, + callbackUrl: { + type: 'string', + nullable: true, + }, + permission: { + type: 'array', + uniqueItems: true, + items: { + type: 'string' + }, + }, + isAuthorized: { + type: 'boolean', + }, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index 86b726e05..f3ba720c2 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -64,6 +64,10 @@ export const meta = { id: 'b234a14e-9ebe-4581-8000-074b3c215962', }, }, + + res: { + type: 'object', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts index 29fa0a29c..bd6e85a07 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -9,6 +9,10 @@ import { RegistryApiService } from '@/core/RegistryApiService.js'; export const meta = { requireCredential: true, + + res: { + type: 'object', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts index 5b460b45d..2352beb13 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -18,6 +18,10 @@ export const meta = { id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a', }, }, + + res: { + type: 'object', + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts index e8c28298e..4155a43e0 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/get.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -18,6 +18,10 @@ export const meta = { id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a', }, }, + + res: { + type: 'object', + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts index 8953ee5d3..b411cdd3d 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -9,6 +9,10 @@ import { RegistryApiService } from '@/core/RegistryApiService.js'; export const meta = { requireCredential: true, + + res: { + type: 'object', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts index 1ff994b82..0aca2a26f 100644 --- a/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts @@ -10,6 +10,28 @@ import { RegistryApiService } from '@/core/RegistryApiService.js'; export const meta = { requireCredential: true, secure: true, + + res: { + type: 'array', + items: { + type: 'object', + properties: { + scopes: { + type: 'array', + items: { + type: 'array', + items: { + type: 'string', + } + } + }, + domain: { + type: 'string', + nullable: true, + }, + }, + }, + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index a36b3a732..52977f5a0 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -40,6 +40,11 @@ export const meta = { id: 'a2defefb-f220-8849-0af6-17f816099323', }, }, + + res: { + type: 'object', + ref: 'UserDetailed', + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts index f00dba4a8..bdc9f9ea8 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts @@ -27,6 +27,33 @@ export const meta = { id: '87a9bb19-111e-4e37-81d3-a3e7426453b0', }, }, + + res: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'misskey:id' + }, + userId: { + type: 'string', + format: 'misskey:id', + }, + name: { type: 'string' }, + on: { + type: 'array', + items: { + type: 'string', + enum: webhookEventTypes, + } + }, + url: { type: 'string' }, + secret: { type: 'string' }, + active: { type: 'boolean' }, + latestSentAt: { type: 'string', format: 'date-time', nullable: true }, + latestStatus: { type: 'integer', nullable: true }, + }, + }, } as const; export const paramDef = { @@ -73,7 +100,17 @@ export default class extends Endpoint { // eslint- this.globalEventService.publishInternalEvent('webhookCreated', webhook); - return webhook; + return { + id: webhook.id, + userId: webhook.userId, + name: webhook.name, + on: webhook.on, + url: webhook.url, + secret: webhook.secret, + active: webhook.active, + latestSentAt: webhook.latestSentAt?.toISOString(), + latestStatus: webhook.latestStatus, + }; }); } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts index aa8921fe2..afb2d0509 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { webhookEventTypes } from '@/models/Webhook.js'; import type { WebhooksRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; @@ -14,6 +15,36 @@ export const meta = { requireCredential: true, kind: 'read:account', + + res: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'misskey:id' + }, + userId: { + type: 'string', + format: 'misskey:id', + }, + name: { type: 'string' }, + on: { + type: 'array', + items: { + type: 'string', + enum: webhookEventTypes, + } + }, + url: { type: 'string' }, + secret: { type: 'string' }, + active: { type: 'boolean' }, + latestSentAt: { type: 'string', format: 'date-time', nullable: true }, + latestStatus: { type: 'integer', nullable: true }, + }, + } + } } as const; export const paramDef = { @@ -33,7 +64,19 @@ export default class extends Endpoint { // eslint- userId: me.id, }); - return webhooks; + return webhooks.map(webhook => ( + { + id: webhook.id, + userId: webhook.userId, + name: webhook.name, + on: webhook.on, + url: webhook.url, + secret: webhook.secret, + active: webhook.active, + latestSentAt: webhook.latestSentAt?.toISOString(), + latestStatus: webhook.latestStatus, + } + )); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts index f1294bb5c..5c6dd908b 100644 --- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts +++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import { webhookEventTypes } from '@/models/Webhook.js'; import type { WebhooksRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -23,6 +24,33 @@ export const meta = { id: '50f614d9-3047-4f7e-90d8-ad6b2d5fb098', }, }, + + res: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'misskey:id' + }, + userId: { + type: 'string', + format: 'misskey:id', + }, + name: { type: 'string' }, + on: { + type: 'array', + items: { + type: 'string', + enum: webhookEventTypes, + } + }, + url: { type: 'string' }, + secret: { type: 'string' }, + active: { type: 'boolean' }, + latestSentAt: { type: 'string', format: 'date-time', nullable: true }, + latestStatus: { type: 'integer', nullable: true }, + }, + }, } as const; export const paramDef = { @@ -49,7 +77,17 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchWebhook); } - return webhook; + return { + id: webhook.id, + userId: webhook.userId, + name: webhook.name, + on: webhook.on, + url: webhook.url, + secret: webhook.secret, + active: webhook.active, + latestSentAt: webhook.latestSentAt?.toISOString(), + latestStatus: webhook.latestStatus, + }; }); } } diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts index caaa3735e..d304d075b 100644 --- a/packages/backend/src/server/api/endpoints/roles/users.ts +++ b/packages/backend/src/server/api/endpoints/roles/users.ts @@ -24,6 +24,25 @@ export const meta = { id: '30aaaee3-4792-48dc-ab0d-cf501a575ac5', }, }, + + res: { + type: 'array', + items: { + type: 'object', + nullable: false, + properties: { + id: { + type: 'string', + format: 'misskey:id' + }, + user: { + type: 'object', + ref: 'User' + }, + }, + required: ['id', 'user'], + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index c8cb63e6b..079f2d7f1 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -15,6 +15,53 @@ export const meta = { cacheSec: 60 * 1, tags: ['meta'], + res: { + type: 'object', + optional: false, nullable: false, + properties: { + machine: { + type: 'string', + nullable: false, + }, + cpu: { + type: 'object', + nullable: false, + properties: { + model: { + type: 'string', + nullable: false, + }, + cores: { + type: 'number', + nullable: false, + }, + }, + }, + mem: { + type: 'object', + properties: { + total: { + type: 'number', + nullable: false, + }, + }, + }, + fs: { + type: 'object', + nullable: false, + properties: { + total: { + type: 'number', + nullable: false, + }, + used: { + type: 'number', + nullable: false, + }, + }, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 6d6d44f75..949867c57 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -12,6 +12,30 @@ export const meta = { description: 'Endpoint for testing input validation.', requireCredential: false, + + res: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'misskey:id' + }, + required: { + type: 'boolean', + }, + string: { + type: 'string', + }, + default: { + type: 'string', + }, + nullableDefault: { + type: 'string', + default: 'hello', + nullable: true, + }, + } + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/achievements.ts b/packages/backend/src/server/api/endpoints/users/achievements.ts index e4845d57b..d6ad718df 100644 --- a/packages/backend/src/server/api/endpoints/users/achievements.ts +++ b/packages/backend/src/server/api/endpoints/users/achievements.ts @@ -10,6 +10,21 @@ import { DI } from '@/di-symbols.js'; export const meta = { requireCredential: true, + + res: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + unlockedAt: { + type: 'number', + }, + }, + }, + } } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts index ae8b4e9b8..985141515 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts @@ -25,6 +25,35 @@ export const meta = { id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686', }, }, + + res: { + type: 'array', + items: { + type: 'object', + nullable: false, + properties: { + id: { + type: 'string', + format: 'misskey:id', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + userId: { + type: 'string', + format: 'misskey:id', + }, + user: { + type: 'object', + ref: 'User', + }, + withReplies: { + type: 'boolean', + }, + }, + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index e085407de..361a4931e 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -14,11 +14,26 @@ * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した * receiveFollowRequest - フォローリクエストされた * followRequestAccepted - 自分の送ったフォローリクエストが承認された + * roleAssigned - ロールが付与された * achievementEarned - 実績を獲得 * app - アプリ通知 * test - テスト通知(サーバー側) */ -export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app', 'test'] as const; +export const notificationTypes = [ + 'note', + 'follow', + 'mention', + 'reply', + 'renote', + 'quote', + 'reaction', + 'pollEnded', + 'receiveFollowRequest', + 'followRequestAccepted', + 'roleAssigned', + 'achievementEarned', + 'app', + 'test'] as const; export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const; export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const; diff --git a/packages/backend/test/docker-compose.yml b/packages/backend/test/docker-compose.yml index da6c01dda..f2d899075 100644 --- a/packages/backend/test/docker-compose.yml +++ b/packages/backend/test/docker-compose.yml @@ -7,7 +7,7 @@ services: - "127.0.0.1:56312:6379" dbtest: - image: postgres:13 + image: postgres:15 ports: - "127.0.0.1:54312:5432" environment: diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts index 73c446444..cb9558b41 100644 --- a/packages/backend/test/e2e/timelines.ts +++ b/packages/backend/test/e2e/timelines.ts @@ -10,9 +10,8 @@ process.env.NODE_ENV = 'test'; process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true'; import * as assert from 'assert'; -import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl, randomString } from '../utils.js'; +import { api, post, randomString, signup, sleep, startServer, uploadUrl } from '../utils.js'; import type { INestApplicationContext } from '@nestjs/common'; -import type * as misskey from 'misskey-js'; function genHost() { return randomString() + '.example.com'; @@ -366,8 +365,8 @@ describe('Timelines', () => { await api('/following/create', { userId: bob.id }, alice); await sleep(1000); const [bobFile, carolFile] = await Promise.all([ - uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'), - uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'), + uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), + uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'), ]); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { fileIds: [bobFile.id] }); @@ -666,7 +665,7 @@ describe('Timelines', () => { test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'); + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { fileIds: [file.id] }); @@ -804,7 +803,7 @@ describe('Timelines', () => { test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'); + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { fileIds: [file.id] }); @@ -999,7 +998,7 @@ describe('Timelines', () => { const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body); await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice); - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'); + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { fileIds: [file.id] }); @@ -1158,7 +1157,7 @@ describe('Timelines', () => { test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => { const [alice, bob] = await Promise.all([signup(), signup()]); - const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'); + const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/public/icon.png'); const bobNote1 = await post(bob, { text: 'hi' }); const bobNote2 = await post(bob, { fileIds: [file.id] }); diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index f644312bc..9879eb8e3 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -19,6 +19,7 @@ import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; +import { NotificationService } from '@/core/NotificationService.js'; import { sleep } from '../utils.js'; import type { TestingModule } from '@nestjs/testing'; import type { MockFunctionMetadata } from 'jest-mock'; @@ -32,6 +33,7 @@ describe('RoleService', () => { let rolesRepository: RolesRepository; let roleAssignmentsRepository: RoleAssignmentsRepository; let metaService: jest.Mocked; + let notificationService: jest.Mocked; let clock: lolex.InstalledClock; function createUser(data: Partial = {}) { @@ -71,6 +73,16 @@ describe('RoleService', () => { CacheService, IdService, GlobalEventService, + { + provide: NotificationService, + useFactory: () => ({ + createNotification: jest.fn(), + }), + }, + { + provide: NotificationService.name, + useExisting: NotificationService, + }, ], }) .useMocker((token) => { @@ -93,6 +105,9 @@ describe('RoleService', () => { roleAssignmentsRepository = app.get(DI.roleAssignmentsRepository); metaService = app.get(MetaService) as jest.Mocked; + notificationService = app.get(NotificationService) as jest.Mocked; + + await roleService.onModuleInit(); }); afterEach(async () => { @@ -273,4 +288,57 @@ describe('RoleService', () => { expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true); }); }); + + describe('assign', () => { + test('公開ロールの場合は通知される', async () => { + const user = await createUser(); + const role = await createRole({ + isPublic: true, + name: 'a', + }); + + await roleService.assign(user.id, role.id); + + clock.uninstall(); + await sleep(100); + + const assignments = await roleAssignmentsRepository.find({ + where: { + userId: user.id, + roleId: role.id, + }, + }); + expect(assignments).toHaveLength(1); + + expect(notificationService.createNotification).toHaveBeenCalled(); + expect(notificationService.createNotification.mock.lastCall![0]).toBe(user.id); + expect(notificationService.createNotification.mock.lastCall![1]).toBe('roleAssigned'); + expect(notificationService.createNotification.mock.lastCall![2]).toEqual({ + roleId: role.id, + }); + }); + + test('非公開ロールの場合は通知されない', async () => { + const user = await createUser(); + const role = await createRole({ + isPublic: false, + name: 'a', + }); + + await roleService.assign(user.id, role.id); + + clock.uninstall(); + await sleep(100); + + const assignments = await roleAssignmentsRepository.find({ + where: { + userId: user.id, + roleId: role.id, + }, + }); + expect(assignments).toHaveLength(1); + + expect(notificationService.createNotification).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index b0825ef11..ef69eff76 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -3,16 +3,16 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent, App } from 'vue'; +import { computed, watch, version as vueVersion, App } from 'vue'; import { compareVersions } from 'compare-versions'; import widgets from '@/widgets/index.js'; import directives from '@/directives/index.js'; import components from '@/components/index.js'; -import { version, ui, lang, updateLocale, locale } from '@/config.js'; +import { version, lang, updateLocale, locale } from '@/config.js'; import { applyTheme } from '@/scripts/theme.js'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js'; -import { i18n, updateI18n } from '@/i18n.js'; -import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js'; +import { updateI18n } from '@/i18n.js'; +import { $i, refreshAccount, login } from '@/account.js'; import { defaultStore, ColdDeviceStorage } from '@/store.js'; import { fetchInstance, instance } from '@/instance.js'; import { deviceKind } from '@/scripts/device-kind.js'; diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index e3fd6d5fc..0159d0c03 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -3,14 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; +import { createApp, markRaw, defineAsyncComponent } from 'vue'; import { common } from './common.js'; -import { version, ui, lang, updateLocale } from '@/config.js'; -import { i18n, updateI18n } from '@/i18n.js'; +import { ui } from '@/config.js'; +import { i18n } from '@/i18n.js'; import { confirm, alert, post, popup, toast } from '@/os.js'; import { useStream } from '@/stream.js'; import * as sound from '@/scripts/sound.js'; -import { $i, refreshAccount, login, updateAccount, signout } from '@/account.js'; +import { $i, updateAccount, signout } from '@/account.js'; import { defaultStore, ColdDeviceStorage } from '@/store.js'; import { makeHotkey } from '@/scripts/hotkey.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; @@ -20,7 +20,6 @@ import { mainRouter } from '@/router.js'; import { initializeSw } from '@/scripts/initialize-sw.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; -import { SnowfallEffect } from '@/scripts/snowfall-effect.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( @@ -79,6 +78,7 @@ export async function mainBoot() { if (defaultStore.state.enableSeasonalScreenEffect) { const month = new Date().getMonth() + 1; if (month === 12 || month === 1) { + const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect; new SnowfallEffect().render(); } } diff --git a/packages/frontend/src/boot/sub-boot.ts b/packages/frontend/src/boot/sub-boot.ts index 9b4670e13..92ee074af 100644 --- a/packages/frontend/src/boot/sub-boot.ts +++ b/packages/frontend/src/boot/sub-boot.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; +import { createApp, defineAsyncComponent } from 'vue'; import { common } from './common.js'; export async function subBoot() { diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue index 4512f2dd6..bf6504d6b 100644 --- a/packages/frontend/src/components/MkChannelPreview.vue +++ b/packages/frontend/src/components/MkChannelPreview.vue @@ -4,49 +4,70 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue index e3db639ff..bfddac752 100644 --- a/packages/frontend/src/components/form/suspense.vue +++ b/packages/frontend/src/components/form/suspense.vue @@ -21,7 +21,6 @@ SPDX-License-Identifier: AGPL-3.0-only