From d0a98f6e6c7596e9649671d7cc88ac14252aa594 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 2 Apr 2025 13:04:03 +0900 Subject: [PATCH 01/41] fix(frontend): Fix #15744 --- packages/frontend/src/components/MkTutorialDialog.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue index 3e91baada4..92f71b01af 100644 --- a/packages/frontend/src/components/MkTutorialDialog.vue +++ b/packages/frontend/src/components/MkTutorialDialog.vue @@ -249,6 +249,7 @@ async function close(skip: boolean) { .pageFooter { position: sticky; + z-index: 1; bottom: 0; left: 0; flex-shrink: 0; From 0bf49818c4477b3bed53017a77885153395662b0 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Wed, 2 Apr 2025 15:44:04 +0900 Subject: [PATCH 02/41] enhance: implement `sentryForFrontend` (#15433) Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- .config/cypress-devcontainer.yml | 5 ++ .config/docker_example.yml | 5 ++ .config/example.yml | 5 ++ .devcontainer/devcontainer.yml | 5 ++ CHANGELOG.md | 3 + chart/files/default.yml | 5 ++ packages/backend/package.json | 1 + packages/backend/src/config.ts | 17 ++++- .../src/core/entities/MetaEntityService.ts | 1 + .../backend/src/models/json-schema/meta.ts | 32 ++++++++ packages/frontend/package.json | 1 + packages/frontend/src/boot/common.ts | 37 +++++++++- packages/misskey-js/src/autogen/types.ts | 15 ++++ pnpm-lock.yaml | 74 +++++++++++++++++++ 14 files changed, 202 insertions(+), 4 deletions(-) diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index e75e32a17a..a028e2685e 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -165,6 +165,11 @@ id: 'aidx' # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' #sentryForFrontend: +# vueIntegration: +# tracingOptions: +# trackComponents: true +# browserTracingIntegration: +# replayIntegration: # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 1ffed00cc7..4be1352bd7 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -177,6 +177,11 @@ id: 'aidx' # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' #sentryForFrontend: +# vueIntegration: +# tracingOptions: +# trackComponents: true +# browserTracingIntegration: +# replayIntegration: # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' diff --git a/.config/example.yml b/.config/example.yml index 71427c84bc..d4584215c9 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -259,6 +259,11 @@ id: 'aidx' # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' #sentryForFrontend: +# vueIntegration: +# tracingOptions: +# trackComponents: true +# browserTracingIntegration: +# replayIntegration: # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 3eb4fc2879..6d904e87b9 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -152,6 +152,11 @@ id: 'aidx' # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' #sentryForFrontend: +# vueIntegration: +# tracingOptions: +# trackComponents: true +# browserTracingIntegration: +# replayIntegration: # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a2da274e4..2cd0fbd8e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ - メッセージにはリアクションも可能です - Enhance: セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。 - Misskeyネイティブでダッシュボードを実装予定です +- Enhance: フロントエンドのエラートラッキングができるように + - `.config/default.yml`中の項目`sentryForFrontend`を適宜設定してください。 + - 外部サービスであるSentryへエラー情報が送信されます。ご利用の地域の法令に従い、適切なプライバシーポリシーを策定の上で運用してください。 - Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように - Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正 diff --git a/chart/files/default.yml b/chart/files/default.yml index 4d17131c25..06f762aafa 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -173,6 +173,11 @@ id: "aidx" # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' #sentryForFrontend: +# vueIntegration: +# tracingOptions: +# trackComponents: true +# browserTracingIntegration: +# replayIntegration: # options: # dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' diff --git a/packages/backend/package.json b/packages/backend/package.json index bcaa6357ce..d7705b2b9e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -186,6 +186,7 @@ "devDependencies": { "@jest/globals": "29.7.0", "@nestjs/platform-express": "10.4.15", + "@sentry/vue": "9.8.0", "@simplewebauthn/types": "12.0.0", "@swc/jest": "0.2.37", "@types/accepts": "1.3.7", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 32ea700748..646fa07911 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -7,7 +7,8 @@ import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; import * as yaml from 'js-yaml'; -import * as Sentry from '@sentry/node'; +import type * as Sentry from '@sentry/node'; +import type * as SentryVue from '@sentry/vue'; import type { RedisOptions } from 'ioredis'; type RedisOptionsSource = Partial & { @@ -62,7 +63,12 @@ type Source = { scope?: 'local' | 'global' | string[]; }; sentryForBackend?: { options: Partial; enableNodeProfiling: boolean; }; - sentryForFrontend?: { options: Partial }; + sentryForFrontend?: { + options: Partial & { dsn: string }; + vueIntegration?: SentryVue.VueIntegrationOptions | null; + browserTracingIntegration?: Parameters[0] | null; + replayIntegration?: Parameters[0] | null; + }; publishTarballInsteadOfProvideRepositoryUrl?: boolean; @@ -198,7 +204,12 @@ export type Config = { redisForTimelines: RedisOptions & RedisOptionsSource; redisForReactions: RedisOptions & RedisOptionsSource; sentryForBackend: { options: Partial; enableNodeProfiling: boolean; } | undefined; - sentryForFrontend: { options: Partial } | undefined; + sentryForFrontend: { + options: Partial & { dsn: string }; + vueIntegration?: SentryVue.VueIntegrationOptions | null; + browserTracingIntegration?: Parameters[0] | null; + replayIntegration?: Parameters[0] | null; + } | undefined; perChannelMaxNoteCacheCount: number; perUserNotificationsMaxCount: number; deactivateAntennaThreshold: number; diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 08717bd066..02783dc450 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -127,6 +127,7 @@ export class MetaEntityService { policies: { ...DEFAULT_POLICIES, ...instance.policies }, + sentryForFrontend: this.config.sentryForFrontend ?? null, mediaProxy: this.config.mediaProxy, enableUrlPreview: instance.urlPreviewEnabled, noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local', diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 1e25c355ca..2cd7620af0 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -211,6 +211,38 @@ export const packedMetaLiteSchema = { type: 'boolean', optional: false, nullable: false, }, + sentryForFrontend: { + type: 'object', + optional: false, nullable: true, + properties: { + options: { + type: 'object', + optional: false, nullable: false, + properties: { + dsn: { + type: 'string', + optional: false, nullable: false, + }, + }, + additionalProperties: true, + }, + vueIntegration: { + type: 'object', + optional: true, nullable: true, + additionalProperties: true, + }, + browserTracingIntegration: { + type: 'object', + optional: true, nullable: true, + additionalProperties: true, + }, + replayIntegration: { + type: 'object', + optional: true, nullable: true, + additionalProperties: true, + }, + }, + }, mediaProxy: { type: 'string', optional: false, nullable: false, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index fe0abb173b..01dcf09d47 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -25,6 +25,7 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "6.0.2", "@rollup/pluginutils": "5.1.4", + "@sentry/vue": "9.8.0", "@syuilo/aiscript": "0.19.0", "@tabler/icons-webfont": "3.31.0", "@twemoji/parser": "15.1.1", diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 7a88b938dd..c8098b6cf8 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -5,7 +5,7 @@ import { computed, watch, version as vueVersion } from 'vue'; import { compareVersions } from 'compare-versions'; -import { version, lang, updateLocale, locale } from '@@/js/config.js'; +import { version, lang, updateLocale, locale, apiUrl } from '@@/js/config.js'; import defaultLightTheme from '@@/themes/l-light.json5'; import defaultDarkTheme from '@@/themes/d-green-lime.json5'; import type { App } from 'vue'; @@ -291,6 +291,41 @@ export async function common(createVue: () => Promise>) { return root; })(); + if (instance.sentryForFrontend) { + const Sentry = await import('@sentry/vue'); + Sentry.init({ + app, + integrations: [ + ...(instance.sentryForFrontend.vueIntegration !== undefined ? [ + Sentry.vueIntegration(instance.sentryForFrontend.vueIntegration ?? undefined), + ] : []), + ...(instance.sentryForFrontend.browserTracingIntegration !== undefined ? [ + Sentry.browserTracingIntegration(instance.sentryForFrontend.browserTracingIntegration ?? undefined), + ] : []), + ...(instance.sentryForFrontend.replayIntegration !== undefined ? [ + Sentry.replayIntegration(instance.sentryForFrontend.replayIntegration ?? undefined), + ] : []), + ], + + // Set tracesSampleRate to 1.0 to capture 100% + tracesSampleRate: 1.0, + + // Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled + ...(instance.sentryForFrontend.browserTracingIntegration !== undefined ? { + tracePropagationTargets: [apiUrl], + } : {}), + + // Capture Replay for 10% of all sessions, + // plus for 100% of sessions with an error + ...(instance.sentryForFrontend.replayIntegration !== undefined ? { + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + } : {}), + + ...instance.sentryForFrontend.options, + }); + } + app.mount(rootEl); // boot.jsのやつを解除 diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index d46df2423a..ef4be07072 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -5309,6 +5309,21 @@ export type components = { enableEmail: boolean; enableServiceWorker: boolean; translatorAvailable: boolean; + sentryForFrontend: ({ + options: { + dsn: string; + [key: string]: unknown; + }; + vueIntegration?: { + [key: string]: unknown; + } | null; + browserTracingIntegration?: { + [key: string]: unknown; + } | null; + replayIntegration?: { + [key: string]: unknown; + } | null; + }) | null; mediaProxy: string; enableUrlPreview: boolean; backgroundImageUrl: string | null; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 146021fbf1..46416c0332 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -444,6 +444,9 @@ importers: '@nestjs/platform-express': specifier: 10.4.15 version: 10.4.15(@nestjs/common@11.0.12(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.12) + '@sentry/vue': + specifier: 9.8.0 + version: 9.8.0(vue@3.5.13(typescript@5.8.2)) '@simplewebauthn/types': specifier: 12.0.0 version: 12.0.0 @@ -709,6 +712,9 @@ importers: '@rollup/pluginutils': specifier: 5.1.4 version: 5.1.4(rollup@4.36.0) + '@sentry/vue': + specifier: 9.8.0 + version: 9.8.0(vue@3.5.13(typescript@5.8.2)) '@syuilo/aiscript': specifier: 0.19.0 version: 0.19.0 @@ -3555,10 +3561,34 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@sentry-internal/browser-utils@9.8.0': + resolution: {integrity: sha512-7aQDeU9ogMLKnEBFM/vvgMMgZDkfMhoZCtX8kq65gn33L4X2B8sI5oyUj2QJtXaRSsiUjbdCaquDLqZBCaLQHA==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@9.8.0': + resolution: {integrity: sha512-xWiCJkD8ROuy2pnojuRLcLI6sezK399gasA5ZL4MCXdkryqZYs55Ef2Ofj4z0RdUc8gMUb81+LTqwbmbfTqNlQ==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@9.8.0': + resolution: {integrity: sha512-/6ELOnyCOItvqv2Os29JhE8ydDds3xibMQ+FomsSkClQdC4bbc/L74nm/fdXVpJkMswtjksiTwZo1nYTS3JsIw==} + engines: {node: '>=18'} + + '@sentry-internal/replay@9.8.0': + resolution: {integrity: sha512-YJhhNnrsufYVIX9s5lNSFFQrBJjUtn5AxvrcnN0fvLymNg3Y73GOUpFmhTxyELjQneKiOViClxjoWSVAN7sqQA==} + engines: {node: '>=18'} + + '@sentry/browser@9.8.0': + resolution: {integrity: sha512-iFM4PGLc6qCb0GaHnA5Uy09k25RXVSepAgS574cm1CH7II1wrRjTozKnPKROW89WDMuxoTOL7Tk7qPGCyWmA4g==} + engines: {node: '>=18'} + '@sentry/core@8.55.0': resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==} engines: {node: '>=14.18'} + '@sentry/core@9.8.0': + resolution: {integrity: sha512-EnN2yLWCbWjooWBPzwlXdZoJG/Bqn3ymbuXX++DUJuBGjSmtixQeTf/hKeVzj4zbib3BbbYsNBasRVjq8Rk5ng==} + engines: {node: '>=18'} + '@sentry/node@8.55.0': resolution: {integrity: sha512-h10LJLDTRAzYgay60Oy7moMookqqSZSviCWkkmHZyaDn+4WURnPp5SKhhfrzPRQcXKrweiOwDSHBgn1tweDssg==} engines: {node: '>=14.18'} @@ -3579,6 +3609,16 @@ packages: engines: {node: '>=14.18'} hasBin: true + '@sentry/vue@9.8.0': + resolution: {integrity: sha512-E+27lL+aU8HjDo3DD3TlgStTIxBZHVqz6jZcL0/tig/JldpFRetO77terRHNfSVlPc0m3aNXuARu7G438f7ZlQ==} + engines: {node: '>=18'} + peerDependencies: + pinia: 2.x || 3.x + vue: 2.x || 3.x + peerDependenciesMeta: + pinia: + optional: true + '@shikijs/core@3.2.1': resolution: {integrity: sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==} @@ -13629,8 +13669,36 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} + '@sentry-internal/browser-utils@9.8.0': + dependencies: + '@sentry/core': 9.8.0 + + '@sentry-internal/feedback@9.8.0': + dependencies: + '@sentry/core': 9.8.0 + + '@sentry-internal/replay-canvas@9.8.0': + dependencies: + '@sentry-internal/replay': 9.8.0 + '@sentry/core': 9.8.0 + + '@sentry-internal/replay@9.8.0': + dependencies: + '@sentry-internal/browser-utils': 9.8.0 + '@sentry/core': 9.8.0 + + '@sentry/browser@9.8.0': + dependencies: + '@sentry-internal/browser-utils': 9.8.0 + '@sentry-internal/feedback': 9.8.0 + '@sentry-internal/replay': 9.8.0 + '@sentry-internal/replay-canvas': 9.8.0 + '@sentry/core': 9.8.0 + '@sentry/core@8.55.0': {} + '@sentry/core@9.8.0': {} + '@sentry/node@8.55.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -13690,6 +13758,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@sentry/vue@9.8.0(vue@3.5.13(typescript@5.8.2))': + dependencies: + '@sentry/browser': 9.8.0 + '@sentry/core': 9.8.0 + vue: 3.5.13(typescript@5.8.2) + '@shikijs/core@3.2.1': dependencies: '@shikijs/types': 3.2.1 From 98eadd709364a11f039a8d45a2498ec6c8226c46 Mon Sep 17 00:00:00 2001 From: "Nanashi." Date: Wed, 2 Apr 2025 16:01:24 +0900 Subject: [PATCH 03/41] =?UTF-8?q?feat:=20=E3=82=A2=E3=83=B3=E3=83=86?= =?UTF-8?q?=E3=83=8A=E3=81=A7=E3=82=BB=E3=83=B3=E3=82=B7=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=96=E3=81=AA=E3=83=81=E3=83=A3=E3=83=B3=E3=83=8D=E3=83=AB?= =?UTF-8?q?=E3=81=8B=E3=82=89=E3=81=AE=E3=83=8E=E3=83=BC=E3=83=88=E3=82=92?= =?UTF-8?q?=E9=99=A4=E5=A4=96=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(#15346)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(db): マイグレーションを追加 * feat(backend): カラムの定義を追加 * wip * feat: フラグを設定出来るように * feat: /notesエンドポイントを対応 * feat: websocketを対応 * test: テストを追加 * docs: CHANGELOGを更新 * docs: CHANGELOGの追加場所を修正 * chore: api.jsonを更新 * docs(CHANGELOG): General欄に移動 * docs: フォーマットを揃える * chore: クエリを削除 * revert: 英訳を消す * chore: note.channelを追加するところを変える * docs: CHANGELOGを更新する * docs(CHANGELOG): 2025.3.2に移動 * chore: changelogを下に移動 * ci: CI再実行用の空コミット --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + locales/index.d.ts | 4 ++ locales/ja-JP.yml | 1 + ...3-addAntennaHideNotesInSensitiveChannel.js | 16 ++++++++ packages/backend/src/core/AntennaService.ts | 2 + .../backend/src/core/NoteCreateService.ts | 5 ++- .../src/core/entities/AntennaEntityService.ts | 1 + packages/backend/src/models/Antenna.ts | 5 +++ .../backend/src/models/json-schema/antenna.ts | 5 +++ .../server/api/endpoints/antennas/create.ts | 2 + .../server/api/endpoints/antennas/notes.ts | 3 ++ .../server/api/endpoints/antennas/update.ts | 2 + packages/backend/test/e2e/antennas.ts | 39 +++++++++++++++++++ .../src/components/MkAntennaEditor.vue | 4 ++ packages/misskey-js/src/autogen/types.ts | 4 ++ 15 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1736230492103-addAntennaHideNotesInSensitiveChannel.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cd0fbd8e9..32df3e3d91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - 外部サービスであるSentryへエラー情報が送信されます。ご利用の地域の法令に従い、適切なプライバシーポリシーを策定の上で運用してください。 - Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように - Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正 +- Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように `#14177` ### Client - Feat: 設定の管理が強化されました diff --git a/locales/index.d.ts b/locales/index.d.ts index 177c9685a1..4741d37cc6 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1714,6 +1714,10 @@ export interface Locale extends ILocale { * ファイルが添付されたノートのみ */ "withFileAntenna": string; + /** + * センシティブなチャンネルのノートを非表示 + */ + "hideNotesInSensitiveChannel": string; /** * ブラウザへのプッシュ通知を有効にする */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 483ece655d..8b19ed9e48 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -424,6 +424,7 @@ antennaExcludeBots: "Botアカウントを除外" antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" notifyAntenna: "新しいノートを通知する" withFileAntenna: "ファイルが添付されたノートのみ" +hideNotesInSensitiveChannel: "センシティブなチャンネルのノートを非表示" enableServiceworker: "ブラウザへのプッシュ通知を有効にする" antennaUsersDescription: "ユーザー名を改行で区切って指定します" caseSensitive: "大文字小文字を区別する" diff --git a/packages/backend/migration/1736230492103-addAntennaHideNotesInSensitiveChannel.js b/packages/backend/migration/1736230492103-addAntennaHideNotesInSensitiveChannel.js new file mode 100644 index 0000000000..74225de96a --- /dev/null +++ b/packages/backend/migration/1736230492103-addAntennaHideNotesInSensitiveChannel.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddAntennaHideNotesInSensitiveChannel1736230492103 { + name = 'AddAntennaHideNotesInSensitiveChannel1736230492103' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" ADD "hideNotesInSensitiveChannel" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "hideNotesInSensitiveChannel"`); + } +} diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index e827ffa68c..5ad5bcf72a 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -114,6 +114,8 @@ export class AntennaService implements OnApplicationShutdown { if (note.visibility === 'specified') return false; if (note.visibility === 'followers') return false; + if (antenna.hideNotesInSensitiveChannel && note.channel?.isSensitive) return false; + if (antenna.excludeBots && noteUser.isBot) return false; if (antenna.localOnly && noteUser.host != null) return false; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 8f416f398c..1ddb2b173d 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -532,7 +532,10 @@ export class NoteCreateService implements OnApplicationShutdown { this.pushToTl(note, user); - this.antennaService.addNoteToAntennas(note, user); + this.antennaService.addNoteToAntennas({ + ...note, + channel: data.channel ?? null, + }, user); if (data.reply) { this.saveReply(data.reply, note); diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index e770028af3..e81c1e8db4 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -41,6 +41,7 @@ export class AntennaEntityService { excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, + hideNotesInSensitiveChannel: antenna.hideNotesInSensitiveChannel, isActive: antenna.isActive, hasUnreadNote: false, // TODO notify: false, // 後方互換性のため diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts index 33e6f48189..0d92c5cade 100644 --- a/packages/backend/src/models/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -100,4 +100,9 @@ export class MiAntenna { default: false, }) public localOnly: boolean; + + @Column('boolean', { + default: false, + }) + public hideNotesInSensitiveChannel: boolean; } diff --git a/packages/backend/src/models/json-schema/antenna.ts b/packages/backend/src/models/json-schema/antenna.ts index b5b9a5b42c..2bdaca80d0 100644 --- a/packages/backend/src/models/json-schema/antenna.ts +++ b/packages/backend/src/models/json-schema/antenna.ts @@ -100,5 +100,10 @@ export const packedAntennaSchema = { optional: false, nullable: false, default: false, }, + hideNotesInSensitiveChannel: { + type: 'boolean', + optional: false, nullable: false, + default: false, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index e0c8ddcc84..9b34b52b16 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -73,6 +73,7 @@ export const paramDef = { excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, + hideNotesInSensitiveChannel: { type: 'boolean' }, }, required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'], } as const; @@ -133,6 +134,7 @@ export default class extends Endpoint { // eslint- excludeBots: ps.excludeBots, withReplies: ps.withReplies, withFile: ps.withFile, + hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel, }); this.globalEventService.publishInternalEvent('antennaCreated', antenna); diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 4b8543c2d1..a44eb6720b 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -108,6 +108,9 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); + // NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。 + // https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255 + this.queryService.generateVisibilityQuery(query, me); this.queryService.generateMutedUserQueryForNotes(query, me); this.queryService.generateBlockedUserQueryForNotes(query, me); diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 10f26b1912..c5ddefebf7 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -72,6 +72,7 @@ export const paramDef = { excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, + hideNotesInSensitiveChannel: { type: 'boolean' }, }, required: ['antennaId'], } as const; @@ -129,6 +130,7 @@ export default class extends Endpoint { // eslint- excludeBots: ps.excludeBots, withReplies: ps.withReplies, withFile: ps.withFile, + hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel, isActive: true, lastUsedAt: new Date(), }); diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index a544db955a..eb9583ee01 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -146,6 +146,7 @@ describe('アンテナ', () => { caseSensitive: false, createdAt: new Date(response.createdAt).toISOString(), excludeKeywords: [['']], + hideNotesInSensitiveChannel: false, hasUnreadNote: false, isActive: true, keywords: [['keyword']], @@ -217,6 +218,8 @@ describe('アンテナ', () => { { parameters: () => ({ withReplies: true }) }, { parameters: () => ({ withFile: false }) }, { parameters: () => ({ withFile: true }) }, + { parameters: () => ({ hideNotesInSensitiveChannel: false }) }, + { parameters: () => ({ hideNotesInSensitiveChannel: true }) }, ]; test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => { const response = await successfulApiCall({ @@ -626,6 +629,42 @@ describe('アンテナ', () => { assert.deepStrictEqual(response, expected); }); + test('が取得できること(センシティブチャンネルのノートを除く)', async () => { + const keyword = 'キーワード'; + const antenna = await successfulApiCall({ + endpoint: 'antennas/create', + parameters: { ...defaultParam, keywords: [[keyword]], hideNotesInSensitiveChannel: true }, + user: alice, + }); + const nonSensitiveChannel = await successfulApiCall({ + endpoint: 'channels/create', + parameters: { name: 'test', isSensitive: false }, + user: alice, + }); + const sensitiveChannel = await successfulApiCall({ + endpoint: 'channels/create', + parameters: { name: 'test', isSensitive: true }, + user: alice, + }); + + const noteInLocal = await post(bob, { text: `test ${keyword}` }); + const noteInNonSensitiveChannel = await post(bob, { text: `test ${keyword}`, channelId: nonSensitiveChannel.id }); + await post(bob, { text: `test ${keyword}`, channelId: sensitiveChannel.id }); + + const response = await successfulApiCall({ + endpoint: 'antennas/notes', + parameters: { antennaId: antenna.id }, + user: alice, + }); + // 最後に投稿したものが先頭に来る。 + const expected = [ + noteInNonSensitiveChannel, + noteInLocal, + ]; + assert.deepStrictEqual(response, expected); + }); + + test.skip('が取得でき、日付指定のPaginationに一貫性があること', async () => { }); test.each([ { label: 'ID指定', offsetBy: 'id' }, diff --git a/packages/frontend/src/components/MkAntennaEditor.vue b/packages/frontend/src/components/MkAntennaEditor.vue index ac71618ee2..e1c8200b73 100644 --- a/packages/frontend/src/components/MkAntennaEditor.vue +++ b/packages/frontend/src/components/MkAntennaEditor.vue @@ -39,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.localOnly }} {{ i18n.ts.caseSensitive }} {{ i18n.ts.withFileAntenna }} + {{ i18n.ts.hideNotesInSensitiveChannel }}
@@ -86,6 +87,7 @@ const initialAntenna = deepMerge(props.antenna ?? {}, { caseSensitive: false, localOnly: false, withFile: false, + hideNotesInSensitiveChannel: false, isActive: true, hasUnreadNote: false, notify: false, @@ -108,6 +110,7 @@ const localOnly = ref(initialAntenna.localOnly); const excludeBots = ref(initialAntenna.excludeBots); const withReplies = ref(initialAntenna.withReplies); const withFile = ref(initialAntenna.withFile); +const hideNotesInSensitiveChannel = ref(initialAntenna.hideNotesInSensitiveChannel); const userLists = ref(null); watch(() => src.value, async () => { @@ -124,6 +127,7 @@ async function saveAntenna() { excludeBots: excludeBots.value, withReplies: withReplies.value, withFile: withFile.value, + hideNotesInSensitiveChannel: hideNotesInSensitiveChannel.value, caseSensitive: caseSensitive.value, localOnly: localOnly.value, users: users.value.trim().split('\n').map(x => x.trim()), diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index ef4be07072..037b09660c 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -4898,6 +4898,8 @@ export type components = { hasUnreadNote: boolean; /** @default false */ notify: boolean; + /** @default false */ + hideNotesInSensitiveChannel: boolean; }; Clip: { /** @@ -11305,6 +11307,7 @@ export type operations = { excludeBots?: boolean; withReplies: boolean; withFile: boolean; + hideNotesInSensitiveChannel?: boolean; }; }; }; @@ -11586,6 +11589,7 @@ export type operations = { excludeBots?: boolean; withReplies?: boolean; withFile?: boolean; + hideNotesInSensitiveChannel?: boolean; }; }; }; From 3a16af4b4b6881f880bf5d04ebd40404f4e87c62 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:01:48 +0900 Subject: [PATCH 04/41] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32df3e3d91..9f54948619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,8 @@ - `.config/default.yml`中の項目`sentryForFrontend`を適宜設定してください。 - 外部サービスであるSentryへエラー情報が送信されます。ご利用の地域の法令に従い、適切なプライバシーポリシーを策定の上で運用してください。 - Enhance: ミュートしているユーザーをユーザー検索の結果から除外するように -- Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正 - Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように `#14177` +- Fix: 通知のページネーションで2つ以上読み込めなくなることがある問題を修正 ### Client - Feat: 設定の管理が強化されました From e1dc1e51110516a86a13fb4006660c19fb130399 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 2 Apr 2025 19:16:16 +0900 Subject: [PATCH 05/41] =?UTF-8?q?=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend-shared/themes/d-astro.json5 | 6 +++--- packages/frontend-shared/themes/d-botanical.json5 | 3 +++ packages/frontend-shared/themes/d-persimmon.json5 | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/frontend-shared/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5 index 48839241bd..5e721534d3 100644 --- a/packages/frontend-shared/themes/d-astro.json5 +++ b/packages/frontend-shared/themes/d-astro.json5 @@ -7,9 +7,9 @@ bg: '#232125', fg: '#efdab9', link: '#78b0a0', - warn: '#ecb637', + warn: '#ffd152', badge: '#31b1ce', - error: '#ec4137', + error: '#ff6652', focus: ':alpha<0.3<@accent', navBg: '@panel', navFg: '@fg', @@ -24,7 +24,7 @@ hashtag: '#ff9156', mention: '#ffd152', modalBg: 'rgba(0, 0, 0, 0.5)', - success: '#86b300', + success: '#78b07f', indicator: '@accent', mentionMe: '#fb5d38', messageBg: '@bg', diff --git a/packages/frontend-shared/themes/d-botanical.json5 b/packages/frontend-shared/themes/d-botanical.json5 index 62208d2378..507880aab1 100644 --- a/packages/frontend-shared/themes/d-botanical.json5 +++ b/packages/frontend-shared/themes/d-botanical.json5 @@ -22,5 +22,8 @@ mentionMe: 'rgb(212, 210, 76)', hashtag: '#5bcbb0', link: '@accent', + success: '@accent', + warn: 'rgb(255, 213, 82)', + error: 'rgb(255, 105, 82)', }, } diff --git a/packages/frontend-shared/themes/d-persimmon.json5 b/packages/frontend-shared/themes/d-persimmon.json5 index 0ab6523dd7..538e3b7e70 100644 --- a/packages/frontend-shared/themes/d-persimmon.json5 +++ b/packages/frontend-shared/themes/d-persimmon.json5 @@ -22,5 +22,8 @@ mentionMe: '#de6161', hashtag: '#68bad0', link: '#a1c758', + error: '#ce5441', + warn: '#d0b868', + success: '#a1c758', }, } From d07552424c388b608882b42abb222058d0d6795a Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 2 Apr 2025 19:48:32 +0900 Subject: [PATCH 06/41] =?UTF-8?q?=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/index.d.ts | 4 ++++ locales/ja-JP.yml | 1 + packages/frontend/src/pages/clip.vue | 2 +- packages/frontend/src/pages/tag.vue | 2 +- packages/frontend/src/utility/get-note-menu.ts | 4 ++-- packages/frontend/src/utility/get-user-menu.ts | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 4741d37cc6..9ef4886cb5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5378,6 +5378,10 @@ export interface Locale extends ILocale { * 上 */ "top": string; + /** + * 埋め込み + */ + "embed": string; "_chat": { /** * まだメッセージはありません diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8b19ed9e48..947b7b4fa5 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1340,6 +1340,7 @@ compress: "圧縮" right: "右" bottom: "下" top: "上" +embed: "埋め込み" _chat: noMessagesYet: "まだメッセージはありません" diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index 8fe65217fb..a2fb02462e 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -150,7 +150,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{ }, }, { icon: 'ti ti-code', - text: i18n.ts.genEmbedCode, + text: i18n.ts.embed, action: () => { genEmbedCode('clips', clip.value!.id); }, diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index 4afef6f4d7..f67777be4e 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -56,7 +56,7 @@ const headerActions = computed(() => [{ label: i18n.ts.more, handler: (ev: MouseEvent) => { os.popupMenu([{ - text: i18n.ts.genEmbedCode, + text: i18n.ts.embed, icon: 'ti ti-code', action: () => { genEmbedCode('tags', props.tag); diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts index 9e1ae7e9ac..dd8bdf43d7 100644 --- a/packages/frontend/src/utility/get-note-menu.ts +++ b/packages/frontend/src/utility/get-note-menu.ts @@ -331,7 +331,7 @@ export function getNoteMenu(props: { }, }); } else { - menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); + menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed)); } if (isSupportShare()) { @@ -489,7 +489,7 @@ export function getNoteMenu(props: { }, }); } else { - menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)); + menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.embed)); } } diff --git a/packages/frontend/src/utility/get-user-menu.ts b/packages/frontend/src/utility/get-user-menu.ts index 10429c8ff1..8cf9f31d3e 100644 --- a/packages/frontend/src/utility/get-user-menu.ts +++ b/packages/frontend/src/utility/get-user-menu.ts @@ -198,7 +198,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router } else { menuItems.push({ icon: 'ti ti-code', - text: i18n.ts.genEmbedCode, + text: i18n.ts.embed, type: 'parent', children: [{ text: i18n.ts.noteOfThisUser, From 1a3866c4f67fb5a745a7966220286680b810d23b Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 3 Apr 2025 11:15:16 +0900 Subject: [PATCH 07/41] =?UTF-8?q?fix(backend/chat):=20=E8=87=AA=E5=88=86?= =?UTF-8?q?=E3=81=8C=E4=BD=9C=E3=81=A3=E3=81=9F=E3=83=81=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=83=AB=E3=83=BC=E3=83=A0=E3=81=AB=E4=BB=96=E4=BA=BA?= =?UTF-8?q?=E3=81=8C=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92?= =?UTF-8?q?=E9=80=81=E3=81=A3=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AB=E6=9C=AA?= =?UTF-8?q?=E8=AA=AD=E3=81=AB=E3=81=AA=E3=82=89=E3=81=AA=E3=81=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #15748 --- packages/backend/src/core/ChatService.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index df1c384b54..6194f624b1 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -211,9 +211,15 @@ export class ChatService { file?: MiDriveFile | null; uri?: string | null; }): Promise> { - const memberships = await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id }); + const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id })).map(m => ({ + userId: m.userId, + isMuted: m.isMuted, + })).concat({ // ownerはmembershipレコードを作らないため + userId: toRoom.ownerId, + isMuted: false, + }); - if (toRoom.ownerId !== fromUser.id && !memberships.some(member => member.userId === fromUser.id)) { + if (!memberships.some(member => member.userId === fromUser.id)) { throw new Error('you are not a member of the room'); } From 455be80b4f7ff04a71d1773b2ec058adb3af3e69 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 3 Apr 2025 11:32:55 +0900 Subject: [PATCH 08/41] =?UTF-8?q?fix(backend):=20=E9=9D=9E=E3=83=AD?= =?UTF-8?q?=E3=82=B0=E3=82=A4=E3=83=B3=E3=81=A7=E3=82=BF=E3=82=A4=E3=83=A0?= =?UTF-8?q?=E3=83=A9=E3=82=A4=E3=83=B3=E3=81=AE=E3=82=B9=E3=83=88=E3=83=AA?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E3=81=AB=E6=8E=A5=E7=B6=9A=E3=81=97=E3=81=9F?= =?UTF-8?q?=E9=9A=9B=E3=80=81=E8=A1=A8=E7=A4=BA=E3=81=AB=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E5=BF=85=E9=A0=88=E3=81=AE=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=8C=E6=B5=81=E3=82=8C=E3=82=8B=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../backend/src/server/api/stream/channels/global-timeline.ts | 1 + .../backend/src/server/api/stream/channels/local-timeline.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f54948619..9da5e9bb2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ - Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正 - Fix: 連合無しモードでも外部から照会可能だった問題を修正 - Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正 +- Fix: 非ログインでタイムラインのストリームに接続した際、表示にログイン必須のノートが流れる場合がある問題を修正 ## 2025.3.1 diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index ed56fe0d40..4f64ce6866 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -50,6 +50,7 @@ class GlobalTimelineChannel extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null) return; + if (note.user.requireSigninToViewContents && this.user == null) return; if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return; diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 491029f5de..c3cf78a24a 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -53,6 +53,7 @@ class LocalTimelineChannel extends Channel { if (note.user.host !== null) return; if (note.visibility !== 'public') return; if (note.channelId != null) return; + if (note.user.requireSigninToViewContents && this.user == null) return; // 関係ない返信は除外 if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) { From d7365eb12659f0d681ec645aaedd5d962aed8b9b Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:54:08 +0900 Subject: [PATCH 09/41] fix(storybook): update obsolete method for preferences (#15750) --- packages/frontend/.storybook/preview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts index 269bc4fb9a..fb855c1410 100644 --- a/packages/frontend/.storybook/preview.ts +++ b/packages/frontend/.storybook/preview.ts @@ -83,7 +83,7 @@ queueMicrotask(() => { widgets(app); misskeyOS = os; if (isChromatic()) { - prefer.set('animation', false); + prefer.commit('animation', false); } }); }); From 1779a23e25e5ba3fad076272d6757ea4d1a3cf16 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 3 Apr 2025 13:31:13 +0900 Subject: [PATCH 10/41] =?UTF-8?q?=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend-shared/themes/l-botanical.json5 | 8 ++++---- packages/frontend-shared/themes/l-coffee.json5 | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/frontend-shared/themes/l-botanical.json5 b/packages/frontend-shared/themes/l-botanical.json5 index 17e9ca246f..4ad539e8a0 100644 --- a/packages/frontend-shared/themes/l-botanical.json5 +++ b/packages/frontend-shared/themes/l-botanical.json5 @@ -13,18 +13,18 @@ fgHighlighted: '#6bc9a0', fgOnWhite: '@accent', divider: '#cfcfcf', - panel: '@X14', + panel: '#ebe7e5', panelHeaderBg: '@panel', panelHeaderDivider: '@divider', header: ':alpha<0.7<@panel', - navBg: '@X14', + navBg: '#ebe7e5', renote: '#229e92', mention: '#da6d35', mentionMe: '#d44c4c', hashtag: '#4cb8d4', link: '@accent', buttonGradateB: ':hue<-70<@accent', - success: '#86b300', - X14: '#ebe7e5' + success: '@accent', + error: '#da5635', }, } diff --git a/packages/frontend-shared/themes/l-coffee.json5 b/packages/frontend-shared/themes/l-coffee.json5 index b64cc73583..df3a12a37b 100644 --- a/packages/frontend-shared/themes/l-coffee.json5 +++ b/packages/frontend-shared/themes/l-coffee.json5 @@ -18,5 +18,8 @@ mention: '@accent', mentionMe: 'rgb(170, 149, 98)', hashtag: '@accent', + error: '#db9184', + warn: '#dbc184', + success: '#a3c975', }, } From 7cecaa5c54797b6b089b2e58a8f7bd08540f3eac Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 3 Apr 2025 14:32:42 +0900 Subject: [PATCH 11/41] perf(frontend): avoid main thread scroll repaint --- packages/frontend/src/components/MkContainer.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index 39ca39aad7..29165d7b71 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -215,6 +215,14 @@ onUnmounted(() => { .content { --MI-stickyTop: 0px; + /* + 理屈は知らないけど、ここでbackgroundを設定しておかないと + スクロールコンテナーが少なくともChromeにおいて + main thread scrolling になってしまい、パフォーマンスが(多分)落ちる。 + backgroundが透明だと裏側を描画しないといけなくなるとかそういう理由かもしれない + */ + background: var(--MI_THEME-panel); + &.omitted { position: relative; max-height: var(--maxHeight); From e07bb1dcbce6eaa2bfe157a6c9f1361dbf9aa280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:28:10 +0900 Subject: [PATCH 12/41] =?UTF-8?q?fix:=20=E3=83=81=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=83=88=E5=91=A8=E3=82=8A=E3=81=AE=E4=BF=AE=E6=AD=A3=20(#1574?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(misskey-js): チャットのChannel型定義を追加 * fix(backend); canChatで塞いでいない書き込み系のAPIを塞ぐ * fix(frontend): チャット周りのフロントエンド型修正 * lint fix * fix broken lockfile * fix * refactor * wip * wip * wip * clean up --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- packages/backend/src/core/ChatService.ts | 4 +- .../src/core/entities/ChatEntityService.ts | 8 +- packages/backend/src/misc/json-schema.ts | 4 +- .../src/models/json-schema/chat-message.ts | 112 +++++++++++++++++- .../endpoints/chat/messages/create-to-room.ts | 2 +- .../endpoints/chat/messages/create-to-user.ts | 2 +- .../api/endpoints/chat/messages/delete.ts | 1 + .../api/endpoints/chat/messages/react.ts | 1 + .../endpoints/chat/messages/room-timeline.ts | 2 +- .../api/endpoints/chat/messages/unreact.ts | 1 + .../endpoints/chat/messages/user-timeline.ts | 2 +- packages/frontend/src/pages/chat/XMessage.vue | 62 +++++----- .../frontend/src/pages/chat/home.home.vue | 4 +- .../src/pages/chat/home.invitations.vue | 9 +- .../src/pages/chat/home.joiningRooms.vue | 15 +-- .../src/pages/chat/home.ownedRooms.vue | 10 +- packages/frontend/src/pages/chat/home.vue | 2 +- packages/frontend/src/pages/chat/message.vue | 10 +- .../frontend/src/pages/chat/room.form.vue | 21 +++- .../frontend/src/pages/chat/room.info.vue | 5 +- .../frontend/src/pages/chat/room.members.vue | 5 +- .../frontend/src/pages/chat/room.search.vue | 3 +- packages/frontend/src/pages/chat/room.vue | 97 +++++++++------ .../frontend/src/use/use-mutation-observer.ts | 4 +- packages/misskey-js/etc/misskey-js.api.md | 60 +++++++++- packages/misskey-js/src/autogen/models.ts | 2 + packages/misskey-js/src/autogen/types.ts | 42 +++++-- packages/misskey-js/src/streaming.types.ts | 51 +++++++- pnpm-lock.yaml | 65 +++++++--- 29 files changed, 453 insertions(+), 153 deletions(-) diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 6194f624b1..3984cefc80 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -99,7 +99,7 @@ export class ChatService { text?: string | null; file?: MiDriveFile | null; uri?: string | null; - }): Promise> { + }): Promise> { if (fromUser.id === toUser.id) { throw new Error('yourself'); } @@ -210,7 +210,7 @@ export class ChatService { text?: string | null; file?: MiDriveFile | null; uri?: string | null; - }): Promise> { + }): Promise> { const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id })).map(m => ({ userId: m.userId, isMuted: m.isMuted, diff --git a/packages/backend/src/core/entities/ChatEntityService.ts b/packages/backend/src/core/entities/ChatEntityService.ts index 099a9e3ad2..da112d5444 100644 --- a/packages/backend/src/core/entities/ChatEntityService.ts +++ b/packages/backend/src/core/entities/ChatEntityService.ts @@ -128,7 +128,7 @@ export class ChatEntityService { packedFiles: Map | null>; }; }, - ): Promise> { + ): Promise> { const packedFiles = options?._hint_?.packedFiles; const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src }); @@ -147,7 +147,7 @@ export class ChatEntityService { createdAt: this.idService.parse(message.id).date.toISOString(), text: message.text, fromUserId: message.fromUserId, - toUserId: message.toUserId, + toUserId: message.toUserId!, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, reactions, @@ -177,7 +177,7 @@ export class ChatEntityService { packedUsers: Map>; }; }, - ): Promise> { + ): Promise> { const packedFiles = options?._hint_?.packedFiles; const packedUsers = options?._hint_?.packedUsers; @@ -199,7 +199,7 @@ export class ChatEntityService { text: message.text, fromUserId: message.fromUserId, fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId), - toRoomId: message.toRoomId, + toRoomId: message.toRoomId!, fileId: message.fileId, file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null, reactions, diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index bc9308ca9b..27aa3d89de 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -63,7 +63,7 @@ import { } from '@/models/json-schema/meta.js'; import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js'; import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js'; -import { packedChatMessageSchema, packedChatMessageLiteSchema } from '@/models/json-schema/chat-message.js'; +import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessageLiteForRoomSchema, packedChatMessageLiteFor1on1Schema } from '@/models/json-schema/chat-message.js'; import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js'; import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js'; import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js'; @@ -126,6 +126,8 @@ export const refs = { AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema, ChatMessage: packedChatMessageSchema, ChatMessageLite: packedChatMessageLiteSchema, + ChatMessageLiteFor1on1: packedChatMessageLiteFor1on1Schema, + ChatMessageLiteForRoom: packedChatMessageLiteForRoomSchema, ChatRoom: packedChatRoomSchema, ChatRoomInvitation: packedChatRoomInvitationSchema, ChatRoomMembership: packedChatRoomMembershipSchema, diff --git a/packages/backend/src/models/json-schema/chat-message.ts b/packages/backend/src/models/json-schema/chat-message.ts index 44b7298702..3b5e85ab69 100644 --- a/packages/backend/src/models/json-schema/chat-message.ts +++ b/packages/backend/src/models/json-schema/chat-message.ts @@ -72,7 +72,7 @@ export const packedChatMessageSchema = { }, user: { type: 'object', - optional: true, nullable: true, + optional: false, nullable: false, ref: 'UserLite', }, }, @@ -144,3 +144,113 @@ export const packedChatMessageLiteSchema = { }, }, } as const; + +export const packedChatMessageLiteFor1on1Schema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + createdAt: { + type: 'string', + format: 'date-time', + optional: false, nullable: false, + }, + fromUserId: { + type: 'string', + optional: false, nullable: false, + }, + toUserId: { + type: 'string', + optional: false, nullable: false, + }, + text: { + type: 'string', + optional: true, nullable: true, + }, + fileId: { + type: 'string', + optional: true, nullable: true, + }, + file: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFile', + }, + reactions: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + reaction: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + }, + }, +} as const; + +export const packedChatMessageLiteForRoomSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + }, + createdAt: { + type: 'string', + format: 'date-time', + optional: false, nullable: false, + }, + fromUserId: { + type: 'string', + optional: false, nullable: false, + }, + fromUser: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + toRoomId: { + type: 'string', + optional: false, nullable: false, + }, + text: { + type: 'string', + optional: true, nullable: true, + }, + fileId: { + type: 'string', + optional: true, nullable: true, + }, + file: { + type: 'object', + optional: true, nullable: true, + ref: 'DriveFile', + }, + reactions: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + properties: { + reaction: { + type: 'string', + optional: false, nullable: false, + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + }, + }, + }, + }, +} as const; diff --git a/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts index 1f334d5750..a988dc60b9 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts @@ -30,7 +30,7 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'ChatMessageLite', + ref: 'ChatMessageLiteForRoom', }, errors: { diff --git a/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts index 6b77a026fb..bbaab8a6c3 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts @@ -30,7 +30,7 @@ export const meta = { res: { type: 'object', optional: false, nullable: false, - ref: 'ChatMessageLite', + ref: 'ChatMessageLiteFor1on1', }, errors: { diff --git a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts index 959599ddcf..25fc774d4f 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts @@ -13,6 +13,7 @@ export const meta = { tags: ['chat'], requireCredential: true, + requiredRolePolicy: 'canChat', kind: 'write:chat', diff --git a/packages/backend/src/server/api/endpoints/chat/messages/react.ts b/packages/backend/src/server/api/endpoints/chat/messages/react.ts index 561e36ed19..0145e380be 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/react.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/react.ts @@ -13,6 +13,7 @@ export const meta = { tags: ['chat'], requireCredential: true, + requiredRolePolicy: 'canChat', kind: 'write:chat', diff --git a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts index 7aef35db04..b6d3356196 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts @@ -23,7 +23,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'ChatMessageLite', + ref: 'ChatMessageLiteForRoom', }, }, diff --git a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts index 4eb25259fb..b97bad8a9c 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts @@ -13,6 +13,7 @@ export const meta = { tags: ['chat'], requireCredential: true, + requiredRolePolicy: 'canChat', kind: 'write:chat', diff --git a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts index 9d308d79b0..a35f121bb1 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts @@ -24,7 +24,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'ChatMessageLite', + ref: 'ChatMessageLiteFor1on1', }, }, diff --git a/packages/frontend/src/pages/chat/XMessage.vue b/packages/frontend/src/pages/chat/XMessage.vue index 33741b1845..eb8b0d79ee 100644 --- a/packages/frontend/src/pages/chat/XMessage.vue +++ b/packages/frontend/src/pages/chat/XMessage.vue @@ -5,33 +5,28 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/utility/timeline-date-separate.ts b/packages/frontend/src/utility/timeline-date-separate.ts new file mode 100644 index 0000000000..f9876a20ff --- /dev/null +++ b/packages/frontend/src/utility/timeline-date-separate.ts @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { computed } from 'vue'; +import type { Ref } from 'vue'; + +function getDateText(dateInstance: Date) { + const date = dateInstance.getDate(); + const month = dateInstance.getMonth() + 1; + return `${month.toString()}/${date.toString()}`; +} + +export type DateSeparetedTimelineItem = { + id: string; + type: 'item'; + data: T; +} | { + id: string; + type: 'date'; + prev: Date; + prevText: string; + next: Date; + nextText: string; +}; + +export function makeDateSeparatedTimelineComputedRef(items: Ref) { + return computed[]>(() => { + const tl: DateSeparetedTimelineItem[] = []; + for (let i = 0; i < items.value.length; i++) { + const item = items.value[i]; + + const date = new Date(item.createdAt); + const nextDate = items.value[i + 1] ? new Date(items.value[i + 1].createdAt) : null; + + tl.push({ + id: item.id, + type: 'item', + data: item, + }); + + if ( + i !== items.value.length - 1 && + nextDate != null && ( + date.getFullYear() !== nextDate.getFullYear() || + date.getMonth() !== nextDate.getMonth() || + date.getDate() !== nextDate.getDate() + ) + ) { + tl.push({ + id: `date-${item.id}`, + type: 'date', + prev: date, + prevText: getDateText(date), + next: nextDate, + nextText: getDateText(nextDate), + }); + } + } + return tl; + }); +} From 440a4a4d8bf6b9e11d25218af65348f9a0cab176 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 3 Apr 2025 15:42:25 +0900 Subject: [PATCH 15/41] refactor(frontend): refactor MkDateSeparatedList --- .../frontend/src/components/MkDateSeparatedList.vue | 13 +++---------- .../frontend/src/utility/timeline-date-separate.ts | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index ec6fcdc311..1cf6f0b744 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -3,16 +3,18 @@ SPDX-FileCopyrightText: syuilo and misskey-project SPDX-License-Identifier: AGPL-3.0-only --> + + + + diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 6c6903c3a4..34cf598b84 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -30,6 +30,7 @@ import PageWithAnimBg from './global/PageWithAnimBg.vue'; import SearchMarker from './global/SearchMarker.vue'; import SearchLabel from './global/SearchLabel.vue'; import SearchKeyword from './global/SearchKeyword.vue'; +import SearchIcon from './global/SearchIcon.vue'; import type { App } from 'vue'; @@ -67,6 +68,7 @@ export const components = { SearchMarker: SearchMarker, SearchLabel: SearchLabel, SearchKeyword: SearchKeyword, + SearchIcon: SearchIcon, }; declare module '@vue/runtime-core' { @@ -98,5 +100,6 @@ declare module '@vue/runtime-core' { SearchMarker: typeof SearchMarker; SearchLabel: typeof SearchLabel; SearchKeyword: typeof SearchKeyword; + SearchIcon: typeof SearchIcon; } } diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index 58fcb0c5e0..d97ea6e459 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -81,7 +81,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -100,7 +100,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 86ae8af213..647fed10e3 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -111,7 +111,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -279,7 +279,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -341,7 +341,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -382,7 +382,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -421,7 +421,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -531,7 +531,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -567,7 +567,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
{{ i18n.ts.reloadRequiredToApplySettings }} @@ -601,7 +601,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
From 154b89f07a67909f2dae360c0d63f2e125808366 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:16:08 +0900 Subject: [PATCH 35/41] =?UTF-8?q?=F0=9F=8E=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/ui/deck.vue | 8 ++++---- packages/frontend/src/ui/universal.vue | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index bf39c07229..422b00e9c2 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
Date: Mon, 7 Apr 2025 08:17:46 +0000 Subject: [PATCH 36/41] Bump version to 2025.4.0-rc.2 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5b45b999f3..879bf1d029 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.4.0-rc.1", + "version": "2025.4.0-rc.2", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 7e92981230..7ab4cb8914 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2025.4.0-rc.1", + "version": "2025.4.0-rc.2", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", From 6c27ab12ebcf0dba20055f4cf79d923f79c6da76 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Mon, 7 Apr 2025 17:30:44 +0900 Subject: [PATCH 37/41] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 434a18442a..2f1e08f2c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ - 再度ログインすればサーバーのバックアップから設定データを復元可能です - エクスポートした設定データを他のサーバーでインポートして適用すること(設定の持ち運び)が可能になりました - 設定情報の移行は自動で行われますが、何らかの理由で失敗した場合、設定→その他→旧設定情報を移行 で再試行可能です + - 過去に作成されたバックアップデータとは現在互換性がありませんのでご注意ください - Feat: 画面を重ねて表示するオプションを実装(実験的) - 設定 → その他 → 実験的機能 → Enable stacking router view - Enhance: プラグインの管理が強化されました From 9d3f3264fdd059f47537da48fd125cdd2f4bad1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:09:11 +0900 Subject: [PATCH 38/41] =?UTF-8?q?enhance:=20=E3=83=81=E3=83=A3=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=81=AE=E9=96=B2=E8=A6=A7=E3=82=92=E7=84=A1=E5=8A=B9?= =?UTF-8?q?=E5=8C=96=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=20(#15765)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance: チャットの閲覧を無効化できるように * fix * fix * fix * readonlyの説明を追加 * enhance: チャットが無効な場合はチャット関連の設定も隠すように * fix * refactor: ChatServiceからApiに関するドメイン知識を排除 --- locales/index.d.ts | 10 ++- locales/ja-JP.yml | 4 +- packages/backend/src/core/ChatService.ts | 36 +++++++- packages/backend/src/core/RoleService.ts | 12 ++- .../src/core/entities/UserEntityService.ts | 2 +- .../backend/src/models/json-schema/role.ts | 5 +- .../src/server/api/endpoints/chat/history.ts | 2 + .../endpoints/chat/messages/create-to-room.ts | 3 +- .../endpoints/chat/messages/create-to-user.ts | 3 +- .../api/endpoints/chat/messages/delete.ts | 3 +- .../api/endpoints/chat/messages/react.ts | 3 +- .../endpoints/chat/messages/room-timeline.ts | 2 + .../api/endpoints/chat/messages/search.ts | 2 + .../api/endpoints/chat/messages/show.ts | 2 + .../api/endpoints/chat/messages/unreact.ts | 3 +- .../endpoints/chat/messages/user-timeline.ts | 2 + .../server/api/endpoints/chat/rooms/create.ts | 3 +- .../server/api/endpoints/chat/rooms/delete.ts | 2 + .../chat/rooms/invitations/create.ts | 3 +- .../chat/rooms/invitations/ignore.ts | 2 + .../endpoints/chat/rooms/invitations/inbox.ts | 2 + .../chat/rooms/invitations/outbox.ts | 2 + .../server/api/endpoints/chat/rooms/join.ts | 2 + .../api/endpoints/chat/rooms/joining.ts | 2 + .../server/api/endpoints/chat/rooms/leave.ts | 2 + .../api/endpoints/chat/rooms/members.ts | 2 + .../server/api/endpoints/chat/rooms/mute.ts | 2 + .../server/api/endpoints/chat/rooms/owned.ts | 2 + .../server/api/endpoints/chat/rooms/show.ts | 2 + .../server/api/endpoints/chat/rooms/update.ts | 2 + packages/frontend-shared/js/const.ts | 2 +- packages/frontend/src/navbar.ts | 1 + .../frontend/src/pages/admin/roles.editor.vue | 21 +++-- packages/frontend/src/pages/admin/roles.vue | 14 +-- packages/frontend/src/pages/chat/XMessage.vue | 10 +-- .../frontend/src/pages/chat/home.home.vue | 4 +- packages/frontend/src/pages/chat/room.vue | 90 ++++++++++--------- .../src/pages/settings/preferences.vue | 75 ++++++++-------- .../frontend/src/pages/settings/privacy.vue | 33 ++++--- packages/frontend/src/router.definition.ts | 12 ++- .../frontend/src/utility/get-user-menu.ts | 2 +- packages/misskey-js/src/autogen/types.ts | 3 +- 42 files changed, 255 insertions(+), 136 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index c157cb2a1f..4341828481 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5386,6 +5386,10 @@ export interface Locale extends ILocale { * 設定を移行しています。しばらくお待ちください... (後ほど、設定→その他→旧設定情報を移行 で手動で移行することもできます) */ "settingsMigrating": string; + /** + * 読み取り専用 + */ + "readonly": string; "_chat": { /** * まだメッセージはありません @@ -5500,6 +5504,10 @@ export interface Locale extends ILocale { * このサーバー、またはこのアカウントでチャットは有効化されていません。 */ "chatNotAvailableForThisAccountOrServer": string; + /** + * このサーバー、またはこのアカウントでチャットは読み取り専用となっています。新たに書き込んだり、チャットルームを作成・参加したりすることはできません。 + */ + "chatIsReadOnlyForThisAccountOrServer": string; /** * 相手のアカウントでチャット機能が使えない状態になっています。 */ @@ -7531,7 +7539,7 @@ export interface Locale extends ILocale { /** * チャットを許可 */ - "canChat": string; + "chatAvailability": string; }; "_condition": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b0084dc440..0d34bbc1ca 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1342,6 +1342,7 @@ bottom: "下" top: "上" embed: "埋め込み" settingsMigrating: "設定を移行しています。しばらくお待ちください... (後ほど、設定→その他→旧設定情報を移行 で手動で移行することもできます)" +readonly: "読み取り専用" _chat: noMessagesYet: "まだメッセージはありません" @@ -1372,6 +1373,7 @@ _chat: muteThisRoom: "このルームをミュート" deleteRoom: "ルームを削除" chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは有効化されていません。" + chatIsReadOnlyForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは読み取り専用となっています。新たに書き込んだり、チャットルームを作成・参加したりすることはできません。" chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。" cannotChatWithTheUser: "このユーザーとのチャットを開始できません" cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" @@ -1950,7 +1952,7 @@ _role: canImportFollowing: "フォローのインポートを許可" canImportMuting: "ミュートのインポートを許可" canImportUserLists: "リストのインポートを許可" - canChat: "チャットを許可" + chatAvailability: "チャットを許可" _condition: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" diff --git a/packages/backend/src/core/ChatService.ts b/packages/backend/src/core/ChatService.ts index 3984cefc80..b0e8cfb61c 100644 --- a/packages/backend/src/core/ChatService.ts +++ b/packages/backend/src/core/ChatService.ts @@ -94,6 +94,40 @@ export class ChatService { ) { } + @bindThis + public async getChatAvailability(userId: MiUser['id']): Promise<{ read: boolean; write: boolean; }> { + const policies = await this.roleService.getUserPolicies(userId); + + switch (policies.chatAvailability) { + case 'available': + return { + read: true, + write: true, + }; + case 'readonly': + return { + read: true, + write: false, + }; + case 'unavailable': + return { + read: false, + write: false, + }; + default: + throw new Error('invalid chat availability (unreachable)'); + } + } + + /** getChatAvailabilityの糖衣。主にAPI呼び出し時に走らせて、権限的に問題ない場合はそのまま続行する */ + @bindThis + public async checkChatAvailability(userId: MiUser['id'], permission: 'read' | 'write') { + const policy = await this.getChatAvailability(userId); + if (policy[permission] === false) { + throw new Error('ROLE_PERMISSION_DENIED'); + } + } + @bindThis public async createMessageToUser(fromUser: { id: MiUser['id']; host: MiUser['host']; }, toUser: MiUser, params: { text?: string | null; @@ -140,7 +174,7 @@ export class ChatService { } } - if (!(await this.roleService.getUserPolicies(toUser.id)).canChat) { + if (!(await this.getChatAvailability(toUser.id)).write) { throw new Error('recipient is cannot chat (policy)'); } diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 0a2659ee32..601959cc96 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -63,7 +63,7 @@ export type RolePolicies = { canImportFollowing: boolean; canImportMuting: boolean; canImportUserLists: boolean; - canChat: boolean; + chatAvailability: 'available' | 'readonly' | 'unavailable'; }; export const DEFAULT_POLICIES: RolePolicies = { @@ -98,7 +98,7 @@ export const DEFAULT_POLICIES: RolePolicies = { canImportFollowing: true, canImportMuting: true, canImportUserLists: true, - canChat: true, + chatAvailability: 'available', }; @Injectable() @@ -370,6 +370,12 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { return aggregate(policies.map(policy => policy.useDefault ? basePolicies[name] : policy.value)); } + function aggregateChatAvailability(vs: RolePolicies['chatAvailability'][]) { + if (vs.some(v => v === 'available')) return 'available'; + if (vs.some(v => v === 'readonly')) return 'readonly'; + return 'unavailable'; + } + return { gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)), ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)), @@ -402,7 +408,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)), canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)), canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)), - canChat: calc('canChat', vs => vs.some(v => v === true)), + chatAvailability: calc('chatAvailability', aggregateChatAvailability), }; } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index ad8052711c..e252ff509e 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -557,7 +557,7 @@ export class UserEntityService implements OnModuleInit { followersVisibility: profile!.followersVisibility, followingVisibility: profile!.followingVisibility, chatScope: user.chatScope, - canChat: this.roleService.getUserPolicies(user.id).then(r => r.canChat), + canChat: this.roleService.getUserPolicies(user.id).then(r => r.chatAvailability === 'available'), roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({ id: role.id, name: role.name, diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index 6f63dcef2e..1cfcb830e0 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -292,9 +292,10 @@ export const packedRolePoliciesSchema = { type: 'boolean', optional: false, nullable: false, }, - canChat: { - type: 'boolean', + chatAvailability: { + type: 'string', optional: false, nullable: false, + enum: ['available', 'readonly', 'unavailable'], }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/chat/history.ts b/packages/backend/src/server/api/endpoints/chat/history.ts index 7553a751e0..fdd9055106 100644 --- a/packages/backend/src/server/api/endpoints/chat/history.ts +++ b/packages/backend/src/server/api/endpoints/chat/history.ts @@ -46,6 +46,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const history = ps.room ? await this.chatService.roomHistory(me.id, ps.limit) : await this.chatService.userHistory(me.id, ps.limit); const packedMessages = await this.chatEntityService.packMessagesDetailed(history, me); diff --git a/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts index a988dc60b9..ad2b82e219 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-room.ts @@ -16,7 +16,6 @@ export const meta = { tags: ['chat'], requireCredential: true, - requiredRolePolicy: 'canChat', prohibitMoved: true, @@ -74,6 +73,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + const room = await this.chatService.findRoomById(ps.toRoomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts index bbaab8a6c3..fa34a7d558 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/create-to-user.ts @@ -16,7 +16,6 @@ export const meta = { tags: ['chat'], requireCredential: true, - requiredRolePolicy: 'canChat', prohibitMoved: true, @@ -86,6 +85,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + let file = null; if (ps.fileId != null) { file = await this.driveFilesRepository.findOneBy({ diff --git a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts index 25fc774d4f..63b75fb6a7 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/delete.ts @@ -13,7 +13,6 @@ export const meta = { tags: ['chat'], requireCredential: true, - requiredRolePolicy: 'canChat', kind: 'write:chat', @@ -43,6 +42,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + const message = await this.chatService.findMyMessageById(me.id, ps.messageId); if (message == null) { throw new ApiError(meta.errors.noSuchMessage); diff --git a/packages/backend/src/server/api/endpoints/chat/messages/react.ts b/packages/backend/src/server/api/endpoints/chat/messages/react.ts index 0145e380be..5f61e7e992 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/react.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/react.ts @@ -13,7 +13,6 @@ export const meta = { tags: ['chat'], requireCredential: true, - requiredRolePolicy: 'canChat', kind: 'write:chat', @@ -44,6 +43,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + await this.chatService.react(ps.messageId, me.id, ps.reaction); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts index b6d3356196..c0e344b889 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/room-timeline.ts @@ -54,6 +54,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const room = await this.chatService.findRoomById(ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/backend/src/server/api/endpoints/chat/messages/search.ts b/packages/backend/src/server/api/endpoints/chat/messages/search.ts index 4c989e5ca9..682597f76d 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/search.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/search.ts @@ -54,6 +54,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + if (ps.roomId != null) { const room = await this.chatService.findRoomById(ps.roomId); if (room == null) { diff --git a/packages/backend/src/server/api/endpoints/chat/messages/show.ts b/packages/backend/src/server/api/endpoints/chat/messages/show.ts index 371f7a7071..9a2bbb8742 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/show.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/show.ts @@ -50,6 +50,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const message = await this.chatService.findMessageById(ps.messageId); if (message == null) { throw new ApiError(meta.errors.noSuchMessage); diff --git a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts index b97bad8a9c..6784bb6ecf 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/unreact.ts @@ -13,7 +13,6 @@ export const meta = { tags: ['chat'], requireCredential: true, - requiredRolePolicy: 'canChat', kind: 'write:chat', @@ -44,6 +43,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + await this.chatService.unreact(ps.messageId, me.id, ps.reaction); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts index a35f121bb1..a057e2e088 100644 --- a/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts +++ b/packages/backend/src/server/api/endpoints/chat/messages/user-timeline.ts @@ -56,6 +56,8 @@ export default class extends Endpoint { // eslint- private getterService: GetterService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const other = await this.getterService.getUser(ps.userId).catch(err => { if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser); throw err; diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/create.ts b/packages/backend/src/server/api/endpoints/chat/rooms/create.ts index fa4cc8ceb4..68a53f0886 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/create.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/create.ts @@ -15,7 +15,6 @@ export const meta = { tags: ['chat'], requireCredential: true, - requiredRolePolicy: 'canChat', prohibitMoved: true, @@ -52,6 +51,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + const room = await this.chatService.createRoom(me, { name: ps.name, description: ps.description ?? '', diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts index 1d77a06dd8..82a8e1f30d 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/delete.ts @@ -42,6 +42,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + const room = await this.chatService.findRoomById(ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts index 5da4a1a772..b1f049f2b9 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/create.ts @@ -15,7 +15,6 @@ export const meta = { tags: ['chat'], requireCredential: true, - requiredRolePolicy: 'canChat', prohibitMoved: true, @@ -57,6 +56,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + const room = await this.chatService.findMyRoomById(me.id, ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts index 8c017f7d01..b8a228089b 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/ignore.ts @@ -42,6 +42,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + await this.chatService.ignoreRoomInvitation(me.id, ps.roomId); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts index 07337480fc..8a02d1c704 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/inbox.ts @@ -47,6 +47,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId); return this.chatEntityService.packRoomInvitations(invitations, me); }); diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts index 12d496e94b..0702ba086c 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/invitations/outbox.ts @@ -55,6 +55,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const room = await this.chatService.findMyRoomById(me.id, ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/join.ts b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts index dbd4d1ea5a..d561f9e03f 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/join.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/join.ts @@ -42,6 +42,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + await this.chatService.joinToRoom(me.id, ps.roomId); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts b/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts index c4c6253236..ba9242c762 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/joining.ts @@ -47,6 +47,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, ps.sinceId, ps.untilId); return this.chatEntityService.packRoomMemberships(memberships, me, { diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts index 724ad61f7e..a3ad0c2d6f 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/leave.ts @@ -42,6 +42,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + await this.chatService.leaveRoom(me.id, ps.roomId); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/members.ts b/packages/backend/src/server/api/endpoints/chat/rooms/members.ts index 407bfe74f1..f5ffa21d32 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/members.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/members.ts @@ -54,6 +54,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const room = await this.chatService.findRoomById(ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts index 5208b8a253..11cbe7b8b9 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/mute.ts @@ -43,6 +43,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + await this.chatService.muteRoom(me.id, ps.roomId, ps.mute); }); } diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts b/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts index 6516120bca..accf7e1bee 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/owned.ts @@ -47,6 +47,8 @@ export default class extends Endpoint { // eslint- private chatService: ChatService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId); return this.chatEntityService.packRooms(rooms, me); }); diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/show.ts b/packages/backend/src/server/api/endpoints/chat/rooms/show.ts index 547618ee7d..50da210d81 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/show.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/show.ts @@ -47,6 +47,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'read'); + const room = await this.chatService.findRoomById(ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/backend/src/server/api/endpoints/chat/rooms/update.ts b/packages/backend/src/server/api/endpoints/chat/rooms/update.ts index 6f2a9c10b5..0cd62cb040 100644 --- a/packages/backend/src/server/api/endpoints/chat/rooms/update.ts +++ b/packages/backend/src/server/api/endpoints/chat/rooms/update.ts @@ -49,6 +49,8 @@ export default class extends Endpoint { // eslint- private chatEntityService: ChatEntityService, ) { super(meta, paramDef, async (ps, me) => { + await this.chatService.checkChatAvailability(me.id, 'write'); + const room = await this.chatService.findMyRoomById(me.id, ps.roomId); if (room == null) { throw new ApiError(meta.errors.noSuchRoom); diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index fa60476b69..de65c3db97 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -108,7 +108,7 @@ export const ROLE_POLICIES = [ 'canImportFollowing', 'canImportMuting', 'canImportUserLists', - 'canChat', + 'chatAvailability', ] as const; export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg'; diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 185d9e81b7..c0fe0f2b85 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -114,6 +114,7 @@ export const navbarItemDef = reactive({ title: i18n.ts.chat, icon: 'ti ti-messages', to: '/chat', + show: computed(() => $i != null && $i.policies.chatAvailability !== 'unavailable'), indicated: computed(() => $i != null && $i.hasUnreadChatMessages), }, achievements: { diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 73119940c1..930a63f5a9 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -165,21 +165,24 @@ SPDX-License-Identifier: AGPL-3.0-only
- - + +
- + - + - - + + + + +
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index df4efd1271..7c950957cf 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -51,12 +51,15 @@ SPDX-License-Identifier: AGPL-3.0-only
- - - - + + + + - + + + + @@ -295,6 +298,7 @@ import MkInput from '@/components/MkInput.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkButton from '@/components/MkButton.vue'; +import MkSelect from '@/components/MkSelect.vue'; import MkRange from '@/components/MkRange.vue'; import MkRolePreview from '@/components/MkRolePreview.vue'; import * as os from '@/os.js'; diff --git a/packages/frontend/src/pages/chat/XMessage.vue b/packages/frontend/src/pages/chat/XMessage.vue index eb8b0d79ee..def6ec7d14 100644 --- a/packages/frontend/src/pages/chat/XMessage.vue +++ b/packages/frontend/src/pages/chat/XMessage.vue @@ -85,7 +85,7 @@ const isMe = computed(() => props.message.fromUserId === $i.id); const urls = computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []); provide(DI.mfmEmojiReactCallback, (reaction) => { - if (!$i.policies.canChat) return; + if ($i.policies.chatAvailability !== 'available') return; sound.playMisskeySfx('reaction'); misskeyApi('chat/messages/react', { @@ -95,7 +95,7 @@ provide(DI.mfmEmojiReactCallback, (reaction) => { }); function react(ev: MouseEvent) { - if (!$i.policies.canChat) return; + if ($i.policies.chatAvailability !== 'available') return; const targetEl = getHTMLElementOrNull(ev.currentTarget ?? ev.target); if (!targetEl) return; @@ -110,7 +110,7 @@ function react(ev: MouseEvent) { } function onReactionClick(record: Misskey.entities.ChatMessage['reactions'][0]) { - if (!$i.policies.canChat) return; + if ($i.policies.chatAvailability !== 'available') return; if (record.user.id === $i.id) { misskeyApi('chat/messages/unreact', { @@ -138,7 +138,7 @@ function onContextmenu(ev: MouseEvent) { function showMenu(ev: MouseEvent, contextmenu = false) { const menu: MenuItem[] = []; - if (!isMe.value && $i.policies.canChat) { + if (!isMe.value && $i.policies.chatAvailability === 'available') { menu.push({ text: i18n.ts.reaction, icon: 'ti ti-mood-plus', @@ -164,7 +164,7 @@ function showMenu(ev: MouseEvent, contextmenu = false) { type: 'divider', }); - if (isMe.value && $i.policies.canChat) { + if (isMe.value && $i.policies.chatAvailability === 'available') { menu.push({ text: i18n.ts.delete, icon: 'ti ti-trash', diff --git a/packages/frontend/src/pages/chat/home.home.vue b/packages/frontend/src/pages/chat/home.home.vue index 17f0e0fbcd..a8ed891de0 100644 --- a/packages/frontend/src/pages/chat/home.home.vue +++ b/packages/frontend/src/pages/chat/home.home.vue @@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only