From 04b221409cac8e54e9f392b1a50094799c866441 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 23 Nov 2024 04:44:33 +0900 Subject: fix(backend): use atomic command to improve security --- packages/backend/src/core/WebAuthnService.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index ad53192f18..ed75e4f467 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -189,14 +189,12 @@ export class WebAuthnService { */ @bindThis public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise { - const challenge = await this.redisClient.get(`webauthn:challenge:${context}`); + const challenge = await this.redisClient.getdel(`webauthn:challenge:${context}`); if (!challenge) { throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`); } - await this.redisClient.del(`webauthn:challenge:${context}`); - const key = await this.userSecurityKeysRepository.findOneBy({ id: response.id, }); -- cgit v1.2.3-freya From a77ad7a16ba3f5c4845fe0716a72c7f01c285e9b Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:45:05 +0900 Subject: fix(backend): アドレス入力で直接ユーザのプロフィールページを表示した際、前提データが足りず描画に失敗する (#15033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): アドレス入力で直接ユーザのプロフィールページを表示した際、前提データが足りず描画に失敗する * fix CHANGELOG.md --- CHANGELOG.md | 2 +- packages/backend/src/server/web/ClientServerService.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 762ecafbdb..7896a42883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 ### Server -- +- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) ## 2024.11.0 diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 1b8873214b..e356e0d76c 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -585,7 +585,10 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noai'); } - const _user = await this.userEntityService.pack(user); + const _user = await this.userEntityService.pack(user, null, { + schema: 'UserDetailed', + userProfile: profile, + }); return await reply.view('user', { user, profile, me, -- cgit v1.2.3-freya From d176db517fd11b63e83daa7c1f45ecd25bd0750e Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:23:07 +0900 Subject: fix(backend/misskey-js): タイポ修正 (#15046) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/server/api/openapi/gen-spec.ts | 2 +- packages/misskey-js/src/autogen/types.ts | 110 ++++++++++----------- 2 files changed, 56 insertions(+), 56 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index efa47a6986..3b20ec1321 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -183,7 +183,7 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) { }, ...(endpoint.meta.limit ? { '429': { - description: 'To many requests', + description: 'Too many requests', content: { 'application/json': { schema: { diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 280abba727..42ca05e057 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -10593,7 +10593,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11112,7 +11112,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11179,7 +11179,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11573,7 +11573,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11633,7 +11633,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -11756,7 +11756,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -13351,7 +13351,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -14184,7 +14184,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -14531,7 +14531,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -14656,7 +14656,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15151,7 +15151,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15624,7 +15624,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15684,7 +15684,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15747,7 +15747,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15806,7 +15806,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -15866,7 +15866,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -16373,7 +16373,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -16648,7 +16648,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -17908,7 +17908,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -17969,7 +17969,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18020,7 +18020,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18071,7 +18071,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18122,7 +18122,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18173,7 +18173,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18224,7 +18224,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18275,7 +18275,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18512,7 +18512,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18572,7 +18572,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18631,7 +18631,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18690,7 +18690,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18749,7 +18749,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18817,7 +18817,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -18885,7 +18885,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -19877,7 +19877,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -20114,7 +20114,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -20174,7 +20174,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -20544,7 +20544,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21023,7 +21023,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21191,7 +21191,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21688,7 +21688,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21746,7 +21746,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -21804,7 +21804,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -22464,7 +22464,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -22898,7 +22898,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23142,7 +23142,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23278,7 +23278,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23416,7 +23416,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23550,7 +23550,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23882,7 +23882,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -23949,7 +23949,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -24279,7 +24279,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -24829,7 +24829,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -26108,7 +26108,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -27398,7 +27398,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -27512,7 +27512,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; -- cgit v1.2.3-freya From dd56623cded7cc7a355d1dc59e46e2d8f1ed3660 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 24 Nov 2024 20:44:59 +0900 Subject: fix: unable to upload to local object storage (#15040) --- packages/backend/src/core/S3Service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts index bb2a463354..37721d2bf1 100644 --- a/packages/backend/src/core/S3Service.ts +++ b/packages/backend/src/core/S3Service.ts @@ -28,7 +28,7 @@ export class S3Service { ? `${meta.objectStorageUseSSL ? 'https' : 'http'}://${meta.objectStorageEndpoint}` : `${meta.objectStorageUseSSL ? 'https' : 'http'}://example.net`; // dummy url to select http(s) agent - const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy); + const agent = this.httpRequestService.getAgentByUrl(new URL(u), !meta.objectStorageUseProxy, true); const handlerOption: NodeHttpHandlerOptions = {}; if (meta.objectStorageUseSSL) { handlerOption.httpsAgent = agent as https.Agent; -- cgit v1.2.3-freya From 074b7b0bee82ea692ba675bf4b35734b540b7752 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:36:03 +0900 Subject: fix(frontend): 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 (#15102) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Resolve frontend/backend contradiction for home visibility embeds This now uses the same check from `packages/frontend/src/scripts/get-note-menu.ts` * Update Changelog --------- Co-authored-by: CenTdemeern1 --- CHANGELOG.md | 2 ++ packages/backend/src/server/web/ClientServerService.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f2c36ffc3..595252d3e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Client - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正 +- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803) ### Server - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index e356e0d76c..1a75096c4e 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -871,7 +871,7 @@ export class ClientServerService { }); if (note == null) return; - if (note.visibility !== 'public') return; + if (['specified', 'followers'].includes(note.visibility)) return; if (note.userHost != null) return; const _note = await this.noteEntityService.pack(note, null, { detail: true }); -- cgit v1.2.3-freya From f123be38b93339f405468c8ed1aaa39d340b7791 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:05:33 +0900 Subject: enhance(frontend): 照会の際にエラーを表示するように (#15147) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance: 照会の失敗理由を表示するように * Update Changelog * fix * fix test * lookupErrors-> remoteLookupErrors --- CHANGELOG.md | 1 + locales/index.d.ts | 59 ++++++++++++++++++++ locales/ja-JP.yml | 19 +++++++ .../src/core/activitypub/ApResolverService.ts | 25 +++++---- .../backend/src/server/api/endpoints/ap/show.ts | 65 +++++++++++++++++++++- packages/backend/test-federation/test/note.test.ts | 6 +- packages/frontend/src/scripts/lookup.ts | 38 ++++++++++++- 7 files changed, 193 insertions(+), 20 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index ee82834de7..fd56700e1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Client - Enhance: PC画面でチャンネルが複数列で表示されるように (Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13) +- Enhance: 照会に失敗した場合、その理由を表示するように - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 - Fix: サーバー情報メニューに区切り線が不足していたのを修正 - Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 0ae188f1f7..63878d3d47 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -10601,6 +10601,65 @@ export interface Locale extends ILocale { */ "sent": string; }; + "_remoteLookupErrors": { + "_federationNotAllowed": { + /** + * このサーバーとは通信できません + */ + "title": string; + /** + * このサーバーとの通信が無効化されているか、このサーバーをブロックしている・ブロックされている可能性があります。 + * サーバー管理者にお問い合わせください。 + */ + "description": string; + }; + "_uriInvalid": { + /** + * URIが不正です + */ + "title": string; + /** + * 入力されたURIに問題があります。URIに使用できない文字を入力していないか確認してください。 + */ + "description": string; + }; + "_requestFailed": { + /** + * リクエストに失敗しました + */ + "title": string; + /** + * このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。 + */ + "description": string; + }; + "_responseInvalid": { + /** + * レスポンスが不正です + */ + "title": string; + /** + * このサーバーと通信することはできましたが、得られたデータが不正なものでした。 + */ + "description": string; + }; + "_responseInvalidIdHostNotMatch": { + /** + * 入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。 + */ + "description": string; + }; + "_noSuchObject": { + /** + * 見つかりません + */ + "title": string; + /** + * 要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。 + */ + "description": string; + }; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1b59708d85..d78bd4ee65 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2826,3 +2826,22 @@ _selfXssPrevention: _followRequest: recieved: "受け取った申請" sent: "送った申請" + +_remoteLookupErrors: + _federationNotAllowed: + title: "このサーバーとは通信できません" + description: "このサーバーとの通信が無効化されているか、このサーバーをブロックしている・ブロックされている可能性があります。\nサーバー管理者にお問い合わせください。" + _uriInvalid: + title: "URIが不正です" + description: "入力されたURIに問題があります。URIに使用できない文字を入力していないか確認してください。" + _requestFailed: + title: "リクエストに失敗しました" + description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。" + _responseInvalid: + title: "レスポンスが不正です" + description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。" + _responseInvalidIdHostNotMatch: + description: "入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。" + _noSuchObject: + title: "見つかりません" + description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。" diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index b0b35274ea..52cc569140 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -20,6 +20,7 @@ import { ApDbResolverService } from './ApDbResolverService.js'; import { ApRendererService } from './ApRendererService.js'; import { ApRequestService } from './ApRequestService.js'; import type { IObject, ICollection, IOrderedCollection } from './type.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export class Resolver { private history: Set; @@ -66,7 +67,7 @@ export class Resolver { if (isCollectionOrOrderedCollection(collection)) { return collection; } else { - throw new Error(`unrecognized collection type: ${collection.type}`); + throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `unrecognized collection type: ${collection.type}`); } } @@ -80,15 +81,15 @@ export class Resolver { // URLs with fragment parts cannot be resolved correctly because // the fragment part does not get transmitted over HTTP(S). // Avoid strange behaviour by not trying to resolve these at all. - throw new Error(`cannot resolve URL with fragment: ${value}`); + throw new IdentifiableError('b94fd5b1-0e3b-4678-9df2-dad4cd515ab2', `cannot resolve URL with fragment: ${value}`); } if (this.history.has(value)) { - throw new Error('cannot resolve already resolved one'); + throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', 'cannot resolve already resolved one'); } if (this.history.size > this.recursionLimit) { - throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`); + throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${this.utilityService.extractDbHost(value)}`); } this.history.add(value); @@ -99,7 +100,7 @@ export class Resolver { } if (!this.utilityService.isFederationAllowedHost(host)) { - throw new Error('Instance is blocked'); + throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', 'Instance is blocked'); } if (this.config.signToActivityPubGet && !this.user) { @@ -115,7 +116,7 @@ export class Resolver { !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : object['@context'] !== 'https://www.w3.org/ns/activitystreams' ) { - throw new Error('invalid response'); + throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response'); } // HttpRequestService / ApRequestService have already checked that @@ -123,11 +124,11 @@ export class Resolver { // object after redirects; here we double-check that no redirects // bounced between hosts if (object.id == null) { - throw new Error('invalid AP object: missing id'); + throw new IdentifiableError('ad2dc287-75c1-44c4-839d-3d2e64576675', 'invalid AP object: missing id'); } if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) { - throw new Error(`invalid AP object ${value}: id ${object.id} has different host`); + throw new IdentifiableError('fd93c2fa-69a8-440f-880b-bf178e0ec877', `invalid AP object ${value}: id ${object.id} has different host`); } return object; @@ -136,7 +137,7 @@ export class Resolver { @bindThis private resolveLocal(url: string): Promise { const parsed = this.apDbResolverService.parseUri(url); - if (!parsed.local) throw new Error('resolveLocal: not local'); + if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', 'resolveLocal: not local'); switch (parsed.type) { case 'notes': @@ -165,7 +166,7 @@ export class Resolver { case 'follows': return this.followRequestsRepository.findOneBy({ id: parsed.id }) .then(async followRequest => { - if (followRequest == null) throw new Error('resolveLocal: invalid follow request ID'); + if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', 'resolveLocal: invalid follow request ID'); const [follower, followee] = await Promise.all([ this.usersRepository.findOneBy({ id: followRequest.followerId, @@ -177,12 +178,12 @@ export class Resolver { }), ]); if (follower == null || followee == null) { - throw new Error('resolveLocal: follower or followee does not exist'); + throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', 'resolveLocal: follower or followee does not exist'); } return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url)); }); default: - throw new Error(`resolveLocal: type ${parsed.type} unhandled`); + throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `resolveLocal: type ${parsed.type} unhandled`); } } } diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 24d5a7b0f1..5c2e82da88 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -19,6 +19,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { ApiError } from '../../error.js'; +import { IdentifiableError } from '@/misc/identifiable-error.js'; export const meta = { tags: ['federation'], @@ -32,6 +33,31 @@ export const meta = { }, errors: { + federationNotAllowed: { + message: 'Federation for this host is not allowed.', + code: 'FEDERATION_NOT_ALLOWED', + id: '974b799e-1a29-4889-b706-18d4dd93e266', + }, + uriInvalid: { + message: 'URI is invalid.', + code: 'URI_INVALID', + id: '1a5eab56-e47b-48c2-8d5e-217b897d70db', + }, + requestFailed: { + message: 'Request failed.', + code: 'REQUEST_FAILED', + id: '81b539cf-4f57-4b29-bc98-032c33c0792e', + }, + responseInvalid: { + message: 'Response from remote server is invalid.', + code: 'RESPONSE_INVALID', + id: '70193c39-54f3-4813-82f0-70a680f7495b', + }, + responseInvalidIdHostNotMatch: { + message: 'Requested URI and response URI host does not match.', + code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH', + id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a', + }, noSuchObject: { message: 'No such object.', code: 'NO_SUCH_OBJECT', @@ -110,7 +136,9 @@ export default class extends Endpoint { // eslint- */ @bindThis private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise | null> { - if (!this.utilityService.isFederationAllowedUri(uri)) return null; + if (!this.utilityService.isFederationAllowedUri(uri)) { + throw new ApiError(meta.errors.federationNotAllowed); + } let local = await this.mergePack(me, ...await Promise.all([ this.apDbResolverService.getUserFromApId(uri), @@ -125,7 +153,40 @@ export default class extends Endpoint { // eslint- // リモートから一旦オブジェクトフェッチ const resolver = this.apResolverService.createResolver(); - const object = await resolver.resolve(uri) as any; + const object = await resolver.resolve(uri).catch((err) => { + if (err instanceof IdentifiableError) { + switch (err.id) { + // resolve + case 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2': + throw new ApiError(meta.errors.uriInvalid); + case '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5': + case 'd592da9f-822f-4d91-83d7-4ceefabcf3d2': + throw new ApiError(meta.errors.requestFailed); + case '09d79f9e-64f1-4316-9cfa-e75c4d091574': + throw new ApiError(meta.errors.federationNotAllowed); + case '72180409-793c-4973-868e-5a118eb5519b': + case 'ad2dc287-75c1-44c4-839d-3d2e64576675': + throw new ApiError(meta.errors.responseInvalid); + case 'fd93c2fa-69a8-440f-880b-bf178e0ec877': + throw new ApiError(meta.errors.responseInvalidIdHostNotMatch); + + // resolveLocal + case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8': + throw new ApiError(meta.errors.uriInvalid); + case 'a9d946e5-d276-47f8-95fb-f04230289bb0': + case '06ae3170-1796-4d93-a697-2611ea6d83b6': + throw new ApiError(meta.errors.noSuchObject); + case '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0': + throw new ApiError(meta.errors.responseInvalid); + } + } + + throw new ApiError(meta.errors.requestFailed); + }); + + if (object.id == null) { + throw new ApiError(meta.errors.responseInvalid); + } // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する // これはDBに存在する可能性があるため再度DB検索 diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts index bacc4cc54f..220c22e198 100644 --- a/packages/backend/test-federation/test/note.test.ts +++ b/packages/backend/test-federation/test/note.test.ts @@ -131,11 +131,7 @@ describe('Note', () => { rejects( async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }), (err: any) => { - /** - * FIXME: this error is not handled - * @see https://github.com/misskey-dev/misskey/issues/12736 - */ - strictEqual(err.code, 'INTERNAL_ERROR'); + strictEqual(err.code, 'REQUEST_FAILED'); return true; }, ); diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts index a261ec0669..ddcbfe1a8d 100644 --- a/packages/frontend/src/scripts/lookup.ts +++ b/packages/frontend/src/scripts/lookup.ts @@ -33,7 +33,43 @@ export async function lookup(router?: Router) { uri: query, }); - os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject); + os.promiseDialog(promise, null, (err) => { + let title = i18n.ts.somethingHappened; + let text = err.message + '\n' + err.id; + + switch (err.id) { + case '974b799e-1a29-4889-b706-18d4dd93e266': + title = i18n.ts._remoteLookupErrors._federationNotAllowed.title; + text = i18n.ts._remoteLookupErrors._federationNotAllowed.description; + break; + case '1a5eab56-e47b-48c2-8d5e-217b897d70db': + title = i18n.ts._remoteLookupErrors._uriInvalid.title; + text = i18n.ts._remoteLookupErrors._uriInvalid.description; + break; + case '81b539cf-4f57-4b29-bc98-032c33c0792e': + title = i18n.ts._remoteLookupErrors._requestFailed.title; + text = i18n.ts._remoteLookupErrors._requestFailed.description; + break; + case '70193c39-54f3-4813-82f0-70a680f7495b': + title = i18n.ts._remoteLookupErrors._responseInvalid.title; + text = i18n.ts._remoteLookupErrors._responseInvalid.description; + break; + case 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a': + title = i18n.ts._remoteLookupErrors._responseInvalid.title; + text = i18n.ts._remoteLookupErrors._responseInvalidIdHostNotMatch.description; + break; + case 'dc94d745-1262-4e63-a17d-fecaa57efc82': + title = i18n.ts._remoteLookupErrors._noSuchObject.title; + text = i18n.ts._remoteLookupErrors._noSuchObject.description; + break; + } + + os.alert({ + type: 'error', + title, + text, + }); + }, i18n.ts.fetchingAsApObject); const res = await promise; -- cgit v1.2.3-freya From ad63f55a0f3d5cf3ad166847fc70edf1a8b8a211 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 19 Dec 2024 11:22:26 -0500 Subject: add file extension to locally-stored media --- packages/backend/src/core/DriveService.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 086f2f94d5..99e0ea98dd 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -37,6 +37,7 @@ import { InternalStorageService } from '@/core/InternalStorageService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { FileInfoService } from '@/core/FileInfoService.js'; +import type { FileInfo } from '@/core/FileInfoService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { correctFilename } from '@/misc/correct-filename.js'; @@ -139,15 +140,19 @@ export class DriveService { /*** * Save file + * @param file * @param path Path for original * @param name Name for original (should be extention corrected) - * @param type Content-Type for original - * @param hash Hash for original - * @param size Size for original + * @param info File metadata */ @bindThis - private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number): Promise { - // thunbnail, webpublic を必要なら生成 + private async save(file: MiDriveFile, path: string, name: string, info: FileInfo): Promise { + const type = info.type.mime; + const ext = info.type.ext; + const hash = info.md5; + const size = info.size; + + // thunbnail, webpublic を必要なら生成 const alts = await this.generateAlts(path, type, !file.uri); if (this.meta.useObjectStorage) { @@ -223,9 +228,9 @@ export class DriveService { return await this.driveFilesRepository.insertOne(file); } else { // use internal storage - const accessKey = randomUUID(); - const thumbnailAccessKey = 'thumbnail-' + randomUUID(); - const webpublicAccessKey = 'webpublic-' + randomUUID(); + const accessKey = `${randomUUID()}.${ext}`; + const thumbnailAccessKey = `thumbnail-${randomUUID()}.${ext}`; + const webpublicAccessKey = `webpublic-${randomUUID()}.${ext}`; // Ugly type is just to help TS figure out that 2nd / 3rd promises are optional. const promises: [Promise, ...(Promise | undefined)[]] = [ @@ -616,7 +621,7 @@ export class DriveService { } } } else { - file = await (this.save(file, path, detectedName, info.type.mime, info.md5, info.size)); + file = await (this.save(file, path, detectedName, info)); } this.registerLogger.succ(`drive file has been created ${file.id}`); -- cgit v1.2.3-freya From 0de93c2bde78bd4c4c7aed87d42cd7054955b4b2 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 19 Dec 2024 12:33:11 -0500 Subject: only attach file extension for browser-safe file types --- packages/backend/src/core/DriveService.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 99e0ea98dd..734ce6b88f 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -148,7 +148,6 @@ export class DriveService { @bindThis private async save(file: MiDriveFile, path: string, name: string, info: FileInfo): Promise { const type = info.type.mime; - const ext = info.type.ext; const hash = info.md5; const size = info.size; @@ -228,9 +227,11 @@ export class DriveService { return await this.driveFilesRepository.insertOne(file); } else { // use internal storage - const accessKey = `${randomUUID()}.${ext}`; - const thumbnailAccessKey = `thumbnail-${randomUUID()}.${ext}`; - const webpublicAccessKey = `webpublic-${randomUUID()}.${ext}`; + const ext = FILE_TYPE_BROWSERSAFE.includes(type) ? info.type.ext : null; + + const accessKey = makeFileKey(ext); + const thumbnailAccessKey = makeFileKey(ext, 'thumbnail'); + const webpublicAccessKey = makeFileKey(ext, 'webpublic'); // Ugly type is just to help TS figure out that 2nd / 3rd promises are optional. const promises: [Promise, ...(Promise | undefined)[]] = [ @@ -867,3 +868,16 @@ export class DriveService { } } } + +function makeFileKey(ext: string | null, prefix?: string): string { + const parts: string[] = [randomUUID()]; + + if (prefix) { + parts.unshift(prefix, '-'); + } + if (ext) { + parts.push('.', ext); + } + + return parts.join(''); +} -- cgit v1.2.3-freya From 83460279c0217096f95225b6ceff5393c8a439d4 Mon Sep 17 00:00:00 2001 From: piuvas Date: Mon, 23 Dec 2024 10:50:31 -0300 Subject: populate myreaction on replies for streams. --- packages/backend/src/server/api/stream/channels/bubble-timeline.ts | 7 +++++++ packages/backend/src/server/api/stream/channels/global-timeline.ts | 7 +++++++ packages/backend/src/server/api/stream/channels/home-timeline.ts | 7 +++++++ packages/backend/src/server/api/stream/channels/hybrid-timeline.ts | 7 +++++++ packages/backend/src/server/api/stream/channels/local-timeline.ts | 7 +++++++ 5 files changed, 35 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index 8693f0c6ac..b8cbe4f283 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -72,6 +72,13 @@ class BubbleTimelineChannel extends Channel { } } + if (this.user && note.reply) { + if (Object.keys(note.reply.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); 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 6fe76747ee..43aa99e8c4 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -67,6 +67,13 @@ class GlobalTimelineChannel extends Channel { } } + if (this.user && note.reply) { + if (Object.keys(note.reply.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 359ab3e223..05040b9099 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -88,6 +88,13 @@ class HomeTimelineChannel extends Channel { } } + if (this.user && note.reply) { + if (Object.keys(note.reply.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 01645fe657..b268904f04 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -106,6 +106,13 @@ class HybridTimelineChannel extends Channel { } } + if (this.user && note.reply) { + if (Object.keys(note.reply.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); 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 1f9d25b44d..8d75a0c3a2 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -77,6 +77,13 @@ class LocalTimelineChannel extends Channel { } } + if (this.user && note.reply) { + if (Object.keys(note.reply.reactions).length > 0) { + const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myRenoteReaction; + } + } + this.connection.cacheNote(note); this.send('note', note); -- cgit v1.2.3-freya From 94c3a71e499f8d508dfd1e49727d6a2d04031873 Mon Sep 17 00:00:00 2001 From: piuvas Date: Mon, 23 Dec 2024 20:42:21 -0300 Subject: improvements. --- .../backend/src/server/api/stream/channels/bubble-timeline.ts | 10 ++++++++-- .../backend/src/server/api/stream/channels/global-timeline.ts | 10 ++++++++-- .../backend/src/server/api/stream/channels/home-timeline.ts | 10 ++++++++-- .../backend/src/server/api/stream/channels/hybrid-timeline.ts | 10 ++++++++-- .../backend/src/server/api/stream/channels/local-timeline.ts | 10 ++++++++-- 5 files changed, 40 insertions(+), 10 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index b8cbe4f283..41b7b7cd3c 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -70,12 +70,18 @@ class BubbleTimelineChannel extends Channel { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; } + if (note.renote && note.renote.reply) { + if (Object.keys(note.renote.reply.reactions).length > 0) { + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + note.renote.reply.myReaction = myReplyReaction; + } + } } if (this.user && note.reply) { if (Object.keys(note.reply.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myRenoteReaction; + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myReplyReaction; } } 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 43aa99e8c4..6da21ac72c 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -65,12 +65,18 @@ class GlobalTimelineChannel extends Channel { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; } + if (note.renote && note.renote.reply) { + if (Object.keys(note.renote.reply.reactions).length > 0) { + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + note.renote.reply.myReaction = myReplyReaction; + } + } } if (this.user && note.reply) { if (Object.keys(note.reply.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myRenoteReaction; + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myReplyReaction; } } diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 05040b9099..d4b65222a0 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -86,12 +86,18 @@ class HomeTimelineChannel extends Channel { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; } + if (note.renote && note.renote.reply) { + if (Object.keys(note.renote.reply.reactions).length > 0) { + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + note.renote.reply.myReaction = myReplyReaction; + } + } } if (this.user && note.reply) { if (Object.keys(note.reply.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myRenoteReaction; + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myReplyReaction; } } diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index b268904f04..c989721463 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -104,12 +104,18 @@ class HybridTimelineChannel extends Channel { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; } + if (note.renote && note.renote.reply) { + if (Object.keys(note.renote.reply.reactions).length > 0) { + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + note.renote.reply.myReaction = myReplyReaction; + } + } } if (this.user && note.reply) { if (Object.keys(note.reply.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myRenoteReaction; + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myReplyReaction; } } 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 8d75a0c3a2..2f1720f8fe 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -75,12 +75,18 @@ class LocalTimelineChannel extends Channel { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; } + if (note.renote && note.renote.reply) { + if (Object.keys(note.renote.reply.reactions).length > 0) { + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + note.renote.reply.myReaction = myReplyReaction; + } + } } if (this.user && note.reply) { if (Object.keys(note.reply.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myRenoteReaction; + const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); + note.reply.myReaction = myReplyReaction; } } -- cgit v1.2.3-freya From 9828562e57f742a9beb1a143fee045cd6aa7c470 Mon Sep 17 00:00:00 2001 From: piuvas Date: Mon, 23 Dec 2024 21:03:04 -0300 Subject: testing w/ reactions and stuff --- .../backend/src/core/entities/NoteEntityService.ts | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index eb6b353752..4f229da211 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -529,6 +529,7 @@ export class NoteEntityService implements OnModuleInit { const oldId = this.idService.gen(Date.now() - 2000); for (const note of notes) { + // get my reaction for a renote. if (isPureRenote(note)) { const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); if (reactionsCount === 0) { @@ -544,6 +545,39 @@ export class NoteEntityService implements OnModuleInit { } else { idsNeedFetchMyReaction.add(note.renote.id); } + // get my reaction for OP if this is a renote of a reply. + if (note.renote.reply) { + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reply.reactions, bufferedReactions?.get(note.renote.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.renote.reply.id, null); + } else if (reactionsCount <= note.renote.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.reply.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.renote.reply.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.renote.reply.id, pairInBuffer[1]); + } else { + const pair = note.renote.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.renote.reply.id, pair ? pair.split('/')[1] : null); + } + } else { + idsNeedFetchMyReaction.add(note.renote.reply.id); + } + } + // get my reaction for OP if this is a reply. + } else if (note.reply) { + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reply.reactions, bufferedReactions?.get(note.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.reply.id, null); + } else if (reactionsCount <= note.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.reply.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.reply.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.reply.id, pairInBuffer[1]); + } else { + const pair = note.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.reply.id, pair ? pair.split('/')[1] : null); + } + } else { + idsNeedFetchMyReaction.add(note.reply.id); + } } else { if (note.id < oldId) { const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); -- cgit v1.2.3-freya From e5312da6fe356d77474770575e0534d62c22149a Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 25 Dec 2024 14:19:05 -0300 Subject: improve readability and conciseness --- .../backend/src/core/entities/NoteEntityService.ts | 84 +++++++--------------- 1 file changed, 25 insertions(+), 59 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 4f229da211..be45e75d74 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -528,78 +528,44 @@ export class NoteEntityService implements OnModuleInit { // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない const oldId = this.idService.gen(Date.now() - 2000); + const targetNotes: MiNote[] = []; for (const note of notes) { - // get my reaction for a renote. if (isPureRenote(note)) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.renote.id, null); - } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.renote.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.renote.id, pairInBuffer[1]); - } else { - const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.renote.id); - } - // get my reaction for OP if this is a renote of a reply. + // we may need to fetch 'my reaction' for renote target. + targetNotes.push(note.renote); if (note.renote.reply) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reply.reactions, bufferedReactions?.get(note.renote.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.renote.reply.id, null); - } else if (reactionsCount <= note.renote.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.reply.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.renote.reply.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.renote.reply.id, pairInBuffer[1]); - } else { - const pair = note.renote.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.renote.reply.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.renote.reply.id); - } + // idem if the renote is also a reply. + targetNotes.push(note.renote.reply); } - // get my reaction for OP if this is a reply. } else if (note.reply) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reply.reactions, bufferedReactions?.get(note.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.reply.id, null); - } else if (reactionsCount <= note.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.reply.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.reply.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.reply.id, pairInBuffer[1]); - } else { - const pair = note.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.reply.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.reply.id); - } + // idem for OP of a regular reply. + targetNotes.push(note.reply); } else { if (note.id < oldId) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.id, null); - } else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.id, pairInBuffer[1]); - } else { - const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.id); - } + targetNotes.push(note); } else { myReactionsMap.set(note.id, null); } } } + for (const note of targetNotes) { + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.id, null); + } else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.id, pairInBuffer[1]); + } else { + const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null); + } + } else { + idsNeedFetchMyReaction.add(note.id); + } + } + const myReactions = idsNeedFetchMyReaction.size > 0 ? await this.noteReactionsRepository.findBy({ userId: meId, noteId: In(Array.from(idsNeedFetchMyReaction)), -- cgit v1.2.3-freya From 64abef8be9897db493670a63817ee2876db079b8 Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 25 Dec 2024 16:41:40 -0300 Subject: turn task into a function --- packages/backend/src/server/api/stream/channel.ts | 9 ++++++++ .../server/api/stream/channels/bubble-timeline.ts | 22 +++++++------------ .../server/api/stream/channels/global-timeline.ts | 22 +++++++------------ .../server/api/stream/channels/home-timeline.ts | 22 +++++++------------ .../server/api/stream/channels/hybrid-timeline.ts | 25 ++++++++-------------- .../server/api/stream/channels/local-timeline.ts | 22 +++++++------------ 6 files changed, 50 insertions(+), 72 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index ae9c7e3e99..cded294bf4 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -10,6 +10,7 @@ import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; import type Connection from './Connection.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; /** * Stream channel @@ -101,6 +102,14 @@ export default abstract class Channel { public dispose?(): void; public onMessage?(type: string, body: JsonValue): void; + + public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService) { + if (this.user === undefined) { return; } + if (Object.keys(note.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note, this.user.id); + note.myReaction = myReaction; + } + } } export type MiChannelService = { diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index 41b7b7cd3c..a0875288a2 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -65,25 +65,19 @@ class BubbleTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; + const reactionsToFetch = []; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; - } - if (note.renote && note.renote.reply) { - if (Object.keys(note.renote.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - note.renote.reply.myReaction = myReplyReaction; + if (note.renote) { + reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); + if (note.renote.reply) { + reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } + } else if (this.user && note.reply) { + reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } - if (this.user && note.reply) { - if (Object.keys(note.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myReplyReaction; - } - } + await Promise.all(reactionsToFetch); this.connection.cacheNote(note); 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 6da21ac72c..d133d84c09 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -60,25 +60,19 @@ class GlobalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; + const reactionsToFetch = []; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; - } - if (note.renote && note.renote.reply) { - if (Object.keys(note.renote.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - note.renote.reply.myReaction = myReplyReaction; + if (note.renote) { + reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); + if (note.renote.reply) { + reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } + } else if (this.user && note.reply) { + reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } - if (this.user && note.reply) { - if (Object.keys(note.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myReplyReaction; - } - } + await Promise.all(reactionsToFetch); this.connection.cacheNote(note); diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index d4b65222a0..8c059e2405 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -81,25 +81,19 @@ class HomeTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; + const reactionsToFetch = []; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; - } - if (note.renote && note.renote.reply) { - if (Object.keys(note.renote.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - note.renote.reply.myReaction = myReplyReaction; + if (note.renote) { + reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); + if (note.renote.reply) { + reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } + } else if (this.user && note.reply) { + reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } - if (this.user && note.reply) { - if (Object.keys(note.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myReplyReaction; - } - } + await Promise.all(reactionsToFetch); this.connection.cacheNote(note); diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index c989721463..8eedcdbd59 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -98,26 +98,19 @@ class HybridTimelineChannel extends Channel { } } - if (this.user && note.renoteId && !note.text) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - console.log(note.renote.reactionAndUserPairCache); - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; - } - if (note.renote && note.renote.reply) { - if (Object.keys(note.renote.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - note.renote.reply.myReaction = myReplyReaction; + const reactionsToFetch = []; + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote) { + reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); + if (note.renote.reply) { + reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } + } else if (this.user && note.reply) { + reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } - if (this.user && note.reply) { - if (Object.keys(note.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myReplyReaction; - } - } + await Promise.all(reactionsToFetch); this.connection.cacheNote(note); 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 2f1720f8fe..89187c49fd 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -70,25 +70,19 @@ class LocalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; + const reactionsToFetch = []; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote && Object.keys(note.renote.reactions).length > 0) { - const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); - note.renote.myReaction = myRenoteReaction; - } - if (note.renote && note.renote.reply) { - if (Object.keys(note.renote.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - note.renote.reply.myReaction = myReplyReaction; + if (note.renote) { + reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); + if (note.renote.reply) { + reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } + } else if (this.user && note.reply) { + reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } - if (this.user && note.reply) { - if (Object.keys(note.reply.reactions).length > 0) { - const myReplyReaction = await this.noteEntityService.populateMyReaction(note.reply, this.user.id); - note.reply.myReaction = myReplyReaction; - } - } + await Promise.all(reactionsToFetch); this.connection.cacheNote(note); -- cgit v1.2.3-freya From 8f96b50b00e84c3874129aa4e1317778948fe02d Mon Sep 17 00:00:00 2001 From: piuvas Date: Thu, 26 Dec 2024 12:27:00 -0300 Subject: improvement --- packages/backend/src/server/api/stream/channel.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index cded294bf4..93d5046902 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -104,8 +104,7 @@ export default abstract class Channel { public onMessage?(type: string, body: JsonValue): void; public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService) { - if (this.user === undefined) { return; } - if (Object.keys(note.reactions).length > 0) { + if (this.user && Object.keys(note.reactions).length > 0) { const myReaction = await noteEntityService.populateMyReaction(note, this.user.id); note.myReaction = myReaction; } -- cgit v1.2.3-freya From 2c742d6e82d98776f28558253f9f5ca3e814de2a Mon Sep 17 00:00:00 2001 From: piuvas Date: Thu, 26 Dec 2024 14:49:22 -0300 Subject: requested change. --- packages/backend/src/server/api/stream/channels/bubble-timeline.ts | 3 ++- packages/backend/src/server/api/stream/channels/global-timeline.ts | 3 ++- packages/backend/src/server/api/stream/channels/home-timeline.ts | 3 ++- packages/backend/src/server/api/stream/channels/hybrid-timeline.ts | 3 ++- packages/backend/src/server/api/stream/channels/local-timeline.ts | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index a0875288a2..b2745db92d 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -73,7 +73,8 @@ class BubbleTimelineChannel extends Channel { reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } - } else if (this.user && note.reply) { + } + if (this.user && note.reply) { reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } 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 d133d84c09..8df59d906d 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -68,7 +68,8 @@ class GlobalTimelineChannel extends Channel { reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } - } else if (this.user && note.reply) { + } + if (this.user && note.reply) { reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index 8c059e2405..f48eff85c9 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -89,7 +89,8 @@ class HomeTimelineChannel extends Channel { reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } - } else if (this.user && note.reply) { + } + if (this.user && note.reply) { reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 8eedcdbd59..8c58b2518e 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -106,7 +106,8 @@ class HybridTimelineChannel extends Channel { reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } - } else if (this.user && note.reply) { + } + if (this.user && note.reply) { reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } 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 89187c49fd..cb832bd3c2 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -78,7 +78,8 @@ class LocalTimelineChannel extends Channel { reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); } } - } else if (this.user && note.reply) { + } + if (this.user && note.reply) { reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); } -- cgit v1.2.3-freya From 79b3d2a711cc659b9e6c1668b4d182906986bc50 Mon Sep 17 00:00:00 2001 From: Yuba Date: Sat, 4 Jan 2025 15:03:00 +0900 Subject: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように (#15205) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use LIKE-LOWER instead of ILIKE, which pg_bigm doesn't support. * changelog: Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように --- CHANGELOG.md | 1 + packages/backend/src/core/SearchService.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index fd56700e1f..fc0d1e89c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) +- Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように ## 2024.11.0 diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index edfc470375..f3c11277c5 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -215,7 +215,7 @@ export class SearchService { } query - .andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }) + .andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') -- cgit v1.2.3-freya From 020882edcf2d79bf403c97b860008bf306fe5f90 Mon Sep 17 00:00:00 2001 From: 4ster1sk <146138447+4ster1sk@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:06:19 +0900 Subject: fix(backend): アプリ作成方式で作成したトークンの権限を表示するように (#15177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/server/api/endpoints/i/apps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts index 91c8597b1b..055b5cc061 100644 --- a/packages/backend/src/server/api/endpoints/i/apps.ts +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -87,7 +87,7 @@ export default class extends Endpoint { // eslint- name: token.name ?? token.app?.name, createdAt: this.idService.parse(token.id).date.toISOString(), lastUsedAt: token.lastUsedAt?.toISOString(), - permission: token.permission, + permission: token.app ? token.app.permission : token.permission, }))); }); } -- cgit v1.2.3-freya From 8ad97e5ede6b1cdfe9472073352ede46e585e7c9 Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:19:59 +0900 Subject: fix(backend): disableClustering設定時の初期化ロジックを調整 (#15224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): disableClustering設定時の初期化ロジックを調整 * onlyServer かつ enableCluster な場合、メインプロセスでlistenするとワーカープロセス側のlistenと衝突するため、メインプロセスはforkのみに制限する(listenしない) * ログの追加 * fix CHANGELOG.md * fix comment --- CHANGELOG.md | 2 +- packages/backend/src/boot/entry.ts | 20 +++++++++++++------- packages/backend/src/boot/master.ts | 24 ++++++++++++++++++------ 3 files changed, 32 insertions(+), 14 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ba1afc52..f065ed307f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) - +- Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) ## 2024.11.0 diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index 25375c3015..da585ad68d 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -68,16 +68,22 @@ process.on('exit', code => { //#endregion -if (cluster.isPrimary || envOption.disableClustering) { - await masterMain(); - +if (!envOption.disableClustering) { if (cluster.isPrimary) { + logger.info(`Start main process... pid: ${process.pid}`); + await masterMain(); ev.mount(); + } else if (cluster.isWorker) { + logger.info(`Start worker process... pid: ${process.pid}`); + await workerMain(); + } else { + throw new Error('Unknown process type'); } -} - -if (cluster.isWorker || envOption.disableClustering) { - await workerMain(); +} else { + // 非clusterの場合はMasterのみが起動するため、Workerの処理は行わない(cluster.isWorker === trueの状態でこのブロックに来ることはない) + logger.info(`Start main process... pid: ${process.pid}`); + await masterMain(); + ev.mount(); } readyRef.value = true; diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 4bc5c799cf..2b181af675 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -91,25 +91,37 @@ export async function masterMain() { }); } - if (envOption.disableClustering) { + bootLogger.info( + `mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]` + ); + + if (!envOption.disableClustering) { + // clusterモジュール有効時 + if (envOption.onlyServer) { - await server(); + // onlyServer かつ enableCluster な場合、メインプロセスはforkのみに制限する(listenしない)。 + // ワーカープロセス側でlistenすると、メインプロセスでポートへの着信を受け入れてワーカープロセスへの分配を行う動作をする。 + // そのため、メインプロセスでも直接listenするとポートの競合が発生して起動に失敗してしまう。 + // see: https://nodejs.org/api/cluster.html#cluster } else if (envOption.onlyQueue) { await jobQueue(); } else { await server(); await jobQueue(); } + + await spawnWorkers(config.clusterLimit); } else { + // clusterモジュール無効時 + if (envOption.onlyServer) { - // nop + await server(); } else if (envOption.onlyQueue) { - // nop + await jobQueue(); } else { await server(); + await jobQueue(); } - - await spawnWorkers(config.clusterLimit); } if (envOption.onlyQueue) { -- cgit v1.2.3-freya From d7835313c35be8565d2cfb1a7cc724f95ddf3db7 Mon Sep 17 00:00:00 2001 From: taichan <40626578+tai-cha@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:33:08 +0900 Subject: fix(backend): ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 (#15200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): skipHideなときにもロックダウンされたノートのprivate化をするように * fix linting * Update packages/backend/src/core/entities/NoteEntityService.ts * Fix: type error * Remove unneeded await * Fix: typo * Remove skipTreatVisibillity --- packages/backend/src/core/entities/NoteEntityService.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 96cc6b028e..97f1c3d739 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -102,8 +102,7 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise { - // FIXME: このvisibility変更処理が当関数にあるのは若干不自然かもしれない(関数名を treatVisibility とかに変える手もある) + private treatVisibility(packedNote: Packed<'Note'>): Packed<'Note'>['visibility'] { if (packedNote.visibility === 'public' || packedNote.visibility === 'home') { const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore; if ((followersOnlyBefore != null) @@ -115,7 +114,11 @@ export class NoteEntityService implements OnModuleInit { packedNote.visibility = 'followers'; } } + return packedNote.visibility; + } + @bindThis + private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise { if (meId === packedNote.userId) return; // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど) @@ -458,6 +461,8 @@ export class NoteEntityService implements OnModuleInit { } : {}), }); + this.treatVisibility(packed); + if (!opts.skipHide) { await this.hideNote(packed, meId); } -- cgit v1.2.3-freya From 55713fcd657983add090a7788c6ffd984cbbd15f Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 8 Jan 2025 19:35:09 +0900 Subject: fix(backend): apOrHtml Constraintが正しく評価されない問題を修正 (#15213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend/ActivityPubServerService): apOrHtml Constraintが正しく評価されない問題を修正 (MisskeyIO#869) * Update Changelog * indent --------- Co-authored-by: あわわわとーにゅ <17376330+u1-liquid@users.noreply.github.com> --- CHANGELOG.md | 2 ++ packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b5f6e661..fd8eaf00e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) - Fix: ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 ( #15200 ) - Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) +- Fix: ActivityPubリクエストかどうかの判定が正しくない問題を修正 + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869) ## 2024.11.0 diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index f34f6583d3..8c4b13a40a 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -519,8 +519,8 @@ export class ActivityPubServerService { }, deriveConstraint(request: IncomingMessage) { const accepted = accepts(request).type(['html', ACTIVITY_JSON, LD_JSON]); - const isAp = typeof accepted === 'string' && !accepted.match(/html/); - return isAp ? 'ap' : 'html'; + if (accepted === false) return null; + return accepted !== 'html' ? 'ap' : 'html'; }, }); -- cgit v1.2.3-freya From 5fc9c1c8cdebec868440cd8ed4b3ad84f214e117 Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 12:51:46 -0300 Subject: shallow clone --- packages/backend/src/server/api/stream/channel.ts | 37 +++++++++++++++++++--- .../server/api/stream/channels/bubble-timeline.ts | 23 +++----------- .../server/api/stream/channels/global-timeline.ts | 23 +++----------- .../server/api/stream/channels/home-timeline.ts | 19 ++--------- .../server/api/stream/channels/hybrid-timeline.ts | 19 ++--------- .../server/api/stream/channels/local-timeline.ts | 19 ++--------- 6 files changed, 51 insertions(+), 89 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 93d5046902..ed6e41c4bb 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -9,8 +9,8 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import type Connection from './Connection.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import type Connection from './Connection.js'; /** * Stream channel @@ -103,14 +103,41 @@ export default abstract class Channel { public onMessage?(type: string, body: JsonValue): void; - public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService) { - if (this.user && Object.keys(note.reactions).length > 0) { - const myReaction = await noteEntityService.populateMyReaction(note, this.user.id); - note.myReaction = myReaction; + public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { + let changed = false; + const clonedNote = { ...note }; + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.myReaction = myReaction; + } + } + } + if (this.user && note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.reply = { ...note.renote.reply }; + clonedNote.renote.reply.myReaction = myReaction; + } } + if (this.user && note.reply && Object.keys(note.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.reply = { ...note.reply }; + clonedNote.reply.myReaction = myReaction; + } + } + return changed ? clonedNote : note; } } + export type MiChannelService = { shouldShare: boolean; requireCredential: T; diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index b2745db92d..98ecf16a83 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -65,24 +65,11 @@ class BubbleTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } - - await Promise.all(reactionsToFetch); - - this.connection.cacheNote(note); - - this.send('note', note); + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); + + this.connection.cacheNote(clonedNote); + + this.send('note', clonedNote); } @bindThis 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 8df59d906d..4443b20bed 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -60,24 +60,11 @@ class GlobalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } - - await Promise.all(reactionsToFetch); - - this.connection.cacheNote(note); - - this.send('note', note); + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); + + this.connection.cacheNote(clonedNote); + + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index f48eff85c9..af1b17b533 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -81,24 +81,11 @@ class HomeTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } - - await Promise.all(reactionsToFetch); + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - this.connection.cacheNote(note); + this.connection.cacheNote(clonedNote); - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 8c58b2518e..7c604c0b58 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -98,24 +98,11 @@ class HybridTimelineChannel extends Channel { } } - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } - - await Promise.all(reactionsToFetch); + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - this.connection.cacheNote(note); + this.connection.cacheNote(clonedNote); - this.send('note', note); + this.send('note', clonedNote); } @bindThis 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 cb832bd3c2..2d48b6ecfb 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -70,24 +70,11 @@ class LocalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } - - await Promise.all(reactionsToFetch); + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - this.connection.cacheNote(note); + this.connection.cacheNote(clonedNote); - this.send('note', note); + this.send('note', clonedNote); } @bindThis -- cgit v1.2.3-freya From e76e6cd08f69ddd7f8c62039b9d3e64a7a6189ca Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 12:58:57 -0300 Subject: small refactor --- packages/backend/src/server/api/stream/channel.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index ed6e41c4bb..6589a7af8d 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -115,14 +115,14 @@ export default abstract class Channel { clonedNote.renote.myReaction = myReaction; } } - } - if (this.user && note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { - const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - if (myReaction) { - changed = true; - clonedNote.renote = { ...note.renote }; - clonedNote.renote.reply = { ...note.renote.reply }; - clonedNote.renote.reply.myReaction = myReaction; + if (note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.reply = { ...note.renote.reply }; + clonedNote.renote.reply.myReaction = myReaction; + } } } if (this.user && note.reply && Object.keys(note.reply.reactions).length > 0) { -- cgit v1.2.3-freya From a3fc9a1085c7ad1fedf64fd6417c04cdcc936887 Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 13:10:20 -0300 Subject: comment code --- packages/backend/src/server/api/stream/channel.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 6589a7af8d..7a044296fe 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -105,6 +105,8 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; + // cloning here seems like the best solution for a race condition + // where multiple users shared the same myReaction. (Sharkey #877) const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { -- cgit v1.2.3-freya From f1d9bb2cf1d9045ec13cdeb231656054b9e5bfde Mon Sep 17 00:00:00 2001 From: piuvas Date: Fri, 10 Jan 2025 22:10:18 -0300 Subject: requested changes --- packages/backend/src/server/api/stream/channel.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 7a044296fe..cfac4b30ce 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -9,8 +9,8 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type Connection from './Connection.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; /** * Stream channel @@ -105,7 +105,7 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; - // cloning here seems like the best solution for a race condition + // cloning here seems like the best solution for not sharing changes with other users. // where multiple users shared the same myReaction. (Sharkey #877) const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { @@ -139,7 +139,6 @@ export default abstract class Channel { } } - export type MiChannelService = { shouldShare: boolean; requireCredential: T; -- cgit v1.2.3-freya From c4192e81ed30292a06b85e7b00578ca87c97d076 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 11 Jan 2025 22:43:42 +0900 Subject: enhance(backend): チャートの処理を一つずつ行うことでDBの同時接続とタイムアウトを削減 (#15239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sync charts one-at-a-time to reduce database contention and timeouts * fix merge resolve failure * Update Changelog * update changelog * add comments --------- Co-authored-by: Hazelnoot --- CHANGELOG.md | 2 ++ .../src/core/chart/ChartManagementService.ts | 10 ++++---- .../processors/CleanChartsProcessorService.ts | 27 +++++++++++----------- .../processors/ResyncChartsProcessorService.ts | 9 ++++---- .../queue/processors/TickChartsProcessorService.ts | 27 +++++++++++----------- 5 files changed, 37 insertions(+), 38 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index fd8eaf00e8..75dd37bc24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ ### Server - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように +- Enhance: チャート更新時にDBに同時接続しないように + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/830) - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index 79681370a1..f04c561063 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -58,9 +58,9 @@ export class ChartManagementService implements OnApplicationShutdown { @bindThis public async start() { // 20分おきにメモリ情報をDBに書き込み - this.saveIntervalId = setInterval(() => { + this.saveIntervalId = setInterval(async () => { for (const chart of this.charts) { - chart.save(); + await chart.save(); } }, 1000 * 60 * 20); } @@ -69,9 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown { public async dispose(): Promise { clearInterval(this.saveIntervalId); if (process.env.NODE_ENV !== 'test') { - await Promise.all( - this.charts.map(chart => chart.save()), - ); + for (const chart of this.charts) { + await chart.save(); + } } } diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts index 110468801c..8c5faa8d07 100644 --- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts @@ -48,20 +48,19 @@ export class CleanChartsProcessorService { public async process(): Promise { this.logger.info('Clean charts...'); - await Promise.all([ - this.federationChart.clean(), - this.notesChart.clean(), - this.usersChart.clean(), - this.activeUsersChart.clean(), - this.instanceChart.clean(), - this.perUserNotesChart.clean(), - this.perUserPvChart.clean(), - this.driveChart.clean(), - this.perUserReactionsChart.clean(), - this.perUserFollowingChart.clean(), - this.perUserDriveChart.clean(), - this.apRequestChart.clean(), - ]); + // DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する + await this.federationChart.clean(); + await this.notesChart.clean(); + await this.usersChart.clean(); + await this.activeUsersChart.clean(); + await this.instanceChart.clean(); + await this.perUserNotesChart.clean(); + await this.perUserPvChart.clean(); + await this.driveChart.clean(); + await this.perUserReactionsChart.clean(); + await this.perUserFollowingChart.clean(); + await this.perUserDriveChart.clean(); + await this.apRequestChart.clean(); this.logger.succ('All charts successfully cleaned.'); } diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts index 570cdf9a75..0c47fdedb3 100644 --- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts @@ -29,13 +29,12 @@ export class ResyncChartsProcessorService { public async process(): Promise { this.logger.info('Resync charts...'); + // DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する // TODO: ユーザーごとのチャートも更新する // TODO: インスタンスごとのチャートも更新する - await Promise.all([ - this.driveChart.resync(), - this.notesChart.resync(), - this.usersChart.resync(), - ]); + await this.driveChart.resync(); + await this.notesChart.resync(); + await this.usersChart.resync(); this.logger.succ('All charts successfully resynced.'); } diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts index 93ec34162d..fc8856a271 100644 --- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts +++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts @@ -48,20 +48,19 @@ export class TickChartsProcessorService { public async process(): Promise { this.logger.info('Tick charts...'); - await Promise.all([ - this.federationChart.tick(false), - this.notesChart.tick(false), - this.usersChart.tick(false), - this.activeUsersChart.tick(false), - this.instanceChart.tick(false), - this.perUserNotesChart.tick(false), - this.perUserPvChart.tick(false), - this.driveChart.tick(false), - this.perUserReactionsChart.tick(false), - this.perUserFollowingChart.tick(false), - this.perUserDriveChart.tick(false), - this.apRequestChart.tick(false), - ]); + // DBへの同時接続を避けるためにPromise.allを使わずひとつずつ実行する + await this.federationChart.tick(false); + await this.notesChart.tick(false); + await this.usersChart.tick(false); + await this.activeUsersChart.tick(false); + await this.instanceChart.tick(false); + await this.perUserNotesChart.tick(false); + await this.perUserPvChart.tick(false); + await this.driveChart.tick(false); + await this.perUserReactionsChart.tick(false); + await this.perUserFollowingChart.tick(false); + await this.perUserDriveChart.tick(false); + await this.apRequestChart.tick(false); this.logger.succ('All charts successfully ticked.'); } -- cgit v1.2.3-freya From 3ceac893c942b0baad2f8a0c6b51baa2b8477b5e Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 12 Jan 2025 12:33:08 +0000 Subject: attribute invite codes to admins/moderators when a regular user (who has the appropriate permissions) creates an invite, we record that user's id in the `createdById` column but when an admin/mod creates an invite via the control panel, we didn't now we do --- packages/backend/src/server/api/endpoints/admin/invite/create.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts index 5ecae3161a..e52b177e2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -68,6 +68,8 @@ export default class extends Endpoint { // eslint- for (let i = 0; i < ps.count; i++) { ticketsPromises.push(this.registrationTicketsRepository.insertOne({ id: this.idService.gen(), + createdBy: me, + createdById: me.id, expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, code: generateInviteCode(), })); -- cgit v1.2.3-freya From 3fc377839c6155d1eb3c55ae7eb24e85e4ad90c7 Mon Sep 17 00:00:00 2001 From: piuvas Date: Sun, 12 Jan 2025 21:11:36 -0300 Subject: comment :3 --- packages/backend/src/server/api/stream/channel.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index cfac4b30ce..047dedd5ce 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -105,8 +105,11 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; - // cloning here seems like the best solution for not sharing changes with other users. - // where multiple users shared the same myReaction. (Sharkey #877) + // StreamingApiServerService creates a single EventEmitter per server process, + // so a new note arriving from redis gets de-serialised once per server process, + // and then that single object is passed to all active channels on each connection. + // If we didn't clone the notes here, different connections would asynchronously write + // different values to the same object, resulting in a random value being sent to each frontend. -- Dakkar const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { -- cgit v1.2.3-freya From 6820878676f5f531bc596076ed5c5ea022051631 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 14 Jan 2025 19:36:35 +0900 Subject: fix: unable to use AiService on arm64 (#15261) --- CHANGELOG.md | 1 + packages/backend/src/core/AiService.ts | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c63226c58..b9f0d6495a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) - Fix: ActivityPubリクエストかどうかの判定が正しくない問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869) +- Fix: AIセンシティブ判定が arm64 環境で動作しない問題を修正 ## 2024.11.0 diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts index ad852fdd6e..33ddabb5e0 100644 --- a/packages/backend/src/core/AiService.ts +++ b/packages/backend/src/core/AiService.ts @@ -15,7 +15,7 @@ import { bindThis } from '@/decorators.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); -const REQUIRED_CPU_FLAGS = ['avx2', 'fma']; +const REQUIRED_CPU_FLAGS_X64 = ['avx2', 'fma']; let isSupportedCpu: undefined | boolean = undefined; @Injectable() @@ -31,8 +31,7 @@ export class AiService { public async detectSensitive(path: string): Promise { try { if (isSupportedCpu === undefined) { - const cpuFlags = await this.getCpuFlags(); - isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required)); + isSupportedCpu = await this.computeIsSupportedCpu(); } if (!isSupportedCpu) { @@ -64,6 +63,22 @@ export class AiService { } } + private async computeIsSupportedCpu(): Promise { + switch (process.arch) { + case 'x64': { + const cpuFlags = await this.getCpuFlags(); + return REQUIRED_CPU_FLAGS_X64.every(required => cpuFlags.includes(required)); + } + case 'arm64': { + // As far as I know, no required CPU flags for ARM64. + return true; + } + default: { + return false; + } + } + } + @bindThis private async getCpuFlags(): Promise { const str = await si.cpuFlags(); -- cgit v1.2.3-freya From 759b9f4cf133dfa6b06c6fcebf22aefc017363bd Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:37:41 +0900 Subject: feat(backend): config(default.yml)からSQLログ全文を出力するか否かを設定可能に (#15268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feature(backend): config(default.yml)からSQLログ全文を出力するか否かを設定可能に * disableHighlightやめる * refactor --- .config/docker_example.yml | 10 +++++++ .config/example.yml | 10 +++++++ CHANGELOG.md | 1 + packages/backend/src/config.ts | 14 ++++++++++ packages/backend/src/postgres.ts | 59 ++++++++++++++++++++++++++++++++++------ 5 files changed, 86 insertions(+), 8 deletions(-) (limited to 'packages/backend/src') diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 3f8e5734ce..0f5ba9696d 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -219,3 +219,13 @@ signToActivityPubGet: true # Upload or download file size limits (bytes) #maxFileSize: 262144000 + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/.config/example.yml b/.config/example.yml index 60a6a0aa71..ea29cedd10 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -321,3 +321,13 @@ signToActivityPubGet: true # PID File of master process #pidFile: /tmp/misskey.pid + +# Log settings +# logging: +# sql: +# # Outputs query parameters during SQL execution to the log. +# # default: false +# enableQueryParamLogging: false +# # Disable query truncation. If set to true, the full text of the query will be output to the log. +# # default: false +# disableQueryTruncation: false diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f0d6495a..071cf22a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Enhance: pg_bigmが利用できるよう、ノートの検索をILIKE演算子でなくLIKE演算子でLOWER()をかけたテキストに対して行うように - Enhance: チャート更新時にDBに同時接続しないように (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/830) +- Enhance: config(default.yml)からSQLログ全文を出力するか否かを設定可能に ( #15266 ) - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 42f1033b9d..a5dc61db13 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -99,6 +99,13 @@ type Source = { perUserNotificationsMaxCount?: number; deactivateAntennaThreshold?: number; pidFile: string; + + logging?: { + sql?: { + disableQueryTruncation? : boolean, + enableQueryParamLogging? : boolean, + } + } }; export type Config = { @@ -151,6 +158,12 @@ export type Config = { inboxJobMaxAttempts: number | undefined; proxyRemoteFiles: boolean | undefined; signToActivityPubGet: boolean | undefined; + logging?: { + sql?: { + disableQueryTruncation? : boolean, + enableQueryParamLogging? : boolean, + } + } version: string; publishTarballInsteadOfProvideRepositoryUrl: boolean; @@ -293,6 +306,7 @@ export function loadConfig(): Config { perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500, deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7), pidFile: config.pidFile, + logging: config.logging, }; } diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 251a03c303..d09240eba1 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -89,27 +89,65 @@ export const dbLogger = new MisskeyLogger('db'); const sqlLogger = dbLogger.createSubLogger('sql', 'gray'); +export type LoggerProps = { + disableQueryTruncation?: boolean; + enableQueryParamLogging?: boolean; +} + +function highlightSql(sql: string) { + return highlight.highlight(sql, { + language: 'sql', ignoreIllegals: true, + }); +} + +function truncateSql(sql: string) { + return sql.length > 100 ? `${sql.substring(0, 100)}...` : sql; +} + +function stringifyParameter(param: any) { + if (param instanceof Date) { + return param.toISOString(); + } else { + return param; + } +} + class MyCustomLogger implements Logger { + constructor(private props: LoggerProps = {}) { + } + + @bindThis + private transformQueryLog(sql: string) { + let modded = sql; + if (!this.props.disableQueryTruncation) { + modded = truncateSql(modded); + } + + return highlightSql(modded); + } + @bindThis - private highlight(sql: string) { - return highlight.highlight(sql, { - language: 'sql', ignoreIllegals: true, - }); + private transformParameters(parameters?: any[]) { + if (this.props.enableQueryParamLogging && parameters && parameters.length > 0) { + return parameters.map(stringifyParameter); + } + + return undefined; } @bindThis public logQuery(query: string, parameters?: any[]) { - sqlLogger.info(this.highlight(query).substring(0, 100)); + sqlLogger.info(this.transformQueryLog(query), this.transformParameters(parameters)); } @bindThis public logQueryError(error: string, query: string, parameters?: any[]) { - sqlLogger.error(this.highlight(query)); + sqlLogger.error(this.transformQueryLog(query), this.transformParameters(parameters)); } @bindThis public logQuerySlow(time: number, query: string, parameters?: any[]) { - sqlLogger.warn(this.highlight(query)); + sqlLogger.warn(this.transformQueryLog(query), this.transformParameters(parameters)); } @bindThis @@ -247,7 +285,12 @@ export function createPostgresDataSource(config: Config) { }, } : false, logging: log, - logger: log ? new MyCustomLogger() : undefined, + logger: log + ? new MyCustomLogger({ + disableQueryTruncation: config.logging?.sql?.disableQueryTruncation, + enableQueryParamLogging: config.logging?.sql?.enableQueryParamLogging, + }) + : undefined, maxQueryExecutionTime: 300, entities: entities, migrations: ['../../migration/*.js'], -- cgit v1.2.3-freya From 7fbfc2e046c978c8d544b3e6e8c8e77fc7ba8da6 Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:47:02 +0900 Subject: ApPersonServiceとApNoteServiceのuri <-> url比較を緩和 (#15233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * https://github.com/misskey-dev/misskey/issues/15039#issuecomment-2576411861 の反映 Co-authored-by: Kagami Sascha Rosylight * fix CHANGELOG.md * remove inspection --------- Co-authored-by: Kagami Sascha Rosylight --- CHANGELOG.md | 1 + .../src/core/activitypub/misc/check-against-url.ts | 6 ++++-- .../src/core/activitypub/models/ApNoteService.ts | 10 ++-------- .../src/core/activitypub/models/ApPersonService.ts | 20 +++++++++----------- 4 files changed, 16 insertions(+), 21 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index f5fd739232..eb9f9aaeeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) - Fix: ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 ( #15200 ) - Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) +- Fix: URLとURIが異なるエンティティの照会に失敗する問題を修正( #15039 ) - Fix: ActivityPubリクエストかどうかの判定が正しくない問題を修正 (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/869) - Fix: AIセンシティブ判定が arm64 環境で動作しない問題を修正 diff --git a/packages/backend/src/core/activitypub/misc/check-against-url.ts b/packages/backend/src/core/activitypub/misc/check-against-url.ts index 78ba891a2e..d679bd8180 100644 --- a/packages/backend/src/core/activitypub/misc/check-against-url.ts +++ b/packages/backend/src/core/activitypub/misc/check-against-url.ts @@ -5,13 +5,15 @@ import type { IObject } from '../type.js'; export function assertActivityMatchesUrls(activity: IObject, urls: string[]) { - const idOk = activity.id !== undefined && urls.includes(activity.id); + const hosts = urls.map(it => new URL(it).host); + + const idOk = activity.id !== undefined && hosts.includes(new URL(activity.id).host); // technically `activity.url` could be an `ApObject = IObject | // string | (IObject | string)[]`, but if it's a complicated thing // and the `activity.id` doesn't match, I think we're fine // rejecting the activity - const urlOk = typeof(activity.url) === 'string' && urls.includes(activity.url); + const urlOk = typeof(activity.url) === 'string' && hosts.includes(new URL(activity.url).host); if (!idOk && !urlOk) { throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${activity?.url}) match location(${urls})`); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index eb2e771a38..cb25aa54b0 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -154,14 +154,8 @@ export class ApNoteService { const url = getOneApHrefNullable(note.url); - if (url != null) { - if (!checkHttps(url)) { - throw new Error('unexpected schema of note url: ' + url); - } - - if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) { - throw new Error(`note url & uri host mismatch: note url: ${url}, note uri: ${note.id}`); - } + if (url && !checkHttps(url)) { + throw new Error('unexpected schema of note url: ' + url); } this.logger.info(`Creating the Note: ${note.id}`); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 8590861ca0..6019906add 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -157,8 +157,12 @@ export class ApPersonService implements OnModuleInit { const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined); if (sharedInboxObject != null) { const sharedInbox = getApId(sharedInboxObject); - if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && this.utilityService.punyHost(sharedInbox) === expectHost)) { - throw new Error('invalid Actor: wrong shared inbox'); + if (!(typeof sharedInbox === 'string' && sharedInbox.length > 0 && new URL(sharedInbox).host === expectHost)) { + this.logger.warn(`invalid Actor: skipping wrong shared inbox, expected host: ${expectHost}, actual URL: ${sharedInbox}`); + x.sharedInbox = undefined; + if (x.endpoints?.sharedInbox) { + x.endpoints.sharedInbox = undefined; + } } } @@ -257,7 +261,7 @@ export class ApPersonService implements OnModuleInit { if (Array.isArray(img)) { img = img.find(item => item && item.url) ?? null; } - + // if we have an explicitly missing image, return an // explicitly-null set of values if ((img == null) || (typeof img === 'object' && img.url == null)) { @@ -344,14 +348,8 @@ export class ApPersonService implements OnModuleInit { throw new Error('Refusing to create person without id'); } - if (url != null) { - if (!checkHttps(url)) { - throw new Error('unexpected schema of person url: ' + url); - } - - if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(person.id)) { - throw new Error(`person url <> uri host mismatch: ${url} <> ${person.id}`); - } + if (url && !checkHttps(url)) { + throw new Error('unexpected schema of person url: ' + url); } // Create user -- cgit v1.2.3-freya From 64501c69a10323067dee739790b5a4fc5104e50d Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:57:58 +0900 Subject: feat(frontend): Botプロテクションの設定変更時は実際に検証を通過しないと保存できないようにする (#15151) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(frontend): CAPTCHAの設定変更時は実際に検証を通過しないと保存できないようにする * なしでも保存できるようにした * fix CHANGELOG.md * フォームが増殖するのを修正 * add comment * add server-side verify * fix ci * fix * fix * fix i18n * add current.ts * fix text * fix * regenerate locales * fix MkFormFooter.vue --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + locales/index.d.ts | 43 ++ locales/ja-JP.yml | 14 + packages/backend/src/core/CaptchaService.ts | 299 +++++++++- packages/backend/src/server/api/EndpointsModule.ts | 8 + packages/backend/src/server/api/endpoints.ts | 4 + .../server/api/endpoints/admin/captcha/current.ts | 70 +++ .../src/server/api/endpoints/admin/captcha/save.ts | 129 +++++ packages/backend/test/unit/CaptchaService.ts | 622 +++++++++++++++++++++ packages/frontend/src/components/MkCaptcha.vue | 64 ++- packages/frontend/src/components/MkFormFooter.vue | 9 +- packages/frontend/src/index.html | 2 +- packages/frontend/src/os.ts | 5 +- .../frontend/src/pages/admin/bot-protection.vue | 240 +++++--- packages/misskey-js/etc/misskey-js.api.md | 8 + packages/misskey-js/src/autogen/apiClientJSDoc.ts | 22 + packages/misskey-js/src/autogen/endpoint.ts | 4 + packages/misskey-js/src/autogen/entities.ts | 2 + packages/misskey-js/src/autogen/types.ts | 140 +++++ 19 files changed, 1597 insertions(+), 89 deletions(-) create mode 100644 packages/backend/src/server/api/endpoints/admin/captcha/current.ts create mode 100644 packages/backend/src/server/api/endpoints/admin/captcha/save.ts create mode 100644 packages/backend/test/unit/CaptchaService.ts (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index eb9f9aaeeb..af5d333927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803) - Fix: 絵文字管理画面で一部の絵文字が表示されない問題を修正 +- Fix: Botプロテクションの設定変更時は実際に検証を通過しないと保存できないように( #15137 ) - Fix: ノート検索が使用できない場合でもチャンネルのノート検索欄がでていた問題を修正 - Fix: `Ui:C:select`で値の変更が画面に反映されない問題を修正 - Fix: MiAuth認可画面で、認可処理に失敗した場合でもコールバックURLに遷移してしまう問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index e85d6a3bd5..7c3ef5d93c 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -10668,6 +10668,49 @@ export interface Locale extends ILocale { "description": string; }; }; + "_captcha": { + /** + * CAPTCHAを通過してください + */ + "verify": string; + /** + * サイトキーとシークレットキーにテスト用の値を入力することでプレビューを確認できます。 + * 詳細は下記ページをご確認ください。 + */ + "testSiteKeyMessage": string; + "_error": { + "_requestFailed": { + /** + * CAPTCHAのリクエストに失敗しました + */ + "title": string; + /** + * しばらく後に実行するか、設定をもう一度ご確認ください。 + */ + "text": string; + }; + "_verificationFailed": { + /** + * CAPTCHAの検証に失敗しました + */ + "title": string; + /** + * 設定が正しいかどうかもう一度確認ください。 + */ + "text": string; + }; + "_unknown": { + /** + * CAPTCHAエラー + */ + "title": string; + /** + * 想定外のエラーが発生しました。 + */ + "text": string; + }; + }; + }; } declare const locales: { [lang: string]: Locale; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 37e51b9398..57a88062c1 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2847,3 +2847,17 @@ _remoteLookupErrors: _noSuchObject: title: "見つかりません" description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。" + +_captcha: + verify: "CAPTCHAを通過してください" + testSiteKeyMessage: "サイトキーとシークレットキーにテスト用の値を入力することでプレビューを確認できます。\n詳細は下記ページをご確認ください。" + _error: + _requestFailed: + title: "CAPTCHAのリクエストに失敗しました" + text: "しばらく後に実行するか、設定をもう一度ご確認ください。" + _verificationFailed: + title: "CAPTCHAの検証に失敗しました" + text: "設定が正しいかどうかもう一度確認ください。" + _unknown: + title: "CAPTCHAエラー" + text: "想定外のエラーが発生しました。" diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 206d0dbe0a..8c7f66236e 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -6,6 +6,65 @@ import { Injectable } from '@nestjs/common'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; +import { MiMeta } from '@/models/Meta.js'; +import Logger from '@/logger.js'; +import { LoggerService } from './LoggerService.js'; + +export const supportedCaptchaProviders = ['none', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile', 'testcaptcha'] as const; +export type CaptchaProvider = typeof supportedCaptchaProviders[number]; + +export const captchaErrorCodes = { + invalidProvider: Symbol('invalidProvider'), + invalidParameters: Symbol('invalidParameters'), + noResponseProvided: Symbol('noResponseProvided'), + requestFailed: Symbol('requestFailed'), + verificationFailed: Symbol('verificationFailed'), + unknown: Symbol('unknown'), +} as const; +export type CaptchaErrorCode = typeof captchaErrorCodes[keyof typeof captchaErrorCodes]; + +export type CaptchaSetting = { + provider: CaptchaProvider; + hcaptcha: { + siteKey: string | null; + secretKey: string | null; + } + mcaptcha: { + siteKey: string | null; + secretKey: string | null; + instanceUrl: string | null; + } + recaptcha: { + siteKey: string | null; + secretKey: string | null; + } + turnstile: { + siteKey: string | null; + secretKey: string | null; + } +} + +export class CaptchaError extends Error { + public readonly code: CaptchaErrorCode; + public readonly cause?: unknown; + + constructor(code: CaptchaErrorCode, message: string, cause?: unknown) { + super(message); + this.code = code; + this.cause = cause; + this.name = 'CaptchaError'; + } +} + +export type CaptchaSaveSuccess = { + success: true; +} +export type CaptchaSaveFailure = { + success: false; + error: CaptchaError; +} +export type CaptchaSaveResult = CaptchaSaveSuccess | CaptchaSaveFailure; type CaptchaResponse = { success: boolean; @@ -14,9 +73,14 @@ type CaptchaResponse = { @Injectable() export class CaptchaService { + private readonly logger: Logger; + constructor( private httpRequestService: HttpRequestService, + private metaService: MetaService, + loggerService: LoggerService, ) { + this.logger = loggerService.getLogger('captcha'); } @bindThis @@ -44,32 +108,32 @@ export class CaptchaService { @bindThis public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('recaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'recaptcha-failed: no response provided'); } const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => { - throw new Error(`recaptcha-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `recaptcha-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw new Error(`recaptcha-failed: ${errorCodes}`); + throw new CaptchaError(captchaErrorCodes.verificationFailed, `recaptcha-failed: ${errorCodes}`); } } @bindThis public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('hcaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'hcaptcha-failed: no response provided'); } const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => { - throw new Error(`hcaptcha-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `hcaptcha-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw new Error(`hcaptcha-failed: ${errorCodes}`); + throw new CaptchaError(captchaErrorCodes.verificationFailed, `hcaptcha-failed: ${errorCodes}`); } } @@ -77,7 +141,7 @@ export class CaptchaService { @bindThis public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('mcaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'mcaptcha-failed: no response provided'); } const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost); @@ -91,46 +155,251 @@ export class CaptchaService { headers: { 'Content-Type': 'application/json', }, - }); + }, { throwErrorWhenResponseNotOk: false }); if (result.status !== 200) { - throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK'); + throw new CaptchaError(captchaErrorCodes.requestFailed, 'mcaptcha-failed: mcaptcha didn\'t return 200 OK'); } const resp = (await result.json()) as { valid: boolean }; if (!resp.valid) { - throw new Error('mcaptcha-request-failed'); + throw new CaptchaError(captchaErrorCodes.verificationFailed, 'mcaptcha-request-failed'); } } @bindThis public async verifyTurnstile(secret: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('turnstile-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'turnstile-failed: no response provided'); } const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => { - throw new Error(`turnstile-request-failed: ${err}`); + throw new CaptchaError(captchaErrorCodes.requestFailed, `turnstile-request-failed: ${err}`); }); if (result.success !== true) { const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : ''; - throw new Error(`turnstile-failed: ${errorCodes}`); + throw new CaptchaError(captchaErrorCodes.verificationFailed, `turnstile-failed: ${errorCodes}`); } } @bindThis public async verifyTestcaptcha(response: string | null | undefined): Promise { if (response == null) { - throw new Error('testcaptcha-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'testcaptcha-failed: no response provided'); } const success = response === 'testcaptcha-passed'; if (!success) { - throw new Error('testcaptcha-failed'); + throw new CaptchaError(captchaErrorCodes.verificationFailed, 'testcaptcha-failed'); + } + } + + @bindThis + public async get(): Promise { + const meta = await this.metaService.fetch(true); + + let provider: CaptchaProvider; + switch (true) { + case meta.enableHcaptcha: { + provider = 'hcaptcha'; + break; + } + case meta.enableMcaptcha: { + provider = 'mcaptcha'; + break; + } + case meta.enableRecaptcha: { + provider = 'recaptcha'; + break; + } + case meta.enableTurnstile: { + provider = 'turnstile'; + break; + } + case meta.enableTestcaptcha: { + provider = 'testcaptcha'; + break; + } + default: { + provider = 'none'; + break; + } + } + + return { + provider: provider, + hcaptcha: { + siteKey: meta.hcaptchaSiteKey, + secretKey: meta.hcaptchaSecretKey, + }, + mcaptcha: { + siteKey: meta.mcaptchaSitekey, + secretKey: meta.mcaptchaSecretKey, + instanceUrl: meta.mcaptchaInstanceUrl, + }, + recaptcha: { + siteKey: meta.recaptchaSiteKey, + secretKey: meta.recaptchaSecretKey, + }, + turnstile: { + siteKey: meta.turnstileSiteKey, + secretKey: meta.turnstileSecretKey, + }, + }; + } + + /** + * captchaの設定を更新します. その際、フロントエンド側で受け取ったcaptchaからの戻り値を検証し、passした場合のみ設定を更新します. + * 実際の検証処理はサービス内で定義されている各captchaプロバイダの検証関数に委譲します. + * + * @param provider 検証するcaptchaのプロバイダ + * @param params + * @param params.sitekey hcaptcha, recaptcha, turnstile, mcaptchaの場合に指定するsitekey. それ以外のプロバイダでは無視されます + * @param params.secret hcaptcha, recaptcha, turnstile, mcaptchaの場合に指定するsecret. それ以外のプロバイダでは無視されます + * @param params.instanceUrl mcaptchaの場合に指定するインスタンスのURL. それ以外のプロバイダでは無視されます + * @param params.captchaResult フロントエンド側で受け取ったcaptchaプロバイダからの戻り値. この値を使ってサーバサイドでの検証を行います + * @see verifyHcaptcha + * @see verifyMcaptcha + * @see verifyRecaptcha + * @see verifyTurnstile + * @see verifyTestcaptcha + */ + @bindThis + public async save( + provider: CaptchaProvider, + params?: { + sitekey?: string | null; + secret?: string | null; + instanceUrl?: string | null; + captchaResult?: string | null; + }, + ): Promise { + if (!supportedCaptchaProviders.includes(provider)) { + return { + success: false, + error: new CaptchaError(captchaErrorCodes.invalidProvider, `Invalid captcha provider: ${provider}`), + }; + } + + const operation = { + none: async () => { + await this.updateMeta(provider, params); + }, + hcaptcha: async () => { + if (!params?.secret || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'hcaptcha-failed: secret and captureResult are required'); + } + + await this.verifyHcaptcha(params.secret, params.captchaResult); + await this.updateMeta(provider, params); + }, + mcaptcha: async () => { + if (!params?.secret || !params.sitekey || !params.instanceUrl || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'mcaptcha-failed: secret, sitekey, instanceUrl and captureResult are required'); + } + + await this.verifyMcaptcha(params.secret, params.sitekey, params.instanceUrl, params.captchaResult); + await this.updateMeta(provider, params); + }, + recaptcha: async () => { + if (!params?.secret || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'recaptcha-failed: secret and captureResult are required'); + } + + await this.verifyRecaptcha(params.secret, params.captchaResult); + await this.updateMeta(provider, params); + }, + turnstile: async () => { + if (!params?.secret || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: secret and captureResult are required'); + } + + await this.verifyTurnstile(params.secret, params.captchaResult); + await this.updateMeta(provider, params); + }, + testcaptcha: async () => { + if (!params?.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'turnstile-failed: captureResult are required'); + } + + await this.verifyTestcaptcha(params.captchaResult); + await this.updateMeta(provider, params); + }, + }[provider]; + + return operation() + .then(() => ({ success: true }) as CaptchaSaveSuccess) + .catch(err => { + this.logger.info(err); + const error = err instanceof CaptchaError + ? err + : new CaptchaError(captchaErrorCodes.unknown, `unknown error: ${err}`); + return { + success: false, + error, + }; + }); + } + + @bindThis + private async updateMeta( + provider: CaptchaProvider, + params?: { + sitekey?: string | null; + secret?: string | null; + instanceUrl?: string | null; + }, + ) { + const metaPartial: Partial< + Pick< + MiMeta, + ('enableHcaptcha' | 'hcaptchaSiteKey' | 'hcaptchaSecretKey') | + ('enableMcaptcha' | 'mcaptchaSitekey' | 'mcaptchaSecretKey' | 'mcaptchaInstanceUrl') | + ('enableRecaptcha' | 'recaptchaSiteKey' | 'recaptchaSecretKey') | + ('enableTurnstile' | 'turnstileSiteKey' | 'turnstileSecretKey') | + ('enableTestcaptcha') + > + > = { + enableHcaptcha: provider === 'hcaptcha', + enableMcaptcha: provider === 'mcaptcha', + enableRecaptcha: provider === 'recaptcha', + enableTurnstile: provider === 'turnstile', + enableTestcaptcha: provider === 'testcaptcha', + }; + + const updateIfNotUndefined = (key: K, value: typeof metaPartial[K]) => { + if (value !== undefined) { + metaPartial[key] = value; + } + }; + switch (provider) { + case 'hcaptcha': { + updateIfNotUndefined('hcaptchaSiteKey', params?.sitekey); + updateIfNotUndefined('hcaptchaSecretKey', params?.secret); + break; + } + case 'mcaptcha': { + updateIfNotUndefined('mcaptchaSitekey', params?.sitekey); + updateIfNotUndefined('mcaptchaSecretKey', params?.secret); + updateIfNotUndefined('mcaptchaInstanceUrl', params?.instanceUrl); + break; + } + case 'recaptcha': { + updateIfNotUndefined('recaptchaSiteKey', params?.sitekey); + updateIfNotUndefined('recaptchaSecretKey', params?.secret); + break; + } + case 'turnstile': { + updateIfNotUndefined('turnstileSiteKey', params?.sitekey); + updateIfNotUndefined('turnstileSecretKey', params?.secret); + break; + } } + + await this.metaService.update(metaPartial); } } diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 5bb194313d..c2462d8b3d 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -28,6 +28,8 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js'; import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js'; import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js'; +import * as ep___admin_captcha_current from './endpoints/admin/captcha/current.js'; +import * as ep___admin_captcha_save from './endpoints/admin/captcha/save.js'; import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js'; import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js'; @@ -416,6 +418,8 @@ const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-de const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default }; const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default }; const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default }; +const $admin_captcha_current: Provider = { provide: 'ep:admin/captcha/current', useClass: ep___admin_captcha_current.default }; +const $admin_captcha_save: Provider = { provide: 'ep:admin/captcha/save', useClass: ep___admin_captcha_save.default }; const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default }; const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default }; const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default }; @@ -808,6 +812,8 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_avatarDecorations_delete, $admin_avatarDecorations_list, $admin_avatarDecorations_update, + $admin_captcha_current, + $admin_captcha_save, $admin_deleteAllFilesOfAUser, $admin_unsetUserAvatar, $admin_unsetUserBanner, @@ -1194,6 +1200,8 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_avatarDecorations_delete, $admin_avatarDecorations_list, $admin_avatarDecorations_update, + $admin_captcha_current, + $admin_captcha_save, $admin_deleteAllFilesOfAUser, $admin_unsetUserAvatar, $admin_unsetUserBanner, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 15809b2678..86728ef381 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -33,6 +33,8 @@ import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-d import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js'; import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js'; import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js'; +import * as ep___admin_captcha_current from './endpoints/admin/captcha/current.js'; +import * as ep___admin_captcha_save from './endpoints/admin/captcha/save.js'; import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js'; import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js'; @@ -420,6 +422,8 @@ const eps = [ ['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete], ['admin/avatar-decorations/list', ep___admin_avatarDecorations_list], ['admin/avatar-decorations/update', ep___admin_avatarDecorations_update], + ['admin/captcha/current', ep___admin_captcha_current], + ['admin/captcha/save', ep___admin_captcha_save], ['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser], ['admin/unset-user-avatar', ep___admin_unsetUserAvatar], ['admin/unset-user-banner', ep___admin_unsetUserBanner], diff --git a/packages/backend/src/server/api/endpoints/admin/captcha/current.ts b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts new file mode 100644 index 0000000000..63ec740348 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js'; + +export const meta = { + tags: ['admin', 'captcha'], + + requireCredential: true, + requireAdmin: true, + + // 実態はmetaの取得であるため + kind: 'read:admin:meta', + + res: { + type: 'object', + properties: { + provider: { + type: 'string', + enum: supportedCaptchaProviders, + }, + hcaptcha: { + type: 'object', + properties: { + siteKey: { type: 'string', nullable: true }, + secretKey: { type: 'string', nullable: true }, + }, + }, + mcaptcha: { + type: 'object', + properties: { + siteKey: { type: 'string', nullable: true }, + secretKey: { type: 'string', nullable: true }, + instanceUrl: { type: 'string', nullable: true }, + }, + }, + recaptcha: { + type: 'object', + properties: { + siteKey: { type: 'string', nullable: true }, + secretKey: { type: 'string', nullable: true }, + }, + }, + turnstile: { + type: 'object', + properties: { + siteKey: { type: 'string', nullable: true }, + secretKey: { type: 'string', nullable: true }, + }, + }, + }, + }, +} as const; + +export const paramDef = {} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private captchaService: CaptchaService, + ) { + super(meta, paramDef, async () => { + return this.captchaService.get(); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/captcha/save.ts b/packages/backend/src/server/api/endpoints/admin/captcha/save.ts new file mode 100644 index 0000000000..98ec278ebe --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/captcha/save.ts @@ -0,0 +1,129 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { captchaErrorCodes, CaptchaService, supportedCaptchaProviders } from '@/core/CaptchaService.js'; +import { ApiError } from '@/server/api/error.js'; + +export const meta = { + tags: ['admin', 'captcha'], + + requireCredential: true, + requireAdmin: true, + + // 実態はmetaの更新であるため + kind: 'write:admin:meta', + + errors: { + invalidProvider: { + message: 'Invalid provider.', + code: 'INVALID_PROVIDER', + id: '14bf7ae1-80cc-4363-acb2-4fd61d086af0', + httpStatusCode: 400, + }, + invalidParameters: { + message: 'Invalid parameters.', + code: 'INVALID_PARAMETERS', + id: '26654194-410e-44e2-b42e-460ff6f92476', + httpStatusCode: 400, + }, + noResponseProvided: { + message: 'No response provided.', + code: 'NO_RESPONSE_PROVIDED', + id: '40acbba8-0937-41fb-bb3f-474514d40afe', + httpStatusCode: 400, + }, + requestFailed: { + message: 'Request failed.', + code: 'REQUEST_FAILED', + id: '0f4fe2f1-2c15-4d6e-b714-efbfcde231cd', + httpStatusCode: 500, + }, + verificationFailed: { + message: 'Verification failed.', + code: 'VERIFICATION_FAILED', + id: 'c41c067f-24f3-4150-84b2-b5a3ae8c2214', + httpStatusCode: 400, + }, + unknown: { + message: 'unknown', + code: 'UNKNOWN', + id: 'f868d509-e257-42a9-99c1-42614b031a97', + httpStatusCode: 500, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + provider: { + type: 'string', + enum: supportedCaptchaProviders, + }, + captchaResult: { + type: 'string', nullable: true, + }, + sitekey: { + type: 'string', nullable: true, + }, + secret: { + type: 'string', nullable: true, + }, + instanceUrl: { + type: 'string', nullable: true, + }, + }, + required: ['provider'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private captchaService: CaptchaService, + ) { + super(meta, paramDef, async (ps) => { + const result = await this.captchaService.save(ps.provider, { + sitekey: ps.sitekey, + secret: ps.secret, + instanceUrl: ps.instanceUrl, + captchaResult: ps.captchaResult, + }); + + if (!result.success) { + switch (result.error.code) { + case captchaErrorCodes.invalidProvider: + throw new ApiError({ + ...meta.errors.invalidProvider, + message: result.error.message, + }); + case captchaErrorCodes.invalidParameters: + throw new ApiError({ + ...meta.errors.invalidParameters, + message: result.error.message, + }); + case captchaErrorCodes.noResponseProvided: + throw new ApiError({ + ...meta.errors.noResponseProvided, + message: result.error.message, + }); + case captchaErrorCodes.requestFailed: + throw new ApiError({ + ...meta.errors.requestFailed, + message: result.error.message, + }); + case captchaErrorCodes.verificationFailed: + throw new ApiError({ + ...meta.errors.verificationFailed, + message: result.error.message, + }); + default: + throw new ApiError(meta.errors.unknown); + } + } + }); + } +} diff --git a/packages/backend/test/unit/CaptchaService.ts b/packages/backend/test/unit/CaptchaService.ts new file mode 100644 index 0000000000..51b70b05a1 --- /dev/null +++ b/packages/backend/test/unit/CaptchaService.ts @@ -0,0 +1,622 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { afterAll, beforeAll, beforeEach, describe, expect, jest } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Response } from 'node-fetch'; +import { + CaptchaError, + CaptchaErrorCode, + captchaErrorCodes, + CaptchaSaveResult, + CaptchaService, +} from '@/core/CaptchaService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { HttpRequestService } from '@/core/HttpRequestService.js'; +import { MetaService } from '@/core/MetaService.js'; +import { MiMeta } from '@/models/Meta.js'; +import { LoggerService } from '@/core/LoggerService.js'; + +describe('CaptchaService', () => { + let app: TestingModule; + let service: CaptchaService; + let httpRequestService: jest.Mocked; + let metaService: jest.Mocked; + + beforeAll(async () => { + app = await Test.createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + CaptchaService, + LoggerService, + { + provide: HttpRequestService, useFactory: () => ({ send: jest.fn() }), + }, + { + provide: MetaService, useFactory: () => ({ + fetch: jest.fn(), + update: jest.fn(), + }), + }, + ], + }).compile(); + + app.enableShutdownHooks(); + + service = app.get(CaptchaService); + httpRequestService = app.get(HttpRequestService) as jest.Mocked; + metaService = app.get(MetaService) as jest.Mocked; + }); + + beforeEach(() => { + httpRequestService.send.mockClear(); + metaService.update.mockClear(); + metaService.fetch.mockClear(); + }); + + afterAll(async () => { + await app.close(); + }); + + function successMock(result: object) { + httpRequestService.send.mockResolvedValue({ + ok: true, + status: 200, + json: async () => (result), + } as Response); + } + + function failureHttpMock() { + httpRequestService.send.mockResolvedValue({ + ok: false, + status: 400, + } as Response); + } + + function failureVerificationMock(result: object) { + httpRequestService.send.mockResolvedValue({ + ok: true, + status: 200, + json: async () => (result), + } as Response); + } + + async function testCaptchaError(code: CaptchaErrorCode, test: () => Promise) { + try { + await test(); + expect(false).toBe(true); + } catch (e) { + expect(e instanceof CaptchaError).toBe(true); + + const _e = e as CaptchaError; + expect(_e.code).toBe(code); + } + } + + describe('verifyRecaptcha', () => { + test('success', async () => { + successMock({ success: true }); + await service.verifyRecaptcha('secret', 'response'); + }); + + test('noResponseProvided', async () => { + await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyRecaptcha('secret', null)); + }); + + test('requestFailed', async () => { + failureHttpMock(); + await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyRecaptcha('secret', 'response')); + }); + + test('verificationFailed', async () => { + failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] }); + await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyRecaptcha('secret', 'response')); + }); + }); + + describe('verifyHcaptcha', () => { + test('success', async () => { + successMock({ success: true }); + await service.verifyHcaptcha('secret', 'response'); + }); + + test('noResponseProvided', async () => { + await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyHcaptcha('secret', null)); + }); + + test('requestFailed', async () => { + failureHttpMock(); + await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyHcaptcha('secret', 'response')); + }); + + test('verificationFailed', async () => { + failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] }); + await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyHcaptcha('secret', 'response')); + }); + }); + + describe('verifyMcaptcha', () => { + const host = 'https://localhost'; + + test('success', async () => { + successMock({ valid: true }); + await service.verifyMcaptcha('secret', 'sitekey', host, 'response'); + }); + + test('noResponseProvided', async () => { + await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyMcaptcha('secret', 'sitekey', host, null)); + }); + + test('requestFailed', async () => { + failureHttpMock(); + await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response')); + }); + + test('verificationFailed', async () => { + failureVerificationMock({ valid: false }); + await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyMcaptcha('secret', 'sitekey', host, 'response')); + }); + }); + + describe('verifyTurnstile', () => { + test('success', async () => { + successMock({ success: true }); + await service.verifyTurnstile('secret', 'response'); + }); + + test('noResponseProvided', async () => { + await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTurnstile('secret', null)); + }); + + test('requestFailed', async () => { + failureHttpMock(); + await testCaptchaError(captchaErrorCodes.requestFailed, () => service.verifyTurnstile('secret', 'response')); + }); + + test('verificationFailed', async () => { + failureVerificationMock({ success: false, 'error-codes': ['code01', 'code02'] }); + await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTurnstile('secret', 'response')); + }); + }); + + describe('verifyTestcaptcha', () => { + test('success', async () => { + await service.verifyTestcaptcha('testcaptcha-passed'); + }); + + test('noResponseProvided', async () => { + await testCaptchaError(captchaErrorCodes.noResponseProvided, () => service.verifyTestcaptcha(null)); + }); + + test('verificationFailed', async () => { + await testCaptchaError(captchaErrorCodes.verificationFailed, () => service.verifyTestcaptcha('testcaptcha-failed')); + }); + }); + + describe('get', () => { + function setupMeta(meta: Partial) { + metaService.fetch.mockResolvedValue(meta as MiMeta); + } + + test('values', async () => { + setupMeta({ + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: false, + hcaptchaSiteKey: 'hcaptcha-sitekey', + hcaptchaSecretKey: 'hcaptcha-secret', + mcaptchaSitekey: 'mcaptcha-sitekey', + mcaptchaSecretKey: 'mcaptcha-secret', + mcaptchaInstanceUrl: 'https://localhost', + recaptchaSiteKey: 'recaptcha-sitekey', + recaptchaSecretKey: 'recaptcha-secret', + turnstileSiteKey: 'turnstile-sitekey', + turnstileSecretKey: 'turnstile-secret', + }); + + const result = await service.get(); + expect(result.provider).toBe('none'); + expect(result.hcaptcha.siteKey).toBe('hcaptcha-sitekey'); + expect(result.hcaptcha.secretKey).toBe('hcaptcha-secret'); + expect(result.mcaptcha.siteKey).toBe('mcaptcha-sitekey'); + expect(result.mcaptcha.secretKey).toBe('mcaptcha-secret'); + expect(result.mcaptcha.instanceUrl).toBe('https://localhost'); + expect(result.recaptcha.siteKey).toBe('recaptcha-sitekey'); + expect(result.recaptcha.secretKey).toBe('recaptcha-secret'); + expect(result.turnstile.siteKey).toBe('turnstile-sitekey'); + expect(result.turnstile.secretKey).toBe('turnstile-secret'); + }); + + describe('provider', () => { + test('none', async () => { + setupMeta({ + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: false, + }); + + const result = await service.get(); + expect(result.provider).toBe('none'); + }); + + test('hcaptcha', async () => { + setupMeta({ + enableHcaptcha: true, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: false, + }); + + const result = await service.get(); + expect(result.provider).toBe('hcaptcha'); + }); + + test('mcaptcha', async () => { + setupMeta({ + enableHcaptcha: false, + enableMcaptcha: true, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: false, + }); + + const result = await service.get(); + expect(result.provider).toBe('mcaptcha'); + }); + + test('recaptcha', async () => { + setupMeta({ + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: true, + enableTurnstile: false, + enableTestcaptcha: false, + }); + + const result = await service.get(); + expect(result.provider).toBe('recaptcha'); + }); + + test('turnstile', async () => { + setupMeta({ + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: true, + enableTestcaptcha: false, + }); + + const result = await service.get(); + expect(result.provider).toBe('turnstile'); + }); + + test('testcaptcha', async () => { + setupMeta({ + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: true, + }); + + const result = await service.get(); + expect(result.provider).toBe('testcaptcha'); + }); + }); + }); + + describe('save', () => { + const host = 'https://localhost'; + + describe('[success] 検証に成功した時だけ保存できる+他のプロバイダの設定値を誤って更新しない', () => { + beforeEach(() => { + successMock({ success: true, valid: true }); + }); + + async function assertSuccess(promise: Promise, expectMeta: Partial) { + await expect(promise) + .resolves + .toStrictEqual({ success: true }); + const partialParams = metaService.update.mock.calls[0][0]; + expect(partialParams).toStrictEqual(expectMeta); + } + + test('none', async () => { + await assertSuccess( + service.save('none'), + { + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: false, + }, + ); + }); + + test('hcaptcha', async () => { + await assertSuccess( + service.save('hcaptcha', { + sitekey: 'hcaptcha-sitekey', + secret: 'hcaptcha-secret', + captchaResult: 'hcaptcha-passed', + }), + { + enableHcaptcha: true, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: false, + hcaptchaSiteKey: 'hcaptcha-sitekey', + hcaptchaSecretKey: 'hcaptcha-secret', + }, + ); + }); + + test('mcaptcha', async () => { + await assertSuccess( + service.save('mcaptcha', { + sitekey: 'mcaptcha-sitekey', + secret: 'mcaptcha-secret', + instanceUrl: host, + captchaResult: 'mcaptcha-passed', + }), + { + enableHcaptcha: false, + enableMcaptcha: true, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: false, + mcaptchaSitekey: 'mcaptcha-sitekey', + mcaptchaSecretKey: 'mcaptcha-secret', + mcaptchaInstanceUrl: host, + }, + ); + }); + + test('recaptcha', async () => { + await assertSuccess( + service.save('recaptcha', { + sitekey: 'recaptcha-sitekey', + secret: 'recaptcha-secret', + captchaResult: 'recaptcha-passed', + }), + { + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: true, + enableTurnstile: false, + enableTestcaptcha: false, + recaptchaSiteKey: 'recaptcha-sitekey', + recaptchaSecretKey: 'recaptcha-secret', + }, + ); + }); + + test('turnstile', async () => { + await assertSuccess( + service.save('turnstile', { + sitekey: 'turnstile-sitekey', + secret: 'turnstile-secret', + captchaResult: 'turnstile-passed', + }), + { + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: true, + enableTestcaptcha: false, + turnstileSiteKey: 'turnstile-sitekey', + turnstileSecretKey: 'turnstile-secret', + }, + ); + }); + + test('testcaptcha', async () => { + await assertSuccess( + service.save('testcaptcha', { + sitekey: 'testcaptcha-sitekey', + secret: 'testcaptcha-secret', + captchaResult: 'testcaptcha-passed', + }), + { + enableHcaptcha: false, + enableMcaptcha: false, + enableRecaptcha: false, + enableTurnstile: false, + enableTestcaptcha: true, + }, + ); + }); + }); + + describe('[failure] 検証に失敗した場合は保存できない+設定値の更新そのものが発生しない', () => { + async function assertFailure(code: CaptchaErrorCode, promise: Promise) { + const res = await promise; + expect(res.success).toBe(false); + if (!res.success) { + expect(res.error.code).toBe(code); + } + expect(metaService.update).not.toBeCalled(); + } + + describe('invalidParameters', () => { + test('hcaptcha', async () => { + await assertFailure( + captchaErrorCodes.invalidParameters, + service.save('hcaptcha', { + sitekey: 'hcaptcha-sitekey', + secret: 'hcaptcha-secret', + captchaResult: null, + }), + ); + }); + + test('mcaptcha', async () => { + await assertFailure( + captchaErrorCodes.invalidParameters, + service.save('mcaptcha', { + sitekey: 'mcaptcha-sitekey', + secret: 'mcaptcha-secret', + instanceUrl: host, + captchaResult: null, + }), + ); + }); + + test('recaptcha', async () => { + await assertFailure( + captchaErrorCodes.invalidParameters, + service.save('recaptcha', { + sitekey: 'recaptcha-sitekey', + secret: 'recaptcha-secret', + captchaResult: null, + }), + ); + }); + + test('turnstile', async () => { + await assertFailure( + captchaErrorCodes.invalidParameters, + service.save('turnstile', { + sitekey: 'turnstile-sitekey', + secret: 'turnstile-secret', + captchaResult: null, + }), + ); + }); + + test('testcaptcha', async () => { + await assertFailure( + captchaErrorCodes.invalidParameters, + service.save('testcaptcha', { + captchaResult: null, + }), + ); + }); + }); + + describe('requestFailed', () => { + beforeEach(() => { + failureHttpMock(); + }); + + test('hcaptcha', async () => { + await assertFailure( + captchaErrorCodes.requestFailed, + service.save('hcaptcha', { + sitekey: 'hcaptcha-sitekey', + secret: 'hcaptcha-secret', + captchaResult: 'hcaptcha-passed', + }), + ); + }); + + test('mcaptcha', async () => { + await assertFailure( + captchaErrorCodes.requestFailed, + service.save('mcaptcha', { + sitekey: 'mcaptcha-sitekey', + secret: 'mcaptcha-secret', + instanceUrl: host, + captchaResult: 'mcaptcha-passed', + }), + ); + }); + + test('recaptcha', async () => { + await assertFailure( + captchaErrorCodes.requestFailed, + service.save('recaptcha', { + sitekey: 'recaptcha-sitekey', + secret: 'recaptcha-secret', + captchaResult: 'recaptcha-passed', + }), + ); + }); + + test('turnstile', async () => { + await assertFailure( + captchaErrorCodes.requestFailed, + service.save('turnstile', { + sitekey: 'turnstile-sitekey', + secret: 'turnstile-secret', + captchaResult: 'turnstile-passed', + }), + ); + }); + + // testchapchaはrequestFailedがない + }); + + describe('verificationFailed', () => { + beforeEach(() => { + failureVerificationMock({ success: false, valid: false, 'error-codes': ['code01', 'code02'] }); + }); + + test('hcaptcha', async () => { + await assertFailure( + captchaErrorCodes.verificationFailed, + service.save('hcaptcha', { + sitekey: 'hcaptcha-sitekey', + secret: 'hcaptcha-secret', + captchaResult: 'hccaptcha-passed', + }), + ); + }); + + test('mcaptcha', async () => { + await assertFailure( + captchaErrorCodes.verificationFailed, + service.save('mcaptcha', { + sitekey: 'mcaptcha-sitekey', + secret: 'mcaptcha-secret', + instanceUrl: host, + captchaResult: 'mcaptcha-passed', + }), + ); + }); + + test('recaptcha', async () => { + await assertFailure( + captchaErrorCodes.verificationFailed, + service.save('recaptcha', { + sitekey: 'recaptcha-sitekey', + secret: 'recaptcha-secret', + captchaResult: 'recaptcha-passed', + }), + ); + }); + + test('turnstile', async () => { + await assertFailure( + captchaErrorCodes.verificationFailed, + service.save('turnstile', { + sitekey: 'turnstile-sitekey', + secret: 'turnstile-secret', + captchaResult: 'turnstile-passed', + }), + ); + }); + + test('testcaptcha', async () => { + await assertFailure( + captchaErrorCodes.verificationFailed, + service.save('testcaptcha', { + captchaResult: 'testcaptcha-failed', + }), + ); + }); + }); + }); + }); +}); diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index 264cf9af06..b1167bbac6 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -30,6 +30,9 @@ import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmount import { defaultStore } from '@/store.js'; // APIs provided by Captcha services +// see: https://docs.hcaptcha.com/configuration/#javascript-api +// see: https://developers.google.com/recaptcha/docs/display?hl=ja +// see: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#explicitly-render-the-turnstile-widget export type Captcha = { render(container: string | Node, options: { readonly [_ in 'sitekey' | 'theme' | 'type' | 'size' | 'tabindex' | 'callback' | 'expired' | 'expired-callback' | 'error-callback' | 'endpoint']?: unknown; @@ -53,6 +56,7 @@ declare global { const props = defineProps<{ provider: CaptchaProvider; sitekey: string | null; // null will show error on request + secretKey?: string | null; instanceUrl?: string | null; modelValue?: string | null; }>(); @@ -64,7 +68,7 @@ const emit = defineEmits<{ const available = ref(false); const captchaEl = shallowRef(); - +const captchaWidgetId = ref(undefined); const testcaptchaInput = ref(''); const testcaptchaPassed = ref(false); @@ -94,6 +98,15 @@ const scriptId = computed(() => `script-${props.provider}`); const captcha = computed(() => window[variable.value] || {} as unknown as Captcha); +watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => { + // 変更があったときはリフレッシュと再レンダリングをしておかないと、変更後の値で再検証が出来ない + if (available.value) { + callback(undefined); + clearWidget(); + await requestRender(); + } +}); + if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') { available.value = true; } else if (src.value !== null) { @@ -106,14 +119,38 @@ if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') } function reset() { - if (captcha.value.reset) captcha.value.reset(); + if (captcha.value.reset && captchaWidgetId.value !== undefined) { + try { + captcha.value.reset(captchaWidgetId.value); + } catch (error: unknown) { + // ignore + if (_DEV_) console.warn(error); + } + } testcaptchaPassed.value = false; testcaptchaInput.value = ''; } +function remove() { + if (captcha.value.remove && captchaWidgetId.value) { + try { + if (_DEV_) console.log('remove', props.provider, captchaWidgetId.value); + captcha.value.remove(captchaWidgetId.value); + } catch (error: unknown) { + // ignore + if (_DEV_) console.warn(error); + } + } +} + async function requestRender() { - if (captcha.value.render && captchaEl.value instanceof Element) { - captcha.value.render(captchaEl.value, { + if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) { + // reCAPTCHAのレンダリング重複判定を回避するため、captchaEl配下に仮のdivを用意する. + // (同じdivに対して複数回renderを呼び出すとreCAPTCHAはエラーを返すので) + const elem = document.createElement('div'); + captchaEl.value.appendChild(elem); + + captchaWidgetId.value = captcha.value.render(elem, { sitekey: props.sitekey, theme: defaultStore.state.darkMode ? 'dark' : 'light', callback: callback, @@ -133,6 +170,23 @@ async function requestRender() { } } +function clearWidget() { + if (props.provider === 'mcaptcha') { + const container = document.getElementById('mcaptcha__widget-container'); + if (container) { + container.innerHTML = ''; + } + } else { + reset(); + remove(); + + if (captchaEl.value) { + // レンダリング先のコンテナの中身を掃除し、フォームが増殖するのを抑止 + captchaEl.value.innerHTML = ''; + } + } +} + function callback(response?: string) { emit('update:modelValue', typeof response === 'string' ? response : null); } @@ -165,7 +219,7 @@ onUnmounted(() => { }); onBeforeUnmount(() => { - reset(); + clearWidget(); }); defineExpose({ diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue index f409f6ce50..96214a9542 100644 --- a/packages/frontend/src/components/MkFormFooter.vue +++ b/packages/frontend/src/components/MkFormFooter.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.tsx.thereAreNChanges({ n: form.modifiedCount.value }) }}
{{ i18n.ts.discard }} - {{ i18n.ts.save }} + {{ i18n.ts.save }}
@@ -18,7 +18,7 @@ import { } from 'vue'; import MkButton from './MkButton.vue'; import { i18n } from '@/i18n.js'; -const props = defineProps<{ +const props = withDefaults(defineProps<{ form: { modifiedCount: { value: number; @@ -26,7 +26,10 @@ const props = defineProps<{ discard: () => void; save: () => void; }; -}>(); + canSaving?: boolean; +}>(), { + canSaving: true, +}); diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 50002f1983..3ff8fdc844 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -136,6 +136,12 @@ type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations // @public (undocumented) type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; +// @public (undocumented) +type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json']; + // @public (undocumented) type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json']; @@ -1261,6 +1267,8 @@ declare namespace entities { AdminAvatarDecorationsListRequest, AdminAvatarDecorationsListResponse, AdminAvatarDecorationsUpdateRequest, + AdminCaptchaCurrentResponse, + AdminCaptchaSaveRequest, AdminDeleteAllFilesOfAUserRequest, AdminUnsetUserAvatarRequest, AdminUnsetUserBannerRequest, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 1837f3db4f..3bcdae6a4a 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -250,6 +250,28 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index cb1f4dbe96..b016d5bbcf 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -36,6 +36,8 @@ import type { AdminAvatarDecorationsListRequest, AdminAvatarDecorationsListResponse, AdminAvatarDecorationsUpdateRequest, + AdminCaptchaCurrentResponse, + AdminCaptchaSaveRequest, AdminDeleteAllFilesOfAUserRequest, AdminUnsetUserAvatarRequest, AdminUnsetUserBannerRequest, @@ -604,6 +606,8 @@ export type Endpoints = { 'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse }; 'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse }; 'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse }; + 'admin/captcha/current': { req: EmptyRequest; res: AdminCaptchaCurrentResponse }; + 'admin/captcha/save': { req: AdminCaptchaSaveRequest; res: EmptyResponse }; 'admin/delete-all-files-of-a-user': { req: AdminDeleteAllFilesOfAUserRequest; res: EmptyResponse }; 'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse }; 'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index a8f474c25c..02be4848c7 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -39,6 +39,8 @@ export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-dec export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json']; export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json']; export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; +export type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json']; +export type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json']; export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json']; export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 42ca05e057..e6a9df3f5a 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -215,6 +215,24 @@ export type paths = { */ post: operations['admin___avatar-decorations___update']; }; + '/admin/captcha/current': { + /** + * admin/captcha/current + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + post: operations['admin___captcha___current']; + }; + '/admin/captcha/save': { + /** + * admin/captcha/save + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + post: operations['admin___captcha___save']; + }; '/admin/delete-all-files-of-a-user': { /** * admin/delete-all-files-of-a-user @@ -6564,6 +6582,128 @@ export type operations = { }; }; }; + /** + * admin/captcha/current + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + admin___captcha___current: { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** @enum {string} */ + provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha'; + hcaptcha: { + siteKey: string | null; + secretKey: string | null; + }; + mcaptcha: { + siteKey: string | null; + secretKey: string | null; + instanceUrl: string | null; + }; + recaptcha: { + siteKey: string | null; + secretKey: string | null; + }; + turnstile: { + siteKey: string | null; + secretKey: string | null; + }; + }; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * admin/captcha/save + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + admin___captcha___save: { + requestBody: { + content: { + 'application/json': { + /** @enum {string} */ + provider: 'none' | 'hcaptcha' | 'mcaptcha' | 'recaptcha' | 'turnstile' | 'testcaptcha'; + captchaResult?: string | null; + sitekey?: string | null; + secret?: string | null; + instanceUrl?: string | null; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * admin/delete-all-files-of-a-user * @description No description provided. -- cgit v1.2.3-freya From 5445b023e5cedb7228710637c895c63328e3db74 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Tue, 14 Jan 2025 20:08:54 +0900 Subject: enhance: 連合モードにあわせてフロントエンドを変化させるように (#15112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(backend): metaにfederation modeに関する情報を公開 * enhance(frontend): 登録画面の注意書きを追加 * enhance(frontend): aboutページ・サーバー情報 * enhance(frontend): サーバー統計 * enhance(frontend): みつけるページ * enhance(frontend): 検索 * enhance(frontend): ユーザー選択 * enhance(frontend): 設定画面 * enhance(frontend): ウィジェット * enhance(frontend): リモートで開くオプション * Update Changelog * enhance(frontend): ステータスバー * i18n --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + locales/index.d.ts | 8 ++++ locales/ja-JP.yml | 2 + .../backend/src/core/entities/MetaEntityService.ts | 1 + packages/backend/src/models/json-schema/meta.ts | 5 +++ .../frontend/src/components/MkInstanceStats.vue | 18 +++++---- .../src/components/MkSignupDialog.rules.vue | 6 ++- .../frontend/src/components/MkUserSelectDialog.vue | 11 +++-- .../frontend/src/components/MkVisitorDashboard.vue | 6 ++- packages/frontend/src/components/MkWidgets.vue | 19 +++++++-- packages/frontend/src/pages/about.vue | 47 ++++++++++++++-------- packages/frontend/src/pages/explore.users.vue | 3 +- packages/frontend/src/pages/search.note.vue | 22 +++++----- packages/frontend/src/pages/search.user.vue | 5 ++- packages/frontend/src/pages/settings/general.vue | 3 +- .../frontend/src/pages/settings/mute-block.vue | 4 +- packages/frontend/src/pages/settings/privacy.vue | 9 +++-- .../src/pages/settings/statusbar.statusbar.vue | 3 +- packages/frontend/src/scripts/please-login.ts | 12 +++++- packages/frontend/src/ui/_common_/common.ts | 18 ++++++--- packages/frontend/src/ui/_common_/statusbars.vue | 3 +- packages/frontend/src/widgets/index.ts | 10 ++++- packages/misskey-js/src/autogen/types.ts | 2 + 23 files changed, 150 insertions(+), 68 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index af5d333927..287d390453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Enhance: PC画面でチャンネルが複数列で表示されるように (Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13) - Enhance: 照会に失敗した場合、その理由を表示するように +- Enhance: 連合がホワイトリスト化・無効化されているサーバー向けのデザイン修正 - Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加 - Enhance: AiScriptの拡張API関数において引数の型チェックをより厳格に - Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 7c3ef5d93c..453d40feea 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5230,6 +5230,14 @@ export interface Locale extends ILocale { * 注意事項を理解した上でオンにします。 */ "acknowledgeNotesAndEnable": string; + /** + * このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。 + */ + "federationSpecified": string; + /** + * このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。 + */ + "federationDisabled": string; "_accountSettings": { /** * コンテンツの表示にログインを必須にする diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 57a88062c1..a3cb9d052a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1303,6 +1303,8 @@ lockdown: "ロックダウン" pleaseSelectAccount: "アカウントを選択してください" availableRoles: "利用可能なロール" acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。" +federationSpecified: "このサーバーはホワイトリスト連合で運用されています。管理者が指定したサーバー以外とやり取りすることはできません。" +federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。" _accountSettings: requireSigninToViewContents: "コンテンツの表示にログインを必須にする" diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 409dca3426..ec0b5360f4 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -132,6 +132,7 @@ export class MetaEntityService { enableUrlPreview: instance.urlPreviewEnabled, noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local', maxFileSize: this.config.maxFileSize, + federation: this.meta.federation, }; return packed; diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index e3fd63464a..e7ae2ee8e5 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -261,6 +261,11 @@ export const packedMetaLiteSchema = { type: 'number', optional: false, nullable: false, }, + federation: { + type: 'string', + enum: ['all', 'specified', 'none'], + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 8ccbf61e48..d8066857fe 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- + @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -46,9 +46,9 @@ SPDX-License-Identifier: AGPL-3.0-only - - - + + +
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -84,13 +84,15 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts new file mode 100644 index 0000000000..411d62edf9 --- /dev/null +++ b/packages/frontend/src/components/MkRoleSelectDialog.stories.impl.ts @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { StoryObj } from '@storybook/vue3'; +import { http, HttpResponse } from 'msw'; +import { role } from '../../.storybook/fakes.js'; +import { commonHandlers } from '../../.storybook/mocks.js'; +import MkRoleSelectDialog from '@/components/MkRoleSelectDialog.vue'; + +const roles = [ + role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), role({ displayOrder: 1 }, '1'), + role({ displayOrder: 2 }, '2'), role({ displayOrder: 2 }, '2'), role({ displayOrder: 3 }, '3'), role({ displayOrder: 3 }, '3'), + role({ displayOrder: 4 }, '4'), role({ displayOrder: 5 }, '5'), role({ displayOrder: 6 }, '6'), role({ displayOrder: 7 }, '7'), + role({ displayOrder: 999, name: 'privateRole', isPublic: false }, '999'), +]; + +export const Default = { + render(args) { + return { + components: { + MkRoleSelectDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + initialRoleIds: undefined, + infoMessage: undefined, + title: undefined, + publicOnly: true, + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/admin/roles/list', ({ params }) => { + return HttpResponse.json(roles); + }), + ], + }, + }, + decorators: [() => ({ + template: '
', + })], +} satisfies StoryObj; + +export const InitialIds = { + ...Default, + args: { + ...Default.args, + initialRoleIds: [roles[0].id, roles[1].id, roles[4].id, roles[6].id, roles[8].id, roles[10].id], + }, +} satisfies StoryObj; + +export const InfoMessage = { + ...Default, + args: { + ...Default.args, + infoMessage: 'This is a message.', + }, +} satisfies StoryObj; + +export const Title = { + ...Default, + args: { + ...Default.args, + title: 'Select roles', + }, +} satisfies StoryObj; + +export const Full = { + ...Default, + args: { + ...Default.args, + initialRoleIds: roles.map(it => it.id), + infoMessage: InfoMessage.args.infoMessage, + title: Title.args.title, + }, +} satisfies StoryObj; + +export const FullWithPrivate = { + ...Default, + args: { + ...Default.args, + initialRoleIds: roles.map(it => it.id), + infoMessage: InfoMessage.args.infoMessage, + title: Title.args.title, + publicOnly: false, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue new file mode 100644 index 0000000000..67a7a3f752 --- /dev/null +++ b/packages/frontend/src/components/MkRoleSelectDialog.vue @@ -0,0 +1,200 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkSortOrderEditor.define.ts b/packages/frontend/src/components/MkSortOrderEditor.define.ts new file mode 100644 index 0000000000..f023b5d72b --- /dev/null +++ b/packages/frontend/src/components/MkSortOrderEditor.define.ts @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export type SortOrderDirection = '+' | '-' + +export type SortOrder = { + key: T; + direction: SortOrderDirection; +} diff --git a/packages/frontend/src/components/MkSortOrderEditor.vue b/packages/frontend/src/components/MkSortOrderEditor.vue new file mode 100644 index 0000000000..da08f12297 --- /dev/null +++ b/packages/frontend/src/components/MkSortOrderEditor.vue @@ -0,0 +1,112 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkTagItem.stories.impl.ts b/packages/frontend/src/components/MkTagItem.stories.impl.ts new file mode 100644 index 0000000000..3f243ff651 --- /dev/null +++ b/packages/frontend/src/components/MkTagItem.stories.impl.ts @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable import/no-default-export */ +import { action } from '@storybook/addon-actions'; +import { StoryObj } from '@storybook/vue3'; +import MkTagItem from './MkTagItem.vue'; + +export const Default = { + render(args) { + return { + components: { + MkTagItem: MkTagItem, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + events() { + return { + click: action('click'), + exButtonClick: action('exButtonClick'), + }; + }, + }, + template: '', + }; + }, + args: { + content: 'name', + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; + +export const Icon = { + ...Default, + args: { + ...Default.args, + iconClass: 'ti ti-arrow-up', + }, +} satisfies StoryObj; + +export const ExButton = { + ...Default, + args: { + ...Default.args, + exButtonIconClass: 'ti ti-x', + }, +} satisfies StoryObj; + +export const IconExButton = { + ...Default, + args: { + ...Default.args, + iconClass: 'ti ti-arrow-up', + exButtonIconClass: 'ti ti-x', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkTagItem.vue b/packages/frontend/src/components/MkTagItem.vue new file mode 100644 index 0000000000..98f2411392 --- /dev/null +++ b/packages/frontend/src/components/MkTagItem.vue @@ -0,0 +1,76 @@ + + + + + + + diff --git a/packages/frontend/src/components/grid/MkCellTooltip.vue b/packages/frontend/src/components/grid/MkCellTooltip.vue new file mode 100644 index 0000000000..fd289c6cd9 --- /dev/null +++ b/packages/frontend/src/components/grid/MkCellTooltip.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue new file mode 100644 index 0000000000..0ffd42abda --- /dev/null +++ b/packages/frontend/src/components/grid/MkDataCell.vue @@ -0,0 +1,391 @@ + + + + + + + diff --git a/packages/frontend/src/components/grid/MkDataRow.vue b/packages/frontend/src/components/grid/MkDataRow.vue new file mode 100644 index 0000000000..280a14bc4a --- /dev/null +++ b/packages/frontend/src/components/grid/MkDataRow.vue @@ -0,0 +1,72 @@ + + + + + + + diff --git a/packages/frontend/src/components/grid/MkGrid.stories.impl.ts b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts new file mode 100644 index 0000000000..5801012f15 --- /dev/null +++ b/packages/frontend/src/components/grid/MkGrid.stories.impl.ts @@ -0,0 +1,223 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { action } from '@storybook/addon-actions'; +import { StoryObj } from '@storybook/vue3'; +import { ref } from 'vue'; +import { commonHandlers } from '../../../.storybook/mocks.js'; +import { boolean, choose, country, date, firstName, integer, lastName, text } from '../../../.storybook/fake-utils.js'; +import MkGrid from './MkGrid.vue'; +import { GridContext, GridEvent } from '@/components/grid/grid-event.js'; +import { DataSource, GridSetting } from '@/components/grid/grid.js'; +import { GridColumnSetting } from '@/components/grid/column.js'; + +function d(p: { + check?: boolean, + name?: string, + email?: string, + age?: number, + birthday?: string, + gender?: string, + country?: string, + reportCount?: number, + createdAt?: string, +}, seed: string) { + const prefix = text(10, seed); + + return { + check: p.check ?? boolean(seed), + name: p.name ?? `${firstName(seed)} ${lastName(seed)}`, + email: p.email ?? `${prefix}@example.com`, + age: p.age ?? integer(20, 80, seed), + birthday: date({}, seed).toISOString(), + gender: p.gender ?? choose(['male', 'female', 'other', 'unknown'], seed), + country: p.country ?? country(seed), + reportCount: p.reportCount ?? integer(0, 9999, seed), + createdAt: p.createdAt ?? date({}, seed).toISOString(), + }; +} + +const defaultCols: GridColumnSetting[] = [ + { bindTo: 'check', icon: 'ti-check', type: 'boolean', width: 50 }, + { bindTo: 'name', title: 'Name', type: 'text', width: 'auto' }, + { bindTo: 'email', title: 'Email', type: 'text', width: 'auto' }, + { bindTo: 'age', title: 'Age', type: 'number', width: 50 }, + { bindTo: 'birthday', title: 'Birthday', type: 'date', width: 'auto' }, + { bindTo: 'gender', title: 'Gender', type: 'text', width: 80 }, + { bindTo: 'country', title: 'Country', type: 'text', width: 120 }, + { bindTo: 'reportCount', title: 'ReportCount', type: 'number', width: 'auto' }, + { bindTo: 'createdAt', title: 'CreatedAt', type: 'date', width: 'auto' }, +]; + +function createArgs(overrides?: { settings?: Partial, data?: DataSource[] }) { + const refData = ref[]>([]); + for (let i = 0; i < 100; i++) { + refData.value.push(d({}, i.toString())); + } + + return { + settings: { + row: overrides?.settings?.row, + cols: [ + ...defaultCols.filter(col => overrides?.settings?.cols?.every(c => c.bindTo !== col.bindTo) ?? true), + ...overrides?.settings?.cols ?? [], + ], + cells: overrides?.settings?.cells, + }, + data: refData.value, + }; +} + +function createRender(params: { settings: GridSetting, data: DataSource[] }) { + return { + render(args) { + return { + components: { + MkGrid, + }, + setup() { + return { + args, + }; + }, + data() { + return { + data: args.data, + }; + }, + computed: { + props() { + return { + ...args, + }; + }, + events() { + return { + event: (event: GridEvent, context: GridContext) => { + switch (event.type) { + case 'cell-value-change': { + args.data[event.row.index][event.column.setting.bindTo] = event.newValue; + } + } + }, + }; + }, + }, + template: '
', + }; + }, + args: { + ...params, + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + ], + }, + }, + } satisfies StoryObj; +} + +export const Default = createRender(createArgs()); + +export const NoNumber = createRender(createArgs({ + settings: { + row: { + showNumber: false, + }, + }, +})); + +export const NoSelectable = createRender(createArgs({ + settings: { + row: { + selectable: false, + }, + }, +})); + +export const Editable = createRender(createArgs({ + settings: { + cols: defaultCols.map(col => ({ ...col, editable: true })), + }, +})); + +export const AdditionalRowStyle = createRender(createArgs({ + settings: { + cols: defaultCols.map(col => ({ ...col, editable: true })), + row: { + styleRules: [ + { + condition: ({ row }) => AdditionalRowStyle.args.data[row.index].check as boolean, + applyStyle: { + style: { + backgroundColor: 'lightgray', + }, + }, + }, + ], + }, + }, +})); + +export const ContextMenu = createRender(createArgs({ + settings: { + cols: [ + { + bindTo: 'check', icon: 'ti-check', type: 'boolean', width: 50, contextMenuFactory: (col, context) => [ + { + type: 'button', + text: 'Check All', + action: () => { + for (const d of ContextMenu.args.data) { + d.check = true; + } + }, + }, + { + type: 'button', + text: 'Uncheck All', + action: () => { + for (const d of ContextMenu.args.data) { + d.check = false; + } + }, + }, + ], + }, + ], + row: { + contextMenuFactory: (row, context) => [ + { + type: 'button', + text: 'Delete', + action: () => { + const idxes = context.rangedRows.map(r => r.index); + const newData = ContextMenu.args.data.filter((d, i) => !idxes.includes(i)); + + ContextMenu.args.data.splice(0); + ContextMenu.args.data.push(...newData); + }, + }, + ], + }, + cells: { + contextMenuFactory: (col, row, value, context) => [ + { + type: 'button', + text: 'Delete', + action: () => { + for (const cell of context.rangedCells) { + ContextMenu.args.data[cell.row.index][cell.column.setting.bindTo] = undefined; + } + }, + }, + ], + }, + }, +})); diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue new file mode 100644 index 0000000000..60738365fb --- /dev/null +++ b/packages/frontend/src/components/grid/MkGrid.vue @@ -0,0 +1,1342 @@ + + + + + + + + + diff --git a/packages/frontend/src/components/grid/MkHeaderCell.vue b/packages/frontend/src/components/grid/MkHeaderCell.vue new file mode 100644 index 0000000000..605d27c6d6 --- /dev/null +++ b/packages/frontend/src/components/grid/MkHeaderCell.vue @@ -0,0 +1,216 @@ + + + + + + + diff --git a/packages/frontend/src/components/grid/MkHeaderRow.vue b/packages/frontend/src/components/grid/MkHeaderRow.vue new file mode 100644 index 0000000000..8affa08fd5 --- /dev/null +++ b/packages/frontend/src/components/grid/MkHeaderRow.vue @@ -0,0 +1,60 @@ + + + + + + + diff --git a/packages/frontend/src/components/grid/MkNumberCell.vue b/packages/frontend/src/components/grid/MkNumberCell.vue new file mode 100644 index 0000000000..674bba96bc --- /dev/null +++ b/packages/frontend/src/components/grid/MkNumberCell.vue @@ -0,0 +1,61 @@ + + + + + + + diff --git a/packages/frontend/src/components/grid/cell-validators.ts b/packages/frontend/src/components/grid/cell-validators.ts new file mode 100644 index 0000000000..949cab2ec6 --- /dev/null +++ b/packages/frontend/src/components/grid/cell-validators.ts @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { CellValue, GridCell } from '@/components/grid/cell.js'; +import { GridColumn } from '@/components/grid/column.js'; +import { GridRow } from '@/components/grid/row.js'; +import { i18n } from '@/i18n.js'; + +export type ValidatorParams = { + column: GridColumn; + row: GridRow; + value: CellValue; + allCells: GridCell[]; +}; + +export type ValidatorResult = { + valid: boolean; + message?: string; +} + +export type GridCellValidator = { + name?: string; + ignoreViolation?: boolean; + validate: (params: ValidatorParams) => ValidatorResult; +} + +export type ValidateViolation = { + valid: boolean; + params: ValidatorParams; + violations: ValidateViolationItem[]; +} + +export type ValidateViolationItem = { + valid: boolean; + validator: GridCellValidator; + result: ValidatorResult; +} + +export function cellValidation(allCells: GridCell[], cell: GridCell, newValue: CellValue): ValidateViolation { + const { column, row } = cell; + const validators = column.setting.validators ?? []; + + const params: ValidatorParams = { + column, + row, + value: newValue, + allCells, + }; + + const violations: ValidateViolationItem[] = validators.map(validator => { + const result = validator.validate(params); + return { + valid: result.valid, + validator, + result, + }; + }); + + return { + valid: violations.every(v => v.result.valid), + params, + violations, + }; +} + +class ValidatorPreset { + required(): GridCellValidator { + return { + name: 'required', + validate: ({ value }): ValidatorResult => { + return { + valid: value !== null && value !== undefined && value !== '', + message: i18n.ts._gridComponent._error.requiredValue, + }; + }, + }; + } + + regex(pattern: RegExp): GridCellValidator { + return { + name: 'regex', + validate: ({ value }): ValidatorResult => { + return { + valid: (typeof value !== 'string') || pattern.test(value.toString() ?? ''), + message: i18n.tsx._gridComponent._error.patternNotMatch({ pattern: pattern.source }), + }; + }, + }; + } + + unique(): GridCellValidator { + return { + name: 'unique', + validate: ({ column, row, value, allCells }): ValidatorResult => { + const bindTo = column.setting.bindTo; + const isUnique = allCells + .filter(it => it.column.setting.bindTo === bindTo && it.row.index !== row.index) + .every(cell => cell.value !== value); + return { + valid: isUnique, + message: i18n.ts._gridComponent._error.notUnique, + }; + }, + }; + } +} + +export const validators = new ValidatorPreset(); diff --git a/packages/frontend/src/components/grid/cell.ts b/packages/frontend/src/components/grid/cell.ts new file mode 100644 index 0000000000..71b7a3e3f1 --- /dev/null +++ b/packages/frontend/src/components/grid/cell.ts @@ -0,0 +1,88 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { ValidateViolation } from '@/components/grid/cell-validators.js'; +import { Size } from '@/components/grid/grid.js'; +import { GridColumn } from '@/components/grid/column.js'; +import { GridRow } from '@/components/grid/row.js'; +import { MenuItem } from '@/types/menu.js'; +import { GridContext } from '@/components/grid/grid-event.js'; + +export type CellValue = string | boolean | number | undefined | null | Array | NonNullable; + +export type CellAddress = { + row: number; + col: number; +} + +export const CELL_ADDRESS_NONE: CellAddress = { + row: -1, + col: -1, +}; + +export type GridCell = { + address: CellAddress; + value: CellValue; + column: GridColumn; + row: GridRow; + selected: boolean; + ranged: boolean; + contentSize: Size; + setting: GridCellSetting; + violation: ValidateViolation; +} + +export type GridCellContextMenuFactory = (col: GridColumn, row: GridRow, value: CellValue, context: GridContext) => MenuItem[]; + +export type GridCellSetting = { + contextMenuFactory?: GridCellContextMenuFactory; +} + +export function createCell( + column: GridColumn, + row: GridRow, + value: CellValue, + setting: GridCellSetting, +): GridCell { + const newValue = (row.using && column.setting.valueTransformer) + ? column.setting.valueTransformer(row, column, value) + : value; + + return { + address: { row: row.index, col: column.index }, + value: newValue, + column, + row, + selected: false, + ranged: false, + contentSize: { width: 0, height: 0 }, + violation: { + valid: true, + params: { + column, + row, + value, + allCells: [], + }, + violations: [], + }, + setting, + }; +} + +export function resetCell(cell: GridCell): void { + cell.selected = false; + cell.ranged = false; + cell.violation = { + valid: true, + params: { + column: cell.column, + row: cell.row, + value: cell.value, + allCells: [], + }, + violations: [], + }; +} diff --git a/packages/frontend/src/components/grid/column.ts b/packages/frontend/src/components/grid/column.ts new file mode 100644 index 0000000000..2f505756fe --- /dev/null +++ b/packages/frontend/src/components/grid/column.ts @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { GridCellValidator } from '@/components/grid/cell-validators.js'; +import { Size, SizeStyle } from '@/components/grid/grid.js'; +import { calcCellWidth } from '@/components/grid/grid-utils.js'; +import { CellValue, GridCell } from '@/components/grid/cell.js'; +import { GridRow } from '@/components/grid/row.js'; +import { MenuItem } from '@/types/menu.js'; +import { GridContext } from '@/components/grid/grid-event.js'; + +export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image' | 'hidden'; + +export type CustomValueEditor = (row: GridRow, col: GridColumn, value: CellValue, cellElement: HTMLElement) => Promise; +export type CellValueTransformer = (row: GridRow, col: GridColumn, value: CellValue) => CellValue; +export type GridColumnContextMenuFactory = (col: GridColumn, context: GridContext) => MenuItem[]; + +export type GridColumnSetting = { + bindTo: string; + title?: string; + icon?: string; + type: ColumnType; + width: SizeStyle; + editable?: boolean; + validators?: GridCellValidator[]; + customValueEditor?: CustomValueEditor; + valueTransformer?: CellValueTransformer; + contextMenuFactory?: GridColumnContextMenuFactory; + events?: { + copy?: (value: CellValue) => string; + paste?: (text: string) => CellValue; + delete?: (cell: GridCell, context: GridContext) => void; + } +}; + +export type GridColumn = { + index: number; + setting: GridColumnSetting; + width: string; + contentSize: Size; +} + +export function createColumn(setting: GridColumnSetting, index: number): GridColumn { + return { + index, + setting, + width: calcCellWidth(setting.width), + contentSize: { width: 0, height: 0 }, + }; +} + diff --git a/packages/frontend/src/components/grid/grid-event.ts b/packages/frontend/src/components/grid/grid-event.ts new file mode 100644 index 0000000000..074b72b956 --- /dev/null +++ b/packages/frontend/src/components/grid/grid-event.ts @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js'; +import { GridState } from '@/components/grid/grid.js'; +import { ValidateViolation } from '@/components/grid/cell-validators.js'; +import { GridColumn } from '@/components/grid/column.js'; +import { GridRow } from '@/components/grid/row.js'; + +export type GridContext = { + selectedCell?: GridCell; + rangedCells: GridCell[]; + rangedRows: GridRow[]; + randedBounds: { + leftTop: CellAddress; + rightBottom: CellAddress; + }; + availableBounds: { + leftTop: CellAddress; + rightBottom: CellAddress; + }; + state: GridState; + rows: GridRow[]; + columns: GridColumn[]; +}; + +export type GridEvent = + GridCellValueChangeEvent | + GridCellValidationEvent + ; + +export type GridCellValueChangeEvent = { + type: 'cell-value-change'; + column: GridColumn; + row: GridRow; + oldValue: CellValue; + newValue: CellValue; +}; + +export type GridCellValidationEvent = { + type: 'cell-validation'; + violation?: ValidateViolation; + all: ValidateViolation[]; +}; diff --git a/packages/frontend/src/components/grid/grid-utils.ts b/packages/frontend/src/components/grid/grid-utils.ts new file mode 100644 index 0000000000..a45bc88926 --- /dev/null +++ b/packages/frontend/src/components/grid/grid-utils.ts @@ -0,0 +1,215 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { isRef, Ref } from 'vue'; +import { DataSource, SizeStyle } from '@/components/grid/grid.js'; +import { CELL_ADDRESS_NONE, CellAddress, CellValue, GridCell } from '@/components/grid/cell.js'; +import { GridRow } from '@/components/grid/row.js'; +import { GridContext } from '@/components/grid/grid-event.js'; +import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; +import { GridColumn, GridColumnSetting } from '@/components/grid/column.js'; + +export function isCellElement(elem: HTMLElement): boolean { + return elem.hasAttribute('data-grid-cell'); +} + +export function isRowElement(elem: HTMLElement): boolean { + return elem.hasAttribute('data-grid-row'); +} + +export function calcCellWidth(widthSetting: SizeStyle): string { + switch (widthSetting) { + case undefined: + case 'auto': { + return 'auto'; + } + default: { + return `${widthSetting}px`; + } + } +} + +function getCellRowByAttribute(elem: HTMLElement): number { + const row = elem.getAttribute('data-grid-cell-row'); + if (row === null) { + throw new Error('data-grid-cell-row attribute not found'); + } + return Number(row); +} + +function getCellColByAttribute(elem: HTMLElement): number { + const col = elem.getAttribute('data-grid-cell-col'); + if (col === null) { + throw new Error('data-grid-cell-col attribute not found'); + } + return Number(col); +} + +export function getCellAddress(elem: HTMLElement, parentNodeCount = 10): CellAddress { + let node = elem; + for (let i = 0; i < parentNodeCount; i++) { + if (!node.parentElement) { + break; + } + + if (isCellElement(node) && isRowElement(node.parentElement)) { + const row = getCellRowByAttribute(node); + const col = getCellColByAttribute(node); + + return { row, col }; + } + + node = node.parentElement; + } + + return CELL_ADDRESS_NONE; +} + +export function getCellElement(elem: HTMLElement, parentNodeCount = 10): HTMLElement | null { + let node = elem; + for (let i = 0; i < parentNodeCount; i++) { + if (isCellElement(node)) { + return node; + } + + if (!node.parentElement) { + break; + } + + node = node.parentElement; + } + + return null; +} + +export function equalCellAddress(a: CellAddress, b: CellAddress): boolean { + return a.row === b.row && a.col === b.col; +} + +/** + * グリッドの選択範囲の内容をタブ区切り形式テキストに変換してクリップボードにコピーする。 + */ +export function copyGridDataToClipboard( + gridItems: Ref | DataSource[], + context: GridContext, +) { + const items = isRef(gridItems) ? gridItems.value : gridItems; + const lines = Array.of(); + const bounds = context.randedBounds; + + for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) { + const rowItems = Array.of(); + for (let col = bounds.leftTop.col; col <= bounds.rightBottom.col; col++) { + const { bindTo, events } = context.columns[col].setting; + const value = items[row][bindTo]; + const transformValue = events?.copy + ? events.copy(value) + : typeof value === 'object' || Array.isArray(value) + ? JSON.stringify(value) + : value?.toString() ?? ''; + rowItems.push(transformValue); + } + lines.push(rowItems.join('\t')); + } + + const text = lines.join('\n'); + copyToClipboard(text); + + if (_DEV_) { + console.log(`Copied to clipboard: ${text}`); + } +} + +/** + * クリップボードからタブ区切りテキストとして値を読み取り、グリッドの選択範囲に貼り付けるためのユーティリティ関数。 + * …と言いつつも、使用箇所により反映方法に差があるため更新操作はコールバック関数に任せている。 + */ +export async function pasteToGridFromClipboard( + context: GridContext, + callback: (row: GridRow, col: GridColumn, parsedValue: CellValue) => void, +) { + function parseValue(value: string, setting: GridColumnSetting): CellValue { + if (setting.events?.paste) { + return setting.events.paste(value); + } else { + switch (setting.type) { + case 'number': { + return Number(value); + } + case 'boolean': { + return value === 'true'; + } + default: { + return value; + } + } + } + } + + const clipBoardText = await navigator.clipboard.readText(); + if (_DEV_) { + console.log(`Paste from clipboard: ${clipBoardText}`); + } + + const bounds = context.randedBounds; + const lines = clipBoardText.replace(/\r/g, '') + .split('\n') + .map(it => it.split('\t')); + + if (lines.length === 1 && lines[0].length === 1) { + // 単独文字列の場合は選択範囲全体に同じテキストを貼り付ける + const ranges = context.rangedCells; + for (const cell of ranges) { + if (cell.column.setting.editable) { + callback(cell.row, cell.column, parseValue(lines[0][0], cell.column.setting)); + } + } + } else { + // 表形式文字列の場合は表形式にパースし、選択範囲に合うように貼り付ける + const offsetRow = bounds.leftTop.row; + const offsetCol = bounds.leftTop.col; + const { columns, rows } = context; + for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) { + const rowIdx = row - offsetRow; + if (lines.length <= rowIdx) { + // クリップボードから読んだ二次元配列よりも選択範囲の方が大きい場合、貼り付け操作を打ち切る + break; + } + + const items = lines[rowIdx]; + for (let col = bounds.leftTop.col; col <= bounds.rightBottom.col; col++) { + const colIdx = col - offsetCol; + if (items.length <= colIdx) { + // クリップボードから読んだ二次元配列よりも選択範囲の方が大きい場合、貼り付け操作を打ち切る + break; + } + + if (columns[col].setting.editable) { + callback(rows[row], columns[col], parseValue(items[colIdx], columns[col].setting)); + } + } + } + } +} + +/** + * グリッドの選択範囲にあるデータを削除するためのユーティリティ関数。 + * …と言いつつも、使用箇所により反映方法に差があるため更新操作はコールバック関数に任せている。 + */ +export function removeDataFromGrid( + context: GridContext, + callback: (cell: GridCell) => void, +) { + for (const cell of context.rangedCells) { + const { editable, events } = cell.column.setting; + if (editable) { + if (events?.delete) { + events.delete(cell, context); + } else { + callback(cell); + } + } + } +} diff --git a/packages/frontend/src/components/grid/grid.ts b/packages/frontend/src/components/grid/grid.ts new file mode 100644 index 0000000000..0cb3b6f28b --- /dev/null +++ b/packages/frontend/src/components/grid/grid.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { EventEmitter } from 'eventemitter3'; +import { CellValue, GridCellSetting } from '@/components/grid/cell.js'; +import { GridColumnSetting } from '@/components/grid/column.js'; +import { GridRowSetting } from '@/components/grid/row.js'; + +export type GridSetting = { + row?: GridRowSetting; + cols: GridColumnSetting[]; + cells?: GridCellSetting; +}; + +export type DataSource = Record; + +export type GridState = + 'normal' | + 'cellSelecting' | + 'cellEditing' | + 'colResizing' | + 'colSelecting' | + 'rowSelecting' | + 'hidden' + ; + +export type Size = { + width: number; + height: number; +} + +export type SizeStyle = number | 'auto' | undefined; + +export type AdditionalStyle = { + className?: string; + style?: Record; +} + +export class GridEventEmitter extends EventEmitter<{ + 'forceRefreshContentSize': void; +}> { +} diff --git a/packages/frontend/src/components/grid/row.ts b/packages/frontend/src/components/grid/row.ts new file mode 100644 index 0000000000..e0a317c9d3 --- /dev/null +++ b/packages/frontend/src/components/grid/row.ts @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { AdditionalStyle } from '@/components/grid/grid.js'; +import { GridCell } from '@/components/grid/cell.js'; +import { GridColumn } from '@/components/grid/column.js'; +import { MenuItem } from '@/types/menu.js'; +import { GridContext } from '@/components/grid/grid-event.js'; + +export const defaultGridRowSetting: Required = { + showNumber: true, + selectable: true, + minimumDefinitionCount: 100, + styleRules: [], + contextMenuFactory: () => [], + events: {}, +}; + +export type GridRowStyleRuleConditionParams = { + row: GridRow, + targetCols: GridColumn[], + cells: GridCell[] +}; + +export type GridRowStyleRule = { + condition: (params: GridRowStyleRuleConditionParams) => boolean; + applyStyle: AdditionalStyle; +} + +export type GridRowContextMenuFactory = (row: GridRow, context: GridContext) => MenuItem[]; + +export type GridRowSetting = { + showNumber?: boolean; + selectable?: boolean; + minimumDefinitionCount?: number; + styleRules?: GridRowStyleRule[]; + contextMenuFactory?: GridRowContextMenuFactory; + events?: { + delete?: (rows: GridRow[]) => void; + } +} + +export type GridRow = { + index: number; + ranged: boolean; + using: boolean; + setting: GridRowSetting; + additionalStyles: AdditionalStyle[]; +} + +export function createRow(index: number, using: boolean, setting: GridRowSetting): GridRow { + return { + index, + ranged: false, + using: using, + setting, + additionalStyles: [], + }; +} + +export function resetRow(row: GridRow): void { + row.ranged = false; + row.using = false; + row.additionalStyles = []; +} + diff --git a/packages/frontend/src/components/hook/useLoading.ts b/packages/frontend/src/components/hook/useLoading.ts new file mode 100644 index 0000000000..6c6ff6ae0d --- /dev/null +++ b/packages/frontend/src/components/hook/useLoading.ts @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { computed, h, ref } from 'vue'; +import MkLoading from '@/components/global/MkLoading.vue'; + +export const useLoading = (props?: { + static?: boolean; + inline?: boolean; + colored?: boolean; + mini?: boolean; + em?: boolean; +}) => { + const showingCnt = ref(0); + + const show = () => { + showingCnt.value++; + }; + + const close = (force?: boolean) => { + if (force) { + showingCnt.value = 0; + } else { + showingCnt.value = Math.max(0, showingCnt.value - 1); + } + }; + + const scope = (fn: () => T) => { + show(); + + const result = fn(); + if (result instanceof Promise) { + return result.finally(() => close()); + } else { + close(); + return result; + } + }; + + const showing = computed(() => showingCnt.value > 0); + const component = computed(() => showing.value ? h(MkLoading, props) : null); + + return { + show, + close, + scope, + component, + showing, + }; +}; diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html index 0be589262f..84ba9dfabc 100644 --- a/packages/frontend/src/index.html +++ b/packages/frontend/src/index.html @@ -20,6 +20,7 @@ worker-src 'self'; script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://*.recaptcha.net https://*.gstatic.com https://challenges.cloudflare.com https://esm.sh; style-src 'self' 'unsafe-inline'; + font-src 'self' data:; img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000; connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com; diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 589ace0155..18c7464d2e 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -602,6 +602,27 @@ export async function selectDriveFolder(multiple: boolean): Promise { + return new Promise((resolve) => { + popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, { + done: roles => { + resolve({ canceled: false, result: roles }); + }, + close: () => { + resolve({ canceled: true, result: undefined }); + }, + }, 'dispose'); + }); +} + export async function pickEmoji(src: HTMLElement, opts: ComponentProps): Promise { return new Promise(resolve => { const { dispose } = popup(MkEmojiPickerDialog, { diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.impl.ts b/packages/frontend/src/pages/admin/custom-emojis-manager.impl.ts new file mode 100644 index 0000000000..de2b2aca8c --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.impl.ts @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export type RequestLogItem = { + failed: boolean; + url: string; + name: string; + error?: string; +}; + +export const gridSortOrderKeys = [ + 'name', + 'category', + 'aliases', + 'type', + 'license', + 'host', + 'uri', + 'publicUrl', + 'isSensitive', + 'localOnly', + 'updatedAt', +]; +export type GridSortOrderKey = typeof gridSortOrderKeys[number]; + +export function emptyStrToUndefined(value: string | null) { + return value ? value : undefined; +} + +export function emptyStrToNull(value: string) { + return value === '' ? null : value; +} + +export function emptyStrToEmptyArray(value: string) { + return value === '' ? [] : value.split(' ').map(it => it.trim()); +} + +export function roleIdsParser(text: string): { id: string, name: string }[] { + // idとnameのペア配列をJSONで受け取る。それ以外の形式は許容しない + try { + const obj = JSON.parse(text); + if (!Array.isArray(obj)) { + return []; + } + if (!obj.every(it => typeof it === 'object' && 'id' in it && 'name' in it)) { + return []; + } + + return obj.map(it => ({ id: it.id, name: it.name })); + } catch (ex) { + console.warn(ex); + return []; + } +} diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue new file mode 100644 index 0000000000..55f9632ce4 --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue @@ -0,0 +1,757 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue new file mode 100644 index 0000000000..a3de5de569 --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue @@ -0,0 +1,477 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.vue new file mode 100644 index 0000000000..ea4303f342 --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.vue @@ -0,0 +1,36 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.logs-folder.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.logs-folder.vue new file mode 100644 index 0000000000..f75f6c0da5 --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.logs-folder.vue @@ -0,0 +1,102 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue new file mode 100644 index 0000000000..9a9d2990ba --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.remote.vue @@ -0,0 +1,441 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts b/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts new file mode 100644 index 0000000000..f62304277a --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.stories.impl.ts @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { delay, http, HttpResponse } from 'msw'; +import { StoryObj } from '@storybook/vue3'; +import { entities } from 'misskey-js'; +import { commonHandlers } from '../../../.storybook/mocks.js'; +import { emoji } from '../../../.storybook/fakes.js'; +import { fakeId } from '../../../.storybook/fake-utils.js'; +import custom_emojis_manager2 from './custom-emojis-manager2.vue'; + +function createRender(params: { + emojis: entities.EmojiDetailedAdmin[]; +}) { + const storedEmojis: entities.EmojiDetailedAdmin[] = [...params.emojis]; + const storedDriveFiles: entities.DriveFile[] = []; + + return { + render(args) { + return { + components: { + custom_emojis_manager2, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + + }, + parameters: { + layout: 'fullscreen', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/v2/admin/emoji/list', async ({ request }) => { + await delay(100); + + const bodyStream = request.body as ReadableStream; + const body = await new Response(bodyStream).json() as entities.V2AdminEmojiListRequest; + + const emojis = storedEmojis; + const limit = body.limit ?? 10; + const page = body.page ?? 1; + const result = emojis.slice((page - 1) * limit, page * limit); + + return HttpResponse.json({ + emojis: result, + count: Math.min(emojis.length, limit), + allCount: emojis.length, + allPages: Math.ceil(emojis.length / limit), + }); + }), + http.post('/api/drive/folders', () => { + return HttpResponse.json([]); + }), + http.post('/api/drive/files', () => { + return HttpResponse.json(storedDriveFiles); + }), + http.post('/api/drive/files/create', async ({ request }) => { + const data = await request.formData(); + const file = data.get('file'); + if (!file || !(file instanceof File)) { + return HttpResponse.json({ error: 'file is required' }, { + status: 400, + }); + } + + // FIXME: ファイルのバイナリに0xEF 0xBF 0xBDが混入してしまい、うまく画像ファイルとして表示できない問題がある + const base64 = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => { + resolve(reader.result as string); + }; + reader.readAsDataURL(new Blob([file], { type: 'image/webp' })); + }); + + const driveFile: entities.DriveFile = { + id: fakeId(file.name), + createdAt: new Date().toISOString(), + name: file.name, + type: file.type, + md5: '', + size: file.size, + isSensitive: false, + blurhash: null, + properties: {}, + url: base64, + thumbnailUrl: null, + comment: null, + folderId: null, + folder: null, + userId: null, + user: null, + }; + + storedDriveFiles.push(driveFile); + + return HttpResponse.json(driveFile); + }), + http.post('api/admin/emoji/add', async ({ request }) => { + await delay(100); + + const bodyStream = request.body as ReadableStream; + const body = await new Response(bodyStream).json() as entities.AdminEmojiAddRequest; + + const fileId = body.fileId; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const file = storedDriveFiles.find(f => f.id === fileId)!; + + const em = emoji({ + id: fakeId(file.name), + name: body.name, + publicUrl: file.url, + originalUrl: file.url, + type: file.type, + aliases: body.aliases, + category: body.category ?? undefined, + license: body.license ?? undefined, + localOnly: body.localOnly, + isSensitive: body.isSensitive, + }); + storedEmojis.push(em); + + return HttpResponse.json(null); + }), + ], + }, + }, + } satisfies StoryObj; +} + +export const Default = createRender({ + emojis: [], +}); + +export const List10 = createRender({ + emojis: Array.from({ length: 10 }, (_, i) => emoji({ name: `emoji_${i}` }, i.toString())), +}); + +export const List100 = createRender({ + emojis: Array.from({ length: 100 }, (_, i) => emoji({ name: `emoji_${i}` }, i.toString())), +}); + +export const List1000 = createRender({ + emojis: Array.from({ length: 1000 }, (_, i) => emoji({ name: `emoji_${i}` }, i.toString())), +}); diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager2.vue b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue new file mode 100644 index 0000000000..a952a5a3d1 --- /dev/null +++ b/packages/frontend/src/pages/admin/custom-emojis-manager2.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index fd15ae1d66..969ca8b9e8 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -121,6 +121,11 @@ const menuDef = computed(() => [{ text: i18n.ts.customEmojis, to: '/admin/emojis', active: currentPage.value?.route.name === 'emojis', + }, { + icon: 'ti ti-icons', + text: i18n.ts.customEmojis + '(beta)', + to: '/admin/emojis2', + active: currentPage.value?.route.name === 'emojis2', }, { icon: 'ti ti-sparkles', text: i18n.ts.avatarDecorations, diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index e98e0b59b1..732b209a36 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -382,6 +382,10 @@ const routes: RouteDef[] = [{ path: '/emojis', name: 'emojis', component: page(() => import('@/pages/custom-emojis-manager.vue')), + }, { + path: '/emojis2', + name: 'emojis2', + component: page(() => import('@/pages/admin/custom-emojis-manager2.vue')), }, { path: '/avatar-decorations', name: 'avatarDecorations', diff --git a/packages/frontend/src/scripts/file-drop.ts b/packages/frontend/src/scripts/file-drop.ts new file mode 100644 index 0000000000..c2e863c0dc --- /dev/null +++ b/packages/frontend/src/scripts/file-drop.ts @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export type DroppedItem = DroppedFile | DroppedDirectory; + +export type DroppedFile = { + isFile: true; + path: string; + file: File; +}; + +export type DroppedDirectory = { + isFile: false; + path: string; + children: DroppedItem[]; +} + +export async function extractDroppedItems(ev: DragEvent): Promise { + const dropItems = ev.dataTransfer?.items; + if (!dropItems || dropItems.length === 0) { + return []; + } + + const apiTestItem = dropItems[0]; + if ('webkitGetAsEntry' in apiTestItem) { + return readDataTransferItems(dropItems); + } else { + // webkitGetAsEntryに対応していない場合はfilesから取得する(ディレクトリのサポートは出来ない) + const dropFiles = ev.dataTransfer.files; + if (dropFiles.length === 0) { + return []; + } + + const droppedFiles = Array.of(); + for (let i = 0; i < dropFiles.length; i++) { + const file = dropFiles.item(i); + if (file) { + droppedFiles.push({ + isFile: true, + path: file.name, + file, + }); + } + } + + return droppedFiles; + } +} + +/** + * ドラッグ&ドロップされたファイルのリストからディレクトリ構造とファイルへの参照({@link File})を取得する。 + */ +export async function readDataTransferItems(itemList: DataTransferItemList): Promise { + async function readEntry(entry: FileSystemEntry): Promise { + if (entry.isFile) { + return { + isFile: true, + path: entry.fullPath, + file: await readFile(entry as FileSystemFileEntry), + }; + } else { + return { + isFile: false, + path: entry.fullPath, + children: await readDirectory(entry as FileSystemDirectoryEntry), + }; + } + } + + function readFile(fileSystemFileEntry: FileSystemFileEntry): Promise { + return new Promise((resolve, reject) => { + fileSystemFileEntry.file(resolve, reject); + }); + } + + function readDirectory(fileSystemDirectoryEntry: FileSystemDirectoryEntry): Promise { + return new Promise(async (resolve) => { + const allEntries = Array.of(); + const reader = fileSystemDirectoryEntry.createReader(); + while (true) { + const entries = await new Promise((res, rej) => reader.readEntries(res, rej)); + if (entries.length === 0) { + break; + } + allEntries.push(...entries); + } + + resolve(await Promise.all(allEntries.map(readEntry))); + }); + } + + // 扱いにくいので配列に変換 + const items = Array.of(); + for (let i = 0; i < itemList.length; i++) { + items.push(itemList[i]); + } + + return Promise.all( + items + .map(it => it.webkitGetAsEntry()) + .filter(it => it) + .map(it => readEntry(it!)), + ); +} + +/** + * {@link DroppedItem}のリストからディレクトリを再帰的に検索し、ファイルのリストを取得する。 + */ +export function flattenDroppedFiles(items: DroppedItem[]): DroppedFile[] { + const result = Array.of(); + for (const item of items) { + if (item.isFile) { + result.push(item); + } else { + result.push(...flattenDroppedFiles(item.children)); + } + } + return result; +} diff --git a/packages/frontend/src/scripts/key-event.ts b/packages/frontend/src/scripts/key-event.ts new file mode 100644 index 0000000000..a72776d48c --- /dev/null +++ b/packages/frontend/src/scripts/key-event.ts @@ -0,0 +1,153 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/** + * {@link KeyboardEvent.code} の値を表す文字列。不足分は適宜追加する + * @see https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values + */ +export type KeyCode = + | 'Backspace' + | 'Tab' + | 'Enter' + | 'Shift' + | 'Control' + | 'Alt' + | 'Pause' + | 'CapsLock' + | 'Escape' + | 'Space' + | 'PageUp' + | 'PageDown' + | 'End' + | 'Home' + | 'ArrowLeft' + | 'ArrowUp' + | 'ArrowRight' + | 'ArrowDown' + | 'Insert' + | 'Delete' + | 'Digit0' + | 'Digit1' + | 'Digit2' + | 'Digit3' + | 'Digit4' + | 'Digit5' + | 'Digit6' + | 'Digit7' + | 'Digit8' + | 'Digit9' + | 'KeyA' + | 'KeyB' + | 'KeyC' + | 'KeyD' + | 'KeyE' + | 'KeyF' + | 'KeyG' + | 'KeyH' + | 'KeyI' + | 'KeyJ' + | 'KeyK' + | 'KeyL' + | 'KeyM' + | 'KeyN' + | 'KeyO' + | 'KeyP' + | 'KeyQ' + | 'KeyR' + | 'KeyS' + | 'KeyT' + | 'KeyU' + | 'KeyV' + | 'KeyW' + | 'KeyX' + | 'KeyY' + | 'KeyZ' + | 'MetaLeft' + | 'MetaRight' + | 'ContextMenu' + | 'F1' + | 'F2' + | 'F3' + | 'F4' + | 'F5' + | 'F6' + | 'F7' + | 'F8' + | 'F9' + | 'F10' + | 'F11' + | 'F12' + | 'NumLock' + | 'ScrollLock' + | 'Semicolon' + | 'Equal' + | 'Comma' + | 'Minus' + | 'Period' + | 'Slash' + | 'Backquote' + | 'BracketLeft' + | 'Backslash' + | 'BracketRight' + | 'Quote' + | 'Meta' + | 'AltGraph' + ; + +/** + * 修飾キーを表す文字列。不足分は適宜追加する。 + */ +export type KeyModifier = + | 'Shift' + | 'Control' + | 'Alt' + | 'Meta' + ; + +/** + * 押下されたキー以外の状態を表す文字列。不足分は適宜追加する。 + */ +export type KeyState = + | 'composing' + | 'repeat' + ; + +export type KeyEventHandler = { + modifiers?: KeyModifier[]; + states?: KeyState[]; + code: KeyCode | 'any'; + handler: (event: KeyboardEvent) => void; +} + +export function handleKeyEvent(event: KeyboardEvent, handlers: KeyEventHandler[]) { + function checkModifier(ev: KeyboardEvent, modifiers? : KeyModifier[]) { + if (modifiers) { + return modifiers.every(modifier => ev.getModifierState(modifier)); + } + return true; + } + + function checkState(ev: KeyboardEvent, states?: KeyState[]) { + if (states) { + return states.every(state => ev.getModifierState(state)); + } + return true; + } + + let hit = false; + for (const handler of handlers.filter(it => it.code === event.code)) { + if (checkModifier(event, handler.modifiers) && checkState(event, handler.states)) { + handler.handler(event); + hit = true; + break; + } + } + + if (!hit) { + for (const handler of handlers.filter(it => it.code === 'any')) { + handler.handler(event); + } + } +} diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index b037aa8acc..c25b4d73bd 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -12,14 +12,28 @@ import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; import { uploadFile } from '@/scripts/upload.js'; -export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promise { +export function chooseFileFromPc( + multiple: boolean, + options?: { + uploadFolder?: string | null; + keepOriginal?: boolean; + nameConverter?: (file: File) => string | undefined; + }, +): Promise { + const uploadFolder = options?.uploadFolder ?? defaultStore.state.uploadFolder; + const keepOriginal = options?.keepOriginal ?? defaultStore.state.keepOriginalUploading; + const nameConverter = options?.nameConverter ?? (() => undefined); + return new Promise((res, rej) => { const input = document.createElement('input'); input.type = 'file'; input.multiple = multiple; input.onchange = () => { if (!input.files) return res([]); - const promises = Array.from(input.files, file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal)); + const promises = Array.from( + input.files, + file => uploadFile(file, uploadFolder, nameConverter(file), keepOriginal), + ); Promise.all(promises).then(driveFiles => { res(driveFiles); @@ -94,7 +108,7 @@ function select(src: HTMLElement | EventTarget | null, label: string | null, mul }, { text: i18n.ts.upload, icon: 'ti ti-upload', - action: () => chooseFileFromPc(multiple, keepOriginal.value).then(files => res(files)), + action: () => chooseFileFromPc(multiple, { keepOriginal: keepOriginal.value }).then(files => res(files)), }, { text: i18n.ts.fromDrive, icon: 'ti ti-cloud', diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 211ddb8287..7098b52205 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1117,6 +1117,9 @@ type EmojiDeleted = { // @public (undocumented) type EmojiDetailed = components['schemas']['EmojiDetailed']; +// @public (undocumented) +type EmojiDetailedAdmin = components['schemas']['EmojiDetailedAdmin']; + // @public (undocumented) type EmojiRequest = operations['emoji']['requestBody']['content']['application/json']; @@ -1294,6 +1297,8 @@ declare namespace entities { AdminEmojiSetCategoryBulkRequest, AdminEmojiSetLicenseBulkRequest, AdminEmojiUpdateRequest, + V2AdminEmojiListRequest, + V2AdminEmojiListResponse, AdminFederationDeleteAllFilesRequest, AdminFederationRefreshRemoteInstanceMetadataRequest, AdminFederationRemoveAllFollowingRequest, @@ -1847,6 +1852,7 @@ declare namespace entities { GalleryPost, EmojiSimple, EmojiDetailed, + EmojiDetailedAdmin, Flash, Signin, RoleCondFormulaLogics, @@ -3420,6 +3426,12 @@ type UsersShowResponse = operations['users___show']['responses']['200']['content // @public (undocumented) type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['content']['application/json']; +// @public (undocumented) +type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestBody']['content']['application/json']; + +// @public (undocumented) +type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; + // Warnings were encountered during analysis: // // src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 3bcdae6a4a..edaa0498e9 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -493,6 +493,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index b016d5bbcf..982717597b 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -62,6 +62,8 @@ import type { AdminEmojiSetCategoryBulkRequest, AdminEmojiSetLicenseBulkRequest, AdminEmojiUpdateRequest, + V2AdminEmojiListRequest, + V2AdminEmojiListResponse, AdminFederationDeleteAllFilesRequest, AdminFederationRefreshRemoteInstanceMetadataRequest, AdminFederationRemoveAllFollowingRequest, @@ -628,6 +630,7 @@ export type Endpoints = { 'admin/emoji/set-category-bulk': { req: AdminEmojiSetCategoryBulkRequest; res: EmptyResponse }; 'admin/emoji/set-license-bulk': { req: AdminEmojiSetLicenseBulkRequest; res: EmptyResponse }; 'admin/emoji/update': { req: AdminEmojiUpdateRequest; res: EmptyResponse }; + 'v2/admin/emoji/list': { req: V2AdminEmojiListRequest; res: V2AdminEmojiListResponse }; 'admin/federation/delete-all-files': { req: AdminFederationDeleteAllFilesRequest; res: EmptyResponse }; 'admin/federation/refresh-remote-instance-metadata': { req: AdminFederationRefreshRemoteInstanceMetadataRequest; res: EmptyResponse }; 'admin/federation/remove-all-following': { req: AdminFederationRemoveAllFollowingRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 02be4848c7..e4299d62c7 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -65,6 +65,8 @@ export type AdminEmojiSetAliasesBulkRequest = operations['admin___emoji___set-al export type AdminEmojiSetCategoryBulkRequest = operations['admin___emoji___set-category-bulk']['requestBody']['content']['application/json']; export type AdminEmojiSetLicenseBulkRequest = operations['admin___emoji___set-license-bulk']['requestBody']['content']['application/json']; export type AdminEmojiUpdateRequest = operations['admin___emoji___update']['requestBody']['content']['application/json']; +export type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestBody']['content']['application/json']; +export type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; export type AdminFederationDeleteAllFilesRequest = operations['admin___federation___delete-all-files']['requestBody']['content']['application/json']; export type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin___federation___refresh-remote-instance-metadata']['requestBody']['content']['application/json']; export type AdminFederationRemoveAllFollowingRequest = operations['admin___federation___remove-all-following']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index 04574849d4..1a30da4437 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -33,6 +33,7 @@ export type FederationInstance = components['schemas']['FederationInstance']; export type GalleryPost = components['schemas']['GalleryPost']; export type EmojiSimple = components['schemas']['EmojiSimple']; export type EmojiDetailed = components['schemas']['EmojiDetailed']; +export type EmojiDetailedAdmin = components['schemas']['EmojiDetailedAdmin']; export type Flash = components['schemas']['Flash']; export type Signin = components['schemas']['Signin']; export type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index ada685604d..75a99263d0 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -414,6 +414,15 @@ export type paths = { */ post: operations['admin___emoji___update']; }; + '/v2/admin/emoji/list': { + /** + * v2/admin/emoji/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* + */ + post: operations['v2___admin___emoji___list']; + }; '/admin/federation/delete-all-files': { /** * admin/federation/delete-all-files @@ -4749,6 +4758,29 @@ export type components = { localOnly: boolean; roleIdsThatCanBeUsedThisEmojiAsReaction: string[]; }; + EmojiDetailedAdmin: { + /** Format: id */ + id: string; + /** Format: date-time */ + updatedAt: string | null; + name: string; + /** @description The local host is represented with `null`. */ + host: string | null; + publicUrl: string; + originalUrl: string; + uri: string | null; + type: string | null; + aliases: string[]; + category: string | null; + license: string | null; + localOnly: boolean; + isSensitive: boolean; + roleIdsThatCanBeUsedThisEmojiAsReaction: { + /** Format: misskey:id */ + id: string; + name: string; + }[]; + }; Flash: { /** * Format: id @@ -7872,6 +7904,97 @@ export type operations = { }; }; }; + /** + * v2/admin/emoji/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* + */ + v2___admin___emoji___list: { + requestBody: { + content: { + 'application/json': { + query?: ({ + updatedAtFrom?: string; + updatedAtTo?: string; + name?: string; + host?: string; + uri?: string; + publicUrl?: string; + originalUrl?: string; + type?: string; + aliases?: string; + category?: string; + license?: string; + isSensitive?: boolean; + localOnly?: boolean; + /** + * @default all + * @enum {string} + */ + hostType?: 'local' | 'remote' | 'all'; + roleIds?: string[]; + }) | null; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; + page?: number; + /** + * @default [ + * "-id" + * ] + */ + sortKeys?: ('+id' | '-id' | '+updatedAt' | '-updatedAt' | '+name' | '-name' | '+host' | '-host' | '+uri' | '-uri' | '+publicUrl' | '-publicUrl' | '+type' | '-type' | '+aliases' | '-aliases' | '+category' | '-category' | '+license' | '-license' | '+isSensitive' | '-isSensitive' | '+localOnly' | '-localOnly' | '+roleIdsThatCanBeUsedThisEmojiAsReaction' | '-roleIdsThatCanBeUsedThisEmojiAsReaction')[]; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + emojis: components['schemas']['EmojiDetailedAdmin'][]; + count: number; + allCount: number; + allPages: number; + }; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * admin/federation/delete-all-files * @description No description provided. -- cgit v1.2.3-freya From 8d7b1f285f536f23377d790044511820214268e0 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 20 Jan 2025 21:15:23 +0900 Subject: refactor: Refactor Endpoints to improve Developer Experience for adding Endpoints (#15301) * chore: reorder endpoints to lexicographic code unit order * refactor: import endpoints in one module and use them for generating EndpointsModule and endpoints --- packages/backend/src/server/api/EndpointsModule.ts | 1550 +-- packages/backend/src/server/api/endpoint-list.ts | 399 + packages/backend/src/server/api/endpoints.ts | 786 +- packages/misskey-js/etc/misskey-js.api.md | 256 +- packages/misskey-js/src/autogen/apiClientJSDoc.ts | 994 +- packages/misskey-js/src/autogen/endpoint.ts | 442 +- packages/misskey-js/src/autogen/entities.ts | 256 +- packages/misskey-js/src/autogen/types.ts | 9864 ++++++++++---------- 8 files changed, 6312 insertions(+), 8235 deletions(-) create mode 100644 packages/backend/src/server/api/endpoint-list.ts (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 87c9841fd0..9cfb2f0ac0 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -6,784 +6,13 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; -import * as ep___admin_abuseReport_notificationRecipient_list from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js'; -import * as ep___admin_abuseReport_notificationRecipient_show from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js'; -import * as ep___admin_abuseReport_notificationRecipient_create from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js'; -import * as ep___admin_abuseReport_notificationRecipient_update from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js'; -import * as ep___admin_abuseReport_notificationRecipient_delete from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js'; -import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; -import * as ep___admin_meta from './endpoints/admin/meta.js'; -import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js'; -import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js'; -import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js'; -import * as ep___admin_ad_create from './endpoints/admin/ad/create.js'; -import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js'; -import * as ep___admin_ad_list from './endpoints/admin/ad/list.js'; -import * as ep___admin_ad_update from './endpoints/admin/ad/update.js'; -import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js'; -import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js'; -import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js'; -import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js'; -import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js'; -import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js'; -import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js'; -import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js'; -import * as ep___admin_captcha_current from './endpoints/admin/captcha/current.js'; -import * as ep___admin_captcha_save from './endpoints/admin/captcha/save.js'; -import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; -import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js'; -import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js'; -import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; -import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; -import * as ep___admin_drive_files from './endpoints/admin/drive/files.js'; -import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js'; -import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js'; -import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js'; -import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js'; -import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js'; -import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js'; -import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js'; -import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js'; -import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js'; -import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js'; -import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js'; -import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js'; -import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js'; -import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js'; -import * as ep___v2_admin_emoji_list from './endpoints/v2/admin/emoji/list.js'; -import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js'; -import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js'; -import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js'; -import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js'; -import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; -import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; -import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; -import * as ep___admin_invite_create from './endpoints/admin/invite/create.js'; -import * as ep___admin_invite_list from './endpoints/admin/invite/list.js'; -import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; -import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; -import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; -import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js'; -import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js'; -import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js'; -import * as ep___admin_relays_add from './endpoints/admin/relays/add.js'; -import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; -import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; -import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; -import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; -import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js'; -import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js'; -import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; -import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; -import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js'; -import * as ep___admin_showUser from './endpoints/admin/show-user.js'; -import * as ep___admin_showUsers from './endpoints/admin/show-users.js'; -import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js'; -import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js'; -import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js'; -import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js'; -import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js'; -import * as ep___admin_roles_create from './endpoints/admin/roles/create.js'; -import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js'; -import * as ep___admin_roles_list from './endpoints/admin/roles/list.js'; -import * as ep___admin_roles_show from './endpoints/admin/roles/show.js'; -import * as ep___admin_roles_update from './endpoints/admin/roles/update.js'; -import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js'; -import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js'; -import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js'; -import * as ep___admin_roles_users from './endpoints/admin/roles/users.js'; -import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js'; -import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js'; -import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js'; -import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js'; -import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js'; -import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js'; -import * as ep___announcements from './endpoints/announcements.js'; -import * as ep___announcements_show from './endpoints/announcements/show.js'; -import * as ep___antennas_create from './endpoints/antennas/create.js'; -import * as ep___antennas_delete from './endpoints/antennas/delete.js'; -import * as ep___antennas_list from './endpoints/antennas/list.js'; -import * as ep___antennas_notes from './endpoints/antennas/notes.js'; -import * as ep___antennas_show from './endpoints/antennas/show.js'; -import * as ep___antennas_update from './endpoints/antennas/update.js'; -import * as ep___ap_get from './endpoints/ap/get.js'; -import * as ep___ap_show from './endpoints/ap/show.js'; -import * as ep___app_create from './endpoints/app/create.js'; -import * as ep___app_show from './endpoints/app/show.js'; -import * as ep___auth_accept from './endpoints/auth/accept.js'; -import * as ep___auth_session_generate from './endpoints/auth/session/generate.js'; -import * as ep___auth_session_show from './endpoints/auth/session/show.js'; -import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js'; -import * as ep___blocking_create from './endpoints/blocking/create.js'; -import * as ep___blocking_delete from './endpoints/blocking/delete.js'; -import * as ep___blocking_list from './endpoints/blocking/list.js'; -import * as ep___channels_create from './endpoints/channels/create.js'; -import * as ep___channels_featured from './endpoints/channels/featured.js'; -import * as ep___channels_follow from './endpoints/channels/follow.js'; -import * as ep___channels_followed from './endpoints/channels/followed.js'; -import * as ep___channels_owned from './endpoints/channels/owned.js'; -import * as ep___channels_show from './endpoints/channels/show.js'; -import * as ep___channels_timeline from './endpoints/channels/timeline.js'; -import * as ep___channels_unfollow from './endpoints/channels/unfollow.js'; -import * as ep___channels_update from './endpoints/channels/update.js'; -import * as ep___channels_favorite from './endpoints/channels/favorite.js'; -import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js'; -import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js'; -import * as ep___channels_search from './endpoints/channels/search.js'; -import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; -import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; -import * as ep___charts_drive from './endpoints/charts/drive.js'; -import * as ep___charts_federation from './endpoints/charts/federation.js'; -import * as ep___charts_instance from './endpoints/charts/instance.js'; -import * as ep___charts_notes from './endpoints/charts/notes.js'; -import * as ep___charts_user_drive from './endpoints/charts/user/drive.js'; -import * as ep___charts_user_following from './endpoints/charts/user/following.js'; -import * as ep___charts_user_notes from './endpoints/charts/user/notes.js'; -import * as ep___charts_user_pv from './endpoints/charts/user/pv.js'; -import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js'; -import * as ep___charts_users from './endpoints/charts/users.js'; -import * as ep___clips_addNote from './endpoints/clips/add-note.js'; -import * as ep___clips_removeNote from './endpoints/clips/remove-note.js'; -import * as ep___clips_create from './endpoints/clips/create.js'; -import * as ep___clips_delete from './endpoints/clips/delete.js'; -import * as ep___clips_list from './endpoints/clips/list.js'; -import * as ep___clips_notes from './endpoints/clips/notes.js'; -import * as ep___clips_show from './endpoints/clips/show.js'; -import * as ep___clips_update from './endpoints/clips/update.js'; -import * as ep___clips_favorite from './endpoints/clips/favorite.js'; -import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js'; -import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js'; -import * as ep___drive from './endpoints/drive.js'; -import * as ep___drive_files from './endpoints/drive/files.js'; -import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js'; -import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js'; -import * as ep___drive_files_create from './endpoints/drive/files/create.js'; -import * as ep___drive_files_delete from './endpoints/drive/files/delete.js'; -import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js'; -import * as ep___drive_files_find from './endpoints/drive/files/find.js'; -import * as ep___drive_files_show from './endpoints/drive/files/show.js'; -import * as ep___drive_files_update from './endpoints/drive/files/update.js'; -import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js'; -import * as ep___drive_folders from './endpoints/drive/folders.js'; -import * as ep___drive_folders_create from './endpoints/drive/folders/create.js'; -import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js'; -import * as ep___drive_folders_find from './endpoints/drive/folders/find.js'; -import * as ep___drive_folders_show from './endpoints/drive/folders/show.js'; -import * as ep___drive_folders_update from './endpoints/drive/folders/update.js'; -import * as ep___drive_stream from './endpoints/drive/stream.js'; -import * as ep___emailAddress_available from './endpoints/email-address/available.js'; -import * as ep___endpoint from './endpoints/endpoint.js'; -import * as ep___endpoints from './endpoints/endpoints.js'; -import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js'; -import * as ep___federation_followers from './endpoints/federation/followers.js'; -import * as ep___federation_following from './endpoints/federation/following.js'; -import * as ep___federation_instances from './endpoints/federation/instances.js'; -import * as ep___federation_showInstance from './endpoints/federation/show-instance.js'; -import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js'; -import * as ep___federation_users from './endpoints/federation/users.js'; -import * as ep___federation_stats from './endpoints/federation/stats.js'; -import * as ep___following_create from './endpoints/following/create.js'; -import * as ep___following_delete from './endpoints/following/delete.js'; -import * as ep___following_update from './endpoints/following/update.js'; -import * as ep___following_update_all from './endpoints/following/update-all.js'; -import * as ep___following_invalidate from './endpoints/following/invalidate.js'; -import * as ep___following_requests_accept from './endpoints/following/requests/accept.js'; -import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js'; -import * as ep___following_requests_list from './endpoints/following/requests/list.js'; -import * as ep___following_requests_sent from './endpoints/following/requests/sent.js'; -import * as ep___following_requests_reject from './endpoints/following/requests/reject.js'; -import * as ep___gallery_featured from './endpoints/gallery/featured.js'; -import * as ep___gallery_popular from './endpoints/gallery/popular.js'; -import * as ep___gallery_posts from './endpoints/gallery/posts.js'; -import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js'; -import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js'; -import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js'; -import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js'; -import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js'; -import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js'; -import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js'; -import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js'; -import * as ep___hashtags_list from './endpoints/hashtags/list.js'; -import * as ep___hashtags_search from './endpoints/hashtags/search.js'; -import * as ep___hashtags_show from './endpoints/hashtags/show.js'; -import * as ep___hashtags_trend from './endpoints/hashtags/trend.js'; -import * as ep___hashtags_users from './endpoints/hashtags/users.js'; -import * as ep___i from './endpoints/i.js'; -import * as ep___i_2fa_done from './endpoints/i/2fa/done.js'; -import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js'; -import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js'; -import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js'; -import * as ep___i_2fa_register from './endpoints/i/2fa/register.js'; -import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js'; -import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js'; -import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js'; -import * as ep___i_apps from './endpoints/i/apps.js'; -import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js'; -import * as ep___i_claimAchievement from './endpoints/i/claim-achievement.js'; -import * as ep___i_changePassword from './endpoints/i/change-password.js'; -import * as ep___i_deleteAccount from './endpoints/i/delete-account.js'; -import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; -import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; -import * as ep___i_exportMute from './endpoints/i/export-mute.js'; -import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; -import * as ep___i_exportClips from './endpoints/i/export-clips.js'; -import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; -import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; -import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; -import * as ep___i_favorites from './endpoints/i/favorites.js'; -import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js'; -import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js'; -import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; -import * as ep___i_importFollowing from './endpoints/i/import-following.js'; -import * as ep___i_importMuting from './endpoints/i/import-muting.js'; -import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js'; -import * as ep___i_importAntennas from './endpoints/i/import-antennas.js'; -import * as ep___i_notifications from './endpoints/i/notifications.js'; -import * as ep___i_notificationsGrouped from './endpoints/i/notifications-grouped.js'; -import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; -import * as ep___i_pages from './endpoints/i/pages.js'; -import * as ep___i_pin from './endpoints/i/pin.js'; -import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js'; -import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js'; -import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js'; -import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js'; -import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js'; -import * as ep___i_registry_get from './endpoints/i/registry/get.js'; -import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js'; -import * as ep___i_registry_keys from './endpoints/i/registry/keys.js'; -import * as ep___i_registry_remove from './endpoints/i/registry/remove.js'; -import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js'; -import * as ep___i_registry_set from './endpoints/i/registry/set.js'; -import * as ep___i_revokeToken from './endpoints/i/revoke-token.js'; -import * as ep___i_signinHistory from './endpoints/i/signin-history.js'; -import * as ep___i_unpin from './endpoints/i/unpin.js'; -import * as ep___i_updateEmail from './endpoints/i/update-email.js'; -import * as ep___i_update from './endpoints/i/update.js'; -import * as ep___i_move from './endpoints/i/move.js'; -import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js'; -import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; -import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; -import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; -import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; -import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js'; -import * as ep___invite_create from './endpoints/invite/create.js'; -import * as ep___invite_delete from './endpoints/invite/delete.js'; -import * as ep___invite_list from './endpoints/invite/list.js'; -import * as ep___invite_limit from './endpoints/invite/limit.js'; -import * as ep___meta from './endpoints/meta.js'; -import * as ep___emojis from './endpoints/emojis.js'; -import * as ep___emoji from './endpoints/emoji.js'; -import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; -import * as ep___mute_create from './endpoints/mute/create.js'; -import * as ep___mute_delete from './endpoints/mute/delete.js'; -import * as ep___mute_list from './endpoints/mute/list.js'; -import * as ep___renoteMute_create from './endpoints/renote-mute/create.js'; -import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js'; -import * as ep___renoteMute_list from './endpoints/renote-mute/list.js'; -import * as ep___my_apps from './endpoints/my/apps.js'; -import * as ep___notes from './endpoints/notes.js'; -import * as ep___notes_children from './endpoints/notes/children.js'; -import * as ep___notes_clips from './endpoints/notes/clips.js'; -import * as ep___notes_conversation from './endpoints/notes/conversation.js'; -import * as ep___notes_create from './endpoints/notes/create.js'; -import * as ep___notes_delete from './endpoints/notes/delete.js'; -import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; -import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; -import * as ep___notes_featured from './endpoints/notes/featured.js'; -import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js'; -import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js'; -import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js'; -import * as ep___notes_mentions from './endpoints/notes/mentions.js'; -import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js'; -import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js'; -import * as ep___notes_reactions from './endpoints/notes/reactions.js'; -import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js'; -import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js'; -import * as ep___notes_renotes from './endpoints/notes/renotes.js'; -import * as ep___notes_replies from './endpoints/notes/replies.js'; -import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js'; -import * as ep___notes_search from './endpoints/notes/search.js'; -import * as ep___notes_show from './endpoints/notes/show.js'; -import * as ep___notes_state from './endpoints/notes/state.js'; -import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js'; -import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js'; -import * as ep___notes_timeline from './endpoints/notes/timeline.js'; -import * as ep___notes_translate from './endpoints/notes/translate.js'; -import * as ep___notes_unrenote from './endpoints/notes/unrenote.js'; -import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js'; -import * as ep___notifications_create from './endpoints/notifications/create.js'; -import * as ep___notifications_flush from './endpoints/notifications/flush.js'; -import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js'; -import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js'; -import * as ep___pagePush from './endpoints/page-push.js'; -import * as ep___pages_create from './endpoints/pages/create.js'; -import * as ep___pages_delete from './endpoints/pages/delete.js'; -import * as ep___pages_featured from './endpoints/pages/featured.js'; -import * as ep___pages_like from './endpoints/pages/like.js'; -import * as ep___pages_show from './endpoints/pages/show.js'; -import * as ep___pages_unlike from './endpoints/pages/unlike.js'; -import * as ep___pages_update from './endpoints/pages/update.js'; -import * as ep___flash_create from './endpoints/flash/create.js'; -import * as ep___flash_delete from './endpoints/flash/delete.js'; -import * as ep___flash_featured from './endpoints/flash/featured.js'; -import * as ep___flash_like from './endpoints/flash/like.js'; -import * as ep___flash_show from './endpoints/flash/show.js'; -import * as ep___flash_unlike from './endpoints/flash/unlike.js'; -import * as ep___flash_update from './endpoints/flash/update.js'; -import * as ep___flash_my from './endpoints/flash/my.js'; -import * as ep___flash_myLikes from './endpoints/flash/my-likes.js'; -import * as ep___ping from './endpoints/ping.js'; -import * as ep___pinnedUsers from './endpoints/pinned-users.js'; -import * as ep___promo_read from './endpoints/promo/read.js'; -import * as ep___roles_list from './endpoints/roles/list.js'; -import * as ep___roles_show from './endpoints/roles/show.js'; -import * as ep___roles_users from './endpoints/roles/users.js'; -import * as ep___roles_notes from './endpoints/roles/notes.js'; -import * as ep___requestResetPassword from './endpoints/request-reset-password.js'; -import * as ep___resetDb from './endpoints/reset-db.js'; -import * as ep___resetPassword from './endpoints/reset-password.js'; -import * as ep___serverInfo from './endpoints/server-info.js'; -import * as ep___stats from './endpoints/stats.js'; -import * as ep___sw_show_registration from './endpoints/sw/show-registration.js'; -import * as ep___sw_update_registration from './endpoints/sw/update-registration.js'; -import * as ep___sw_register from './endpoints/sw/register.js'; -import * as ep___sw_unregister from './endpoints/sw/unregister.js'; -import * as ep___test from './endpoints/test.js'; -import * as ep___username_available from './endpoints/username/available.js'; -import * as ep___users from './endpoints/users.js'; -import * as ep___users_clips from './endpoints/users/clips.js'; -import * as ep___users_followers from './endpoints/users/followers.js'; -import * as ep___users_following from './endpoints/users/following.js'; -import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js'; -import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js'; -import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js'; -import * as ep___users_lists_create from './endpoints/users/lists/create.js'; -import * as ep___users_lists_delete from './endpoints/users/lists/delete.js'; -import * as ep___users_lists_list from './endpoints/users/lists/list.js'; -import * as ep___users_lists_pull from './endpoints/users/lists/pull.js'; -import * as ep___users_lists_push from './endpoints/users/lists/push.js'; -import * as ep___users_lists_show from './endpoints/users/lists/show.js'; -import * as ep___users_lists_update from './endpoints/users/lists/update.js'; -import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; -import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; -import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js'; -import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js'; -import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js'; -import * as ep___users_notes from './endpoints/users/notes.js'; -import * as ep___users_pages from './endpoints/users/pages.js'; -import * as ep___users_flashs from './endpoints/users/flashs.js'; -import * as ep___users_reactions from './endpoints/users/reactions.js'; -import * as ep___users_recommendation from './endpoints/users/recommendation.js'; -import * as ep___users_relation from './endpoints/users/relation.js'; -import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js'; -import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js'; -import * as ep___users_search from './endpoints/users/search.js'; -import * as ep___users_show from './endpoints/users/show.js'; -import * as ep___users_achievements from './endpoints/users/achievements.js'; -import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; -import * as ep___fetchRss from './endpoints/fetch-rss.js'; -import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js'; -import * as ep___retention from './endpoints/retention.js'; -import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js'; -import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js'; -import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js'; -import * as ep___reversi_games from './endpoints/reversi/games.js'; -import * as ep___reversi_match from './endpoints/reversi/match.js'; -import * as ep___reversi_invitations from './endpoints/reversi/invitations.js'; -import * as ep___reversi_showGame from './endpoints/reversi/show-game.js'; -import * as ep___reversi_surrender from './endpoints/reversi/surrender.js'; -import * as ep___reversi_verify from './endpoints/reversi/verify.js'; +import * as endpointsObject from './endpoint-list.js'; import { GetterService } from './GetterService.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import type { Provider } from '@nestjs/common'; -const $admin_meta: Provider = { provide: 'ep:admin/meta', useClass: ep___admin_meta.default }; -const $admin_abuseUserReports: Provider = { provide: 'ep:admin/abuse-user-reports', useClass: ep___admin_abuseUserReports.default }; -const $admin_abuseReport_notificationRecipient_list: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/list', useClass: ep___admin_abuseReport_notificationRecipient_list.default }; -const $admin_abuseReport_notificationRecipient_show: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/show', useClass: ep___admin_abuseReport_notificationRecipient_show.default }; -const $admin_abuseReport_notificationRecipient_create: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/create', useClass: ep___admin_abuseReport_notificationRecipient_create.default }; -const $admin_abuseReport_notificationRecipient_update: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/update', useClass: ep___admin_abuseReport_notificationRecipient_update.default }; -const $admin_abuseReport_notificationRecipient_delete: Provider = { provide: 'ep:admin/abuse-report/notification-recipient/delete', useClass: ep___admin_abuseReport_notificationRecipient_delete.default }; -const $admin_accounts_create: Provider = { provide: 'ep:admin/accounts/create', useClass: ep___admin_accounts_create.default }; -const $admin_accounts_delete: Provider = { provide: 'ep:admin/accounts/delete', useClass: ep___admin_accounts_delete.default }; -const $admin_accounts_findByEmail: Provider = { provide: 'ep:admin/accounts/find-by-email', useClass: ep___admin_accounts_findByEmail.default }; -const $admin_ad_create: Provider = { provide: 'ep:admin/ad/create', useClass: ep___admin_ad_create.default }; -const $admin_ad_delete: Provider = { provide: 'ep:admin/ad/delete', useClass: ep___admin_ad_delete.default }; -const $admin_ad_list: Provider = { provide: 'ep:admin/ad/list', useClass: ep___admin_ad_list.default }; -const $admin_ad_update: Provider = { provide: 'ep:admin/ad/update', useClass: ep___admin_ad_update.default }; -const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements/create', useClass: ep___admin_announcements_create.default }; -const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default }; -const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default }; -const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default }; -const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-decorations/create', useClass: ep___admin_avatarDecorations_create.default }; -const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default }; -const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default }; -const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default }; -const $admin_captcha_current: Provider = { provide: 'ep:admin/captcha/current', useClass: ep___admin_captcha_current.default }; -const $admin_captcha_save: Provider = { provide: 'ep:admin/captcha/save', useClass: ep___admin_captcha_save.default }; -const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default }; -const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default }; -const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default }; -const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default }; -const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default }; -const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default }; -const $admin_drive_showFile: Provider = { provide: 'ep:admin/drive/show-file', useClass: ep___admin_drive_showFile.default }; -const $admin_emoji_addAliasesBulk: Provider = { provide: 'ep:admin/emoji/add-aliases-bulk', useClass: ep___admin_emoji_addAliasesBulk.default }; -const $admin_emoji_add: Provider = { provide: 'ep:admin/emoji/add', useClass: ep___admin_emoji_add.default }; -const $admin_emoji_copy: Provider = { provide: 'ep:admin/emoji/copy', useClass: ep___admin_emoji_copy.default }; -const $admin_emoji_deleteBulk: Provider = { provide: 'ep:admin/emoji/delete-bulk', useClass: ep___admin_emoji_deleteBulk.default }; -const $admin_emoji_delete: Provider = { provide: 'ep:admin/emoji/delete', useClass: ep___admin_emoji_delete.default }; -const $admin_emoji_importZip: Provider = { provide: 'ep:admin/emoji/import-zip', useClass: ep___admin_emoji_importZip.default }; -const $admin_emoji_listRemote: Provider = { provide: 'ep:admin/emoji/list-remote', useClass: ep___admin_emoji_listRemote.default }; -const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass: ep___admin_emoji_list.default }; -const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default }; -const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default }; -const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default }; -const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default }; -const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default }; -const $admin_emoji_v2_list: Provider = { provide: 'ep:v2/admin/emoji/list', useClass: ep___v2_admin_emoji_list.default }; -const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default }; -const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default }; -const $admin_federation_removeAllFollowing: Provider = { provide: 'ep:admin/federation/remove-all-following', useClass: ep___admin_federation_removeAllFollowing.default }; -const $admin_federation_updateInstance: Provider = { provide: 'ep:admin/federation/update-instance', useClass: ep___admin_federation_updateInstance.default }; -const $admin_getIndexStats: Provider = { provide: 'ep:admin/get-index-stats', useClass: ep___admin_getIndexStats.default }; -const $admin_getTableStats: Provider = { provide: 'ep:admin/get-table-stats', useClass: ep___admin_getTableStats.default }; -const $admin_getUserIps: Provider = { provide: 'ep:admin/get-user-ips', useClass: ep___admin_getUserIps.default }; -const $admin_invite_create: Provider = { provide: 'ep:admin/invite/create', useClass: ep___admin_invite_create.default }; -const $admin_invite_list: Provider = { provide: 'ep:admin/invite/list', useClass: ep___admin_invite_list.default }; -const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useClass: ep___admin_promo_create.default }; -const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default }; -const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default }; -const $admin_queue_inboxDelayed: Provider = { provide: 'ep:admin/queue/inbox-delayed', useClass: ep___admin_queue_inboxDelayed.default }; -const $admin_queue_promote: Provider = { provide: 'ep:admin/queue/promote', useClass: ep___admin_queue_promote.default }; -const $admin_queue_stats: Provider = { provide: 'ep:admin/queue/stats', useClass: ep___admin_queue_stats.default }; -const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass: ep___admin_relays_add.default }; -const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default }; -const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default }; -const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default }; -const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default }; -const $admin_forwardAbuseUserReport: Provider = { provide: 'ep:admin/forward-abuse-user-report', useClass: ep___admin_forwardAbuseUserReport.default }; -const $admin_updateAbuseUserReport: Provider = { provide: 'ep:admin/update-abuse-user-report', useClass: ep___admin_updateAbuseUserReport.default }; -const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default }; -const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default }; -const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default }; -const $admin_showUser: Provider = { provide: 'ep:admin/show-user', useClass: ep___admin_showUser.default }; -const $admin_showUsers: Provider = { provide: 'ep:admin/show-users', useClass: ep___admin_showUsers.default }; -const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default }; -const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default }; -const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default }; -const $admin_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default }; -const $admin_updateUserNote: Provider = { provide: 'ep:admin/update-user-note', useClass: ep___admin_updateUserNote.default }; -const $admin_roles_create: Provider = { provide: 'ep:admin/roles/create', useClass: ep___admin_roles_create.default }; -const $admin_roles_delete: Provider = { provide: 'ep:admin/roles/delete', useClass: ep___admin_roles_delete.default }; -const $admin_roles_list: Provider = { provide: 'ep:admin/roles/list', useClass: ep___admin_roles_list.default }; -const $admin_roles_show: Provider = { provide: 'ep:admin/roles/show', useClass: ep___admin_roles_show.default }; -const $admin_roles_update: Provider = { provide: 'ep:admin/roles/update', useClass: ep___admin_roles_update.default }; -const $admin_roles_assign: Provider = { provide: 'ep:admin/roles/assign', useClass: ep___admin_roles_assign.default }; -const $admin_roles_unassign: Provider = { provide: 'ep:admin/roles/unassign', useClass: ep___admin_roles_unassign.default }; -const $admin_roles_updateDefaultPolicies: Provider = { provide: 'ep:admin/roles/update-default-policies', useClass: ep___admin_roles_updateDefaultPolicies.default }; -const $admin_roles_users: Provider = { provide: 'ep:admin/roles/users', useClass: ep___admin_roles_users.default }; -const $admin_systemWebhook_create: Provider = { provide: 'ep:admin/system-webhook/create', useClass: ep___admin_systemWebhook_create.default }; -const $admin_systemWebhook_delete: Provider = { provide: 'ep:admin/system-webhook/delete', useClass: ep___admin_systemWebhook_delete.default }; -const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default }; -const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default }; -const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default }; -const $admin_systemWebhook_test: Provider = { provide: 'ep:admin/system-webhook/test', useClass: ep___admin_systemWebhook_test.default }; -const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default }; -const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default }; -const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default }; -const $antennas_delete: Provider = { provide: 'ep:antennas/delete', useClass: ep___antennas_delete.default }; -const $antennas_list: Provider = { provide: 'ep:antennas/list', useClass: ep___antennas_list.default }; -const $antennas_notes: Provider = { provide: 'ep:antennas/notes', useClass: ep___antennas_notes.default }; -const $antennas_show: Provider = { provide: 'ep:antennas/show', useClass: ep___antennas_show.default }; -const $antennas_update: Provider = { provide: 'ep:antennas/update', useClass: ep___antennas_update.default }; -const $ap_get: Provider = { provide: 'ep:ap/get', useClass: ep___ap_get.default }; -const $ap_show: Provider = { provide: 'ep:ap/show', useClass: ep___ap_show.default }; -const $app_create: Provider = { provide: 'ep:app/create', useClass: ep___app_create.default }; -const $app_show: Provider = { provide: 'ep:app/show', useClass: ep___app_show.default }; -const $auth_accept: Provider = { provide: 'ep:auth/accept', useClass: ep___auth_accept.default }; -const $auth_session_generate: Provider = { provide: 'ep:auth/session/generate', useClass: ep___auth_session_generate.default }; -const $auth_session_show: Provider = { provide: 'ep:auth/session/show', useClass: ep___auth_session_show.default }; -const $auth_session_userkey: Provider = { provide: 'ep:auth/session/userkey', useClass: ep___auth_session_userkey.default }; -const $blocking_create: Provider = { provide: 'ep:blocking/create', useClass: ep___blocking_create.default }; -const $blocking_delete: Provider = { provide: 'ep:blocking/delete', useClass: ep___blocking_delete.default }; -const $blocking_list: Provider = { provide: 'ep:blocking/list', useClass: ep___blocking_list.default }; -const $channels_create: Provider = { provide: 'ep:channels/create', useClass: ep___channels_create.default }; -const $channels_featured: Provider = { provide: 'ep:channels/featured', useClass: ep___channels_featured.default }; -const $channels_follow: Provider = { provide: 'ep:channels/follow', useClass: ep___channels_follow.default }; -const $channels_followed: Provider = { provide: 'ep:channels/followed', useClass: ep___channels_followed.default }; -const $channels_owned: Provider = { provide: 'ep:channels/owned', useClass: ep___channels_owned.default }; -const $channels_show: Provider = { provide: 'ep:channels/show', useClass: ep___channels_show.default }; -const $channels_timeline: Provider = { provide: 'ep:channels/timeline', useClass: ep___channels_timeline.default }; -const $channels_unfollow: Provider = { provide: 'ep:channels/unfollow', useClass: ep___channels_unfollow.default }; -const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep___channels_update.default }; -const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default }; -const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default }; -const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default }; -const $channels_search: Provider = { provide: 'ep:channels/search', useClass: ep___channels_search.default }; -const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default }; -const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default }; -const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default }; -const $charts_federation: Provider = { provide: 'ep:charts/federation', useClass: ep___charts_federation.default }; -const $charts_instance: Provider = { provide: 'ep:charts/instance', useClass: ep___charts_instance.default }; -const $charts_notes: Provider = { provide: 'ep:charts/notes', useClass: ep___charts_notes.default }; -const $charts_user_drive: Provider = { provide: 'ep:charts/user/drive', useClass: ep___charts_user_drive.default }; -const $charts_user_following: Provider = { provide: 'ep:charts/user/following', useClass: ep___charts_user_following.default }; -const $charts_user_notes: Provider = { provide: 'ep:charts/user/notes', useClass: ep___charts_user_notes.default }; -const $charts_user_pv: Provider = { provide: 'ep:charts/user/pv', useClass: ep___charts_user_pv.default }; -const $charts_user_reactions: Provider = { provide: 'ep:charts/user/reactions', useClass: ep___charts_user_reactions.default }; -const $charts_users: Provider = { provide: 'ep:charts/users', useClass: ep___charts_users.default }; -const $clips_addNote: Provider = { provide: 'ep:clips/add-note', useClass: ep___clips_addNote.default }; -const $clips_removeNote: Provider = { provide: 'ep:clips/remove-note', useClass: ep___clips_removeNote.default }; -const $clips_create: Provider = { provide: 'ep:clips/create', useClass: ep___clips_create.default }; -const $clips_delete: Provider = { provide: 'ep:clips/delete', useClass: ep___clips_delete.default }; -const $clips_list: Provider = { provide: 'ep:clips/list', useClass: ep___clips_list.default }; -const $clips_notes: Provider = { provide: 'ep:clips/notes', useClass: ep___clips_notes.default }; -const $clips_show: Provider = { provide: 'ep:clips/show', useClass: ep___clips_show.default }; -const $clips_update: Provider = { provide: 'ep:clips/update', useClass: ep___clips_update.default }; -const $clips_favorite: Provider = { provide: 'ep:clips/favorite', useClass: ep___clips_favorite.default }; -const $clips_unfavorite: Provider = { provide: 'ep:clips/unfavorite', useClass: ep___clips_unfavorite.default }; -const $clips_myFavorites: Provider = { provide: 'ep:clips/my-favorites', useClass: ep___clips_myFavorites.default }; -const $drive: Provider = { provide: 'ep:drive', useClass: ep___drive.default }; -const $drive_files: Provider = { provide: 'ep:drive/files', useClass: ep___drive_files.default }; -const $drive_files_attachedNotes: Provider = { provide: 'ep:drive/files/attached-notes', useClass: ep___drive_files_attachedNotes.default }; -const $drive_files_checkExistence: Provider = { provide: 'ep:drive/files/check-existence', useClass: ep___drive_files_checkExistence.default }; -const $drive_files_create: Provider = { provide: 'ep:drive/files/create', useClass: ep___drive_files_create.default }; -const $drive_files_delete: Provider = { provide: 'ep:drive/files/delete', useClass: ep___drive_files_delete.default }; -const $drive_files_findByHash: Provider = { provide: 'ep:drive/files/find-by-hash', useClass: ep___drive_files_findByHash.default }; -const $drive_files_find: Provider = { provide: 'ep:drive/files/find', useClass: ep___drive_files_find.default }; -const $drive_files_show: Provider = { provide: 'ep:drive/files/show', useClass: ep___drive_files_show.default }; -const $drive_files_update: Provider = { provide: 'ep:drive/files/update', useClass: ep___drive_files_update.default }; -const $drive_files_uploadFromUrl: Provider = { provide: 'ep:drive/files/upload-from-url', useClass: ep___drive_files_uploadFromUrl.default }; -const $drive_folders: Provider = { provide: 'ep:drive/folders', useClass: ep___drive_folders.default }; -const $drive_folders_create: Provider = { provide: 'ep:drive/folders/create', useClass: ep___drive_folders_create.default }; -const $drive_folders_delete: Provider = { provide: 'ep:drive/folders/delete', useClass: ep___drive_folders_delete.default }; -const $drive_folders_find: Provider = { provide: 'ep:drive/folders/find', useClass: ep___drive_folders_find.default }; -const $drive_folders_show: Provider = { provide: 'ep:drive/folders/show', useClass: ep___drive_folders_show.default }; -const $drive_folders_update: Provider = { provide: 'ep:drive/folders/update', useClass: ep___drive_folders_update.default }; -const $drive_stream: Provider = { provide: 'ep:drive/stream', useClass: ep___drive_stream.default }; -const $emailAddress_available: Provider = { provide: 'ep:email-address/available', useClass: ep___emailAddress_available.default }; -const $endpoint: Provider = { provide: 'ep:endpoint', useClass: ep___endpoint.default }; -const $endpoints: Provider = { provide: 'ep:endpoints', useClass: ep___endpoints.default }; -const $exportCustomEmojis: Provider = { provide: 'ep:export-custom-emojis', useClass: ep___exportCustomEmojis.default }; -const $federation_followers: Provider = { provide: 'ep:federation/followers', useClass: ep___federation_followers.default }; -const $federation_following: Provider = { provide: 'ep:federation/following', useClass: ep___federation_following.default }; -const $federation_instances: Provider = { provide: 'ep:federation/instances', useClass: ep___federation_instances.default }; -const $federation_showInstance: Provider = { provide: 'ep:federation/show-instance', useClass: ep___federation_showInstance.default }; -const $federation_updateRemoteUser: Provider = { provide: 'ep:federation/update-remote-user', useClass: ep___federation_updateRemoteUser.default }; -const $federation_users: Provider = { provide: 'ep:federation/users', useClass: ep___federation_users.default }; -const $federation_stats: Provider = { provide: 'ep:federation/stats', useClass: ep___federation_stats.default }; -const $following_create: Provider = { provide: 'ep:following/create', useClass: ep___following_create.default }; -const $following_delete: Provider = { provide: 'ep:following/delete', useClass: ep___following_delete.default }; -const $following_update: Provider = { provide: 'ep:following/update', useClass: ep___following_update.default }; -const $following_update_all: Provider = { provide: 'ep:following/update-all', useClass: ep___following_update_all.default }; -const $following_invalidate: Provider = { provide: 'ep:following/invalidate', useClass: ep___following_invalidate.default }; -const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default }; -const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default }; -const $following_requests_list: Provider = { provide: 'ep:following/requests/list', useClass: ep___following_requests_list.default }; -const $following_requests_sent: Provider = { provide: 'ep:following/requests/sent', useClass: ep___following_requests_sent.default }; -const $following_requests_reject: Provider = { provide: 'ep:following/requests/reject', useClass: ep___following_requests_reject.default }; -const $gallery_featured: Provider = { provide: 'ep:gallery/featured', useClass: ep___gallery_featured.default }; -const $gallery_popular: Provider = { provide: 'ep:gallery/popular', useClass: ep___gallery_popular.default }; -const $gallery_posts: Provider = { provide: 'ep:gallery/posts', useClass: ep___gallery_posts.default }; -const $gallery_posts_create: Provider = { provide: 'ep:gallery/posts/create', useClass: ep___gallery_posts_create.default }; -const $gallery_posts_delete: Provider = { provide: 'ep:gallery/posts/delete', useClass: ep___gallery_posts_delete.default }; -const $gallery_posts_like: Provider = { provide: 'ep:gallery/posts/like', useClass: ep___gallery_posts_like.default }; -const $gallery_posts_show: Provider = { provide: 'ep:gallery/posts/show', useClass: ep___gallery_posts_show.default }; -const $gallery_posts_unlike: Provider = { provide: 'ep:gallery/posts/unlike', useClass: ep___gallery_posts_unlike.default }; -const $gallery_posts_update: Provider = { provide: 'ep:gallery/posts/update', useClass: ep___gallery_posts_update.default }; -const $getOnlineUsersCount: Provider = { provide: 'ep:get-online-users-count', useClass: ep___getOnlineUsersCount.default }; -const $getAvatarDecorations: Provider = { provide: 'ep:get-avatar-decorations', useClass: ep___getAvatarDecorations.default }; -const $hashtags_list: Provider = { provide: 'ep:hashtags/list', useClass: ep___hashtags_list.default }; -const $hashtags_search: Provider = { provide: 'ep:hashtags/search', useClass: ep___hashtags_search.default }; -const $hashtags_show: Provider = { provide: 'ep:hashtags/show', useClass: ep___hashtags_show.default }; -const $hashtags_trend: Provider = { provide: 'ep:hashtags/trend', useClass: ep___hashtags_trend.default }; -const $hashtags_users: Provider = { provide: 'ep:hashtags/users', useClass: ep___hashtags_users.default }; -const $i: Provider = { provide: 'ep:i', useClass: ep___i.default }; -const $i_2fa_done: Provider = { provide: 'ep:i/2fa/done', useClass: ep___i_2fa_done.default }; -const $i_2fa_keyDone: Provider = { provide: 'ep:i/2fa/key-done', useClass: ep___i_2fa_keyDone.default }; -const $i_2fa_passwordLess: Provider = { provide: 'ep:i/2fa/password-less', useClass: ep___i_2fa_passwordLess.default }; -const $i_2fa_registerKey: Provider = { provide: 'ep:i/2fa/register-key', useClass: ep___i_2fa_registerKey.default }; -const $i_2fa_register: Provider = { provide: 'ep:i/2fa/register', useClass: ep___i_2fa_register.default }; -const $i_2fa_updateKey: Provider = { provide: 'ep:i/2fa/update-key', useClass: ep___i_2fa_updateKey.default }; -const $i_2fa_removeKey: Provider = { provide: 'ep:i/2fa/remove-key', useClass: ep___i_2fa_removeKey.default }; -const $i_2fa_unregister: Provider = { provide: 'ep:i/2fa/unregister', useClass: ep___i_2fa_unregister.default }; -const $i_apps: Provider = { provide: 'ep:i/apps', useClass: ep___i_apps.default }; -const $i_authorizedApps: Provider = { provide: 'ep:i/authorized-apps', useClass: ep___i_authorizedApps.default }; -const $i_claimAchievement: Provider = { provide: 'ep:i/claim-achievement', useClass: ep___i_claimAchievement.default }; -const $i_changePassword: Provider = { provide: 'ep:i/change-password', useClass: ep___i_changePassword.default }; -const $i_deleteAccount: Provider = { provide: 'ep:i/delete-account', useClass: ep___i_deleteAccount.default }; -const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass: ep___i_exportBlocking.default }; -const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default }; -const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default }; -const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default }; -const $i_exportClips: Provider = { provide: 'ep:i/export-clips', useClass: ep___i_exportClips.default }; -const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default }; -const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default }; -const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default }; -const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default }; -const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default }; -const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default }; -const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass: ep___i_importBlocking.default }; -const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default }; -const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default }; -const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default }; -const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default }; -const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default }; -const $i_notificationsGrouped: Provider = { provide: 'ep:i/notifications-grouped', useClass: ep___i_notificationsGrouped.default }; -const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default }; -const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default }; -const $i_pin: Provider = { provide: 'ep:i/pin', useClass: ep___i_pin.default }; -const $i_readAllUnreadNotes: Provider = { provide: 'ep:i/read-all-unread-notes', useClass: ep___i_readAllUnreadNotes.default }; -const $i_readAnnouncement: Provider = { provide: 'ep:i/read-announcement', useClass: ep___i_readAnnouncement.default }; -const $i_regenerateToken: Provider = { provide: 'ep:i/regenerate-token', useClass: ep___i_regenerateToken.default }; -const $i_registry_getAll: Provider = { provide: 'ep:i/registry/get-all', useClass: ep___i_registry_getAll.default }; -const $i_registry_getDetail: Provider = { provide: 'ep:i/registry/get-detail', useClass: ep___i_registry_getDetail.default }; -const $i_registry_get: Provider = { provide: 'ep:i/registry/get', useClass: ep___i_registry_get.default }; -const $i_registry_keysWithType: Provider = { provide: 'ep:i/registry/keys-with-type', useClass: ep___i_registry_keysWithType.default }; -const $i_registry_keys: Provider = { provide: 'ep:i/registry/keys', useClass: ep___i_registry_keys.default }; -const $i_registry_remove: Provider = { provide: 'ep:i/registry/remove', useClass: ep___i_registry_remove.default }; -const $i_registry_scopesWithDomain: Provider = { provide: 'ep:i/registry/scopes-with-domain', useClass: ep___i_registry_scopesWithDomain.default }; -const $i_registry_set: Provider = { provide: 'ep:i/registry/set', useClass: ep___i_registry_set.default }; -const $i_revokeToken: Provider = { provide: 'ep:i/revoke-token', useClass: ep___i_revokeToken.default }; -const $i_signinHistory: Provider = { provide: 'ep:i/signin-history', useClass: ep___i_signinHistory.default }; -const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.default }; -const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default }; -const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default }; -const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default }; -const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default }; -const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default }; -const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default }; -const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default }; -const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default }; -const $i_webhooks_test: Provider = { provide: 'ep:i/webhooks/test', useClass: ep___i_webhooks_test.default }; -const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default }; -const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default }; -const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default }; -const $invite_limit: Provider = { provide: 'ep:invite/limit', useClass: ep___invite_limit.default }; -const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default }; -const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default }; -const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default }; -const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default }; -const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default }; -const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default }; -const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default }; -const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default }; -const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default }; -const $renoteMute_list: Provider = { provide: 'ep:renote-mute/list', useClass: ep___renoteMute_list.default }; -const $my_apps: Provider = { provide: 'ep:my/apps', useClass: ep___my_apps.default }; -const $notes: Provider = { provide: 'ep:notes', useClass: ep___notes.default }; -const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep___notes_children.default }; -const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default }; -const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default }; -const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default }; -const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default }; -const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default }; -const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default }; -const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default }; -const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default }; -const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default }; -const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', useClass: ep___notes_localTimeline.default }; -const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default }; -const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default }; -const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default }; -const $notes_reactions: Provider = { provide: 'ep:notes/reactions', useClass: ep___notes_reactions.default }; -const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create', useClass: ep___notes_reactions_create.default }; -const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default }; -const $notes_renotes: Provider = { provide: 'ep:notes/renotes', useClass: ep___notes_renotes.default }; -const $notes_replies: Provider = { provide: 'ep:notes/replies', useClass: ep___notes_replies.default }; -const $notes_searchByTag: Provider = { provide: 'ep:notes/search-by-tag', useClass: ep___notes_searchByTag.default }; -const $notes_search: Provider = { provide: 'ep:notes/search', useClass: ep___notes_search.default }; -const $notes_show: Provider = { provide: 'ep:notes/show', useClass: ep___notes_show.default }; -const $notes_state: Provider = { provide: 'ep:notes/state', useClass: ep___notes_state.default }; -const $notes_threadMuting_create: Provider = { provide: 'ep:notes/thread-muting/create', useClass: ep___notes_threadMuting_create.default }; -const $notes_threadMuting_delete: Provider = { provide: 'ep:notes/thread-muting/delete', useClass: ep___notes_threadMuting_delete.default }; -const $notes_timeline: Provider = { provide: 'ep:notes/timeline', useClass: ep___notes_timeline.default }; -const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep___notes_translate.default }; -const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default }; -const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default }; -const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default }; -const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default }; -const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default }; -const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default }; -const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default }; -const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default }; -const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default }; -const $pages_featured: Provider = { provide: 'ep:pages/featured', useClass: ep___pages_featured.default }; -const $pages_like: Provider = { provide: 'ep:pages/like', useClass: ep___pages_like.default }; -const $pages_show: Provider = { provide: 'ep:pages/show', useClass: ep___pages_show.default }; -const $pages_unlike: Provider = { provide: 'ep:pages/unlike', useClass: ep___pages_unlike.default }; -const $pages_update: Provider = { provide: 'ep:pages/update', useClass: ep___pages_update.default }; -const $flash_create: Provider = { provide: 'ep:flash/create', useClass: ep___flash_create.default }; -const $flash_delete: Provider = { provide: 'ep:flash/delete', useClass: ep___flash_delete.default }; -const $flash_featured: Provider = { provide: 'ep:flash/featured', useClass: ep___flash_featured.default }; -const $flash_like: Provider = { provide: 'ep:flash/like', useClass: ep___flash_like.default }; -const $flash_show: Provider = { provide: 'ep:flash/show', useClass: ep___flash_show.default }; -const $flash_unlike: Provider = { provide: 'ep:flash/unlike', useClass: ep___flash_unlike.default }; -const $flash_update: Provider = { provide: 'ep:flash/update', useClass: ep___flash_update.default }; -const $flash_my: Provider = { provide: 'ep:flash/my', useClass: ep___flash_my.default }; -const $flash_myLikes: Provider = { provide: 'ep:flash/my-likes', useClass: ep___flash_myLikes.default }; -const $ping: Provider = { provide: 'ep:ping', useClass: ep___ping.default }; -const $pinnedUsers: Provider = { provide: 'ep:pinned-users', useClass: ep___pinnedUsers.default }; -const $promo_read: Provider = { provide: 'ep:promo/read', useClass: ep___promo_read.default }; -const $roles_list: Provider = { provide: 'ep:roles/list', useClass: ep___roles_list.default }; -const $roles_show: Provider = { provide: 'ep:roles/show', useClass: ep___roles_show.default }; -const $roles_users: Provider = { provide: 'ep:roles/users', useClass: ep___roles_users.default }; -const $roles_notes: Provider = { provide: 'ep:roles/notes', useClass: ep___roles_notes.default }; -const $requestResetPassword: Provider = { provide: 'ep:request-reset-password', useClass: ep___requestResetPassword.default }; -const $resetDb: Provider = { provide: 'ep:reset-db', useClass: ep___resetDb.default }; -const $resetPassword: Provider = { provide: 'ep:reset-password', useClass: ep___resetPassword.default }; -const $serverInfo: Provider = { provide: 'ep:server-info', useClass: ep___serverInfo.default }; -const $stats: Provider = { provide: 'ep:stats', useClass: ep___stats.default }; -const $sw_show_registration: Provider = { provide: 'ep:sw/show-registration', useClass: ep___sw_show_registration.default }; -const $sw_update_registration: Provider = { provide: 'ep:sw/update-registration', useClass: ep___sw_update_registration.default }; -const $sw_register: Provider = { provide: 'ep:sw/register', useClass: ep___sw_register.default }; -const $sw_unregister: Provider = { provide: 'ep:sw/unregister', useClass: ep___sw_unregister.default }; -const $test: Provider = { provide: 'ep:test', useClass: ep___test.default }; -const $username_available: Provider = { provide: 'ep:username/available', useClass: ep___username_available.default }; -const $users: Provider = { provide: 'ep:users', useClass: ep___users.default }; -const $users_clips: Provider = { provide: 'ep:users/clips', useClass: ep___users_clips.default }; -const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep___users_followers.default }; -const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default }; -const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default }; -const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default }; -const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default }; -const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default }; -const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default }; -const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default }; -const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass: ep___users_lists_pull.default }; -const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default }; -const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default }; -const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default }; -const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default }; -const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default }; -const $users_lists_createFromPublic: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_createFromPublic.default }; -const $users_lists_updateMembership: Provider = { provide: 'ep:users/lists/update-membership', useClass: ep___users_lists_updateMembership.default }; -const $users_lists_getMemberships: Provider = { provide: 'ep:users/lists/get-memberships', useClass: ep___users_lists_getMemberships.default }; -const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default }; -const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default }; -const $users_flashs: Provider = { provide: 'ep:users/flashs', useClass: ep___users_flashs.default }; -const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default }; -const $users_recommendation: Provider = { provide: 'ep:users/recommendation', useClass: ep___users_recommendation.default }; -const $users_relation: Provider = { provide: 'ep:users/relation', useClass: ep___users_relation.default }; -const $users_reportAbuse: Provider = { provide: 'ep:users/report-abuse', useClass: ep___users_reportAbuse.default }; -const $users_searchByUsernameAndHost: Provider = { provide: 'ep:users/search-by-username-and-host', useClass: ep___users_searchByUsernameAndHost.default }; -const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___users_search.default }; -const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default }; -const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default }; -const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default }; -const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default }; -const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default }; -const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default }; -const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default }; -const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default }; -const $reversi_cancelMatch: Provider = { provide: 'ep:reversi/cancel-match', useClass: ep___reversi_cancelMatch.default }; -const $reversi_games: Provider = { provide: 'ep:reversi/games', useClass: ep___reversi_games.default }; -const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___reversi_match.default }; -const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default }; -const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default }; -const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default }; -const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default }; +const endpoints = Object.entries(endpointsObject); +const endpointProviders = endpoints.map(([path, endpoint]): Provider => ({ provide: `ep:${path}`, useClass: endpoint.default })); @Module({ imports: [ @@ -792,779 +21,10 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ providers: [ GetterService, ApiLoggerService, - $admin_meta, - $admin_abuseUserReports, - $admin_abuseReport_notificationRecipient_list, - $admin_abuseReport_notificationRecipient_show, - $admin_abuseReport_notificationRecipient_create, - $admin_abuseReport_notificationRecipient_update, - $admin_abuseReport_notificationRecipient_delete, - $admin_accounts_create, - $admin_accounts_delete, - $admin_accounts_findByEmail, - $admin_ad_create, - $admin_ad_delete, - $admin_ad_list, - $admin_ad_update, - $admin_announcements_create, - $admin_announcements_delete, - $admin_announcements_list, - $admin_announcements_update, - $admin_avatarDecorations_create, - $admin_avatarDecorations_delete, - $admin_avatarDecorations_list, - $admin_avatarDecorations_update, - $admin_captcha_current, - $admin_captcha_save, - $admin_deleteAllFilesOfAUser, - $admin_unsetUserAvatar, - $admin_unsetUserBanner, - $admin_drive_cleanRemoteFiles, - $admin_drive_cleanup, - $admin_drive_files, - $admin_drive_showFile, - $admin_emoji_addAliasesBulk, - $admin_emoji_add, - $admin_emoji_copy, - $admin_emoji_deleteBulk, - $admin_emoji_delete, - $admin_emoji_importZip, - $admin_emoji_listRemote, - $admin_emoji_list, - $admin_emoji_removeAliasesBulk, - $admin_emoji_setAliasesBulk, - $admin_emoji_setCategoryBulk, - $admin_emoji_setLicenseBulk, - $admin_emoji_update, - $admin_emoji_v2_list, - $admin_federation_deleteAllFiles, - $admin_federation_refreshRemoteInstanceMetadata, - $admin_federation_removeAllFollowing, - $admin_federation_updateInstance, - $admin_getIndexStats, - $admin_getTableStats, - $admin_getUserIps, - $admin_invite_create, - $admin_invite_list, - $admin_promo_create, - $admin_queue_clear, - $admin_queue_deliverDelayed, - $admin_queue_inboxDelayed, - $admin_queue_promote, - $admin_queue_stats, - $admin_relays_add, - $admin_relays_list, - $admin_relays_remove, - $admin_resetPassword, - $admin_resolveAbuseUserReport, - $admin_forwardAbuseUserReport, - $admin_updateAbuseUserReport, - $admin_sendEmail, - $admin_serverInfo, - $admin_showModerationLogs, - $admin_showUser, - $admin_showUsers, - $admin_suspendUser, - $admin_unsuspendUser, - $admin_updateMeta, - $admin_deleteAccount, - $admin_updateUserNote, - $admin_roles_create, - $admin_roles_delete, - $admin_roles_list, - $admin_roles_show, - $admin_roles_update, - $admin_roles_assign, - $admin_roles_unassign, - $admin_roles_updateDefaultPolicies, - $admin_roles_users, - $admin_systemWebhook_create, - $admin_systemWebhook_delete, - $admin_systemWebhook_list, - $admin_systemWebhook_show, - $admin_systemWebhook_update, - $admin_systemWebhook_test, - $announcements, - $announcements_show, - $antennas_create, - $antennas_delete, - $antennas_list, - $antennas_notes, - $antennas_show, - $antennas_update, - $ap_get, - $ap_show, - $app_create, - $app_show, - $auth_accept, - $auth_session_generate, - $auth_session_show, - $auth_session_userkey, - $blocking_create, - $blocking_delete, - $blocking_list, - $channels_create, - $channels_featured, - $channels_follow, - $channels_followed, - $channels_owned, - $channels_show, - $channels_timeline, - $channels_unfollow, - $channels_update, - $channels_favorite, - $channels_unfavorite, - $channels_myFavorites, - $channels_search, - $charts_activeUsers, - $charts_apRequest, - $charts_drive, - $charts_federation, - $charts_instance, - $charts_notes, - $charts_user_drive, - $charts_user_following, - $charts_user_notes, - $charts_user_pv, - $charts_user_reactions, - $charts_users, - $clips_addNote, - $clips_removeNote, - $clips_create, - $clips_delete, - $clips_list, - $clips_notes, - $clips_show, - $clips_update, - $clips_favorite, - $clips_unfavorite, - $clips_myFavorites, - $drive, - $drive_files, - $drive_files_attachedNotes, - $drive_files_checkExistence, - $drive_files_create, - $drive_files_delete, - $drive_files_findByHash, - $drive_files_find, - $drive_files_show, - $drive_files_update, - $drive_files_uploadFromUrl, - $drive_folders, - $drive_folders_create, - $drive_folders_delete, - $drive_folders_find, - $drive_folders_show, - $drive_folders_update, - $drive_stream, - $emailAddress_available, - $endpoint, - $endpoints, - $exportCustomEmojis, - $federation_followers, - $federation_following, - $federation_instances, - $federation_showInstance, - $federation_updateRemoteUser, - $federation_users, - $federation_stats, - $following_create, - $following_delete, - $following_update, - $following_update_all, - $following_invalidate, - $following_requests_accept, - $following_requests_cancel, - $following_requests_list, - $following_requests_sent, - $following_requests_reject, - $gallery_featured, - $gallery_popular, - $gallery_posts, - $gallery_posts_create, - $gallery_posts_delete, - $gallery_posts_like, - $gallery_posts_show, - $gallery_posts_unlike, - $gallery_posts_update, - $getOnlineUsersCount, - $getAvatarDecorations, - $hashtags_list, - $hashtags_search, - $hashtags_show, - $hashtags_trend, - $hashtags_users, - $i, - $i_2fa_done, - $i_2fa_keyDone, - $i_2fa_passwordLess, - $i_2fa_registerKey, - $i_2fa_register, - $i_2fa_updateKey, - $i_2fa_removeKey, - $i_2fa_unregister, - $i_apps, - $i_authorizedApps, - $i_claimAchievement, - $i_changePassword, - $i_deleteAccount, - $i_exportBlocking, - $i_exportFollowing, - $i_exportMute, - $i_exportNotes, - $i_exportClips, - $i_exportFavorites, - $i_exportUserLists, - $i_exportAntennas, - $i_favorites, - $i_gallery_likes, - $i_gallery_posts, - $i_importBlocking, - $i_importFollowing, - $i_importMuting, - $i_importUserLists, - $i_importAntennas, - $i_notifications, - $i_notificationsGrouped, - $i_pageLikes, - $i_pages, - $i_pin, - $i_readAllUnreadNotes, - $i_readAnnouncement, - $i_regenerateToken, - $i_registry_getAll, - $i_registry_getDetail, - $i_registry_get, - $i_registry_keysWithType, - $i_registry_keys, - $i_registry_remove, - $i_registry_scopesWithDomain, - $i_registry_set, - $i_revokeToken, - $i_signinHistory, - $i_unpin, - $i_updateEmail, - $i_update, - $i_move, - $i_webhooks_create, - $i_webhooks_list, - $i_webhooks_show, - $i_webhooks_update, - $i_webhooks_delete, - $i_webhooks_test, - $invite_create, - $invite_delete, - $invite_list, - $invite_limit, - $meta, - $emojis, - $emoji, - $miauth_genToken, - $mute_create, - $mute_delete, - $mute_list, - $renoteMute_create, - $renoteMute_delete, - $renoteMute_list, - $my_apps, - $notes, - $notes_children, - $notes_clips, - $notes_conversation, - $notes_create, - $notes_delete, - $notes_favorites_create, - $notes_favorites_delete, - $notes_featured, - $notes_globalTimeline, - $notes_hybridTimeline, - $notes_localTimeline, - $notes_mentions, - $notes_polls_recommendation, - $notes_polls_vote, - $notes_reactions, - $notes_reactions_create, - $notes_reactions_delete, - $notes_renotes, - $notes_replies, - $notes_searchByTag, - $notes_search, - $notes_show, - $notes_state, - $notes_threadMuting_create, - $notes_threadMuting_delete, - $notes_timeline, - $notes_translate, - $notes_unrenote, - $notes_userListTimeline, - $notifications_create, - $notifications_flush, - $notifications_markAllAsRead, - $notifications_testNotification, - $pagePush, - $pages_create, - $pages_delete, - $pages_featured, - $pages_like, - $pages_show, - $pages_unlike, - $pages_update, - $flash_create, - $flash_delete, - $flash_featured, - $flash_like, - $flash_show, - $flash_unlike, - $flash_update, - $flash_my, - $flash_myLikes, - $ping, - $pinnedUsers, - $promo_read, - $roles_list, - $roles_show, - $roles_users, - $roles_notes, - $requestResetPassword, - $resetDb, - $resetPassword, - $serverInfo, - $stats, - $sw_show_registration, - $sw_update_registration, - $sw_register, - $sw_unregister, - $test, - $username_available, - $users, - $users_clips, - $users_followers, - $users_following, - $users_gallery_posts, - $users_getFrequentlyRepliedUsers, - $users_featuredNotes, - $users_lists_create, - $users_lists_delete, - $users_lists_list, - $users_lists_pull, - $users_lists_push, - $users_lists_show, - $users_lists_update, - $users_lists_favorite, - $users_lists_unfavorite, - $users_lists_createFromPublic, - $users_lists_updateMembership, - $users_lists_getMemberships, - $users_notes, - $users_pages, - $users_flashs, - $users_reactions, - $users_recommendation, - $users_relation, - $users_reportAbuse, - $users_searchByUsernameAndHost, - $users_search, - $users_show, - $users_achievements, - $users_updateMemo, - $fetchRss, - $fetchExternalResources, - $retention, - $bubbleGame_register, - $bubbleGame_ranking, - $reversi_cancelMatch, - $reversi_games, - $reversi_match, - $reversi_invitations, - $reversi_showGame, - $reversi_surrender, - $reversi_verify, + ...endpointProviders, ], exports: [ - $admin_meta, - $admin_abuseUserReports, - $admin_abuseReport_notificationRecipient_list, - $admin_abuseReport_notificationRecipient_show, - $admin_abuseReport_notificationRecipient_create, - $admin_abuseReport_notificationRecipient_update, - $admin_abuseReport_notificationRecipient_delete, - $admin_accounts_create, - $admin_accounts_delete, - $admin_accounts_findByEmail, - $admin_ad_create, - $admin_ad_delete, - $admin_ad_list, - $admin_ad_update, - $admin_announcements_create, - $admin_announcements_delete, - $admin_announcements_list, - $admin_announcements_update, - $admin_avatarDecorations_create, - $admin_avatarDecorations_delete, - $admin_avatarDecorations_list, - $admin_avatarDecorations_update, - $admin_captcha_current, - $admin_captcha_save, - $admin_deleteAllFilesOfAUser, - $admin_unsetUserAvatar, - $admin_unsetUserBanner, - $admin_drive_cleanRemoteFiles, - $admin_drive_cleanup, - $admin_drive_files, - $admin_drive_showFile, - $admin_emoji_addAliasesBulk, - $admin_emoji_add, - $admin_emoji_copy, - $admin_emoji_deleteBulk, - $admin_emoji_delete, - $admin_emoji_importZip, - $admin_emoji_listRemote, - $admin_emoji_list, - $admin_emoji_removeAliasesBulk, - $admin_emoji_setAliasesBulk, - $admin_emoji_setCategoryBulk, - $admin_emoji_setLicenseBulk, - $admin_emoji_update, - $admin_emoji_v2_list, - $admin_federation_deleteAllFiles, - $admin_federation_refreshRemoteInstanceMetadata, - $admin_federation_removeAllFollowing, - $admin_federation_updateInstance, - $admin_getIndexStats, - $admin_getTableStats, - $admin_getUserIps, - $admin_invite_create, - $admin_invite_list, - $admin_promo_create, - $admin_queue_clear, - $admin_queue_deliverDelayed, - $admin_queue_inboxDelayed, - $admin_queue_promote, - $admin_queue_stats, - $admin_relays_add, - $admin_relays_list, - $admin_relays_remove, - $admin_resetPassword, - $admin_resolveAbuseUserReport, - $admin_forwardAbuseUserReport, - $admin_updateAbuseUserReport, - $admin_sendEmail, - $admin_serverInfo, - $admin_showModerationLogs, - $admin_showUser, - $admin_showUsers, - $admin_suspendUser, - $admin_unsuspendUser, - $admin_updateMeta, - $admin_deleteAccount, - $admin_updateUserNote, - $admin_roles_create, - $admin_roles_delete, - $admin_roles_list, - $admin_roles_show, - $admin_roles_update, - $admin_roles_assign, - $admin_roles_unassign, - $admin_roles_updateDefaultPolicies, - $admin_roles_users, - $admin_systemWebhook_create, - $admin_systemWebhook_delete, - $admin_systemWebhook_list, - $admin_systemWebhook_show, - $admin_systemWebhook_update, - $admin_systemWebhook_test, - $announcements, - $announcements_show, - $antennas_create, - $antennas_delete, - $antennas_list, - $antennas_notes, - $antennas_show, - $antennas_update, - $ap_get, - $ap_show, - $app_create, - $app_show, - $auth_accept, - $auth_session_generate, - $auth_session_show, - $auth_session_userkey, - $blocking_create, - $blocking_delete, - $blocking_list, - $channels_create, - $channels_featured, - $channels_follow, - $channels_followed, - $channels_owned, - $channels_show, - $channels_timeline, - $channels_unfollow, - $channels_update, - $channels_favorite, - $channels_unfavorite, - $channels_myFavorites, - $channels_search, - $charts_activeUsers, - $charts_apRequest, - $charts_drive, - $charts_federation, - $charts_instance, - $charts_notes, - $charts_user_drive, - $charts_user_following, - $charts_user_notes, - $charts_user_pv, - $charts_user_reactions, - $charts_users, - $clips_addNote, - $clips_removeNote, - $clips_create, - $clips_delete, - $clips_list, - $clips_notes, - $clips_show, - $clips_update, - $clips_favorite, - $clips_unfavorite, - $clips_myFavorites, - $drive, - $drive_files, - $drive_files_attachedNotes, - $drive_files_checkExistence, - $drive_files_create, - $drive_files_delete, - $drive_files_findByHash, - $drive_files_find, - $drive_files_show, - $drive_files_update, - $drive_files_uploadFromUrl, - $drive_folders, - $drive_folders_create, - $drive_folders_delete, - $drive_folders_find, - $drive_folders_show, - $drive_folders_update, - $drive_stream, - $emailAddress_available, - $endpoint, - $endpoints, - $exportCustomEmojis, - $federation_followers, - $federation_following, - $federation_instances, - $federation_showInstance, - $federation_updateRemoteUser, - $federation_users, - $federation_stats, - $following_create, - $following_delete, - $following_update, - $following_update_all, - $following_invalidate, - $following_requests_accept, - $following_requests_cancel, - $following_requests_list, - $following_requests_reject, - $gallery_featured, - $gallery_popular, - $gallery_posts, - $gallery_posts_create, - $gallery_posts_delete, - $gallery_posts_like, - $gallery_posts_show, - $gallery_posts_unlike, - $gallery_posts_update, - $getOnlineUsersCount, - $getAvatarDecorations, - $hashtags_list, - $hashtags_search, - $hashtags_show, - $hashtags_trend, - $hashtags_users, - $i, - $i_2fa_done, - $i_2fa_keyDone, - $i_2fa_passwordLess, - $i_2fa_registerKey, - $i_2fa_register, - $i_2fa_updateKey, - $i_2fa_removeKey, - $i_2fa_unregister, - $i_apps, - $i_authorizedApps, - $i_claimAchievement, - $i_changePassword, - $i_deleteAccount, - $i_exportBlocking, - $i_exportFollowing, - $i_exportMute, - $i_exportNotes, - $i_exportClips, - $i_exportFavorites, - $i_exportUserLists, - $i_exportAntennas, - $i_favorites, - $i_gallery_likes, - $i_gallery_posts, - $i_importBlocking, - $i_importFollowing, - $i_importMuting, - $i_importUserLists, - $i_importAntennas, - $i_notifications, - $i_notificationsGrouped, - $i_pageLikes, - $i_pages, - $i_pin, - $i_readAllUnreadNotes, - $i_readAnnouncement, - $i_regenerateToken, - $i_registry_getAll, - $i_registry_getDetail, - $i_registry_get, - $i_registry_keysWithType, - $i_registry_keys, - $i_registry_remove, - $i_registry_scopesWithDomain, - $i_registry_set, - $i_revokeToken, - $i_signinHistory, - $i_unpin, - $i_updateEmail, - $i_update, - $i_move, - $i_webhooks_create, - $i_webhooks_list, - $i_webhooks_show, - $i_webhooks_update, - $i_webhooks_delete, - $i_webhooks_test, - $invite_create, - $invite_delete, - $invite_list, - $invite_limit, - $meta, - $emojis, - $emoji, - $miauth_genToken, - $mute_create, - $mute_delete, - $mute_list, - $renoteMute_create, - $renoteMute_delete, - $renoteMute_list, - $my_apps, - $notes, - $notes_children, - $notes_clips, - $notes_conversation, - $notes_create, - $notes_delete, - $notes_favorites_create, - $notes_favorites_delete, - $notes_featured, - $notes_globalTimeline, - $notes_hybridTimeline, - $notes_localTimeline, - $notes_mentions, - $notes_polls_recommendation, - $notes_polls_vote, - $notes_reactions, - $notes_reactions_create, - $notes_reactions_delete, - $notes_renotes, - $notes_replies, - $notes_searchByTag, - $notes_search, - $notes_show, - $notes_state, - $notes_threadMuting_create, - $notes_threadMuting_delete, - $notes_timeline, - $notes_translate, - $notes_unrenote, - $notes_userListTimeline, - $notifications_create, - $notifications_flush, - $notifications_markAllAsRead, - $notifications_testNotification, - $pagePush, - $pages_create, - $pages_delete, - $pages_featured, - $pages_like, - $pages_show, - $pages_unlike, - $pages_update, - $flash_create, - $flash_delete, - $flash_featured, - $flash_like, - $flash_show, - $flash_unlike, - $flash_update, - $flash_my, - $flash_myLikes, - $ping, - $pinnedUsers, - $promo_read, - $roles_list, - $roles_show, - $roles_users, - $roles_notes, - $requestResetPassword, - $resetDb, - $resetPassword, - $serverInfo, - $stats, - $sw_register, - $sw_unregister, - $test, - $username_available, - $users, - $users_clips, - $users_followers, - $users_following, - $users_gallery_posts, - $users_getFrequentlyRepliedUsers, - $users_featuredNotes, - $users_lists_create, - $users_lists_delete, - $users_lists_list, - $users_lists_pull, - $users_lists_push, - $users_lists_show, - $users_lists_update, - $users_lists_favorite, - $users_lists_unfavorite, - $users_lists_createFromPublic, - $users_lists_updateMembership, - $users_lists_getMemberships, - $users_notes, - $users_pages, - $users_flashs, - $users_reactions, - $users_recommendation, - $users_relation, - $users_reportAbuse, - $users_searchByUsernameAndHost, - $users_search, - $users_show, - $users_achievements, - $users_updateMemo, - $fetchRss, - $fetchExternalResources, - $retention, - $bubbleGame_register, - $bubbleGame_ranking, - $reversi_cancelMatch, - $reversi_games, - $reversi_match, - $reversi_invitations, - $reversi_showGame, - $reversi_surrender, - $reversi_verify, + ...endpointProviders, ], }) export class EndpointsModule {} diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts new file mode 100644 index 0000000000..28f7cfea04 --- /dev/null +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -0,0 +1,399 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* + * This file contains list of all endpoints exported as pathname of API endpoint + * + * When you add new endpoint, you should add it to this file. + * This file is used to generate API documentation and EndpointsModule. + */ + +export * as 'admin/abuse-report/notification-recipient/create' from './endpoints/admin/abuse-report/notification-recipient/create.js'; +export * as 'admin/abuse-report/notification-recipient/delete' from './endpoints/admin/abuse-report/notification-recipient/delete.js'; +export * as 'admin/abuse-report/notification-recipient/list' from './endpoints/admin/abuse-report/notification-recipient/list.js'; +export * as 'admin/abuse-report/notification-recipient/show' from './endpoints/admin/abuse-report/notification-recipient/show.js'; +export * as 'admin/abuse-report/notification-recipient/update' from './endpoints/admin/abuse-report/notification-recipient/update.js'; +export * as 'admin/abuse-user-reports' from './endpoints/admin/abuse-user-reports.js'; +export * as 'admin/accounts/create' from './endpoints/admin/accounts/create.js'; +export * as 'admin/accounts/delete' from './endpoints/admin/accounts/delete.js'; +export * as 'admin/accounts/find-by-email' from './endpoints/admin/accounts/find-by-email.js'; +export * as 'admin/ad/create' from './endpoints/admin/ad/create.js'; +export * as 'admin/ad/delete' from './endpoints/admin/ad/delete.js'; +export * as 'admin/ad/list' from './endpoints/admin/ad/list.js'; +export * as 'admin/ad/update' from './endpoints/admin/ad/update.js'; +export * as 'admin/announcements/create' from './endpoints/admin/announcements/create.js'; +export * as 'admin/announcements/delete' from './endpoints/admin/announcements/delete.js'; +export * as 'admin/announcements/list' from './endpoints/admin/announcements/list.js'; +export * as 'admin/announcements/update' from './endpoints/admin/announcements/update.js'; +export * as 'admin/avatar-decorations/create' from './endpoints/admin/avatar-decorations/create.js'; +export * as 'admin/avatar-decorations/delete' from './endpoints/admin/avatar-decorations/delete.js'; +export * as 'admin/avatar-decorations/list' from './endpoints/admin/avatar-decorations/list.js'; +export * as 'admin/avatar-decorations/update' from './endpoints/admin/avatar-decorations/update.js'; +export * as 'admin/captcha/current' from './endpoints/admin/captcha/current.js'; +export * as 'admin/captcha/save' from './endpoints/admin/captcha/save.js'; +export * as 'admin/delete-account' from './endpoints/admin/delete-account.js'; +export * as 'admin/delete-all-files-of-a-user' from './endpoints/admin/delete-all-files-of-a-user.js'; +export * as 'admin/drive/clean-remote-files' from './endpoints/admin/drive/clean-remote-files.js'; +export * as 'admin/drive/cleanup' from './endpoints/admin/drive/cleanup.js'; +export * as 'admin/drive/files' from './endpoints/admin/drive/files.js'; +export * as 'admin/drive/show-file' from './endpoints/admin/drive/show-file.js'; +export * as 'admin/emoji/add' from './endpoints/admin/emoji/add.js'; +export * as 'admin/emoji/add-aliases-bulk' from './endpoints/admin/emoji/add-aliases-bulk.js'; +export * as 'admin/emoji/copy' from './endpoints/admin/emoji/copy.js'; +export * as 'admin/emoji/delete' from './endpoints/admin/emoji/delete.js'; +export * as 'admin/emoji/delete-bulk' from './endpoints/admin/emoji/delete-bulk.js'; +export * as 'admin/emoji/import-zip' from './endpoints/admin/emoji/import-zip.js'; +export * as 'admin/emoji/list' from './endpoints/admin/emoji/list.js'; +export * as 'admin/emoji/list-remote' from './endpoints/admin/emoji/list-remote.js'; +export * as 'admin/emoji/remove-aliases-bulk' from './endpoints/admin/emoji/remove-aliases-bulk.js'; +export * as 'admin/emoji/set-aliases-bulk' from './endpoints/admin/emoji/set-aliases-bulk.js'; +export * as 'admin/emoji/set-category-bulk' from './endpoints/admin/emoji/set-category-bulk.js'; +export * as 'admin/emoji/set-license-bulk' from './endpoints/admin/emoji/set-license-bulk.js'; +export * as 'admin/emoji/update' from './endpoints/admin/emoji/update.js'; +export * as 'admin/federation/delete-all-files' from './endpoints/admin/federation/delete-all-files.js'; +export * as 'admin/federation/refresh-remote-instance-metadata' from './endpoints/admin/federation/refresh-remote-instance-metadata.js'; +export * as 'admin/federation/remove-all-following' from './endpoints/admin/federation/remove-all-following.js'; +export * as 'admin/federation/update-instance' from './endpoints/admin/federation/update-instance.js'; +export * as 'admin/forward-abuse-user-report' from './endpoints/admin/forward-abuse-user-report.js'; +export * as 'admin/get-index-stats' from './endpoints/admin/get-index-stats.js'; +export * as 'admin/get-table-stats' from './endpoints/admin/get-table-stats.js'; +export * as 'admin/get-user-ips' from './endpoints/admin/get-user-ips.js'; +export * as 'admin/invite/create' from './endpoints/admin/invite/create.js'; +export * as 'admin/invite/list' from './endpoints/admin/invite/list.js'; +export * as 'admin/meta' from './endpoints/admin/meta.js'; +export * as 'admin/promo/create' from './endpoints/admin/promo/create.js'; +export * as 'admin/queue/clear' from './endpoints/admin/queue/clear.js'; +export * as 'admin/queue/deliver-delayed' from './endpoints/admin/queue/deliver-delayed.js'; +export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-delayed.js'; +export * as 'admin/queue/promote' from './endpoints/admin/queue/promote.js'; +export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js'; +export * as 'admin/relays/add' from './endpoints/admin/relays/add.js'; +export * as 'admin/relays/list' from './endpoints/admin/relays/list.js'; +export * as 'admin/relays/remove' from './endpoints/admin/relays/remove.js'; +export * as 'admin/reset-password' from './endpoints/admin/reset-password.js'; +export * as 'admin/resolve-abuse-user-report' from './endpoints/admin/resolve-abuse-user-report.js'; +export * as 'admin/roles/assign' from './endpoints/admin/roles/assign.js'; +export * as 'admin/roles/create' from './endpoints/admin/roles/create.js'; +export * as 'admin/roles/delete' from './endpoints/admin/roles/delete.js'; +export * as 'admin/roles/list' from './endpoints/admin/roles/list.js'; +export * as 'admin/roles/show' from './endpoints/admin/roles/show.js'; +export * as 'admin/roles/unassign' from './endpoints/admin/roles/unassign.js'; +export * as 'admin/roles/update' from './endpoints/admin/roles/update.js'; +export * as 'admin/roles/update-default-policies' from './endpoints/admin/roles/update-default-policies.js'; +export * as 'admin/roles/users' from './endpoints/admin/roles/users.js'; +export * as 'admin/send-email' from './endpoints/admin/send-email.js'; +export * as 'admin/server-info' from './endpoints/admin/server-info.js'; +export * as 'admin/show-moderation-logs' from './endpoints/admin/show-moderation-logs.js'; +export * as 'admin/show-user' from './endpoints/admin/show-user.js'; +export * as 'admin/show-users' from './endpoints/admin/show-users.js'; +export * as 'admin/suspend-user' from './endpoints/admin/suspend-user.js'; +export * as 'admin/system-webhook/create' from './endpoints/admin/system-webhook/create.js'; +export * as 'admin/system-webhook/delete' from './endpoints/admin/system-webhook/delete.js'; +export * as 'admin/system-webhook/list' from './endpoints/admin/system-webhook/list.js'; +export * as 'admin/system-webhook/show' from './endpoints/admin/system-webhook/show.js'; +export * as 'admin/system-webhook/test' from './endpoints/admin/system-webhook/test.js'; +export * as 'admin/system-webhook/update' from './endpoints/admin/system-webhook/update.js'; +export * as 'admin/unset-user-avatar' from './endpoints/admin/unset-user-avatar.js'; +export * as 'admin/unset-user-banner' from './endpoints/admin/unset-user-banner.js'; +export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js'; +export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js'; +export * as 'admin/update-meta' from './endpoints/admin/update-meta.js'; +export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js'; +export * as 'announcements' from './endpoints/announcements.js'; +export * as 'announcements/show' from './endpoints/announcements/show.js'; +export * as 'antennas/create' from './endpoints/antennas/create.js'; +export * as 'antennas/delete' from './endpoints/antennas/delete.js'; +export * as 'antennas/list' from './endpoints/antennas/list.js'; +export * as 'antennas/notes' from './endpoints/antennas/notes.js'; +export * as 'antennas/show' from './endpoints/antennas/show.js'; +export * as 'antennas/update' from './endpoints/antennas/update.js'; +export * as 'ap/get' from './endpoints/ap/get.js'; +export * as 'ap/show' from './endpoints/ap/show.js'; +export * as 'app/create' from './endpoints/app/create.js'; +export * as 'app/show' from './endpoints/app/show.js'; +export * as 'auth/accept' from './endpoints/auth/accept.js'; +export * as 'auth/session/generate' from './endpoints/auth/session/generate.js'; +export * as 'auth/session/show' from './endpoints/auth/session/show.js'; +export * as 'auth/session/userkey' from './endpoints/auth/session/userkey.js'; +export * as 'blocking/create' from './endpoints/blocking/create.js'; +export * as 'blocking/delete' from './endpoints/blocking/delete.js'; +export * as 'blocking/list' from './endpoints/blocking/list.js'; +export * as 'bubble-game/ranking' from './endpoints/bubble-game/ranking.js'; +export * as 'bubble-game/register' from './endpoints/bubble-game/register.js'; +export * as 'channels/create' from './endpoints/channels/create.js'; +export * as 'channels/favorite' from './endpoints/channels/favorite.js'; +export * as 'channels/featured' from './endpoints/channels/featured.js'; +export * as 'channels/follow' from './endpoints/channels/follow.js'; +export * as 'channels/followed' from './endpoints/channels/followed.js'; +export * as 'channels/my-favorites' from './endpoints/channels/my-favorites.js'; +export * as 'channels/owned' from './endpoints/channels/owned.js'; +export * as 'channels/search' from './endpoints/channels/search.js'; +export * as 'channels/show' from './endpoints/channels/show.js'; +export * as 'channels/timeline' from './endpoints/channels/timeline.js'; +export * as 'channels/unfavorite' from './endpoints/channels/unfavorite.js'; +export * as 'channels/unfollow' from './endpoints/channels/unfollow.js'; +export * as 'channels/update' from './endpoints/channels/update.js'; +export * as 'charts/active-users' from './endpoints/charts/active-users.js'; +export * as 'charts/ap-request' from './endpoints/charts/ap-request.js'; +export * as 'charts/drive' from './endpoints/charts/drive.js'; +export * as 'charts/federation' from './endpoints/charts/federation.js'; +export * as 'charts/instance' from './endpoints/charts/instance.js'; +export * as 'charts/notes' from './endpoints/charts/notes.js'; +export * as 'charts/user/drive' from './endpoints/charts/user/drive.js'; +export * as 'charts/user/following' from './endpoints/charts/user/following.js'; +export * as 'charts/user/notes' from './endpoints/charts/user/notes.js'; +export * as 'charts/user/pv' from './endpoints/charts/user/pv.js'; +export * as 'charts/user/reactions' from './endpoints/charts/user/reactions.js'; +export * as 'charts/users' from './endpoints/charts/users.js'; +export * as 'clips/add-note' from './endpoints/clips/add-note.js'; +export * as 'clips/create' from './endpoints/clips/create.js'; +export * as 'clips/delete' from './endpoints/clips/delete.js'; +export * as 'clips/favorite' from './endpoints/clips/favorite.js'; +export * as 'clips/list' from './endpoints/clips/list.js'; +export * as 'clips/my-favorites' from './endpoints/clips/my-favorites.js'; +export * as 'clips/notes' from './endpoints/clips/notes.js'; +export * as 'clips/remove-note' from './endpoints/clips/remove-note.js'; +export * as 'clips/show' from './endpoints/clips/show.js'; +export * as 'clips/unfavorite' from './endpoints/clips/unfavorite.js'; +export * as 'clips/update' from './endpoints/clips/update.js'; +export * as 'drive' from './endpoints/drive.js'; +export * as 'drive/files' from './endpoints/drive/files.js'; +export * as 'drive/files/attached-notes' from './endpoints/drive/files/attached-notes.js'; +export * as 'drive/files/check-existence' from './endpoints/drive/files/check-existence.js'; +export * as 'drive/files/create' from './endpoints/drive/files/create.js'; +export * as 'drive/files/delete' from './endpoints/drive/files/delete.js'; +export * as 'drive/files/find' from './endpoints/drive/files/find.js'; +export * as 'drive/files/find-by-hash' from './endpoints/drive/files/find-by-hash.js'; +export * as 'drive/files/show' from './endpoints/drive/files/show.js'; +export * as 'drive/files/update' from './endpoints/drive/files/update.js'; +export * as 'drive/files/upload-from-url' from './endpoints/drive/files/upload-from-url.js'; +export * as 'drive/folders' from './endpoints/drive/folders.js'; +export * as 'drive/folders/create' from './endpoints/drive/folders/create.js'; +export * as 'drive/folders/delete' from './endpoints/drive/folders/delete.js'; +export * as 'drive/folders/find' from './endpoints/drive/folders/find.js'; +export * as 'drive/folders/show' from './endpoints/drive/folders/show.js'; +export * as 'drive/folders/update' from './endpoints/drive/folders/update.js'; +export * as 'drive/stream' from './endpoints/drive/stream.js'; +export * as 'email-address/available' from './endpoints/email-address/available.js'; +export * as 'emoji' from './endpoints/emoji.js'; +export * as 'emojis' from './endpoints/emojis.js'; +export * as 'endpoint' from './endpoints/endpoint.js'; +export * as 'endpoints' from './endpoints/endpoints.js'; +export * as 'export-custom-emojis' from './endpoints/export-custom-emojis.js'; +export * as 'federation/followers' from './endpoints/federation/followers.js'; +export * as 'federation/following' from './endpoints/federation/following.js'; +export * as 'federation/instances' from './endpoints/federation/instances.js'; +export * as 'federation/show-instance' from './endpoints/federation/show-instance.js'; +export * as 'federation/stats' from './endpoints/federation/stats.js'; +export * as 'federation/update-remote-user' from './endpoints/federation/update-remote-user.js'; +export * as 'federation/users' from './endpoints/federation/users.js'; +export * as 'fetch-external-resources' from './endpoints/fetch-external-resources.js'; +export * as 'fetch-rss' from './endpoints/fetch-rss.js'; +export * as 'flash/create' from './endpoints/flash/create.js'; +export * as 'flash/delete' from './endpoints/flash/delete.js'; +export * as 'flash/featured' from './endpoints/flash/featured.js'; +export * as 'flash/like' from './endpoints/flash/like.js'; +export * as 'flash/my' from './endpoints/flash/my.js'; +export * as 'flash/my-likes' from './endpoints/flash/my-likes.js'; +export * as 'flash/show' from './endpoints/flash/show.js'; +export * as 'flash/unlike' from './endpoints/flash/unlike.js'; +export * as 'flash/update' from './endpoints/flash/update.js'; +export * as 'following/create' from './endpoints/following/create.js'; +export * as 'following/delete' from './endpoints/following/delete.js'; +export * as 'following/invalidate' from './endpoints/following/invalidate.js'; +export * as 'following/requests/accept' from './endpoints/following/requests/accept.js'; +export * as 'following/requests/cancel' from './endpoints/following/requests/cancel.js'; +export * as 'following/requests/list' from './endpoints/following/requests/list.js'; +export * as 'following/requests/reject' from './endpoints/following/requests/reject.js'; +export * as 'following/requests/sent' from './endpoints/following/requests/sent.js'; +export * as 'following/update' from './endpoints/following/update.js'; +export * as 'following/update-all' from './endpoints/following/update-all.js'; +export * as 'gallery/featured' from './endpoints/gallery/featured.js'; +export * as 'gallery/popular' from './endpoints/gallery/popular.js'; +export * as 'gallery/posts' from './endpoints/gallery/posts.js'; +export * as 'gallery/posts/create' from './endpoints/gallery/posts/create.js'; +export * as 'gallery/posts/delete' from './endpoints/gallery/posts/delete.js'; +export * as 'gallery/posts/like' from './endpoints/gallery/posts/like.js'; +export * as 'gallery/posts/show' from './endpoints/gallery/posts/show.js'; +export * as 'gallery/posts/unlike' from './endpoints/gallery/posts/unlike.js'; +export * as 'gallery/posts/update' from './endpoints/gallery/posts/update.js'; +export * as 'get-avatar-decorations' from './endpoints/get-avatar-decorations.js'; +export * as 'get-online-users-count' from './endpoints/get-online-users-count.js'; +export * as 'hashtags/list' from './endpoints/hashtags/list.js'; +export * as 'hashtags/search' from './endpoints/hashtags/search.js'; +export * as 'hashtags/show' from './endpoints/hashtags/show.js'; +export * as 'hashtags/trend' from './endpoints/hashtags/trend.js'; +export * as 'hashtags/users' from './endpoints/hashtags/users.js'; +export * as 'i' from './endpoints/i.js'; +export * as 'i/2fa/done' from './endpoints/i/2fa/done.js'; +export * as 'i/2fa/key-done' from './endpoints/i/2fa/key-done.js'; +export * as 'i/2fa/password-less' from './endpoints/i/2fa/password-less.js'; +export * as 'i/2fa/register' from './endpoints/i/2fa/register.js'; +export * as 'i/2fa/register-key' from './endpoints/i/2fa/register-key.js'; +export * as 'i/2fa/remove-key' from './endpoints/i/2fa/remove-key.js'; +export * as 'i/2fa/unregister' from './endpoints/i/2fa/unregister.js'; +export * as 'i/2fa/update-key' from './endpoints/i/2fa/update-key.js'; +export * as 'i/apps' from './endpoints/i/apps.js'; +export * as 'i/authorized-apps' from './endpoints/i/authorized-apps.js'; +export * as 'i/change-password' from './endpoints/i/change-password.js'; +export * as 'i/claim-achievement' from './endpoints/i/claim-achievement.js'; +export * as 'i/delete-account' from './endpoints/i/delete-account.js'; +export * as 'i/export-antennas' from './endpoints/i/export-antennas.js'; +export * as 'i/export-blocking' from './endpoints/i/export-blocking.js'; +export * as 'i/export-clips' from './endpoints/i/export-clips.js'; +export * as 'i/export-favorites' from './endpoints/i/export-favorites.js'; +export * as 'i/export-following' from './endpoints/i/export-following.js'; +export * as 'i/export-mute' from './endpoints/i/export-mute.js'; +export * as 'i/export-notes' from './endpoints/i/export-notes.js'; +export * as 'i/export-user-lists' from './endpoints/i/export-user-lists.js'; +export * as 'i/favorites' from './endpoints/i/favorites.js'; +export * as 'i/gallery/likes' from './endpoints/i/gallery/likes.js'; +export * as 'i/gallery/posts' from './endpoints/i/gallery/posts.js'; +export * as 'i/import-antennas' from './endpoints/i/import-antennas.js'; +export * as 'i/import-blocking' from './endpoints/i/import-blocking.js'; +export * as 'i/import-following' from './endpoints/i/import-following.js'; +export * as 'i/import-muting' from './endpoints/i/import-muting.js'; +export * as 'i/import-user-lists' from './endpoints/i/import-user-lists.js'; +export * as 'i/move' from './endpoints/i/move.js'; +export * as 'i/notifications' from './endpoints/i/notifications.js'; +export * as 'i/notifications-grouped' from './endpoints/i/notifications-grouped.js'; +export * as 'i/page-likes' from './endpoints/i/page-likes.js'; +export * as 'i/pages' from './endpoints/i/pages.js'; +export * as 'i/pin' from './endpoints/i/pin.js'; +export * as 'i/read-all-unread-notes' from './endpoints/i/read-all-unread-notes.js'; +export * as 'i/read-announcement' from './endpoints/i/read-announcement.js'; +export * as 'i/regenerate-token' from './endpoints/i/regenerate-token.js'; +export * as 'i/registry/get' from './endpoints/i/registry/get.js'; +export * as 'i/registry/get-all' from './endpoints/i/registry/get-all.js'; +export * as 'i/registry/get-detail' from './endpoints/i/registry/get-detail.js'; +export * as 'i/registry/keys' from './endpoints/i/registry/keys.js'; +export * as 'i/registry/keys-with-type' from './endpoints/i/registry/keys-with-type.js'; +export * as 'i/registry/remove' from './endpoints/i/registry/remove.js'; +export * as 'i/registry/scopes-with-domain' from './endpoints/i/registry/scopes-with-domain.js'; +export * as 'i/registry/set' from './endpoints/i/registry/set.js'; +export * as 'i/revoke-token' from './endpoints/i/revoke-token.js'; +export * as 'i/signin-history' from './endpoints/i/signin-history.js'; +export * as 'i/unpin' from './endpoints/i/unpin.js'; +export * as 'i/update' from './endpoints/i/update.js'; +export * as 'i/update-email' from './endpoints/i/update-email.js'; +export * as 'i/webhooks/create' from './endpoints/i/webhooks/create.js'; +export * as 'i/webhooks/delete' from './endpoints/i/webhooks/delete.js'; +export * as 'i/webhooks/list' from './endpoints/i/webhooks/list.js'; +export * as 'i/webhooks/show' from './endpoints/i/webhooks/show.js'; +export * as 'i/webhooks/test' from './endpoints/i/webhooks/test.js'; +export * as 'i/webhooks/update' from './endpoints/i/webhooks/update.js'; +export * as 'invite/create' from './endpoints/invite/create.js'; +export * as 'invite/delete' from './endpoints/invite/delete.js'; +export * as 'invite/limit' from './endpoints/invite/limit.js'; +export * as 'invite/list' from './endpoints/invite/list.js'; +export * as 'meta' from './endpoints/meta.js'; +export * as 'miauth/gen-token' from './endpoints/miauth/gen-token.js'; +export * as 'mute/create' from './endpoints/mute/create.js'; +export * as 'mute/delete' from './endpoints/mute/delete.js'; +export * as 'mute/list' from './endpoints/mute/list.js'; +export * as 'my/apps' from './endpoints/my/apps.js'; +export * as 'notes' from './endpoints/notes.js'; +export * as 'notes/children' from './endpoints/notes/children.js'; +export * as 'notes/clips' from './endpoints/notes/clips.js'; +export * as 'notes/conversation' from './endpoints/notes/conversation.js'; +export * as 'notes/create' from './endpoints/notes/create.js'; +export * as 'notes/delete' from './endpoints/notes/delete.js'; +export * as 'notes/favorites/create' from './endpoints/notes/favorites/create.js'; +export * as 'notes/favorites/delete' from './endpoints/notes/favorites/delete.js'; +export * as 'notes/featured' from './endpoints/notes/featured.js'; +export * as 'notes/global-timeline' from './endpoints/notes/global-timeline.js'; +export * as 'notes/hybrid-timeline' from './endpoints/notes/hybrid-timeline.js'; +export * as 'notes/local-timeline' from './endpoints/notes/local-timeline.js'; +export * as 'notes/mentions' from './endpoints/notes/mentions.js'; +export * as 'notes/polls/recommendation' from './endpoints/notes/polls/recommendation.js'; +export * as 'notes/polls/vote' from './endpoints/notes/polls/vote.js'; +export * as 'notes/reactions' from './endpoints/notes/reactions.js'; +export * as 'notes/reactions/create' from './endpoints/notes/reactions/create.js'; +export * as 'notes/reactions/delete' from './endpoints/notes/reactions/delete.js'; +export * as 'notes/renotes' from './endpoints/notes/renotes.js'; +export * as 'notes/replies' from './endpoints/notes/replies.js'; +export * as 'notes/search' from './endpoints/notes/search.js'; +export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js'; +export * as 'notes/show' from './endpoints/notes/show.js'; +export * as 'notes/state' from './endpoints/notes/state.js'; +export * as 'notes/thread-muting/create' from './endpoints/notes/thread-muting/create.js'; +export * as 'notes/thread-muting/delete' from './endpoints/notes/thread-muting/delete.js'; +export * as 'notes/timeline' from './endpoints/notes/timeline.js'; +export * as 'notes/translate' from './endpoints/notes/translate.js'; +export * as 'notes/unrenote' from './endpoints/notes/unrenote.js'; +export * as 'notes/user-list-timeline' from './endpoints/notes/user-list-timeline.js'; +export * as 'notifications/create' from './endpoints/notifications/create.js'; +export * as 'notifications/flush' from './endpoints/notifications/flush.js'; +export * as 'notifications/mark-all-as-read' from './endpoints/notifications/mark-all-as-read.js'; +export * as 'notifications/test-notification' from './endpoints/notifications/test-notification.js'; +export * as 'page-push' from './endpoints/page-push.js'; +export * as 'pages/create' from './endpoints/pages/create.js'; +export * as 'pages/delete' from './endpoints/pages/delete.js'; +export * as 'pages/featured' from './endpoints/pages/featured.js'; +export * as 'pages/like' from './endpoints/pages/like.js'; +export * as 'pages/show' from './endpoints/pages/show.js'; +export * as 'pages/unlike' from './endpoints/pages/unlike.js'; +export * as 'pages/update' from './endpoints/pages/update.js'; +export * as 'ping' from './endpoints/ping.js'; +export * as 'pinned-users' from './endpoints/pinned-users.js'; +export * as 'promo/read' from './endpoints/promo/read.js'; +export * as 'renote-mute/create' from './endpoints/renote-mute/create.js'; +export * as 'renote-mute/delete' from './endpoints/renote-mute/delete.js'; +export * as 'renote-mute/list' from './endpoints/renote-mute/list.js'; +export * as 'request-reset-password' from './endpoints/request-reset-password.js'; +export * as 'reset-db' from './endpoints/reset-db.js'; +export * as 'reset-password' from './endpoints/reset-password.js'; +export * as 'retention' from './endpoints/retention.js'; +export * as 'reversi/cancel-match' from './endpoints/reversi/cancel-match.js'; +export * as 'reversi/games' from './endpoints/reversi/games.js'; +export * as 'reversi/invitations' from './endpoints/reversi/invitations.js'; +export * as 'reversi/match' from './endpoints/reversi/match.js'; +export * as 'reversi/show-game' from './endpoints/reversi/show-game.js'; +export * as 'reversi/surrender' from './endpoints/reversi/surrender.js'; +export * as 'reversi/verify' from './endpoints/reversi/verify.js'; +export * as 'roles/list' from './endpoints/roles/list.js'; +export * as 'roles/notes' from './endpoints/roles/notes.js'; +export * as 'roles/show' from './endpoints/roles/show.js'; +export * as 'roles/users' from './endpoints/roles/users.js'; +export * as 'server-info' from './endpoints/server-info.js'; +export * as 'stats' from './endpoints/stats.js'; +export * as 'sw/register' from './endpoints/sw/register.js'; +export * as 'sw/show-registration' from './endpoints/sw/show-registration.js'; +export * as 'sw/unregister' from './endpoints/sw/unregister.js'; +export * as 'sw/update-registration' from './endpoints/sw/update-registration.js'; +export * as 'test' from './endpoints/test.js'; +export * as 'username/available' from './endpoints/username/available.js'; +export * as 'users' from './endpoints/users.js'; +export * as 'users/achievements' from './endpoints/users/achievements.js'; +export * as 'users/clips' from './endpoints/users/clips.js'; +export * as 'users/featured-notes' from './endpoints/users/featured-notes.js'; +export * as 'users/flashs' from './endpoints/users/flashs.js'; +export * as 'users/followers' from './endpoints/users/followers.js'; +export * as 'users/following' from './endpoints/users/following.js'; +export * as 'users/gallery/posts' from './endpoints/users/gallery/posts.js'; +export * as 'users/get-frequently-replied-users' from './endpoints/users/get-frequently-replied-users.js'; +export * as 'users/lists/create' from './endpoints/users/lists/create.js'; +export * as 'users/lists/create-from-public' from './endpoints/users/lists/create-from-public.js'; +export * as 'users/lists/delete' from './endpoints/users/lists/delete.js'; +export * as 'users/lists/favorite' from './endpoints/users/lists/favorite.js'; +export * as 'users/lists/get-memberships' from './endpoints/users/lists/get-memberships.js'; +export * as 'users/lists/list' from './endpoints/users/lists/list.js'; +export * as 'users/lists/pull' from './endpoints/users/lists/pull.js'; +export * as 'users/lists/push' from './endpoints/users/lists/push.js'; +export * as 'users/lists/show' from './endpoints/users/lists/show.js'; +export * as 'users/lists/unfavorite' from './endpoints/users/lists/unfavorite.js'; +export * as 'users/lists/update' from './endpoints/users/lists/update.js'; +export * as 'users/lists/update-membership' from './endpoints/users/lists/update-membership.js'; +export * as 'users/notes' from './endpoints/users/notes.js'; +export * as 'users/pages' from './endpoints/users/pages.js'; +export * as 'users/reactions' from './endpoints/users/reactions.js'; +export * as 'users/recommendation' from './endpoints/users/recommendation.js'; +export * as 'users/relation' from './endpoints/users/relation.js'; +export * as 'users/report-abuse' from './endpoints/users/report-abuse.js'; +export * as 'users/search' from './endpoints/users/search.js'; +export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js'; +export * as 'users/show' from './endpoints/users/show.js'; +export * as 'users/update-memo' from './endpoints/users/update-memo.js'; +export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js'; diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 4d0c45cc91..a9a2ebc041 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -6,789 +6,7 @@ import { permissions } from 'misskey-js'; import type { KeyOf, Schema } from '@/misc/json-schema.js'; -import * as ep___admin_abuseReport_notificationRecipient_list - from '@/server/api/endpoints/admin/abuse-report/notification-recipient/list.js'; -import * as ep___admin_abuseReport_notificationRecipient_show - from '@/server/api/endpoints/admin/abuse-report/notification-recipient/show.js'; -import * as ep___admin_abuseReport_notificationRecipient_create - from '@/server/api/endpoints/admin/abuse-report/notification-recipient/create.js'; -import * as ep___admin_abuseReport_notificationRecipient_update - from '@/server/api/endpoints/admin/abuse-report/notification-recipient/update.js'; -import * as ep___admin_abuseReport_notificationRecipient_delete - from '@/server/api/endpoints/admin/abuse-report/notification-recipient/delete.js'; -import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js'; -import * as ep___admin_meta from './endpoints/admin/meta.js'; -import * as ep___admin_accounts_create from './endpoints/admin/accounts/create.js'; -import * as ep___admin_accounts_delete from './endpoints/admin/accounts/delete.js'; -import * as ep___admin_accounts_findByEmail from './endpoints/admin/accounts/find-by-email.js'; -import * as ep___admin_ad_create from './endpoints/admin/ad/create.js'; -import * as ep___admin_ad_delete from './endpoints/admin/ad/delete.js'; -import * as ep___admin_ad_list from './endpoints/admin/ad/list.js'; -import * as ep___admin_ad_update from './endpoints/admin/ad/update.js'; -import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js'; -import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js'; -import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js'; -import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js'; -import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js'; -import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js'; -import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js'; -import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js'; -import * as ep___admin_captcha_current from './endpoints/admin/captcha/current.js'; -import * as ep___admin_captcha_save from './endpoints/admin/captcha/save.js'; -import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js'; -import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js'; -import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js'; -import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js'; -import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js'; -import * as ep___admin_drive_files from './endpoints/admin/drive/files.js'; -import * as ep___admin_drive_showFile from './endpoints/admin/drive/show-file.js'; -import * as ep___admin_emoji_addAliasesBulk from './endpoints/admin/emoji/add-aliases-bulk.js'; -import * as ep___admin_emoji_add from './endpoints/admin/emoji/add.js'; -import * as ep___admin_emoji_copy from './endpoints/admin/emoji/copy.js'; -import * as ep___admin_emoji_deleteBulk from './endpoints/admin/emoji/delete-bulk.js'; -import * as ep___admin_emoji_delete from './endpoints/admin/emoji/delete.js'; -import * as ep___admin_emoji_importZip from './endpoints/admin/emoji/import-zip.js'; -import * as ep___admin_emoji_listRemote from './endpoints/admin/emoji/list-remote.js'; -import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js'; -import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js'; -import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js'; -import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js'; -import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js'; -import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js'; -import * as ep___v2_admin_emoji_list from './endpoints/v2/admin/emoji/list.js'; -import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js'; -import * as ep___admin_federation_refreshRemoteInstanceMetadata - from './endpoints/admin/federation/refresh-remote-instance-metadata.js'; -import * as ep___admin_federation_removeAllFollowing from './endpoints/admin/federation/remove-all-following.js'; -import * as ep___admin_federation_updateInstance from './endpoints/admin/federation/update-instance.js'; -import * as ep___admin_getIndexStats from './endpoints/admin/get-index-stats.js'; -import * as ep___admin_getTableStats from './endpoints/admin/get-table-stats.js'; -import * as ep___admin_getUserIps from './endpoints/admin/get-user-ips.js'; -import * as ep___admin_invite_create from './endpoints/admin/invite/create.js'; -import * as ep___admin_invite_list from './endpoints/admin/invite/list.js'; -import * as ep___admin_promo_create from './endpoints/admin/promo/create.js'; -import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js'; -import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js'; -import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js'; -import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js'; -import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js'; -import * as ep___admin_relays_add from './endpoints/admin/relays/add.js'; -import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; -import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; -import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; -import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; -import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js'; -import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js'; -import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; -import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; -import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js'; -import * as ep___admin_showUser from './endpoints/admin/show-user.js'; -import * as ep___admin_showUsers from './endpoints/admin/show-users.js'; -import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js'; -import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js'; -import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js'; -import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js'; -import * as ep___admin_updateUserNote from './endpoints/admin/update-user-note.js'; -import * as ep___admin_roles_create from './endpoints/admin/roles/create.js'; -import * as ep___admin_roles_delete from './endpoints/admin/roles/delete.js'; -import * as ep___admin_roles_list from './endpoints/admin/roles/list.js'; -import * as ep___admin_roles_show from './endpoints/admin/roles/show.js'; -import * as ep___admin_roles_update from './endpoints/admin/roles/update.js'; -import * as ep___admin_roles_assign from './endpoints/admin/roles/assign.js'; -import * as ep___admin_roles_unassign from './endpoints/admin/roles/unassign.js'; -import * as ep___admin_roles_updateDefaultPolicies from './endpoints/admin/roles/update-default-policies.js'; -import * as ep___admin_roles_users from './endpoints/admin/roles/users.js'; -import * as ep___admin_systemWebhook_create from './endpoints/admin/system-webhook/create.js'; -import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webhook/delete.js'; -import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js'; -import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js'; -import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js'; -import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js'; -import * as ep___announcements from './endpoints/announcements.js'; -import * as ep___announcements_show from './endpoints/announcements/show.js'; -import * as ep___antennas_create from './endpoints/antennas/create.js'; -import * as ep___antennas_delete from './endpoints/antennas/delete.js'; -import * as ep___antennas_list from './endpoints/antennas/list.js'; -import * as ep___antennas_notes from './endpoints/antennas/notes.js'; -import * as ep___antennas_show from './endpoints/antennas/show.js'; -import * as ep___antennas_update from './endpoints/antennas/update.js'; -import * as ep___ap_get from './endpoints/ap/get.js'; -import * as ep___ap_show from './endpoints/ap/show.js'; -import * as ep___app_create from './endpoints/app/create.js'; -import * as ep___app_show from './endpoints/app/show.js'; -import * as ep___auth_accept from './endpoints/auth/accept.js'; -import * as ep___auth_session_generate from './endpoints/auth/session/generate.js'; -import * as ep___auth_session_show from './endpoints/auth/session/show.js'; -import * as ep___auth_session_userkey from './endpoints/auth/session/userkey.js'; -import * as ep___blocking_create from './endpoints/blocking/create.js'; -import * as ep___blocking_delete from './endpoints/blocking/delete.js'; -import * as ep___blocking_list from './endpoints/blocking/list.js'; -import * as ep___channels_create from './endpoints/channels/create.js'; -import * as ep___channels_featured from './endpoints/channels/featured.js'; -import * as ep___channels_follow from './endpoints/channels/follow.js'; -import * as ep___channels_followed from './endpoints/channels/followed.js'; -import * as ep___channels_owned from './endpoints/channels/owned.js'; -import * as ep___channels_show from './endpoints/channels/show.js'; -import * as ep___channels_timeline from './endpoints/channels/timeline.js'; -import * as ep___channels_unfollow from './endpoints/channels/unfollow.js'; -import * as ep___channels_update from './endpoints/channels/update.js'; -import * as ep___channels_favorite from './endpoints/channels/favorite.js'; -import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js'; -import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js'; -import * as ep___channels_search from './endpoints/channels/search.js'; -import * as ep___charts_activeUsers from './endpoints/charts/active-users.js'; -import * as ep___charts_apRequest from './endpoints/charts/ap-request.js'; -import * as ep___charts_drive from './endpoints/charts/drive.js'; -import * as ep___charts_federation from './endpoints/charts/federation.js'; -import * as ep___charts_instance from './endpoints/charts/instance.js'; -import * as ep___charts_notes from './endpoints/charts/notes.js'; -import * as ep___charts_user_drive from './endpoints/charts/user/drive.js'; -import * as ep___charts_user_following from './endpoints/charts/user/following.js'; -import * as ep___charts_user_notes from './endpoints/charts/user/notes.js'; -import * as ep___charts_user_pv from './endpoints/charts/user/pv.js'; -import * as ep___charts_user_reactions from './endpoints/charts/user/reactions.js'; -import * as ep___charts_users from './endpoints/charts/users.js'; -import * as ep___clips_addNote from './endpoints/clips/add-note.js'; -import * as ep___clips_removeNote from './endpoints/clips/remove-note.js'; -import * as ep___clips_create from './endpoints/clips/create.js'; -import * as ep___clips_delete from './endpoints/clips/delete.js'; -import * as ep___clips_list from './endpoints/clips/list.js'; -import * as ep___clips_notes from './endpoints/clips/notes.js'; -import * as ep___clips_show from './endpoints/clips/show.js'; -import * as ep___clips_update from './endpoints/clips/update.js'; -import * as ep___clips_favorite from './endpoints/clips/favorite.js'; -import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js'; -import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js'; -import * as ep___drive from './endpoints/drive.js'; -import * as ep___drive_files from './endpoints/drive/files.js'; -import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js'; -import * as ep___drive_files_checkExistence from './endpoints/drive/files/check-existence.js'; -import * as ep___drive_files_create from './endpoints/drive/files/create.js'; -import * as ep___drive_files_delete from './endpoints/drive/files/delete.js'; -import * as ep___drive_files_findByHash from './endpoints/drive/files/find-by-hash.js'; -import * as ep___drive_files_find from './endpoints/drive/files/find.js'; -import * as ep___drive_files_show from './endpoints/drive/files/show.js'; -import * as ep___drive_files_update from './endpoints/drive/files/update.js'; -import * as ep___drive_files_uploadFromUrl from './endpoints/drive/files/upload-from-url.js'; -import * as ep___drive_folders from './endpoints/drive/folders.js'; -import * as ep___drive_folders_create from './endpoints/drive/folders/create.js'; -import * as ep___drive_folders_delete from './endpoints/drive/folders/delete.js'; -import * as ep___drive_folders_find from './endpoints/drive/folders/find.js'; -import * as ep___drive_folders_show from './endpoints/drive/folders/show.js'; -import * as ep___drive_folders_update from './endpoints/drive/folders/update.js'; -import * as ep___drive_stream from './endpoints/drive/stream.js'; -import * as ep___emailAddress_available from './endpoints/email-address/available.js'; -import * as ep___endpoint from './endpoints/endpoint.js'; -import * as ep___endpoints from './endpoints/endpoints.js'; -import * as ep___exportCustomEmojis from './endpoints/export-custom-emojis.js'; -import * as ep___federation_followers from './endpoints/federation/followers.js'; -import * as ep___federation_following from './endpoints/federation/following.js'; -import * as ep___federation_instances from './endpoints/federation/instances.js'; -import * as ep___federation_showInstance from './endpoints/federation/show-instance.js'; -import * as ep___federation_updateRemoteUser from './endpoints/federation/update-remote-user.js'; -import * as ep___federation_users from './endpoints/federation/users.js'; -import * as ep___federation_stats from './endpoints/federation/stats.js'; -import * as ep___following_create from './endpoints/following/create.js'; -import * as ep___following_delete from './endpoints/following/delete.js'; -import * as ep___following_update from './endpoints/following/update.js'; -import * as ep___following_update_all from './endpoints/following/update-all.js'; -import * as ep___following_invalidate from './endpoints/following/invalidate.js'; -import * as ep___following_requests_accept from './endpoints/following/requests/accept.js'; -import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js'; -import * as ep___following_requests_list from './endpoints/following/requests/list.js'; -import * as ep___following_requests_sent from './endpoints/following/requests/sent.js'; -import * as ep___following_requests_reject from './endpoints/following/requests/reject.js'; -import * as ep___gallery_featured from './endpoints/gallery/featured.js'; -import * as ep___gallery_popular from './endpoints/gallery/popular.js'; -import * as ep___gallery_posts from './endpoints/gallery/posts.js'; -import * as ep___gallery_posts_create from './endpoints/gallery/posts/create.js'; -import * as ep___gallery_posts_delete from './endpoints/gallery/posts/delete.js'; -import * as ep___gallery_posts_like from './endpoints/gallery/posts/like.js'; -import * as ep___gallery_posts_show from './endpoints/gallery/posts/show.js'; -import * as ep___gallery_posts_unlike from './endpoints/gallery/posts/unlike.js'; -import * as ep___gallery_posts_update from './endpoints/gallery/posts/update.js'; -import * as ep___getOnlineUsersCount from './endpoints/get-online-users-count.js'; -import * as ep___getAvatarDecorations from './endpoints/get-avatar-decorations.js'; -import * as ep___hashtags_list from './endpoints/hashtags/list.js'; -import * as ep___hashtags_search from './endpoints/hashtags/search.js'; -import * as ep___hashtags_show from './endpoints/hashtags/show.js'; -import * as ep___hashtags_trend from './endpoints/hashtags/trend.js'; -import * as ep___hashtags_users from './endpoints/hashtags/users.js'; -import * as ep___i from './endpoints/i.js'; -import * as ep___i_2fa_done from './endpoints/i/2fa/done.js'; -import * as ep___i_2fa_keyDone from './endpoints/i/2fa/key-done.js'; -import * as ep___i_2fa_passwordLess from './endpoints/i/2fa/password-less.js'; -import * as ep___i_2fa_registerKey from './endpoints/i/2fa/register-key.js'; -import * as ep___i_2fa_register from './endpoints/i/2fa/register.js'; -import * as ep___i_2fa_updateKey from './endpoints/i/2fa/update-key.js'; -import * as ep___i_2fa_removeKey from './endpoints/i/2fa/remove-key.js'; -import * as ep___i_2fa_unregister from './endpoints/i/2fa/unregister.js'; -import * as ep___i_apps from './endpoints/i/apps.js'; -import * as ep___i_authorizedApps from './endpoints/i/authorized-apps.js'; -import * as ep___i_claimAchievement from './endpoints/i/claim-achievement.js'; -import * as ep___i_changePassword from './endpoints/i/change-password.js'; -import * as ep___i_deleteAccount from './endpoints/i/delete-account.js'; -import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js'; -import * as ep___i_exportFollowing from './endpoints/i/export-following.js'; -import * as ep___i_exportMute from './endpoints/i/export-mute.js'; -import * as ep___i_exportNotes from './endpoints/i/export-notes.js'; -import * as ep___i_exportClips from './endpoints/i/export-clips.js'; -import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js'; -import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js'; -import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js'; -import * as ep___i_favorites from './endpoints/i/favorites.js'; -import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js'; -import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js'; -import * as ep___i_importBlocking from './endpoints/i/import-blocking.js'; -import * as ep___i_importFollowing from './endpoints/i/import-following.js'; -import * as ep___i_importMuting from './endpoints/i/import-muting.js'; -import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js'; -import * as ep___i_importAntennas from './endpoints/i/import-antennas.js'; -import * as ep___i_notifications from './endpoints/i/notifications.js'; -import * as ep___i_notificationsGrouped from './endpoints/i/notifications-grouped.js'; -import * as ep___i_pageLikes from './endpoints/i/page-likes.js'; -import * as ep___i_pages from './endpoints/i/pages.js'; -import * as ep___i_pin from './endpoints/i/pin.js'; -import * as ep___i_readAllUnreadNotes from './endpoints/i/read-all-unread-notes.js'; -import * as ep___i_readAnnouncement from './endpoints/i/read-announcement.js'; -import * as ep___i_regenerateToken from './endpoints/i/regenerate-token.js'; -import * as ep___i_registry_getAll from './endpoints/i/registry/get-all.js'; -import * as ep___i_registry_getDetail from './endpoints/i/registry/get-detail.js'; -import * as ep___i_registry_get from './endpoints/i/registry/get.js'; -import * as ep___i_registry_keysWithType from './endpoints/i/registry/keys-with-type.js'; -import * as ep___i_registry_keys from './endpoints/i/registry/keys.js'; -import * as ep___i_registry_remove from './endpoints/i/registry/remove.js'; -import * as ep___i_registry_scopesWithDomain from './endpoints/i/registry/scopes-with-domain.js'; -import * as ep___i_registry_set from './endpoints/i/registry/set.js'; -import * as ep___i_revokeToken from './endpoints/i/revoke-token.js'; -import * as ep___i_signinHistory from './endpoints/i/signin-history.js'; -import * as ep___i_unpin from './endpoints/i/unpin.js'; -import * as ep___i_updateEmail from './endpoints/i/update-email.js'; -import * as ep___i_update from './endpoints/i/update.js'; -import * as ep___i_move from './endpoints/i/move.js'; -import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js'; -import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js'; -import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js'; -import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js'; -import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js'; -import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js'; -import * as ep___invite_create from './endpoints/invite/create.js'; -import * as ep___invite_delete from './endpoints/invite/delete.js'; -import * as ep___invite_list from './endpoints/invite/list.js'; -import * as ep___invite_limit from './endpoints/invite/limit.js'; -import * as ep___meta from './endpoints/meta.js'; -import * as ep___emojis from './endpoints/emojis.js'; -import * as ep___emoji from './endpoints/emoji.js'; -import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js'; -import * as ep___mute_create from './endpoints/mute/create.js'; -import * as ep___mute_delete from './endpoints/mute/delete.js'; -import * as ep___mute_list from './endpoints/mute/list.js'; -import * as ep___renoteMute_create from './endpoints/renote-mute/create.js'; -import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js'; -import * as ep___renoteMute_list from './endpoints/renote-mute/list.js'; -import * as ep___my_apps from './endpoints/my/apps.js'; -import * as ep___notes from './endpoints/notes.js'; -import * as ep___notes_children from './endpoints/notes/children.js'; -import * as ep___notes_clips from './endpoints/notes/clips.js'; -import * as ep___notes_conversation from './endpoints/notes/conversation.js'; -import * as ep___notes_create from './endpoints/notes/create.js'; -import * as ep___notes_delete from './endpoints/notes/delete.js'; -import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js'; -import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js'; -import * as ep___notes_featured from './endpoints/notes/featured.js'; -import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js'; -import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js'; -import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js'; -import * as ep___notes_mentions from './endpoints/notes/mentions.js'; -import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js'; -import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js'; -import * as ep___notes_reactions from './endpoints/notes/reactions.js'; -import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js'; -import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js'; -import * as ep___notes_renotes from './endpoints/notes/renotes.js'; -import * as ep___notes_replies from './endpoints/notes/replies.js'; -import * as ep___notes_searchByTag from './endpoints/notes/search-by-tag.js'; -import * as ep___notes_search from './endpoints/notes/search.js'; -import * as ep___notes_show from './endpoints/notes/show.js'; -import * as ep___notes_state from './endpoints/notes/state.js'; -import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting/create.js'; -import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js'; -import * as ep___notes_timeline from './endpoints/notes/timeline.js'; -import * as ep___notes_translate from './endpoints/notes/translate.js'; -import * as ep___notes_unrenote from './endpoints/notes/unrenote.js'; -import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js'; -import * as ep___notifications_create from './endpoints/notifications/create.js'; -import * as ep___notifications_flush from './endpoints/notifications/flush.js'; -import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js'; -import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js'; -import * as ep___pagePush from './endpoints/page-push.js'; -import * as ep___pages_create from './endpoints/pages/create.js'; -import * as ep___pages_delete from './endpoints/pages/delete.js'; -import * as ep___pages_featured from './endpoints/pages/featured.js'; -import * as ep___pages_like from './endpoints/pages/like.js'; -import * as ep___pages_show from './endpoints/pages/show.js'; -import * as ep___pages_unlike from './endpoints/pages/unlike.js'; -import * as ep___pages_update from './endpoints/pages/update.js'; -import * as ep___flash_create from './endpoints/flash/create.js'; -import * as ep___flash_delete from './endpoints/flash/delete.js'; -import * as ep___flash_featured from './endpoints/flash/featured.js'; -import * as ep___flash_like from './endpoints/flash/like.js'; -import * as ep___flash_show from './endpoints/flash/show.js'; -import * as ep___flash_unlike from './endpoints/flash/unlike.js'; -import * as ep___flash_update from './endpoints/flash/update.js'; -import * as ep___flash_my from './endpoints/flash/my.js'; -import * as ep___flash_myLikes from './endpoints/flash/my-likes.js'; -import * as ep___ping from './endpoints/ping.js'; -import * as ep___pinnedUsers from './endpoints/pinned-users.js'; -import * as ep___promo_read from './endpoints/promo/read.js'; -import * as ep___roles_list from './endpoints/roles/list.js'; -import * as ep___roles_show from './endpoints/roles/show.js'; -import * as ep___roles_users from './endpoints/roles/users.js'; -import * as ep___roles_notes from './endpoints/roles/notes.js'; -import * as ep___requestResetPassword from './endpoints/request-reset-password.js'; -import * as ep___resetDb from './endpoints/reset-db.js'; -import * as ep___resetPassword from './endpoints/reset-password.js'; -import * as ep___serverInfo from './endpoints/server-info.js'; -import * as ep___stats from './endpoints/stats.js'; -import * as ep___sw_show_registration from './endpoints/sw/show-registration.js'; -import * as ep___sw_update_registration from './endpoints/sw/update-registration.js'; -import * as ep___sw_register from './endpoints/sw/register.js'; -import * as ep___sw_unregister from './endpoints/sw/unregister.js'; -import * as ep___test from './endpoints/test.js'; -import * as ep___username_available from './endpoints/username/available.js'; -import * as ep___users from './endpoints/users.js'; -import * as ep___users_clips from './endpoints/users/clips.js'; -import * as ep___users_followers from './endpoints/users/followers.js'; -import * as ep___users_following from './endpoints/users/following.js'; -import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js'; -import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js'; -import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js'; -import * as ep___users_lists_create from './endpoints/users/lists/create.js'; -import * as ep___users_lists_delete from './endpoints/users/lists/delete.js'; -import * as ep___users_lists_list from './endpoints/users/lists/list.js'; -import * as ep___users_lists_pull from './endpoints/users/lists/pull.js'; -import * as ep___users_lists_push from './endpoints/users/lists/push.js'; -import * as ep___users_lists_show from './endpoints/users/lists/show.js'; -import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js'; -import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js'; -import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js'; -import * as ep___users_lists_update from './endpoints/users/lists/update.js'; -import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js'; -import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js'; -import * as ep___users_notes from './endpoints/users/notes.js'; -import * as ep___users_pages from './endpoints/users/pages.js'; -import * as ep___users_flashs from './endpoints/users/flashs.js'; -import * as ep___users_reactions from './endpoints/users/reactions.js'; -import * as ep___users_recommendation from './endpoints/users/recommendation.js'; -import * as ep___users_relation from './endpoints/users/relation.js'; -import * as ep___users_reportAbuse from './endpoints/users/report-abuse.js'; -import * as ep___users_searchByUsernameAndHost from './endpoints/users/search-by-username-and-host.js'; -import * as ep___users_search from './endpoints/users/search.js'; -import * as ep___users_show from './endpoints/users/show.js'; -import * as ep___users_achievements from './endpoints/users/achievements.js'; -import * as ep___users_updateMemo from './endpoints/users/update-memo.js'; -import * as ep___fetchRss from './endpoints/fetch-rss.js'; -import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js'; -import * as ep___retention from './endpoints/retention.js'; -import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js'; -import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js'; -import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js'; -import * as ep___reversi_games from './endpoints/reversi/games.js'; -import * as ep___reversi_match from './endpoints/reversi/match.js'; -import * as ep___reversi_invitations from './endpoints/reversi/invitations.js'; -import * as ep___reversi_showGame from './endpoints/reversi/show-game.js'; -import * as ep___reversi_surrender from './endpoints/reversi/surrender.js'; -import * as ep___reversi_verify from './endpoints/reversi/verify.js'; - -const eps = [ - ['admin/meta', ep___admin_meta], - ['admin/abuse-user-reports', ep___admin_abuseUserReports], - ['admin/abuse-report/notification-recipient/list', ep___admin_abuseReport_notificationRecipient_list], - ['admin/abuse-report/notification-recipient/show', ep___admin_abuseReport_notificationRecipient_show], - ['admin/abuse-report/notification-recipient/create', ep___admin_abuseReport_notificationRecipient_create], - ['admin/abuse-report/notification-recipient/update', ep___admin_abuseReport_notificationRecipient_update], - ['admin/abuse-report/notification-recipient/delete', ep___admin_abuseReport_notificationRecipient_delete], - ['admin/accounts/create', ep___admin_accounts_create], - ['admin/accounts/delete', ep___admin_accounts_delete], - ['admin/accounts/find-by-email', ep___admin_accounts_findByEmail], - ['admin/ad/create', ep___admin_ad_create], - ['admin/ad/delete', ep___admin_ad_delete], - ['admin/ad/list', ep___admin_ad_list], - ['admin/ad/update', ep___admin_ad_update], - ['admin/announcements/create', ep___admin_announcements_create], - ['admin/announcements/delete', ep___admin_announcements_delete], - ['admin/announcements/list', ep___admin_announcements_list], - ['admin/announcements/update', ep___admin_announcements_update], - ['admin/avatar-decorations/create', ep___admin_avatarDecorations_create], - ['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete], - ['admin/avatar-decorations/list', ep___admin_avatarDecorations_list], - ['admin/avatar-decorations/update', ep___admin_avatarDecorations_update], - ['admin/captcha/current', ep___admin_captcha_current], - ['admin/captcha/save', ep___admin_captcha_save], - ['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser], - ['admin/unset-user-avatar', ep___admin_unsetUserAvatar], - ['admin/unset-user-banner', ep___admin_unsetUserBanner], - ['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles], - ['admin/drive/cleanup', ep___admin_drive_cleanup], - ['admin/drive/files', ep___admin_drive_files], - ['admin/drive/show-file', ep___admin_drive_showFile], - ['admin/emoji/add-aliases-bulk', ep___admin_emoji_addAliasesBulk], - ['admin/emoji/add', ep___admin_emoji_add], - ['admin/emoji/copy', ep___admin_emoji_copy], - ['admin/emoji/delete-bulk', ep___admin_emoji_deleteBulk], - ['admin/emoji/delete', ep___admin_emoji_delete], - ['admin/emoji/import-zip', ep___admin_emoji_importZip], - ['admin/emoji/list-remote', ep___admin_emoji_listRemote], - ['admin/emoji/list', ep___admin_emoji_list], - ['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk], - ['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk], - ['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk], - ['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk], - ['admin/emoji/update', ep___admin_emoji_update], - ['v2/admin/emoji/list', ep___v2_admin_emoji_list], - ['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles], - ['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata], - ['admin/federation/remove-all-following', ep___admin_federation_removeAllFollowing], - ['admin/federation/update-instance', ep___admin_federation_updateInstance], - ['admin/get-index-stats', ep___admin_getIndexStats], - ['admin/get-table-stats', ep___admin_getTableStats], - ['admin/get-user-ips', ep___admin_getUserIps], - ['admin/invite/create', ep___admin_invite_create], - ['admin/invite/list', ep___admin_invite_list], - ['admin/promo/create', ep___admin_promo_create], - ['admin/queue/clear', ep___admin_queue_clear], - ['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed], - ['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed], - ['admin/queue/promote', ep___admin_queue_promote], - ['admin/queue/stats', ep___admin_queue_stats], - ['admin/relays/add', ep___admin_relays_add], - ['admin/relays/list', ep___admin_relays_list], - ['admin/relays/remove', ep___admin_relays_remove], - ['admin/reset-password', ep___admin_resetPassword], - ['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport], - ['admin/forward-abuse-user-report', ep___admin_forwardAbuseUserReport], - ['admin/update-abuse-user-report', ep___admin_updateAbuseUserReport], - ['admin/send-email', ep___admin_sendEmail], - ['admin/server-info', ep___admin_serverInfo], - ['admin/show-moderation-logs', ep___admin_showModerationLogs], - ['admin/show-user', ep___admin_showUser], - ['admin/show-users', ep___admin_showUsers], - ['admin/suspend-user', ep___admin_suspendUser], - ['admin/unsuspend-user', ep___admin_unsuspendUser], - ['admin/update-meta', ep___admin_updateMeta], - ['admin/delete-account', ep___admin_deleteAccount], - ['admin/update-user-note', ep___admin_updateUserNote], - ['admin/roles/create', ep___admin_roles_create], - ['admin/roles/delete', ep___admin_roles_delete], - ['admin/roles/list', ep___admin_roles_list], - ['admin/roles/show', ep___admin_roles_show], - ['admin/roles/update', ep___admin_roles_update], - ['admin/roles/assign', ep___admin_roles_assign], - ['admin/roles/unassign', ep___admin_roles_unassign], - ['admin/roles/update-default-policies', ep___admin_roles_updateDefaultPolicies], - ['admin/roles/users', ep___admin_roles_users], - ['admin/system-webhook/create', ep___admin_systemWebhook_create], - ['admin/system-webhook/delete', ep___admin_systemWebhook_delete], - ['admin/system-webhook/list', ep___admin_systemWebhook_list], - ['admin/system-webhook/show', ep___admin_systemWebhook_show], - ['admin/system-webhook/update', ep___admin_systemWebhook_update], - ['admin/system-webhook/test', ep___admin_systemWebhook_test], - ['announcements', ep___announcements], - ['announcements/show', ep___announcements_show], - ['antennas/create', ep___antennas_create], - ['antennas/delete', ep___antennas_delete], - ['antennas/list', ep___antennas_list], - ['antennas/notes', ep___antennas_notes], - ['antennas/show', ep___antennas_show], - ['antennas/update', ep___antennas_update], - ['ap/get', ep___ap_get], - ['ap/show', ep___ap_show], - ['app/create', ep___app_create], - ['app/show', ep___app_show], - ['auth/accept', ep___auth_accept], - ['auth/session/generate', ep___auth_session_generate], - ['auth/session/show', ep___auth_session_show], - ['auth/session/userkey', ep___auth_session_userkey], - ['blocking/create', ep___blocking_create], - ['blocking/delete', ep___blocking_delete], - ['blocking/list', ep___blocking_list], - ['channels/create', ep___channels_create], - ['channels/featured', ep___channels_featured], - ['channels/follow', ep___channels_follow], - ['channels/followed', ep___channels_followed], - ['channels/owned', ep___channels_owned], - ['channels/show', ep___channels_show], - ['channels/timeline', ep___channels_timeline], - ['channels/unfollow', ep___channels_unfollow], - ['channels/update', ep___channels_update], - ['channels/favorite', ep___channels_favorite], - ['channels/unfavorite', ep___channels_unfavorite], - ['channels/my-favorites', ep___channels_myFavorites], - ['channels/search', ep___channels_search], - ['charts/active-users', ep___charts_activeUsers], - ['charts/ap-request', ep___charts_apRequest], - ['charts/drive', ep___charts_drive], - ['charts/federation', ep___charts_federation], - ['charts/instance', ep___charts_instance], - ['charts/notes', ep___charts_notes], - ['charts/user/drive', ep___charts_user_drive], - ['charts/user/following', ep___charts_user_following], - ['charts/user/notes', ep___charts_user_notes], - ['charts/user/pv', ep___charts_user_pv], - ['charts/user/reactions', ep___charts_user_reactions], - ['charts/users', ep___charts_users], - ['clips/add-note', ep___clips_addNote], - ['clips/remove-note', ep___clips_removeNote], - ['clips/create', ep___clips_create], - ['clips/delete', ep___clips_delete], - ['clips/list', ep___clips_list], - ['clips/notes', ep___clips_notes], - ['clips/show', ep___clips_show], - ['clips/update', ep___clips_update], - ['clips/favorite', ep___clips_favorite], - ['clips/unfavorite', ep___clips_unfavorite], - ['clips/my-favorites', ep___clips_myFavorites], - ['drive', ep___drive], - ['drive/files', ep___drive_files], - ['drive/files/attached-notes', ep___drive_files_attachedNotes], - ['drive/files/check-existence', ep___drive_files_checkExistence], - ['drive/files/create', ep___drive_files_create], - ['drive/files/delete', ep___drive_files_delete], - ['drive/files/find-by-hash', ep___drive_files_findByHash], - ['drive/files/find', ep___drive_files_find], - ['drive/files/show', ep___drive_files_show], - ['drive/files/update', ep___drive_files_update], - ['drive/files/upload-from-url', ep___drive_files_uploadFromUrl], - ['drive/folders', ep___drive_folders], - ['drive/folders/create', ep___drive_folders_create], - ['drive/folders/delete', ep___drive_folders_delete], - ['drive/folders/find', ep___drive_folders_find], - ['drive/folders/show', ep___drive_folders_show], - ['drive/folders/update', ep___drive_folders_update], - ['drive/stream', ep___drive_stream], - ['email-address/available', ep___emailAddress_available], - ['endpoint', ep___endpoint], - ['endpoints', ep___endpoints], - ['export-custom-emojis', ep___exportCustomEmojis], - ['federation/followers', ep___federation_followers], - ['federation/following', ep___federation_following], - ['federation/instances', ep___federation_instances], - ['federation/show-instance', ep___federation_showInstance], - ['federation/update-remote-user', ep___federation_updateRemoteUser], - ['federation/users', ep___federation_users], - ['federation/stats', ep___federation_stats], - ['following/create', ep___following_create], - ['following/delete', ep___following_delete], - ['following/update', ep___following_update], - ['following/update-all', ep___following_update_all], - ['following/invalidate', ep___following_invalidate], - ['following/requests/accept', ep___following_requests_accept], - ['following/requests/cancel', ep___following_requests_cancel], - ['following/requests/list', ep___following_requests_list], - ['following/requests/sent', ep___following_requests_sent], - ['following/requests/reject', ep___following_requests_reject], - ['gallery/featured', ep___gallery_featured], - ['gallery/popular', ep___gallery_popular], - ['gallery/posts', ep___gallery_posts], - ['gallery/posts/create', ep___gallery_posts_create], - ['gallery/posts/delete', ep___gallery_posts_delete], - ['gallery/posts/like', ep___gallery_posts_like], - ['gallery/posts/show', ep___gallery_posts_show], - ['gallery/posts/unlike', ep___gallery_posts_unlike], - ['gallery/posts/update', ep___gallery_posts_update], - ['get-online-users-count', ep___getOnlineUsersCount], - ['get-avatar-decorations', ep___getAvatarDecorations], - ['hashtags/list', ep___hashtags_list], - ['hashtags/search', ep___hashtags_search], - ['hashtags/show', ep___hashtags_show], - ['hashtags/trend', ep___hashtags_trend], - ['hashtags/users', ep___hashtags_users], - ['i', ep___i], - ['i/2fa/done', ep___i_2fa_done], - ['i/2fa/key-done', ep___i_2fa_keyDone], - ['i/2fa/password-less', ep___i_2fa_passwordLess], - ['i/2fa/register-key', ep___i_2fa_registerKey], - ['i/2fa/register', ep___i_2fa_register], - ['i/2fa/update-key', ep___i_2fa_updateKey], - ['i/2fa/remove-key', ep___i_2fa_removeKey], - ['i/2fa/unregister', ep___i_2fa_unregister], - ['i/apps', ep___i_apps], - ['i/authorized-apps', ep___i_authorizedApps], - ['i/claim-achievement', ep___i_claimAchievement], - ['i/change-password', ep___i_changePassword], - ['i/delete-account', ep___i_deleteAccount], - ['i/export-blocking', ep___i_exportBlocking], - ['i/export-following', ep___i_exportFollowing], - ['i/export-mute', ep___i_exportMute], - ['i/export-notes', ep___i_exportNotes], - ['i/export-clips', ep___i_exportClips], - ['i/export-favorites', ep___i_exportFavorites], - ['i/export-user-lists', ep___i_exportUserLists], - ['i/export-antennas', ep___i_exportAntennas], - ['i/favorites', ep___i_favorites], - ['i/gallery/likes', ep___i_gallery_likes], - ['i/gallery/posts', ep___i_gallery_posts], - ['i/import-blocking', ep___i_importBlocking], - ['i/import-following', ep___i_importFollowing], - ['i/import-muting', ep___i_importMuting], - ['i/import-user-lists', ep___i_importUserLists], - ['i/import-antennas', ep___i_importAntennas], - ['i/notifications', ep___i_notifications], - ['i/notifications-grouped', ep___i_notificationsGrouped], - ['i/page-likes', ep___i_pageLikes], - ['i/pages', ep___i_pages], - ['i/pin', ep___i_pin], - ['i/read-all-unread-notes', ep___i_readAllUnreadNotes], - ['i/read-announcement', ep___i_readAnnouncement], - ['i/regenerate-token', ep___i_regenerateToken], - ['i/registry/get-all', ep___i_registry_getAll], - ['i/registry/get-detail', ep___i_registry_getDetail], - ['i/registry/get', ep___i_registry_get], - ['i/registry/keys-with-type', ep___i_registry_keysWithType], - ['i/registry/keys', ep___i_registry_keys], - ['i/registry/remove', ep___i_registry_remove], - ['i/registry/scopes-with-domain', ep___i_registry_scopesWithDomain], - ['i/registry/set', ep___i_registry_set], - ['i/revoke-token', ep___i_revokeToken], - ['i/signin-history', ep___i_signinHistory], - ['i/unpin', ep___i_unpin], - ['i/update-email', ep___i_updateEmail], - ['i/update', ep___i_update], - ['i/move', ep___i_move], - ['i/webhooks/create', ep___i_webhooks_create], - ['i/webhooks/list', ep___i_webhooks_list], - ['i/webhooks/show', ep___i_webhooks_show], - ['i/webhooks/update', ep___i_webhooks_update], - ['i/webhooks/delete', ep___i_webhooks_delete], - ['i/webhooks/test', ep___i_webhooks_test], - ['invite/create', ep___invite_create], - ['invite/delete', ep___invite_delete], - ['invite/list', ep___invite_list], - ['invite/limit', ep___invite_limit], - ['meta', ep___meta], - ['emojis', ep___emojis], - ['emoji', ep___emoji], - ['miauth/gen-token', ep___miauth_genToken], - ['mute/create', ep___mute_create], - ['mute/delete', ep___mute_delete], - ['mute/list', ep___mute_list], - ['renote-mute/create', ep___renoteMute_create], - ['renote-mute/delete', ep___renoteMute_delete], - ['renote-mute/list', ep___renoteMute_list], - ['my/apps', ep___my_apps], - ['notes', ep___notes], - ['notes/children', ep___notes_children], - ['notes/clips', ep___notes_clips], - ['notes/conversation', ep___notes_conversation], - ['notes/create', ep___notes_create], - ['notes/delete', ep___notes_delete], - ['notes/favorites/create', ep___notes_favorites_create], - ['notes/favorites/delete', ep___notes_favorites_delete], - ['notes/featured', ep___notes_featured], - ['notes/global-timeline', ep___notes_globalTimeline], - ['notes/hybrid-timeline', ep___notes_hybridTimeline], - ['notes/local-timeline', ep___notes_localTimeline], - ['notes/mentions', ep___notes_mentions], - ['notes/polls/recommendation', ep___notes_polls_recommendation], - ['notes/polls/vote', ep___notes_polls_vote], - ['notes/reactions', ep___notes_reactions], - ['notes/reactions/create', ep___notes_reactions_create], - ['notes/reactions/delete', ep___notes_reactions_delete], - ['notes/renotes', ep___notes_renotes], - ['notes/replies', ep___notes_replies], - ['notes/search-by-tag', ep___notes_searchByTag], - ['notes/search', ep___notes_search], - ['notes/show', ep___notes_show], - ['notes/state', ep___notes_state], - ['notes/thread-muting/create', ep___notes_threadMuting_create], - ['notes/thread-muting/delete', ep___notes_threadMuting_delete], - ['notes/timeline', ep___notes_timeline], - ['notes/translate', ep___notes_translate], - ['notes/unrenote', ep___notes_unrenote], - ['notes/user-list-timeline', ep___notes_userListTimeline], - ['notifications/create', ep___notifications_create], - ['notifications/flush', ep___notifications_flush], - ['notifications/mark-all-as-read', ep___notifications_markAllAsRead], - ['notifications/test-notification', ep___notifications_testNotification], - ['page-push', ep___pagePush], - ['pages/create', ep___pages_create], - ['pages/delete', ep___pages_delete], - ['pages/featured', ep___pages_featured], - ['pages/like', ep___pages_like], - ['pages/show', ep___pages_show], - ['pages/unlike', ep___pages_unlike], - ['pages/update', ep___pages_update], - ['flash/create', ep___flash_create], - ['flash/delete', ep___flash_delete], - ['flash/featured', ep___flash_featured], - ['flash/like', ep___flash_like], - ['flash/show', ep___flash_show], - ['flash/unlike', ep___flash_unlike], - ['flash/update', ep___flash_update], - ['flash/my', ep___flash_my], - ['flash/my-likes', ep___flash_myLikes], - ['ping', ep___ping], - ['pinned-users', ep___pinnedUsers], - ['promo/read', ep___promo_read], - ['roles/list', ep___roles_list], - ['roles/show', ep___roles_show], - ['roles/users', ep___roles_users], - ['roles/notes', ep___roles_notes], - ['request-reset-password', ep___requestResetPassword], - ['reset-db', ep___resetDb], - ['reset-password', ep___resetPassword], - ['server-info', ep___serverInfo], - ['stats', ep___stats], - ['sw/show-registration', ep___sw_show_registration], - ['sw/update-registration', ep___sw_update_registration], - ['sw/register', ep___sw_register], - ['sw/unregister', ep___sw_unregister], - ['test', ep___test], - ['username/available', ep___username_available], - ['users', ep___users], - ['users/clips', ep___users_clips], - ['users/followers', ep___users_followers], - ['users/following', ep___users_following], - ['users/gallery/posts', ep___users_gallery_posts], - ['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers], - ['users/featured-notes', ep___users_featuredNotes], - ['users/lists/create', ep___users_lists_create], - ['users/lists/delete', ep___users_lists_delete], - ['users/lists/list', ep___users_lists_list], - ['users/lists/pull', ep___users_lists_pull], - ['users/lists/push', ep___users_lists_push], - ['users/lists/show', ep___users_lists_show], - ['users/lists/favorite', ep___users_lists_favorite], - ['users/lists/unfavorite', ep___users_lists_unfavorite], - ['users/lists/update', ep___users_lists_update], - ['users/lists/create-from-public', ep___users_lists_createFromPublic], - ['users/lists/update-membership', ep___users_lists_updateMembership], - ['users/lists/get-memberships', ep___users_lists_getMemberships], - ['users/notes', ep___users_notes], - ['users/pages', ep___users_pages], - ['users/flashs', ep___users_flashs], - ['users/reactions', ep___users_reactions], - ['users/recommendation', ep___users_recommendation], - ['users/relation', ep___users_relation], - ['users/report-abuse', ep___users_reportAbuse], - ['users/search-by-username-and-host', ep___users_searchByUsernameAndHost], - ['users/search', ep___users_search], - ['users/show', ep___users_show], - ['users/achievements', ep___users_achievements], - ['users/update-memo', ep___users_updateMemo], - ['fetch-rss', ep___fetchRss], - ['fetch-external-resources', ep___fetchExternalResources], - ['retention', ep___retention], - ['bubble-game/register', ep___bubbleGame_register], - ['bubble-game/ranking', ep___bubbleGame_ranking], - ['reversi/cancel-match', ep___reversi_cancelMatch], - ['reversi/games', ep___reversi_games], - ['reversi/match', ep___reversi_match], - ['reversi/invitations', ep___reversi_invitations], - ['reversi/show-game', ep___reversi_showGame], - ['reversi/surrender', ep___reversi_surrender], - ['reversi/verify', ep___reversi_verify], -]; +import * as endpointsObject from './endpoint-list.js'; interface IEndpointMetaBase { readonly stability?: 'deprecated' | 'experimental' | 'stable'; @@ -912,7 +130,7 @@ export interface IEndpoint { params: Schema; } -const endpoints: IEndpoint[] = (eps as [string, any]).map(([name, ep]) => { +const endpoints: IEndpoint[] = Object.entries(endpointsObject).map(([name, ep]) => { return { name: name, get meta() { diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 7098b52205..ac7babb250 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1236,18 +1236,17 @@ declare namespace entities { PartialRolePolicyOverride, EmptyRequest, EmptyResponse, - AdminMetaResponse, - AdminAbuseUserReportsRequest, - AdminAbuseUserReportsResponse, + AdminAbuseReportNotificationRecipientCreateRequest, + AdminAbuseReportNotificationRecipientCreateResponse, + AdminAbuseReportNotificationRecipientDeleteRequest, AdminAbuseReportNotificationRecipientListRequest, AdminAbuseReportNotificationRecipientListResponse, AdminAbuseReportNotificationRecipientShowRequest, AdminAbuseReportNotificationRecipientShowResponse, - AdminAbuseReportNotificationRecipientCreateRequest, - AdminAbuseReportNotificationRecipientCreateResponse, AdminAbuseReportNotificationRecipientUpdateRequest, AdminAbuseReportNotificationRecipientUpdateResponse, - AdminAbuseReportNotificationRecipientDeleteRequest, + AdminAbuseUserReportsRequest, + AdminAbuseUserReportsResponse, AdminAccountsCreateRequest, AdminAccountsCreateResponse, AdminAccountsDeleteRequest, @@ -1273,36 +1272,34 @@ declare namespace entities { AdminAvatarDecorationsUpdateRequest, AdminCaptchaCurrentResponse, AdminCaptchaSaveRequest, + AdminDeleteAccountRequest, AdminDeleteAllFilesOfAUserRequest, - AdminUnsetUserAvatarRequest, - AdminUnsetUserBannerRequest, AdminDriveFilesRequest, AdminDriveFilesResponse, AdminDriveShowFileRequest, AdminDriveShowFileResponse, - AdminEmojiAddAliasesBulkRequest, AdminEmojiAddRequest, AdminEmojiAddResponse, + AdminEmojiAddAliasesBulkRequest, AdminEmojiCopyRequest, AdminEmojiCopyResponse, - AdminEmojiDeleteBulkRequest, AdminEmojiDeleteRequest, + AdminEmojiDeleteBulkRequest, AdminEmojiImportZipRequest, - AdminEmojiListRemoteRequest, - AdminEmojiListRemoteResponse, AdminEmojiListRequest, AdminEmojiListResponse, + AdminEmojiListRemoteRequest, + AdminEmojiListRemoteResponse, AdminEmojiRemoveAliasesBulkRequest, AdminEmojiSetAliasesBulkRequest, AdminEmojiSetCategoryBulkRequest, AdminEmojiSetLicenseBulkRequest, AdminEmojiUpdateRequest, - V2AdminEmojiListRequest, - V2AdminEmojiListResponse, AdminFederationDeleteAllFilesRequest, AdminFederationRefreshRemoteInstanceMetadataRequest, AdminFederationRemoveAllFollowingRequest, AdminFederationUpdateInstanceRequest, + AdminForwardAbuseUserReportRequest, AdminGetIndexStatsResponse, AdminGetTableStatsResponse, AdminGetUserIpsRequest, @@ -1311,6 +1308,7 @@ declare namespace entities { AdminInviteCreateResponse, AdminInviteListRequest, AdminInviteListResponse, + AdminMetaResponse, AdminPromoCreateRequest, AdminQueueDeliverDelayedResponse, AdminQueueInboxDelayedResponse, @@ -1323,33 +1321,27 @@ declare namespace entities { AdminResetPasswordRequest, AdminResetPasswordResponse, AdminResolveAbuseUserReportRequest, - AdminForwardAbuseUserReportRequest, - AdminUpdateAbuseUserReportRequest, - AdminSendEmailRequest, - AdminServerInfoResponse, - AdminShowModerationLogsRequest, - AdminShowModerationLogsResponse, - AdminShowUserRequest, - AdminShowUserResponse, - AdminShowUsersRequest, - AdminShowUsersResponse, - AdminSuspendUserRequest, - AdminUnsuspendUserRequest, - AdminUpdateMetaRequest, - AdminDeleteAccountRequest, - AdminUpdateUserNoteRequest, + AdminRolesAssignRequest, AdminRolesCreateRequest, AdminRolesCreateResponse, AdminRolesDeleteRequest, AdminRolesListResponse, AdminRolesShowRequest, AdminRolesShowResponse, - AdminRolesUpdateRequest, - AdminRolesAssignRequest, AdminRolesUnassignRequest, + AdminRolesUpdateRequest, AdminRolesUpdateDefaultPoliciesRequest, AdminRolesUsersRequest, AdminRolesUsersResponse, + AdminSendEmailRequest, + AdminServerInfoResponse, + AdminShowModerationLogsRequest, + AdminShowModerationLogsResponse, + AdminShowUserRequest, + AdminShowUserResponse, + AdminShowUsersRequest, + AdminShowUsersResponse, + AdminSuspendUserRequest, AdminSystemWebhookCreateRequest, AdminSystemWebhookCreateResponse, AdminSystemWebhookDeleteRequest, @@ -1357,9 +1349,15 @@ declare namespace entities { AdminSystemWebhookListResponse, AdminSystemWebhookShowRequest, AdminSystemWebhookShowResponse, + AdminSystemWebhookTestRequest, AdminSystemWebhookUpdateRequest, AdminSystemWebhookUpdateResponse, - AdminSystemWebhookTestRequest, + AdminUnsetUserAvatarRequest, + AdminUnsetUserBannerRequest, + AdminUnsuspendUserRequest, + AdminUpdateAbuseUserReportRequest, + AdminUpdateMetaRequest, + AdminUpdateUserNoteRequest, AnnouncementsRequest, AnnouncementsResponse, AnnouncementsShowRequest, @@ -1395,26 +1393,29 @@ declare namespace entities { BlockingDeleteResponse, BlockingListRequest, BlockingListResponse, + BubbleGameRankingRequest, + BubbleGameRankingResponse, + BubbleGameRegisterRequest, ChannelsCreateRequest, ChannelsCreateResponse, + ChannelsFavoriteRequest, ChannelsFeaturedResponse, ChannelsFollowRequest, ChannelsFollowedRequest, ChannelsFollowedResponse, + ChannelsMyFavoritesResponse, ChannelsOwnedRequest, ChannelsOwnedResponse, + ChannelsSearchRequest, + ChannelsSearchResponse, ChannelsShowRequest, ChannelsShowResponse, ChannelsTimelineRequest, ChannelsTimelineResponse, + ChannelsUnfavoriteRequest, ChannelsUnfollowRequest, ChannelsUpdateRequest, ChannelsUpdateResponse, - ChannelsFavoriteRequest, - ChannelsUnfavoriteRequest, - ChannelsMyFavoritesResponse, - ChannelsSearchRequest, - ChannelsSearchResponse, ChartsActiveUsersRequest, ChartsActiveUsersResponse, ChartsApRequestRequest, @@ -1440,20 +1441,20 @@ declare namespace entities { ChartsUsersRequest, ChartsUsersResponse, ClipsAddNoteRequest, - ClipsRemoveNoteRequest, ClipsCreateRequest, ClipsCreateResponse, ClipsDeleteRequest, + ClipsFavoriteRequest, ClipsListResponse, + ClipsMyFavoritesResponse, ClipsNotesRequest, ClipsNotesResponse, + ClipsRemoveNoteRequest, ClipsShowRequest, ClipsShowResponse, + ClipsUnfavoriteRequest, ClipsUpdateRequest, ClipsUpdateResponse, - ClipsFavoriteRequest, - ClipsUnfavoriteRequest, - ClipsMyFavoritesResponse, DriveResponse, DriveFilesRequest, DriveFilesResponse, @@ -1464,10 +1465,10 @@ declare namespace entities { DriveFilesCreateRequest, DriveFilesCreateResponse, DriveFilesDeleteRequest, - DriveFilesFindByHashRequest, - DriveFilesFindByHashResponse, DriveFilesFindRequest, DriveFilesFindResponse, + DriveFilesFindByHashRequest, + DriveFilesFindByHashResponse, DriveFilesShowRequest, DriveFilesShowResponse, DriveFilesUpdateRequest, @@ -1488,6 +1489,9 @@ declare namespace entities { DriveStreamResponse, EmailAddressAvailableRequest, EmailAddressAvailableResponse, + EmojiRequest, + EmojiResponse, + EmojisResponse, EndpointRequest, EndpointResponse, EndpointsResponse, @@ -1499,18 +1503,33 @@ declare namespace entities { FederationInstancesResponse, FederationShowInstanceRequest, FederationShowInstanceResponse, + FederationStatsRequest, + FederationStatsResponse, FederationUpdateRemoteUserRequest, FederationUsersRequest, FederationUsersResponse, - FederationStatsRequest, - FederationStatsResponse, + FetchExternalResourcesRequest, + FetchExternalResourcesResponse, + FetchRssRequest, + FetchRssResponse, + FlashCreateRequest, + FlashCreateResponse, + FlashDeleteRequest, + FlashFeaturedRequest, + FlashFeaturedResponse, + FlashLikeRequest, + FlashMyRequest, + FlashMyResponse, + FlashMyLikesRequest, + FlashMyLikesResponse, + FlashShowRequest, + FlashShowResponse, + FlashUnlikeRequest, + FlashUpdateRequest, FollowingCreateRequest, FollowingCreateResponse, FollowingDeleteRequest, FollowingDeleteResponse, - FollowingUpdateRequest, - FollowingUpdateResponse, - FollowingUpdateAllRequest, FollowingInvalidateRequest, FollowingInvalidateResponse, FollowingRequestsAcceptRequest, @@ -1518,9 +1537,12 @@ declare namespace entities { FollowingRequestsCancelResponse, FollowingRequestsListRequest, FollowingRequestsListResponse, + FollowingRequestsRejectRequest, FollowingRequestsSentRequest, FollowingRequestsSentResponse, - FollowingRequestsRejectRequest, + FollowingUpdateRequest, + FollowingUpdateResponse, + FollowingUpdateAllRequest, GalleryFeaturedRequest, GalleryFeaturedResponse, GalleryPopularResponse, @@ -1535,8 +1557,8 @@ declare namespace entities { GalleryPostsUnlikeRequest, GalleryPostsUpdateRequest, GalleryPostsUpdateResponse, - GetOnlineUsersCountResponse, GetAvatarDecorationsResponse, + GetOnlineUsersCountResponse, HashtagsListRequest, HashtagsListResponse, HashtagsSearchRequest, @@ -1552,19 +1574,19 @@ declare namespace entities { I2faKeyDoneRequest, I2faKeyDoneResponse, I2faPasswordLessRequest, - I2faRegisterKeyRequest, - I2faRegisterKeyResponse, I2faRegisterRequest, I2faRegisterResponse, - I2faUpdateKeyRequest, + I2faRegisterKeyRequest, + I2faRegisterKeyResponse, I2faRemoveKeyRequest, I2faUnregisterRequest, + I2faUpdateKeyRequest, IAppsRequest, IAppsResponse, IAuthorizedAppsRequest, IAuthorizedAppsResponse, - IClaimAchievementRequest, IChangePasswordRequest, + IClaimAchievementRequest, IDeleteAccountRequest, IExportFollowingRequest, IFavoritesRequest, @@ -1573,11 +1595,13 @@ declare namespace entities { IGalleryLikesResponse, IGalleryPostsRequest, IGalleryPostsResponse, + IImportAntennasRequest, IImportBlockingRequest, IImportFollowingRequest, IImportMutingRequest, IImportUserListsRequest, - IImportAntennasRequest, + IMoveRequest, + IMoveResponse, INotificationsRequest, INotificationsResponse, INotificationsGroupedRequest, @@ -1590,16 +1614,16 @@ declare namespace entities { IPinResponse, IReadAnnouncementRequest, IRegenerateTokenRequest, + IRegistryGetRequest, + IRegistryGetResponse, IRegistryGetAllRequest, IRegistryGetAllResponse, IRegistryGetDetailRequest, IRegistryGetDetailResponse, - IRegistryGetRequest, - IRegistryGetResponse, - IRegistryKeysWithTypeRequest, - IRegistryKeysWithTypeResponse, IRegistryKeysRequest, IRegistryKeysResponse, + IRegistryKeysWithTypeRequest, + IRegistryKeysWithTypeResponse, IRegistryRemoveRequest, IRegistryScopesWithDomainResponse, IRegistrySetRequest, @@ -1608,40 +1632,31 @@ declare namespace entities { ISigninHistoryResponse, IUnpinRequest, IUnpinResponse, - IUpdateEmailRequest, - IUpdateEmailResponse, IUpdateRequest, IUpdateResponse, - IMoveRequest, - IMoveResponse, + IUpdateEmailRequest, + IUpdateEmailResponse, IWebhooksCreateRequest, IWebhooksCreateResponse, + IWebhooksDeleteRequest, IWebhooksListResponse, IWebhooksShowRequest, IWebhooksShowResponse, - IWebhooksUpdateRequest, - IWebhooksDeleteRequest, IWebhooksTestRequest, + IWebhooksUpdateRequest, InviteCreateResponse, InviteDeleteRequest, + InviteLimitResponse, InviteListRequest, InviteListResponse, - InviteLimitResponse, MetaRequest, MetaResponse, - EmojisResponse, - EmojiRequest, - EmojiResponse, MiauthGenTokenRequest, MiauthGenTokenResponse, MuteCreateRequest, MuteDeleteRequest, MuteListRequest, MuteListResponse, - RenoteMuteCreateRequest, - RenoteMuteDeleteRequest, - RenoteMuteListRequest, - RenoteMuteListResponse, MyAppsRequest, MyAppsResponse, NotesRequest, @@ -1678,10 +1693,10 @@ declare namespace entities { NotesRenotesResponse, NotesRepliesRequest, NotesRepliesResponse, - NotesSearchByTagRequest, - NotesSearchByTagResponse, NotesSearchRequest, NotesSearchResponse, + NotesSearchByTagRequest, + NotesSearchByTagResponse, NotesShowRequest, NotesShowResponse, NotesStateRequest, @@ -1706,49 +1721,57 @@ declare namespace entities { PagesShowResponse, PagesUnlikeRequest, PagesUpdateRequest, - FlashCreateRequest, - FlashCreateResponse, - FlashDeleteRequest, - FlashFeaturedRequest, - FlashFeaturedResponse, - FlashLikeRequest, - FlashShowRequest, - FlashShowResponse, - FlashUnlikeRequest, - FlashUpdateRequest, - FlashMyRequest, - FlashMyResponse, - FlashMyLikesRequest, - FlashMyLikesResponse, PingResponse, PinnedUsersResponse, PromoReadRequest, + RenoteMuteCreateRequest, + RenoteMuteDeleteRequest, + RenoteMuteListRequest, + RenoteMuteListResponse, + RequestResetPasswordRequest, + ResetPasswordRequest, + RetentionResponse, + ReversiCancelMatchRequest, + ReversiGamesRequest, + ReversiGamesResponse, + ReversiInvitationsResponse, + ReversiMatchRequest, + ReversiMatchResponse, + ReversiShowGameRequest, + ReversiShowGameResponse, + ReversiSurrenderRequest, + ReversiVerifyRequest, + ReversiVerifyResponse, RolesListResponse, + RolesNotesRequest, + RolesNotesResponse, RolesShowRequest, RolesShowResponse, RolesUsersRequest, RolesUsersResponse, - RolesNotesRequest, - RolesNotesResponse, - RequestResetPasswordRequest, - ResetPasswordRequest, ServerInfoResponse, StatsResponse, + SwRegisterRequest, + SwRegisterResponse, SwShowRegistrationRequest, SwShowRegistrationResponse, + SwUnregisterRequest, SwUpdateRegistrationRequest, SwUpdateRegistrationResponse, - SwRegisterRequest, - SwRegisterResponse, - SwUnregisterRequest, TestRequest, TestResponse, UsernameAvailableRequest, UsernameAvailableResponse, UsersRequest, UsersResponse, + UsersAchievementsRequest, + UsersAchievementsResponse, UsersClipsRequest, UsersClipsResponse, + UsersFeaturedNotesRequest, + UsersFeaturedNotesResponse, + UsersFlashsRequest, + UsersFlashsResponse, UsersFollowersRequest, UsersFollowersResponse, UsersFollowingRequest, @@ -1757,32 +1780,28 @@ declare namespace entities { UsersGalleryPostsResponse, UsersGetFrequentlyRepliedUsersRequest, UsersGetFrequentlyRepliedUsersResponse, - UsersFeaturedNotesRequest, - UsersFeaturedNotesResponse, UsersListsCreateRequest, UsersListsCreateResponse, + UsersListsCreateFromPublicRequest, + UsersListsCreateFromPublicResponse, UsersListsDeleteRequest, + UsersListsFavoriteRequest, + UsersListsGetMembershipsRequest, + UsersListsGetMembershipsResponse, UsersListsListRequest, UsersListsListResponse, UsersListsPullRequest, UsersListsPushRequest, UsersListsShowRequest, UsersListsShowResponse, - UsersListsFavoriteRequest, UsersListsUnfavoriteRequest, UsersListsUpdateRequest, UsersListsUpdateResponse, - UsersListsCreateFromPublicRequest, - UsersListsCreateFromPublicResponse, UsersListsUpdateMembershipRequest, - UsersListsGetMembershipsRequest, - UsersListsGetMembershipsResponse, UsersNotesRequest, UsersNotesResponse, UsersPagesRequest, UsersPagesResponse, - UsersFlashsRequest, - UsersFlashsResponse, UsersReactionsRequest, UsersReactionsResponse, UsersRecommendationRequest, @@ -1790,34 +1809,15 @@ declare namespace entities { UsersRelationRequest, UsersRelationResponse, UsersReportAbuseRequest, - UsersSearchByUsernameAndHostRequest, - UsersSearchByUsernameAndHostResponse, UsersSearchRequest, UsersSearchResponse, + UsersSearchByUsernameAndHostRequest, + UsersSearchByUsernameAndHostResponse, UsersShowRequest, UsersShowResponse, - UsersAchievementsRequest, - UsersAchievementsResponse, UsersUpdateMemoRequest, - FetchRssRequest, - FetchRssResponse, - FetchExternalResourcesRequest, - FetchExternalResourcesResponse, - RetentionResponse, - BubbleGameRegisterRequest, - BubbleGameRankingRequest, - BubbleGameRankingResponse, - ReversiCancelMatchRequest, - ReversiGamesRequest, - ReversiGamesResponse, - ReversiMatchRequest, - ReversiMatchResponse, - ReversiInvitationsResponse, - ReversiShowGameRequest, - ReversiShowGameResponse, - ReversiSurrenderRequest, - ReversiVerifyRequest, - ReversiVerifyResponse, + V2AdminEmojiListRequest, + V2AdminEmojiListResponse, Error_2 as Error, UserLite, UserDetailedNotMeOnly, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index edaa0498e9..6bace3924c 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -6,9 +6,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -17,9 +18,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -49,18 +51,6 @@ declare module '../api.js' { credential?: string | null, ): Promise>; - /** - * No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - /** * No description provided. * @@ -76,10 +66,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* + * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -275,20 +264,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* + * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -297,9 +275,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* + * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -354,7 +332,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -365,7 +343,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -387,7 +365,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -398,7 +376,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -421,7 +399,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -432,7 +410,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -496,9 +474,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* + * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -509,7 +487,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -520,7 +498,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -531,7 +509,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -540,9 +518,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:federation* + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -603,6 +581,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * @@ -727,9 +716,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -738,9 +727,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -749,9 +738,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -760,9 +749,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* + * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -771,9 +760,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* + * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -782,9 +771,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -793,9 +782,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -804,9 +793,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -815,9 +804,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* + * **Credential required**: *No* / **Permission**: *read:admin:roles* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -826,9 +815,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -837,9 +826,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* + * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -848,9 +837,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* + * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -859,9 +848,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -870,9 +859,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -881,9 +870,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:roles* + * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -892,9 +881,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -903,9 +893,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -914,9 +905,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -925,9 +917,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -936,9 +929,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -947,9 +941,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* / **Permission**: *read:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -958,10 +953,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -970,10 +964,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -982,10 +975,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -994,10 +986,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1006,10 +997,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1018,10 +1008,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1240,9 +1229,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1251,9 +1240,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1264,7 +1253,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1273,9 +1262,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:channels* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1284,9 +1273,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:channels* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1295,9 +1284,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1306,9 +1295,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:channels* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1317,9 +1306,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *Yes* / **Permission**: *read:channels* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1328,9 +1317,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *Yes* / **Permission**: *read:channels* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1339,9 +1328,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1350,9 +1339,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1361,9 +1350,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:channels* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1372,9 +1361,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1383,9 +1372,31 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - request( + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:channels* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( endpoint: E, params: P, credential?: string | null, @@ -1528,7 +1539,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1539,7 +1550,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1548,9 +1559,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1570,9 +1581,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1583,7 +1594,7 @@ declare module '../api.js' { * * **Credential required**: *No* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1594,7 +1605,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1603,9 +1614,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* + * **Credential required**: *No* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1625,9 +1636,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1700,22 +1711,22 @@ declare module '../api.js' { ): Promise>; /** - * Search for a drive file by a hash of the contents. + * Search for a drive file by the given parameters. * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Search for a drive file by the given parameters. + * Search for a drive file by a hash of the contents. * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1842,6 +1853,28 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * @@ -1920,6 +1953,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * @@ -1942,12 +1986,24 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1956,9 +2012,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *Yes* / **Permission**: *write:flash* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1967,9 +2023,86 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *Yes* / **Permission**: *write:flash* */ - request( + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:flash* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:flash-likes* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:flash* + */ + request( endpoint: E, params: P, credential?: string | null, @@ -1980,7 +2113,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -1991,7 +2124,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2041,6 +2174,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:following* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * @@ -2057,7 +2201,18 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:following* */ - request( + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:following* + */ + request( endpoint: E, params: P, credential?: string | null, @@ -2167,7 +2322,7 @@ declare module '../api.js' { * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2178,7 +2333,7 @@ declare module '../api.js' { * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2292,7 +2447,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2304,7 +2459,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2316,7 +2471,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2328,7 +2483,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2340,7 +2495,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2373,9 +2528,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2384,10 +2540,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2411,7 +2566,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2423,7 +2578,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2435,7 +2590,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2447,7 +2602,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2459,7 +2614,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2471,7 +2626,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2483,7 +2638,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2495,7 +2650,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2540,7 +2695,19 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* + */ + request( endpoint: E, params: P, credential?: string | null, @@ -2588,7 +2755,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2688,7 +2855,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2699,7 +2866,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2710,7 +2877,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2721,7 +2888,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2732,7 +2899,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2807,18 +2974,6 @@ declare module '../api.js' { credential?: string | null, ): Promise>; - /** - * No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - /** * No description provided. * @@ -2836,7 +2991,7 @@ declare module '../api.js' { * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2856,9 +3011,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2869,7 +3024,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2878,9 +3033,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2889,9 +3044,10 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2900,10 +3056,9 @@ declare module '../api.js' { /** * No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2936,7 +3091,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2947,7 +3102,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -2964,28 +3119,6 @@ declare module '../api.js' { credential?: string | null, ): Promise>; - /** - * No description provided. - * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - /** * No description provided. * @@ -3031,39 +3164,6 @@ declare module '../api.js' { credential?: string | null, ): Promise>; - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:mutes* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:mutes* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:mutes* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - /** * No description provided. * @@ -3300,7 +3400,7 @@ declare module '../api.js' { * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3311,7 +3411,7 @@ declare module '../api.js' { * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3541,9 +3641,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3552,9 +3652,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3563,9 +3663,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3574,9 +3674,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3585,9 +3685,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3596,20 +3696,42 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * No description provided. + * Request a users password to be reset. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *No* */ - request( + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * Only available when running with NODE_ENV=testing. Reset the database and flush Redis. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * Complete the password reset that was previously requested. + * + * **Credential required**: *No* + */ + request( endpoint: E, params: P, credential?: string | null, @@ -3618,9 +3740,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:flash* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3629,9 +3751,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:flash-likes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3642,7 +3764,7 @@ declare module '../api.js' { * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3651,9 +3773,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3664,7 +3786,7 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3673,9 +3795,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3684,9 +3806,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3697,7 +3819,7 @@ declare module '../api.js' { * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3708,40 +3830,40 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Request a users password to be reset. + * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Only available when running with NODE_ENV=testing. Reset the database and flush Redis. + * No description provided. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Complete the password reset that was previously requested. + * No description provided. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3770,47 +3892,47 @@ declare module '../api.js' { ): Promise>; /** - * Check push notification registration exists. + * Register to receive push notifications. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Update push notification registration. + * Check push notification registration exists. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Register to receive push notifications. + * Unregister from receiving push notifications. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Unregister from receiving push notifications. + * Update push notification registration. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -3849,6 +3971,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * Show all clips this user owns. * @@ -3861,165 +3994,165 @@ declare module '../api.js' { ): Promise>; /** - * Show everyone that follows this user. + * No description provided. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Show everyone that this user is following. + * Show all flashs this user created. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Show all gallery posts by the given user. + * Show everyone that follows this user. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Get a list of other users that the specified user frequently replies to. + * Show everyone that this user is following. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * No description provided. + * Show all gallery posts by the given user. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Create a new list of users. + * Get a list of other users that the specified user frequently replies to. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Delete an existing list of users. + * Create a new list of users. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Show all lists that the authenticated user has created. + * No description provided. * - * **Credential required**: *No* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Remove a user from a list. + * Delete an existing list of users. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Add a user to an existing list. + * No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Show the properties of a list. + * No description provided. * * **Credential required**: *No* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * No description provided. + * Show all lists that the authenticated user has created. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * No description provided. + * Remove a user from a list. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Update the properties of a list. + * Add a user to an existing list. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * No description provided. + * Show the properties of a list. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* / **Permission**: *read:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -4030,18 +4163,18 @@ declare module '../api.js' { * * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * No description provided. + * Update the properties of a list. * - * **Credential required**: *No* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -4050,31 +4183,31 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Show all pages this user created. + * No description provided. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * Show all flashs this user created. + * Show all pages this user created. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -4124,17 +4257,6 @@ declare module '../api.js' { credential?: string | null, ): Promise>; - /** - * Search for a user by username and/or host. - * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - /** * Search for users. * @@ -4147,22 +4269,22 @@ declare module '../api.js' { ): Promise>; /** - * Show the properties of a user. + * Search for a user by username and/or host. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, ): Promise>; /** - * No description provided. + * Show the properties of a user. * * **Credential required**: *No* */ - request( + request( endpoint: E, params: P, credential?: string | null, @@ -4182,131 +4304,9 @@ declare module '../api.js' { /** * No description provided. * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:account* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *No* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - request( - endpoint: E, - params: P, - credential?: string | null, - ): Promise>; - - /** - * No description provided. - * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - request( + request( endpoint: E, params: P, credential?: string | null, diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 982717597b..a9903b9139 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -1,18 +1,17 @@ import type { EmptyRequest, EmptyResponse, - AdminMetaResponse, - AdminAbuseUserReportsRequest, - AdminAbuseUserReportsResponse, + AdminAbuseReportNotificationRecipientCreateRequest, + AdminAbuseReportNotificationRecipientCreateResponse, + AdminAbuseReportNotificationRecipientDeleteRequest, AdminAbuseReportNotificationRecipientListRequest, AdminAbuseReportNotificationRecipientListResponse, AdminAbuseReportNotificationRecipientShowRequest, AdminAbuseReportNotificationRecipientShowResponse, - AdminAbuseReportNotificationRecipientCreateRequest, - AdminAbuseReportNotificationRecipientCreateResponse, AdminAbuseReportNotificationRecipientUpdateRequest, AdminAbuseReportNotificationRecipientUpdateResponse, - AdminAbuseReportNotificationRecipientDeleteRequest, + AdminAbuseUserReportsRequest, + AdminAbuseUserReportsResponse, AdminAccountsCreateRequest, AdminAccountsCreateResponse, AdminAccountsDeleteRequest, @@ -38,36 +37,34 @@ import type { AdminAvatarDecorationsUpdateRequest, AdminCaptchaCurrentResponse, AdminCaptchaSaveRequest, + AdminDeleteAccountRequest, AdminDeleteAllFilesOfAUserRequest, - AdminUnsetUserAvatarRequest, - AdminUnsetUserBannerRequest, AdminDriveFilesRequest, AdminDriveFilesResponse, AdminDriveShowFileRequest, AdminDriveShowFileResponse, - AdminEmojiAddAliasesBulkRequest, AdminEmojiAddRequest, AdminEmojiAddResponse, + AdminEmojiAddAliasesBulkRequest, AdminEmojiCopyRequest, AdminEmojiCopyResponse, - AdminEmojiDeleteBulkRequest, AdminEmojiDeleteRequest, + AdminEmojiDeleteBulkRequest, AdminEmojiImportZipRequest, - AdminEmojiListRemoteRequest, - AdminEmojiListRemoteResponse, AdminEmojiListRequest, AdminEmojiListResponse, + AdminEmojiListRemoteRequest, + AdminEmojiListRemoteResponse, AdminEmojiRemoveAliasesBulkRequest, AdminEmojiSetAliasesBulkRequest, AdminEmojiSetCategoryBulkRequest, AdminEmojiSetLicenseBulkRequest, AdminEmojiUpdateRequest, - V2AdminEmojiListRequest, - V2AdminEmojiListResponse, AdminFederationDeleteAllFilesRequest, AdminFederationRefreshRemoteInstanceMetadataRequest, AdminFederationRemoveAllFollowingRequest, AdminFederationUpdateInstanceRequest, + AdminForwardAbuseUserReportRequest, AdminGetIndexStatsResponse, AdminGetTableStatsResponse, AdminGetUserIpsRequest, @@ -76,6 +73,7 @@ import type { AdminInviteCreateResponse, AdminInviteListRequest, AdminInviteListResponse, + AdminMetaResponse, AdminPromoCreateRequest, AdminQueueDeliverDelayedResponse, AdminQueueInboxDelayedResponse, @@ -88,33 +86,27 @@ import type { AdminResetPasswordRequest, AdminResetPasswordResponse, AdminResolveAbuseUserReportRequest, - AdminForwardAbuseUserReportRequest, - AdminUpdateAbuseUserReportRequest, - AdminSendEmailRequest, - AdminServerInfoResponse, - AdminShowModerationLogsRequest, - AdminShowModerationLogsResponse, - AdminShowUserRequest, - AdminShowUserResponse, - AdminShowUsersRequest, - AdminShowUsersResponse, - AdminSuspendUserRequest, - AdminUnsuspendUserRequest, - AdminUpdateMetaRequest, - AdminDeleteAccountRequest, - AdminUpdateUserNoteRequest, + AdminRolesAssignRequest, AdminRolesCreateRequest, AdminRolesCreateResponse, AdminRolesDeleteRequest, AdminRolesListResponse, AdminRolesShowRequest, AdminRolesShowResponse, - AdminRolesUpdateRequest, - AdminRolesAssignRequest, AdminRolesUnassignRequest, + AdminRolesUpdateRequest, AdminRolesUpdateDefaultPoliciesRequest, AdminRolesUsersRequest, AdminRolesUsersResponse, + AdminSendEmailRequest, + AdminServerInfoResponse, + AdminShowModerationLogsRequest, + AdminShowModerationLogsResponse, + AdminShowUserRequest, + AdminShowUserResponse, + AdminShowUsersRequest, + AdminShowUsersResponse, + AdminSuspendUserRequest, AdminSystemWebhookCreateRequest, AdminSystemWebhookCreateResponse, AdminSystemWebhookDeleteRequest, @@ -122,9 +114,15 @@ import type { AdminSystemWebhookListResponse, AdminSystemWebhookShowRequest, AdminSystemWebhookShowResponse, + AdminSystemWebhookTestRequest, AdminSystemWebhookUpdateRequest, AdminSystemWebhookUpdateResponse, - AdminSystemWebhookTestRequest, + AdminUnsetUserAvatarRequest, + AdminUnsetUserBannerRequest, + AdminUnsuspendUserRequest, + AdminUpdateAbuseUserReportRequest, + AdminUpdateMetaRequest, + AdminUpdateUserNoteRequest, AnnouncementsRequest, AnnouncementsResponse, AnnouncementsShowRequest, @@ -160,26 +158,29 @@ import type { BlockingDeleteResponse, BlockingListRequest, BlockingListResponse, + BubbleGameRankingRequest, + BubbleGameRankingResponse, + BubbleGameRegisterRequest, ChannelsCreateRequest, ChannelsCreateResponse, + ChannelsFavoriteRequest, ChannelsFeaturedResponse, ChannelsFollowRequest, ChannelsFollowedRequest, ChannelsFollowedResponse, + ChannelsMyFavoritesResponse, ChannelsOwnedRequest, ChannelsOwnedResponse, + ChannelsSearchRequest, + ChannelsSearchResponse, ChannelsShowRequest, ChannelsShowResponse, ChannelsTimelineRequest, ChannelsTimelineResponse, + ChannelsUnfavoriteRequest, ChannelsUnfollowRequest, ChannelsUpdateRequest, ChannelsUpdateResponse, - ChannelsFavoriteRequest, - ChannelsUnfavoriteRequest, - ChannelsMyFavoritesResponse, - ChannelsSearchRequest, - ChannelsSearchResponse, ChartsActiveUsersRequest, ChartsActiveUsersResponse, ChartsApRequestRequest, @@ -205,20 +206,20 @@ import type { ChartsUsersRequest, ChartsUsersResponse, ClipsAddNoteRequest, - ClipsRemoveNoteRequest, ClipsCreateRequest, ClipsCreateResponse, ClipsDeleteRequest, + ClipsFavoriteRequest, ClipsListResponse, + ClipsMyFavoritesResponse, ClipsNotesRequest, ClipsNotesResponse, + ClipsRemoveNoteRequest, ClipsShowRequest, ClipsShowResponse, + ClipsUnfavoriteRequest, ClipsUpdateRequest, ClipsUpdateResponse, - ClipsFavoriteRequest, - ClipsUnfavoriteRequest, - ClipsMyFavoritesResponse, DriveResponse, DriveFilesRequest, DriveFilesResponse, @@ -229,10 +230,10 @@ import type { DriveFilesCreateRequest, DriveFilesCreateResponse, DriveFilesDeleteRequest, - DriveFilesFindByHashRequest, - DriveFilesFindByHashResponse, DriveFilesFindRequest, DriveFilesFindResponse, + DriveFilesFindByHashRequest, + DriveFilesFindByHashResponse, DriveFilesShowRequest, DriveFilesShowResponse, DriveFilesUpdateRequest, @@ -253,6 +254,9 @@ import type { DriveStreamResponse, EmailAddressAvailableRequest, EmailAddressAvailableResponse, + EmojiRequest, + EmojiResponse, + EmojisResponse, EndpointRequest, EndpointResponse, EndpointsResponse, @@ -264,18 +268,33 @@ import type { FederationInstancesResponse, FederationShowInstanceRequest, FederationShowInstanceResponse, + FederationStatsRequest, + FederationStatsResponse, FederationUpdateRemoteUserRequest, FederationUsersRequest, FederationUsersResponse, - FederationStatsRequest, - FederationStatsResponse, + FetchExternalResourcesRequest, + FetchExternalResourcesResponse, + FetchRssRequest, + FetchRssResponse, + FlashCreateRequest, + FlashCreateResponse, + FlashDeleteRequest, + FlashFeaturedRequest, + FlashFeaturedResponse, + FlashLikeRequest, + FlashMyRequest, + FlashMyResponse, + FlashMyLikesRequest, + FlashMyLikesResponse, + FlashShowRequest, + FlashShowResponse, + FlashUnlikeRequest, + FlashUpdateRequest, FollowingCreateRequest, FollowingCreateResponse, FollowingDeleteRequest, FollowingDeleteResponse, - FollowingUpdateRequest, - FollowingUpdateResponse, - FollowingUpdateAllRequest, FollowingInvalidateRequest, FollowingInvalidateResponse, FollowingRequestsAcceptRequest, @@ -283,9 +302,12 @@ import type { FollowingRequestsCancelResponse, FollowingRequestsListRequest, FollowingRequestsListResponse, + FollowingRequestsRejectRequest, FollowingRequestsSentRequest, FollowingRequestsSentResponse, - FollowingRequestsRejectRequest, + FollowingUpdateRequest, + FollowingUpdateResponse, + FollowingUpdateAllRequest, GalleryFeaturedRequest, GalleryFeaturedResponse, GalleryPopularResponse, @@ -300,8 +322,8 @@ import type { GalleryPostsUnlikeRequest, GalleryPostsUpdateRequest, GalleryPostsUpdateResponse, - GetOnlineUsersCountResponse, GetAvatarDecorationsResponse, + GetOnlineUsersCountResponse, HashtagsListRequest, HashtagsListResponse, HashtagsSearchRequest, @@ -317,19 +339,19 @@ import type { I2faKeyDoneRequest, I2faKeyDoneResponse, I2faPasswordLessRequest, - I2faRegisterKeyRequest, - I2faRegisterKeyResponse, I2faRegisterRequest, I2faRegisterResponse, - I2faUpdateKeyRequest, + I2faRegisterKeyRequest, + I2faRegisterKeyResponse, I2faRemoveKeyRequest, I2faUnregisterRequest, + I2faUpdateKeyRequest, IAppsRequest, IAppsResponse, IAuthorizedAppsRequest, IAuthorizedAppsResponse, - IClaimAchievementRequest, IChangePasswordRequest, + IClaimAchievementRequest, IDeleteAccountRequest, IExportFollowingRequest, IFavoritesRequest, @@ -338,11 +360,13 @@ import type { IGalleryLikesResponse, IGalleryPostsRequest, IGalleryPostsResponse, + IImportAntennasRequest, IImportBlockingRequest, IImportFollowingRequest, IImportMutingRequest, IImportUserListsRequest, - IImportAntennasRequest, + IMoveRequest, + IMoveResponse, INotificationsRequest, INotificationsResponse, INotificationsGroupedRequest, @@ -355,16 +379,16 @@ import type { IPinResponse, IReadAnnouncementRequest, IRegenerateTokenRequest, + IRegistryGetRequest, + IRegistryGetResponse, IRegistryGetAllRequest, IRegistryGetAllResponse, IRegistryGetDetailRequest, IRegistryGetDetailResponse, - IRegistryGetRequest, - IRegistryGetResponse, - IRegistryKeysWithTypeRequest, - IRegistryKeysWithTypeResponse, IRegistryKeysRequest, IRegistryKeysResponse, + IRegistryKeysWithTypeRequest, + IRegistryKeysWithTypeResponse, IRegistryRemoveRequest, IRegistryScopesWithDomainResponse, IRegistrySetRequest, @@ -373,40 +397,31 @@ import type { ISigninHistoryResponse, IUnpinRequest, IUnpinResponse, - IUpdateEmailRequest, - IUpdateEmailResponse, IUpdateRequest, IUpdateResponse, - IMoveRequest, - IMoveResponse, + IUpdateEmailRequest, + IUpdateEmailResponse, IWebhooksCreateRequest, IWebhooksCreateResponse, + IWebhooksDeleteRequest, IWebhooksListResponse, IWebhooksShowRequest, IWebhooksShowResponse, - IWebhooksUpdateRequest, - IWebhooksDeleteRequest, IWebhooksTestRequest, + IWebhooksUpdateRequest, InviteCreateResponse, InviteDeleteRequest, + InviteLimitResponse, InviteListRequest, InviteListResponse, - InviteLimitResponse, MetaRequest, MetaResponse, - EmojisResponse, - EmojiRequest, - EmojiResponse, MiauthGenTokenRequest, MiauthGenTokenResponse, MuteCreateRequest, MuteDeleteRequest, MuteListRequest, MuteListResponse, - RenoteMuteCreateRequest, - RenoteMuteDeleteRequest, - RenoteMuteListRequest, - RenoteMuteListResponse, MyAppsRequest, MyAppsResponse, NotesRequest, @@ -443,10 +458,10 @@ import type { NotesRenotesResponse, NotesRepliesRequest, NotesRepliesResponse, - NotesSearchByTagRequest, - NotesSearchByTagResponse, NotesSearchRequest, NotesSearchResponse, + NotesSearchByTagRequest, + NotesSearchByTagResponse, NotesShowRequest, NotesShowResponse, NotesStateRequest, @@ -471,49 +486,57 @@ import type { PagesShowResponse, PagesUnlikeRequest, PagesUpdateRequest, - FlashCreateRequest, - FlashCreateResponse, - FlashDeleteRequest, - FlashFeaturedRequest, - FlashFeaturedResponse, - FlashLikeRequest, - FlashShowRequest, - FlashShowResponse, - FlashUnlikeRequest, - FlashUpdateRequest, - FlashMyRequest, - FlashMyResponse, - FlashMyLikesRequest, - FlashMyLikesResponse, PingResponse, PinnedUsersResponse, PromoReadRequest, + RenoteMuteCreateRequest, + RenoteMuteDeleteRequest, + RenoteMuteListRequest, + RenoteMuteListResponse, + RequestResetPasswordRequest, + ResetPasswordRequest, + RetentionResponse, + ReversiCancelMatchRequest, + ReversiGamesRequest, + ReversiGamesResponse, + ReversiInvitationsResponse, + ReversiMatchRequest, + ReversiMatchResponse, + ReversiShowGameRequest, + ReversiShowGameResponse, + ReversiSurrenderRequest, + ReversiVerifyRequest, + ReversiVerifyResponse, RolesListResponse, + RolesNotesRequest, + RolesNotesResponse, RolesShowRequest, RolesShowResponse, RolesUsersRequest, RolesUsersResponse, - RolesNotesRequest, - RolesNotesResponse, - RequestResetPasswordRequest, - ResetPasswordRequest, ServerInfoResponse, StatsResponse, + SwRegisterRequest, + SwRegisterResponse, SwShowRegistrationRequest, SwShowRegistrationResponse, + SwUnregisterRequest, SwUpdateRegistrationRequest, SwUpdateRegistrationResponse, - SwRegisterRequest, - SwRegisterResponse, - SwUnregisterRequest, TestRequest, TestResponse, UsernameAvailableRequest, UsernameAvailableResponse, UsersRequest, UsersResponse, + UsersAchievementsRequest, + UsersAchievementsResponse, UsersClipsRequest, UsersClipsResponse, + UsersFeaturedNotesRequest, + UsersFeaturedNotesResponse, + UsersFlashsRequest, + UsersFlashsResponse, UsersFollowersRequest, UsersFollowersResponse, UsersFollowingRequest, @@ -522,32 +545,28 @@ import type { UsersGalleryPostsResponse, UsersGetFrequentlyRepliedUsersRequest, UsersGetFrequentlyRepliedUsersResponse, - UsersFeaturedNotesRequest, - UsersFeaturedNotesResponse, UsersListsCreateRequest, UsersListsCreateResponse, + UsersListsCreateFromPublicRequest, + UsersListsCreateFromPublicResponse, UsersListsDeleteRequest, + UsersListsFavoriteRequest, + UsersListsGetMembershipsRequest, + UsersListsGetMembershipsResponse, UsersListsListRequest, UsersListsListResponse, UsersListsPullRequest, UsersListsPushRequest, UsersListsShowRequest, UsersListsShowResponse, - UsersListsFavoriteRequest, UsersListsUnfavoriteRequest, UsersListsUpdateRequest, UsersListsUpdateResponse, - UsersListsCreateFromPublicRequest, - UsersListsCreateFromPublicResponse, UsersListsUpdateMembershipRequest, - UsersListsGetMembershipsRequest, - UsersListsGetMembershipsResponse, UsersNotesRequest, UsersNotesResponse, UsersPagesRequest, UsersPagesResponse, - UsersFlashsRequest, - UsersFlashsResponse, UsersReactionsRequest, UsersReactionsResponse, UsersRecommendationRequest, @@ -555,44 +574,24 @@ import type { UsersRelationRequest, UsersRelationResponse, UsersReportAbuseRequest, - UsersSearchByUsernameAndHostRequest, - UsersSearchByUsernameAndHostResponse, UsersSearchRequest, UsersSearchResponse, + UsersSearchByUsernameAndHostRequest, + UsersSearchByUsernameAndHostResponse, UsersShowRequest, UsersShowResponse, - UsersAchievementsRequest, - UsersAchievementsResponse, UsersUpdateMemoRequest, - FetchRssRequest, - FetchRssResponse, - FetchExternalResourcesRequest, - FetchExternalResourcesResponse, - RetentionResponse, - BubbleGameRegisterRequest, - BubbleGameRankingRequest, - BubbleGameRankingResponse, - ReversiCancelMatchRequest, - ReversiGamesRequest, - ReversiGamesResponse, - ReversiMatchRequest, - ReversiMatchResponse, - ReversiInvitationsResponse, - ReversiShowGameRequest, - ReversiShowGameResponse, - ReversiSurrenderRequest, - ReversiVerifyRequest, - ReversiVerifyResponse, + V2AdminEmojiListRequest, + V2AdminEmojiListResponse, } from './entities.js'; export type Endpoints = { - 'admin/meta': { req: EmptyRequest; res: AdminMetaResponse }; - 'admin/abuse-user-reports': { req: AdminAbuseUserReportsRequest; res: AdminAbuseUserReportsResponse }; + 'admin/abuse-report/notification-recipient/create': { req: AdminAbuseReportNotificationRecipientCreateRequest; res: AdminAbuseReportNotificationRecipientCreateResponse }; + 'admin/abuse-report/notification-recipient/delete': { req: AdminAbuseReportNotificationRecipientDeleteRequest; res: EmptyResponse }; 'admin/abuse-report/notification-recipient/list': { req: AdminAbuseReportNotificationRecipientListRequest; res: AdminAbuseReportNotificationRecipientListResponse }; 'admin/abuse-report/notification-recipient/show': { req: AdminAbuseReportNotificationRecipientShowRequest; res: AdminAbuseReportNotificationRecipientShowResponse }; - 'admin/abuse-report/notification-recipient/create': { req: AdminAbuseReportNotificationRecipientCreateRequest; res: AdminAbuseReportNotificationRecipientCreateResponse }; 'admin/abuse-report/notification-recipient/update': { req: AdminAbuseReportNotificationRecipientUpdateRequest; res: AdminAbuseReportNotificationRecipientUpdateResponse }; - 'admin/abuse-report/notification-recipient/delete': { req: AdminAbuseReportNotificationRecipientDeleteRequest; res: EmptyResponse }; + 'admin/abuse-user-reports': { req: AdminAbuseUserReportsRequest; res: AdminAbuseUserReportsResponse }; 'admin/accounts/create': { req: AdminAccountsCreateRequest; res: AdminAccountsCreateResponse }; 'admin/accounts/delete': { req: AdminAccountsDeleteRequest; res: EmptyResponse }; 'admin/accounts/find-by-email': { req: AdminAccountsFindByEmailRequest; res: AdminAccountsFindByEmailResponse }; @@ -610,36 +609,36 @@ export type Endpoints = { 'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse }; 'admin/captcha/current': { req: EmptyRequest; res: AdminCaptchaCurrentResponse }; 'admin/captcha/save': { req: AdminCaptchaSaveRequest; res: EmptyResponse }; + 'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse }; 'admin/delete-all-files-of-a-user': { req: AdminDeleteAllFilesOfAUserRequest; res: EmptyResponse }; - 'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse }; - 'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse }; 'admin/drive/clean-remote-files': { req: EmptyRequest; res: EmptyResponse }; 'admin/drive/cleanup': { req: EmptyRequest; res: EmptyResponse }; 'admin/drive/files': { req: AdminDriveFilesRequest; res: AdminDriveFilesResponse }; 'admin/drive/show-file': { req: AdminDriveShowFileRequest; res: AdminDriveShowFileResponse }; - 'admin/emoji/add-aliases-bulk': { req: AdminEmojiAddAliasesBulkRequest; res: EmptyResponse }; 'admin/emoji/add': { req: AdminEmojiAddRequest; res: AdminEmojiAddResponse }; + 'admin/emoji/add-aliases-bulk': { req: AdminEmojiAddAliasesBulkRequest; res: EmptyResponse }; 'admin/emoji/copy': { req: AdminEmojiCopyRequest; res: AdminEmojiCopyResponse }; - 'admin/emoji/delete-bulk': { req: AdminEmojiDeleteBulkRequest; res: EmptyResponse }; 'admin/emoji/delete': { req: AdminEmojiDeleteRequest; res: EmptyResponse }; + 'admin/emoji/delete-bulk': { req: AdminEmojiDeleteBulkRequest; res: EmptyResponse }; 'admin/emoji/import-zip': { req: AdminEmojiImportZipRequest; res: EmptyResponse }; - 'admin/emoji/list-remote': { req: AdminEmojiListRemoteRequest; res: AdminEmojiListRemoteResponse }; 'admin/emoji/list': { req: AdminEmojiListRequest; res: AdminEmojiListResponse }; + 'admin/emoji/list-remote': { req: AdminEmojiListRemoteRequest; res: AdminEmojiListRemoteResponse }; 'admin/emoji/remove-aliases-bulk': { req: AdminEmojiRemoveAliasesBulkRequest; res: EmptyResponse }; 'admin/emoji/set-aliases-bulk': { req: AdminEmojiSetAliasesBulkRequest; res: EmptyResponse }; 'admin/emoji/set-category-bulk': { req: AdminEmojiSetCategoryBulkRequest; res: EmptyResponse }; 'admin/emoji/set-license-bulk': { req: AdminEmojiSetLicenseBulkRequest; res: EmptyResponse }; 'admin/emoji/update': { req: AdminEmojiUpdateRequest; res: EmptyResponse }; - 'v2/admin/emoji/list': { req: V2AdminEmojiListRequest; res: V2AdminEmojiListResponse }; 'admin/federation/delete-all-files': { req: AdminFederationDeleteAllFilesRequest; res: EmptyResponse }; 'admin/federation/refresh-remote-instance-metadata': { req: AdminFederationRefreshRemoteInstanceMetadataRequest; res: EmptyResponse }; 'admin/federation/remove-all-following': { req: AdminFederationRemoveAllFollowingRequest; res: EmptyResponse }; 'admin/federation/update-instance': { req: AdminFederationUpdateInstanceRequest; res: EmptyResponse }; + 'admin/forward-abuse-user-report': { req: AdminForwardAbuseUserReportRequest; res: EmptyResponse }; 'admin/get-index-stats': { req: EmptyRequest; res: AdminGetIndexStatsResponse }; 'admin/get-table-stats': { req: EmptyRequest; res: AdminGetTableStatsResponse }; 'admin/get-user-ips': { req: AdminGetUserIpsRequest; res: AdminGetUserIpsResponse }; 'admin/invite/create': { req: AdminInviteCreateRequest; res: AdminInviteCreateResponse }; 'admin/invite/list': { req: AdminInviteListRequest; res: AdminInviteListResponse }; + 'admin/meta': { req: EmptyRequest; res: AdminMetaResponse }; 'admin/promo/create': { req: AdminPromoCreateRequest; res: EmptyResponse }; 'admin/queue/clear': { req: EmptyRequest; res: EmptyResponse }; 'admin/queue/deliver-delayed': { req: EmptyRequest; res: AdminQueueDeliverDelayedResponse }; @@ -651,33 +650,33 @@ export type Endpoints = { 'admin/relays/remove': { req: AdminRelaysRemoveRequest; res: EmptyResponse }; 'admin/reset-password': { req: AdminResetPasswordRequest; res: AdminResetPasswordResponse }; 'admin/resolve-abuse-user-report': { req: AdminResolveAbuseUserReportRequest; res: EmptyResponse }; - 'admin/forward-abuse-user-report': { req: AdminForwardAbuseUserReportRequest; res: EmptyResponse }; - 'admin/update-abuse-user-report': { req: AdminUpdateAbuseUserReportRequest; res: EmptyResponse }; - 'admin/send-email': { req: AdminSendEmailRequest; res: EmptyResponse }; - 'admin/server-info': { req: EmptyRequest; res: AdminServerInfoResponse }; - 'admin/show-moderation-logs': { req: AdminShowModerationLogsRequest; res: AdminShowModerationLogsResponse }; - 'admin/show-user': { req: AdminShowUserRequest; res: AdminShowUserResponse }; - 'admin/show-users': { req: AdminShowUsersRequest; res: AdminShowUsersResponse }; - 'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse }; - 'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse }; - 'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse }; - 'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse }; - 'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse }; + 'admin/roles/assign': { req: AdminRolesAssignRequest; res: EmptyResponse }; 'admin/roles/create': { req: AdminRolesCreateRequest; res: AdminRolesCreateResponse }; 'admin/roles/delete': { req: AdminRolesDeleteRequest; res: EmptyResponse }; 'admin/roles/list': { req: EmptyRequest; res: AdminRolesListResponse }; 'admin/roles/show': { req: AdminRolesShowRequest; res: AdminRolesShowResponse }; - 'admin/roles/update': { req: AdminRolesUpdateRequest; res: EmptyResponse }; - 'admin/roles/assign': { req: AdminRolesAssignRequest; res: EmptyResponse }; 'admin/roles/unassign': { req: AdminRolesUnassignRequest; res: EmptyResponse }; + 'admin/roles/update': { req: AdminRolesUpdateRequest; res: EmptyResponse }; 'admin/roles/update-default-policies': { req: AdminRolesUpdateDefaultPoliciesRequest; res: EmptyResponse }; 'admin/roles/users': { req: AdminRolesUsersRequest; res: AdminRolesUsersResponse }; + 'admin/send-email': { req: AdminSendEmailRequest; res: EmptyResponse }; + 'admin/server-info': { req: EmptyRequest; res: AdminServerInfoResponse }; + 'admin/show-moderation-logs': { req: AdminShowModerationLogsRequest; res: AdminShowModerationLogsResponse }; + 'admin/show-user': { req: AdminShowUserRequest; res: AdminShowUserResponse }; + 'admin/show-users': { req: AdminShowUsersRequest; res: AdminShowUsersResponse }; + 'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse }; 'admin/system-webhook/create': { req: AdminSystemWebhookCreateRequest; res: AdminSystemWebhookCreateResponse }; 'admin/system-webhook/delete': { req: AdminSystemWebhookDeleteRequest; res: EmptyResponse }; 'admin/system-webhook/list': { req: AdminSystemWebhookListRequest; res: AdminSystemWebhookListResponse }; 'admin/system-webhook/show': { req: AdminSystemWebhookShowRequest; res: AdminSystemWebhookShowResponse }; - 'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse }; 'admin/system-webhook/test': { req: AdminSystemWebhookTestRequest; res: EmptyResponse }; + 'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse }; + 'admin/unset-user-avatar': { req: AdminUnsetUserAvatarRequest; res: EmptyResponse }; + 'admin/unset-user-banner': { req: AdminUnsetUserBannerRequest; res: EmptyResponse }; + 'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse }; + 'admin/update-abuse-user-report': { req: AdminUpdateAbuseUserReportRequest; res: EmptyResponse }; + 'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse }; + 'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse }; 'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse }; 'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse }; 'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse }; @@ -697,19 +696,21 @@ export type Endpoints = { 'blocking/create': { req: BlockingCreateRequest; res: BlockingCreateResponse }; 'blocking/delete': { req: BlockingDeleteRequest; res: BlockingDeleteResponse }; 'blocking/list': { req: BlockingListRequest; res: BlockingListResponse }; + 'bubble-game/ranking': { req: BubbleGameRankingRequest; res: BubbleGameRankingResponse }; + 'bubble-game/register': { req: BubbleGameRegisterRequest; res: EmptyResponse }; 'channels/create': { req: ChannelsCreateRequest; res: ChannelsCreateResponse }; + 'channels/favorite': { req: ChannelsFavoriteRequest; res: EmptyResponse }; 'channels/featured': { req: EmptyRequest; res: ChannelsFeaturedResponse }; 'channels/follow': { req: ChannelsFollowRequest; res: EmptyResponse }; 'channels/followed': { req: ChannelsFollowedRequest; res: ChannelsFollowedResponse }; + 'channels/my-favorites': { req: EmptyRequest; res: ChannelsMyFavoritesResponse }; 'channels/owned': { req: ChannelsOwnedRequest; res: ChannelsOwnedResponse }; + 'channels/search': { req: ChannelsSearchRequest; res: ChannelsSearchResponse }; 'channels/show': { req: ChannelsShowRequest; res: ChannelsShowResponse }; 'channels/timeline': { req: ChannelsTimelineRequest; res: ChannelsTimelineResponse }; + 'channels/unfavorite': { req: ChannelsUnfavoriteRequest; res: EmptyResponse }; 'channels/unfollow': { req: ChannelsUnfollowRequest; res: EmptyResponse }; 'channels/update': { req: ChannelsUpdateRequest; res: ChannelsUpdateResponse }; - 'channels/favorite': { req: ChannelsFavoriteRequest; res: EmptyResponse }; - 'channels/unfavorite': { req: ChannelsUnfavoriteRequest; res: EmptyResponse }; - 'channels/my-favorites': { req: EmptyRequest; res: ChannelsMyFavoritesResponse }; - 'channels/search': { req: ChannelsSearchRequest; res: ChannelsSearchResponse }; 'charts/active-users': { req: ChartsActiveUsersRequest; res: ChartsActiveUsersResponse }; 'charts/ap-request': { req: ChartsApRequestRequest; res: ChartsApRequestResponse }; 'charts/drive': { req: ChartsDriveRequest; res: ChartsDriveResponse }; @@ -723,24 +724,24 @@ export type Endpoints = { 'charts/user/reactions': { req: ChartsUserReactionsRequest; res: ChartsUserReactionsResponse }; 'charts/users': { req: ChartsUsersRequest; res: ChartsUsersResponse }; 'clips/add-note': { req: ClipsAddNoteRequest; res: EmptyResponse }; - 'clips/remove-note': { req: ClipsRemoveNoteRequest; res: EmptyResponse }; 'clips/create': { req: ClipsCreateRequest; res: ClipsCreateResponse }; 'clips/delete': { req: ClipsDeleteRequest; res: EmptyResponse }; + 'clips/favorite': { req: ClipsFavoriteRequest; res: EmptyResponse }; 'clips/list': { req: EmptyRequest; res: ClipsListResponse }; + 'clips/my-favorites': { req: EmptyRequest; res: ClipsMyFavoritesResponse }; 'clips/notes': { req: ClipsNotesRequest; res: ClipsNotesResponse }; + 'clips/remove-note': { req: ClipsRemoveNoteRequest; res: EmptyResponse }; 'clips/show': { req: ClipsShowRequest; res: ClipsShowResponse }; - 'clips/update': { req: ClipsUpdateRequest; res: ClipsUpdateResponse }; - 'clips/favorite': { req: ClipsFavoriteRequest; res: EmptyResponse }; 'clips/unfavorite': { req: ClipsUnfavoriteRequest; res: EmptyResponse }; - 'clips/my-favorites': { req: EmptyRequest; res: ClipsMyFavoritesResponse }; + 'clips/update': { req: ClipsUpdateRequest; res: ClipsUpdateResponse }; 'drive': { req: EmptyRequest; res: DriveResponse }; 'drive/files': { req: DriveFilesRequest; res: DriveFilesResponse }; 'drive/files/attached-notes': { req: DriveFilesAttachedNotesRequest; res: DriveFilesAttachedNotesResponse }; 'drive/files/check-existence': { req: DriveFilesCheckExistenceRequest; res: DriveFilesCheckExistenceResponse }; 'drive/files/create': { req: DriveFilesCreateRequest; res: DriveFilesCreateResponse }; 'drive/files/delete': { req: DriveFilesDeleteRequest; res: EmptyResponse }; - 'drive/files/find-by-hash': { req: DriveFilesFindByHashRequest; res: DriveFilesFindByHashResponse }; 'drive/files/find': { req: DriveFilesFindRequest; res: DriveFilesFindResponse }; + 'drive/files/find-by-hash': { req: DriveFilesFindByHashRequest; res: DriveFilesFindByHashResponse }; 'drive/files/show': { req: DriveFilesShowRequest; res: DriveFilesShowResponse }; 'drive/files/update': { req: DriveFilesUpdateRequest; res: DriveFilesUpdateResponse }; 'drive/files/upload-from-url': { req: DriveFilesUploadFromUrlRequest; res: EmptyResponse }; @@ -752,6 +753,8 @@ export type Endpoints = { 'drive/folders/update': { req: DriveFoldersUpdateRequest; res: DriveFoldersUpdateResponse }; 'drive/stream': { req: DriveStreamRequest; res: DriveStreamResponse }; 'email-address/available': { req: EmailAddressAvailableRequest; res: EmailAddressAvailableResponse }; + 'emoji': { req: EmojiRequest; res: EmojiResponse }; + 'emojis': { req: EmptyRequest; res: EmojisResponse }; 'endpoint': { req: EndpointRequest; res: EndpointResponse }; 'endpoints': { req: EmptyRequest; res: EndpointsResponse }; 'export-custom-emojis': { req: EmptyRequest; res: EmptyResponse }; @@ -759,19 +762,30 @@ export type Endpoints = { 'federation/following': { req: FederationFollowingRequest; res: FederationFollowingResponse }; 'federation/instances': { req: FederationInstancesRequest; res: FederationInstancesResponse }; 'federation/show-instance': { req: FederationShowInstanceRequest; res: FederationShowInstanceResponse }; + 'federation/stats': { req: FederationStatsRequest; res: FederationStatsResponse }; 'federation/update-remote-user': { req: FederationUpdateRemoteUserRequest; res: EmptyResponse }; 'federation/users': { req: FederationUsersRequest; res: FederationUsersResponse }; - 'federation/stats': { req: FederationStatsRequest; res: FederationStatsResponse }; + 'fetch-external-resources': { req: FetchExternalResourcesRequest; res: FetchExternalResourcesResponse }; + 'fetch-rss': { req: FetchRssRequest; res: FetchRssResponse }; + 'flash/create': { req: FlashCreateRequest; res: FlashCreateResponse }; + 'flash/delete': { req: FlashDeleteRequest; res: EmptyResponse }; + 'flash/featured': { req: FlashFeaturedRequest; res: FlashFeaturedResponse }; + 'flash/like': { req: FlashLikeRequest; res: EmptyResponse }; + 'flash/my': { req: FlashMyRequest; res: FlashMyResponse }; + 'flash/my-likes': { req: FlashMyLikesRequest; res: FlashMyLikesResponse }; + 'flash/show': { req: FlashShowRequest; res: FlashShowResponse }; + 'flash/unlike': { req: FlashUnlikeRequest; res: EmptyResponse }; + 'flash/update': { req: FlashUpdateRequest; res: EmptyResponse }; 'following/create': { req: FollowingCreateRequest; res: FollowingCreateResponse }; 'following/delete': { req: FollowingDeleteRequest; res: FollowingDeleteResponse }; - 'following/update': { req: FollowingUpdateRequest; res: FollowingUpdateResponse }; - 'following/update-all': { req: FollowingUpdateAllRequest; res: EmptyResponse }; 'following/invalidate': { req: FollowingInvalidateRequest; res: FollowingInvalidateResponse }; 'following/requests/accept': { req: FollowingRequestsAcceptRequest; res: EmptyResponse }; 'following/requests/cancel': { req: FollowingRequestsCancelRequest; res: FollowingRequestsCancelResponse }; 'following/requests/list': { req: FollowingRequestsListRequest; res: FollowingRequestsListResponse }; - 'following/requests/sent': { req: FollowingRequestsSentRequest; res: FollowingRequestsSentResponse }; 'following/requests/reject': { req: FollowingRequestsRejectRequest; res: EmptyResponse }; + 'following/requests/sent': { req: FollowingRequestsSentRequest; res: FollowingRequestsSentResponse }; + 'following/update': { req: FollowingUpdateRequest; res: FollowingUpdateResponse }; + 'following/update-all': { req: FollowingUpdateAllRequest; res: EmptyResponse }; 'gallery/featured': { req: GalleryFeaturedRequest; res: GalleryFeaturedResponse }; 'gallery/popular': { req: EmptyRequest; res: GalleryPopularResponse }; 'gallery/posts': { req: GalleryPostsRequest; res: GalleryPostsResponse }; @@ -781,8 +795,8 @@ export type Endpoints = { 'gallery/posts/show': { req: GalleryPostsShowRequest; res: GalleryPostsShowResponse }; 'gallery/posts/unlike': { req: GalleryPostsUnlikeRequest; res: EmptyResponse }; 'gallery/posts/update': { req: GalleryPostsUpdateRequest; res: GalleryPostsUpdateResponse }; - 'get-online-users-count': { req: EmptyRequest; res: GetOnlineUsersCountResponse }; 'get-avatar-decorations': { req: EmptyRequest; res: GetAvatarDecorationsResponse }; + 'get-online-users-count': { req: EmptyRequest; res: GetOnlineUsersCountResponse }; 'hashtags/list': { req: HashtagsListRequest; res: HashtagsListResponse }; 'hashtags/search': { req: HashtagsSearchRequest; res: HashtagsSearchResponse }; 'hashtags/show': { req: HashtagsShowRequest; res: HashtagsShowResponse }; @@ -792,32 +806,33 @@ export type Endpoints = { 'i/2fa/done': { req: I2faDoneRequest; res: I2faDoneResponse }; 'i/2fa/key-done': { req: I2faKeyDoneRequest; res: I2faKeyDoneResponse }; 'i/2fa/password-less': { req: I2faPasswordLessRequest; res: EmptyResponse }; - 'i/2fa/register-key': { req: I2faRegisterKeyRequest; res: I2faRegisterKeyResponse }; 'i/2fa/register': { req: I2faRegisterRequest; res: I2faRegisterResponse }; - 'i/2fa/update-key': { req: I2faUpdateKeyRequest; res: EmptyResponse }; + 'i/2fa/register-key': { req: I2faRegisterKeyRequest; res: I2faRegisterKeyResponse }; 'i/2fa/remove-key': { req: I2faRemoveKeyRequest; res: EmptyResponse }; 'i/2fa/unregister': { req: I2faUnregisterRequest; res: EmptyResponse }; + 'i/2fa/update-key': { req: I2faUpdateKeyRequest; res: EmptyResponse }; 'i/apps': { req: IAppsRequest; res: IAppsResponse }; 'i/authorized-apps': { req: IAuthorizedAppsRequest; res: IAuthorizedAppsResponse }; - 'i/claim-achievement': { req: IClaimAchievementRequest; res: EmptyResponse }; 'i/change-password': { req: IChangePasswordRequest; res: EmptyResponse }; + 'i/claim-achievement': { req: IClaimAchievementRequest; res: EmptyResponse }; 'i/delete-account': { req: IDeleteAccountRequest; res: EmptyResponse }; + 'i/export-antennas': { req: EmptyRequest; res: EmptyResponse }; 'i/export-blocking': { req: EmptyRequest; res: EmptyResponse }; + 'i/export-clips': { req: EmptyRequest; res: EmptyResponse }; + 'i/export-favorites': { req: EmptyRequest; res: EmptyResponse }; 'i/export-following': { req: IExportFollowingRequest; res: EmptyResponse }; 'i/export-mute': { req: EmptyRequest; res: EmptyResponse }; 'i/export-notes': { req: EmptyRequest; res: EmptyResponse }; - 'i/export-clips': { req: EmptyRequest; res: EmptyResponse }; - 'i/export-favorites': { req: EmptyRequest; res: EmptyResponse }; 'i/export-user-lists': { req: EmptyRequest; res: EmptyResponse }; - 'i/export-antennas': { req: EmptyRequest; res: EmptyResponse }; 'i/favorites': { req: IFavoritesRequest; res: IFavoritesResponse }; 'i/gallery/likes': { req: IGalleryLikesRequest; res: IGalleryLikesResponse }; 'i/gallery/posts': { req: IGalleryPostsRequest; res: IGalleryPostsResponse }; + 'i/import-antennas': { req: IImportAntennasRequest; res: EmptyResponse }; 'i/import-blocking': { req: IImportBlockingRequest; res: EmptyResponse }; 'i/import-following': { req: IImportFollowingRequest; res: EmptyResponse }; 'i/import-muting': { req: IImportMutingRequest; res: EmptyResponse }; 'i/import-user-lists': { req: IImportUserListsRequest; res: EmptyResponse }; - 'i/import-antennas': { req: IImportAntennasRequest; res: EmptyResponse }; + 'i/move': { req: IMoveRequest; res: IMoveResponse }; 'i/notifications': { req: INotificationsRequest; res: INotificationsResponse }; 'i/notifications-grouped': { req: INotificationsGroupedRequest; res: INotificationsGroupedResponse }; 'i/page-likes': { req: IPageLikesRequest; res: IPageLikesResponse }; @@ -826,40 +841,34 @@ export type Endpoints = { 'i/read-all-unread-notes': { req: EmptyRequest; res: EmptyResponse }; 'i/read-announcement': { req: IReadAnnouncementRequest; res: EmptyResponse }; 'i/regenerate-token': { req: IRegenerateTokenRequest; res: EmptyResponse }; + 'i/registry/get': { req: IRegistryGetRequest; res: IRegistryGetResponse }; 'i/registry/get-all': { req: IRegistryGetAllRequest; res: IRegistryGetAllResponse }; 'i/registry/get-detail': { req: IRegistryGetDetailRequest; res: IRegistryGetDetailResponse }; - 'i/registry/get': { req: IRegistryGetRequest; res: IRegistryGetResponse }; - 'i/registry/keys-with-type': { req: IRegistryKeysWithTypeRequest; res: IRegistryKeysWithTypeResponse }; 'i/registry/keys': { req: IRegistryKeysRequest; res: IRegistryKeysResponse }; + 'i/registry/keys-with-type': { req: IRegistryKeysWithTypeRequest; res: IRegistryKeysWithTypeResponse }; 'i/registry/remove': { req: IRegistryRemoveRequest; res: EmptyResponse }; 'i/registry/scopes-with-domain': { req: EmptyRequest; res: IRegistryScopesWithDomainResponse }; 'i/registry/set': { req: IRegistrySetRequest; res: EmptyResponse }; 'i/revoke-token': { req: IRevokeTokenRequest; res: EmptyResponse }; 'i/signin-history': { req: ISigninHistoryRequest; res: ISigninHistoryResponse }; 'i/unpin': { req: IUnpinRequest; res: IUnpinResponse }; - 'i/update-email': { req: IUpdateEmailRequest; res: IUpdateEmailResponse }; 'i/update': { req: IUpdateRequest; res: IUpdateResponse }; - 'i/move': { req: IMoveRequest; res: IMoveResponse }; + 'i/update-email': { req: IUpdateEmailRequest; res: IUpdateEmailResponse }; 'i/webhooks/create': { req: IWebhooksCreateRequest; res: IWebhooksCreateResponse }; + 'i/webhooks/delete': { req: IWebhooksDeleteRequest; res: EmptyResponse }; 'i/webhooks/list': { req: EmptyRequest; res: IWebhooksListResponse }; 'i/webhooks/show': { req: IWebhooksShowRequest; res: IWebhooksShowResponse }; - 'i/webhooks/update': { req: IWebhooksUpdateRequest; res: EmptyResponse }; - 'i/webhooks/delete': { req: IWebhooksDeleteRequest; res: EmptyResponse }; 'i/webhooks/test': { req: IWebhooksTestRequest; res: EmptyResponse }; + 'i/webhooks/update': { req: IWebhooksUpdateRequest; res: EmptyResponse }; 'invite/create': { req: EmptyRequest; res: InviteCreateResponse }; 'invite/delete': { req: InviteDeleteRequest; res: EmptyResponse }; - 'invite/list': { req: InviteListRequest; res: InviteListResponse }; 'invite/limit': { req: EmptyRequest; res: InviteLimitResponse }; + 'invite/list': { req: InviteListRequest; res: InviteListResponse }; 'meta': { req: MetaRequest; res: MetaResponse }; - 'emojis': { req: EmptyRequest; res: EmojisResponse }; - 'emoji': { req: EmojiRequest; res: EmojiResponse }; 'miauth/gen-token': { req: MiauthGenTokenRequest; res: MiauthGenTokenResponse }; 'mute/create': { req: MuteCreateRequest; res: EmptyResponse }; 'mute/delete': { req: MuteDeleteRequest; res: EmptyResponse }; 'mute/list': { req: MuteListRequest; res: MuteListResponse }; - 'renote-mute/create': { req: RenoteMuteCreateRequest; res: EmptyResponse }; - 'renote-mute/delete': { req: RenoteMuteDeleteRequest; res: EmptyResponse }; - 'renote-mute/list': { req: RenoteMuteListRequest; res: RenoteMuteListResponse }; 'my/apps': { req: MyAppsRequest; res: MyAppsResponse }; 'notes': { req: NotesRequest; res: NotesResponse }; 'notes/children': { req: NotesChildrenRequest; res: NotesChildrenResponse }; @@ -881,8 +890,8 @@ export type Endpoints = { 'notes/reactions/delete': { req: NotesReactionsDeleteRequest; res: EmptyResponse }; 'notes/renotes': { req: NotesRenotesRequest; res: NotesRenotesResponse }; 'notes/replies': { req: NotesRepliesRequest; res: NotesRepliesResponse }; - 'notes/search-by-tag': { req: NotesSearchByTagRequest; res: NotesSearchByTagResponse }; 'notes/search': { req: NotesSearchRequest; res: NotesSearchResponse }; + 'notes/search-by-tag': { req: NotesSearchByTagRequest; res: NotesSearchByTagResponse }; 'notes/show': { req: NotesShowRequest; res: NotesShowResponse }; 'notes/state': { req: NotesStateRequest; res: NotesStateResponse }; 'notes/thread-muting/create': { req: NotesThreadMutingCreateRequest; res: EmptyResponse }; @@ -903,76 +912,67 @@ export type Endpoints = { 'pages/show': { req: PagesShowRequest; res: PagesShowResponse }; 'pages/unlike': { req: PagesUnlikeRequest; res: EmptyResponse }; 'pages/update': { req: PagesUpdateRequest; res: EmptyResponse }; - 'flash/create': { req: FlashCreateRequest; res: FlashCreateResponse }; - 'flash/delete': { req: FlashDeleteRequest; res: EmptyResponse }; - 'flash/featured': { req: FlashFeaturedRequest; res: FlashFeaturedResponse }; - 'flash/like': { req: FlashLikeRequest; res: EmptyResponse }; - 'flash/show': { req: FlashShowRequest; res: FlashShowResponse }; - 'flash/unlike': { req: FlashUnlikeRequest; res: EmptyResponse }; - 'flash/update': { req: FlashUpdateRequest; res: EmptyResponse }; - 'flash/my': { req: FlashMyRequest; res: FlashMyResponse }; - 'flash/my-likes': { req: FlashMyLikesRequest; res: FlashMyLikesResponse }; 'ping': { req: EmptyRequest; res: PingResponse }; 'pinned-users': { req: EmptyRequest; res: PinnedUsersResponse }; 'promo/read': { req: PromoReadRequest; res: EmptyResponse }; - 'roles/list': { req: EmptyRequest; res: RolesListResponse }; - 'roles/show': { req: RolesShowRequest; res: RolesShowResponse }; - 'roles/users': { req: RolesUsersRequest; res: RolesUsersResponse }; - 'roles/notes': { req: RolesNotesRequest; res: RolesNotesResponse }; + 'renote-mute/create': { req: RenoteMuteCreateRequest; res: EmptyResponse }; + 'renote-mute/delete': { req: RenoteMuteDeleteRequest; res: EmptyResponse }; + 'renote-mute/list': { req: RenoteMuteListRequest; res: RenoteMuteListResponse }; 'request-reset-password': { req: RequestResetPasswordRequest; res: EmptyResponse }; 'reset-db': { req: EmptyRequest; res: EmptyResponse }; 'reset-password': { req: ResetPasswordRequest; res: EmptyResponse }; + 'retention': { req: EmptyRequest; res: RetentionResponse }; + 'reversi/cancel-match': { req: ReversiCancelMatchRequest; res: EmptyResponse }; + 'reversi/games': { req: ReversiGamesRequest; res: ReversiGamesResponse }; + 'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse }; + 'reversi/match': { req: ReversiMatchRequest; res: ReversiMatchResponse }; + 'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse }; + 'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse }; + 'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse }; + 'roles/list': { req: EmptyRequest; res: RolesListResponse }; + 'roles/notes': { req: RolesNotesRequest; res: RolesNotesResponse }; + 'roles/show': { req: RolesShowRequest; res: RolesShowResponse }; + 'roles/users': { req: RolesUsersRequest; res: RolesUsersResponse }; 'server-info': { req: EmptyRequest; res: ServerInfoResponse }; 'stats': { req: EmptyRequest; res: StatsResponse }; - 'sw/show-registration': { req: SwShowRegistrationRequest; res: SwShowRegistrationResponse }; - 'sw/update-registration': { req: SwUpdateRegistrationRequest; res: SwUpdateRegistrationResponse }; 'sw/register': { req: SwRegisterRequest; res: SwRegisterResponse }; + 'sw/show-registration': { req: SwShowRegistrationRequest; res: SwShowRegistrationResponse }; 'sw/unregister': { req: SwUnregisterRequest; res: EmptyResponse }; + 'sw/update-registration': { req: SwUpdateRegistrationRequest; res: SwUpdateRegistrationResponse }; 'test': { req: TestRequest; res: TestResponse }; 'username/available': { req: UsernameAvailableRequest; res: UsernameAvailableResponse }; 'users': { req: UsersRequest; res: UsersResponse }; + 'users/achievements': { req: UsersAchievementsRequest; res: UsersAchievementsResponse }; 'users/clips': { req: UsersClipsRequest; res: UsersClipsResponse }; + 'users/featured-notes': { req: UsersFeaturedNotesRequest; res: UsersFeaturedNotesResponse }; + 'users/flashs': { req: UsersFlashsRequest; res: UsersFlashsResponse }; 'users/followers': { req: UsersFollowersRequest; res: UsersFollowersResponse }; 'users/following': { req: UsersFollowingRequest; res: UsersFollowingResponse }; 'users/gallery/posts': { req: UsersGalleryPostsRequest; res: UsersGalleryPostsResponse }; 'users/get-frequently-replied-users': { req: UsersGetFrequentlyRepliedUsersRequest; res: UsersGetFrequentlyRepliedUsersResponse }; - 'users/featured-notes': { req: UsersFeaturedNotesRequest; res: UsersFeaturedNotesResponse }; 'users/lists/create': { req: UsersListsCreateRequest; res: UsersListsCreateResponse }; + 'users/lists/create-from-public': { req: UsersListsCreateFromPublicRequest; res: UsersListsCreateFromPublicResponse }; 'users/lists/delete': { req: UsersListsDeleteRequest; res: EmptyResponse }; + 'users/lists/favorite': { req: UsersListsFavoriteRequest; res: EmptyResponse }; + 'users/lists/get-memberships': { req: UsersListsGetMembershipsRequest; res: UsersListsGetMembershipsResponse }; 'users/lists/list': { req: UsersListsListRequest; res: UsersListsListResponse }; 'users/lists/pull': { req: UsersListsPullRequest; res: EmptyResponse }; 'users/lists/push': { req: UsersListsPushRequest; res: EmptyResponse }; 'users/lists/show': { req: UsersListsShowRequest; res: UsersListsShowResponse }; - 'users/lists/favorite': { req: UsersListsFavoriteRequest; res: EmptyResponse }; 'users/lists/unfavorite': { req: UsersListsUnfavoriteRequest; res: EmptyResponse }; 'users/lists/update': { req: UsersListsUpdateRequest; res: UsersListsUpdateResponse }; - 'users/lists/create-from-public': { req: UsersListsCreateFromPublicRequest; res: UsersListsCreateFromPublicResponse }; 'users/lists/update-membership': { req: UsersListsUpdateMembershipRequest; res: EmptyResponse }; - 'users/lists/get-memberships': { req: UsersListsGetMembershipsRequest; res: UsersListsGetMembershipsResponse }; 'users/notes': { req: UsersNotesRequest; res: UsersNotesResponse }; 'users/pages': { req: UsersPagesRequest; res: UsersPagesResponse }; - 'users/flashs': { req: UsersFlashsRequest; res: UsersFlashsResponse }; 'users/reactions': { req: UsersReactionsRequest; res: UsersReactionsResponse }; 'users/recommendation': { req: UsersRecommendationRequest; res: UsersRecommendationResponse }; 'users/relation': { req: UsersRelationRequest; res: UsersRelationResponse }; 'users/report-abuse': { req: UsersReportAbuseRequest; res: EmptyResponse }; - 'users/search-by-username-and-host': { req: UsersSearchByUsernameAndHostRequest; res: UsersSearchByUsernameAndHostResponse }; 'users/search': { req: UsersSearchRequest; res: UsersSearchResponse }; + 'users/search-by-username-and-host': { req: UsersSearchByUsernameAndHostRequest; res: UsersSearchByUsernameAndHostResponse }; 'users/show': { req: UsersShowRequest; res: UsersShowResponse }; - 'users/achievements': { req: UsersAchievementsRequest; res: UsersAchievementsResponse }; 'users/update-memo': { req: UsersUpdateMemoRequest; res: EmptyResponse }; - 'fetch-rss': { req: FetchRssRequest; res: FetchRssResponse }; - 'fetch-external-resources': { req: FetchExternalResourcesRequest; res: FetchExternalResourcesResponse }; - 'retention': { req: EmptyRequest; res: RetentionResponse }; - 'bubble-game/register': { req: BubbleGameRegisterRequest; res: EmptyResponse }; - 'bubble-game/ranking': { req: BubbleGameRankingRequest; res: BubbleGameRankingResponse }; - 'reversi/cancel-match': { req: ReversiCancelMatchRequest; res: EmptyResponse }; - 'reversi/games': { req: ReversiGamesRequest; res: ReversiGamesResponse }; - 'reversi/match': { req: ReversiMatchRequest; res: ReversiMatchResponse }; - 'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse }; - 'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse }; - 'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse }; - 'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse }; + 'v2/admin/emoji/list': { req: V2AdminEmojiListRequest; res: V2AdminEmojiListResponse }; } /** diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index e4299d62c7..b7639abca8 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -4,18 +4,17 @@ import { operations } from './types.js'; export type EmptyRequest = Record | undefined; export type EmptyResponse = Record | undefined; -export type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json']; -export type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json']; -export type AdminAbuseUserReportsResponse = operations['admin___abuse-user-reports']['responses']['200']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientCreateRequest = operations['admin___abuse-report___notification-recipient___create']['requestBody']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientCreateResponse = operations['admin___abuse-report___notification-recipient___create']['responses']['200']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientDeleteRequest = operations['admin___abuse-report___notification-recipient___delete']['requestBody']['content']['application/json']; export type AdminAbuseReportNotificationRecipientListRequest = operations['admin___abuse-report___notification-recipient___list']['requestBody']['content']['application/json']; export type AdminAbuseReportNotificationRecipientListResponse = operations['admin___abuse-report___notification-recipient___list']['responses']['200']['content']['application/json']; export type AdminAbuseReportNotificationRecipientShowRequest = operations['admin___abuse-report___notification-recipient___show']['requestBody']['content']['application/json']; export type AdminAbuseReportNotificationRecipientShowResponse = operations['admin___abuse-report___notification-recipient___show']['responses']['200']['content']['application/json']; -export type AdminAbuseReportNotificationRecipientCreateRequest = operations['admin___abuse-report___notification-recipient___create']['requestBody']['content']['application/json']; -export type AdminAbuseReportNotificationRecipientCreateResponse = operations['admin___abuse-report___notification-recipient___create']['responses']['200']['content']['application/json']; export type AdminAbuseReportNotificationRecipientUpdateRequest = operations['admin___abuse-report___notification-recipient___update']['requestBody']['content']['application/json']; export type AdminAbuseReportNotificationRecipientUpdateResponse = operations['admin___abuse-report___notification-recipient___update']['responses']['200']['content']['application/json']; -export type AdminAbuseReportNotificationRecipientDeleteRequest = operations['admin___abuse-report___notification-recipient___delete']['requestBody']['content']['application/json']; +export type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json']; +export type AdminAbuseUserReportsResponse = operations['admin___abuse-user-reports']['responses']['200']['content']['application/json']; export type AdminAccountsCreateRequest = operations['admin___accounts___create']['requestBody']['content']['application/json']; export type AdminAccountsCreateResponse = operations['admin___accounts___create']['responses']['200']['content']['application/json']; export type AdminAccountsDeleteRequest = operations['admin___accounts___delete']['requestBody']['content']['application/json']; @@ -41,36 +40,34 @@ export type AdminAvatarDecorationsListResponse = operations['admin___avatar-deco export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; export type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json']; export type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json']; +export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json']; export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json']; -export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; -export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; export type AdminDriveFilesRequest = operations['admin___drive___files']['requestBody']['content']['application/json']; export type AdminDriveFilesResponse = operations['admin___drive___files']['responses']['200']['content']['application/json']; export type AdminDriveShowFileRequest = operations['admin___drive___show-file']['requestBody']['content']['application/json']; export type AdminDriveShowFileResponse = operations['admin___drive___show-file']['responses']['200']['content']['application/json']; -export type AdminEmojiAddAliasesBulkRequest = operations['admin___emoji___add-aliases-bulk']['requestBody']['content']['application/json']; export type AdminEmojiAddRequest = operations['admin___emoji___add']['requestBody']['content']['application/json']; export type AdminEmojiAddResponse = operations['admin___emoji___add']['responses']['200']['content']['application/json']; +export type AdminEmojiAddAliasesBulkRequest = operations['admin___emoji___add-aliases-bulk']['requestBody']['content']['application/json']; export type AdminEmojiCopyRequest = operations['admin___emoji___copy']['requestBody']['content']['application/json']; export type AdminEmojiCopyResponse = operations['admin___emoji___copy']['responses']['200']['content']['application/json']; -export type AdminEmojiDeleteBulkRequest = operations['admin___emoji___delete-bulk']['requestBody']['content']['application/json']; export type AdminEmojiDeleteRequest = operations['admin___emoji___delete']['requestBody']['content']['application/json']; +export type AdminEmojiDeleteBulkRequest = operations['admin___emoji___delete-bulk']['requestBody']['content']['application/json']; export type AdminEmojiImportZipRequest = operations['admin___emoji___import-zip']['requestBody']['content']['application/json']; -export type AdminEmojiListRemoteRequest = operations['admin___emoji___list-remote']['requestBody']['content']['application/json']; -export type AdminEmojiListRemoteResponse = operations['admin___emoji___list-remote']['responses']['200']['content']['application/json']; export type AdminEmojiListRequest = operations['admin___emoji___list']['requestBody']['content']['application/json']; export type AdminEmojiListResponse = operations['admin___emoji___list']['responses']['200']['content']['application/json']; +export type AdminEmojiListRemoteRequest = operations['admin___emoji___list-remote']['requestBody']['content']['application/json']; +export type AdminEmojiListRemoteResponse = operations['admin___emoji___list-remote']['responses']['200']['content']['application/json']; export type AdminEmojiRemoveAliasesBulkRequest = operations['admin___emoji___remove-aliases-bulk']['requestBody']['content']['application/json']; export type AdminEmojiSetAliasesBulkRequest = operations['admin___emoji___set-aliases-bulk']['requestBody']['content']['application/json']; export type AdminEmojiSetCategoryBulkRequest = operations['admin___emoji___set-category-bulk']['requestBody']['content']['application/json']; export type AdminEmojiSetLicenseBulkRequest = operations['admin___emoji___set-license-bulk']['requestBody']['content']['application/json']; export type AdminEmojiUpdateRequest = operations['admin___emoji___update']['requestBody']['content']['application/json']; -export type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestBody']['content']['application/json']; -export type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; export type AdminFederationDeleteAllFilesRequest = operations['admin___federation___delete-all-files']['requestBody']['content']['application/json']; export type AdminFederationRefreshRemoteInstanceMetadataRequest = operations['admin___federation___refresh-remote-instance-metadata']['requestBody']['content']['application/json']; export type AdminFederationRemoveAllFollowingRequest = operations['admin___federation___remove-all-following']['requestBody']['content']['application/json']; export type AdminFederationUpdateInstanceRequest = operations['admin___federation___update-instance']['requestBody']['content']['application/json']; +export type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json']; export type AdminGetIndexStatsResponse = operations['admin___get-index-stats']['responses']['200']['content']['application/json']; export type AdminGetTableStatsResponse = operations['admin___get-table-stats']['responses']['200']['content']['application/json']; export type AdminGetUserIpsRequest = operations['admin___get-user-ips']['requestBody']['content']['application/json']; @@ -79,6 +76,7 @@ export type AdminInviteCreateRequest = operations['admin___invite___create']['re export type AdminInviteCreateResponse = operations['admin___invite___create']['responses']['200']['content']['application/json']; export type AdminInviteListRequest = operations['admin___invite___list']['requestBody']['content']['application/json']; export type AdminInviteListResponse = operations['admin___invite___list']['responses']['200']['content']['application/json']; +export type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json']; export type AdminPromoCreateRequest = operations['admin___promo___create']['requestBody']['content']['application/json']; export type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json']; export type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json']; @@ -91,33 +89,27 @@ export type AdminRelaysRemoveRequest = operations['admin___relays___remove']['re export type AdminResetPasswordRequest = operations['admin___reset-password']['requestBody']['content']['application/json']; export type AdminResetPasswordResponse = operations['admin___reset-password']['responses']['200']['content']['application/json']; export type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json']; -export type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json']; -export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json']; -export type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json']; -export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json']; -export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json']; -export type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json']; -export type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json']; -export type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json']; -export type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json']; -export type AdminShowUsersResponse = operations['admin___show-users']['responses']['200']['content']['application/json']; -export type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json']; -export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json']; -export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json']; -export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json']; -export type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json']; +export type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json']; export type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json']; export type AdminRolesCreateResponse = operations['admin___roles___create']['responses']['200']['content']['application/json']; export type AdminRolesDeleteRequest = operations['admin___roles___delete']['requestBody']['content']['application/json']; export type AdminRolesListResponse = operations['admin___roles___list']['responses']['200']['content']['application/json']; export type AdminRolesShowRequest = operations['admin___roles___show']['requestBody']['content']['application/json']; export type AdminRolesShowResponse = operations['admin___roles___show']['responses']['200']['content']['application/json']; -export type AdminRolesUpdateRequest = operations['admin___roles___update']['requestBody']['content']['application/json']; -export type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json']; export type AdminRolesUnassignRequest = operations['admin___roles___unassign']['requestBody']['content']['application/json']; +export type AdminRolesUpdateRequest = operations['admin___roles___update']['requestBody']['content']['application/json']; export type AdminRolesUpdateDefaultPoliciesRequest = operations['admin___roles___update-default-policies']['requestBody']['content']['application/json']; export type AdminRolesUsersRequest = operations['admin___roles___users']['requestBody']['content']['application/json']; export type AdminRolesUsersResponse = operations['admin___roles___users']['responses']['200']['content']['application/json']; +export type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json']; +export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json']; +export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json']; +export type AdminShowModerationLogsResponse = operations['admin___show-moderation-logs']['responses']['200']['content']['application/json']; +export type AdminShowUserRequest = operations['admin___show-user']['requestBody']['content']['application/json']; +export type AdminShowUserResponse = operations['admin___show-user']['responses']['200']['content']['application/json']; +export type AdminShowUsersRequest = operations['admin___show-users']['requestBody']['content']['application/json']; +export type AdminShowUsersResponse = operations['admin___show-users']['responses']['200']['content']['application/json']; +export type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json']; export type AdminSystemWebhookCreateRequest = operations['admin___system-webhook___create']['requestBody']['content']['application/json']; export type AdminSystemWebhookCreateResponse = operations['admin___system-webhook___create']['responses']['200']['content']['application/json']; export type AdminSystemWebhookDeleteRequest = operations['admin___system-webhook___delete']['requestBody']['content']['application/json']; @@ -125,9 +117,15 @@ export type AdminSystemWebhookListRequest = operations['admin___system-webhook__ export type AdminSystemWebhookListResponse = operations['admin___system-webhook___list']['responses']['200']['content']['application/json']; export type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show']['requestBody']['content']['application/json']; export type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json']; +export type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json']; export type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json']; export type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json']; -export type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json']; +export type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; +export type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requestBody']['content']['application/json']; +export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json']; +export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json']; +export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json']; +export type AdminUpdateUserNoteRequest = operations['admin___update-user-note']['requestBody']['content']['application/json']; export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json']; @@ -163,26 +161,29 @@ export type BlockingDeleteRequest = operations['blocking___delete']['requestBody export type BlockingDeleteResponse = operations['blocking___delete']['responses']['200']['content']['application/json']; export type BlockingListRequest = operations['blocking___list']['requestBody']['content']['application/json']; export type BlockingListResponse = operations['blocking___list']['responses']['200']['content']['application/json']; +export type BubbleGameRankingRequest = operations['bubble-game___ranking']['requestBody']['content']['application/json']; +export type BubbleGameRankingResponse = operations['bubble-game___ranking']['responses']['200']['content']['application/json']; +export type BubbleGameRegisterRequest = operations['bubble-game___register']['requestBody']['content']['application/json']; export type ChannelsCreateRequest = operations['channels___create']['requestBody']['content']['application/json']; export type ChannelsCreateResponse = operations['channels___create']['responses']['200']['content']['application/json']; +export type ChannelsFavoriteRequest = operations['channels___favorite']['requestBody']['content']['application/json']; export type ChannelsFeaturedResponse = operations['channels___featured']['responses']['200']['content']['application/json']; export type ChannelsFollowRequest = operations['channels___follow']['requestBody']['content']['application/json']; export type ChannelsFollowedRequest = operations['channels___followed']['requestBody']['content']['application/json']; export type ChannelsFollowedResponse = operations['channels___followed']['responses']['200']['content']['application/json']; +export type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json']; export type ChannelsOwnedRequest = operations['channels___owned']['requestBody']['content']['application/json']; export type ChannelsOwnedResponse = operations['channels___owned']['responses']['200']['content']['application/json']; +export type ChannelsSearchRequest = operations['channels___search']['requestBody']['content']['application/json']; +export type ChannelsSearchResponse = operations['channels___search']['responses']['200']['content']['application/json']; export type ChannelsShowRequest = operations['channels___show']['requestBody']['content']['application/json']; export type ChannelsShowResponse = operations['channels___show']['responses']['200']['content']['application/json']; export type ChannelsTimelineRequest = operations['channels___timeline']['requestBody']['content']['application/json']; export type ChannelsTimelineResponse = operations['channels___timeline']['responses']['200']['content']['application/json']; +export type ChannelsUnfavoriteRequest = operations['channels___unfavorite']['requestBody']['content']['application/json']; export type ChannelsUnfollowRequest = operations['channels___unfollow']['requestBody']['content']['application/json']; export type ChannelsUpdateRequest = operations['channels___update']['requestBody']['content']['application/json']; export type ChannelsUpdateResponse = operations['channels___update']['responses']['200']['content']['application/json']; -export type ChannelsFavoriteRequest = operations['channels___favorite']['requestBody']['content']['application/json']; -export type ChannelsUnfavoriteRequest = operations['channels___unfavorite']['requestBody']['content']['application/json']; -export type ChannelsMyFavoritesResponse = operations['channels___my-favorites']['responses']['200']['content']['application/json']; -export type ChannelsSearchRequest = operations['channels___search']['requestBody']['content']['application/json']; -export type ChannelsSearchResponse = operations['channels___search']['responses']['200']['content']['application/json']; export type ChartsActiveUsersRequest = operations['charts___active-users']['requestBody']['content']['application/json']; export type ChartsActiveUsersResponse = operations['charts___active-users']['responses']['200']['content']['application/json']; export type ChartsApRequestRequest = operations['charts___ap-request']['requestBody']['content']['application/json']; @@ -208,20 +209,20 @@ export type ChartsUserReactionsResponse = operations['charts___user___reactions' export type ChartsUsersRequest = operations['charts___users']['requestBody']['content']['application/json']; export type ChartsUsersResponse = operations['charts___users']['responses']['200']['content']['application/json']; export type ClipsAddNoteRequest = operations['clips___add-note']['requestBody']['content']['application/json']; -export type ClipsRemoveNoteRequest = operations['clips___remove-note']['requestBody']['content']['application/json']; export type ClipsCreateRequest = operations['clips___create']['requestBody']['content']['application/json']; export type ClipsCreateResponse = operations['clips___create']['responses']['200']['content']['application/json']; export type ClipsDeleteRequest = operations['clips___delete']['requestBody']['content']['application/json']; +export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; export type ClipsListResponse = operations['clips___list']['responses']['200']['content']['application/json']; +export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; export type ClipsNotesRequest = operations['clips___notes']['requestBody']['content']['application/json']; export type ClipsNotesResponse = operations['clips___notes']['responses']['200']['content']['application/json']; +export type ClipsRemoveNoteRequest = operations['clips___remove-note']['requestBody']['content']['application/json']; export type ClipsShowRequest = operations['clips___show']['requestBody']['content']['application/json']; export type ClipsShowResponse = operations['clips___show']['responses']['200']['content']['application/json']; +export type ClipsUnfavoriteRequest = operations['clips___unfavorite']['requestBody']['content']['application/json']; export type ClipsUpdateRequest = operations['clips___update']['requestBody']['content']['application/json']; export type ClipsUpdateResponse = operations['clips___update']['responses']['200']['content']['application/json']; -export type ClipsFavoriteRequest = operations['clips___favorite']['requestBody']['content']['application/json']; -export type ClipsUnfavoriteRequest = operations['clips___unfavorite']['requestBody']['content']['application/json']; -export type ClipsMyFavoritesResponse = operations['clips___my-favorites']['responses']['200']['content']['application/json']; export type DriveResponse = operations['drive']['responses']['200']['content']['application/json']; export type DriveFilesRequest = operations['drive___files']['requestBody']['content']['application/json']; export type DriveFilesResponse = operations['drive___files']['responses']['200']['content']['application/json']; @@ -232,10 +233,10 @@ export type DriveFilesCheckExistenceResponse = operations['drive___files___check export type DriveFilesCreateRequest = operations['drive___files___create']['requestBody']['content']['multipart/form-data']; export type DriveFilesCreateResponse = operations['drive___files___create']['responses']['200']['content']['application/json']; export type DriveFilesDeleteRequest = operations['drive___files___delete']['requestBody']['content']['application/json']; -export type DriveFilesFindByHashRequest = operations['drive___files___find-by-hash']['requestBody']['content']['application/json']; -export type DriveFilesFindByHashResponse = operations['drive___files___find-by-hash']['responses']['200']['content']['application/json']; export type DriveFilesFindRequest = operations['drive___files___find']['requestBody']['content']['application/json']; export type DriveFilesFindResponse = operations['drive___files___find']['responses']['200']['content']['application/json']; +export type DriveFilesFindByHashRequest = operations['drive___files___find-by-hash']['requestBody']['content']['application/json']; +export type DriveFilesFindByHashResponse = operations['drive___files___find-by-hash']['responses']['200']['content']['application/json']; export type DriveFilesShowRequest = operations['drive___files___show']['requestBody']['content']['application/json']; export type DriveFilesShowResponse = operations['drive___files___show']['responses']['200']['content']['application/json']; export type DriveFilesUpdateRequest = operations['drive___files___update']['requestBody']['content']['application/json']; @@ -256,6 +257,9 @@ export type DriveStreamRequest = operations['drive___stream']['requestBody']['co export type DriveStreamResponse = operations['drive___stream']['responses']['200']['content']['application/json']; export type EmailAddressAvailableRequest = operations['email-address___available']['requestBody']['content']['application/json']; export type EmailAddressAvailableResponse = operations['email-address___available']['responses']['200']['content']['application/json']; +export type EmojiRequest = operations['emoji']['requestBody']['content']['application/json']; +export type EmojiResponse = operations['emoji']['responses']['200']['content']['application/json']; +export type EmojisResponse = operations['emojis']['responses']['200']['content']['application/json']; export type EndpointRequest = operations['endpoint']['requestBody']['content']['application/json']; export type EndpointResponse = operations['endpoint']['responses']['200']['content']['application/json']; export type EndpointsResponse = operations['endpoints']['responses']['200']['content']['application/json']; @@ -267,18 +271,33 @@ export type FederationInstancesRequest = operations['federation___instances']['r export type FederationInstancesResponse = operations['federation___instances']['responses']['200']['content']['application/json']; export type FederationShowInstanceRequest = operations['federation___show-instance']['requestBody']['content']['application/json']; export type FederationShowInstanceResponse = operations['federation___show-instance']['responses']['200']['content']['application/json']; +export type FederationStatsRequest = operations['federation___stats']['requestBody']['content']['application/json']; +export type FederationStatsResponse = operations['federation___stats']['responses']['200']['content']['application/json']; export type FederationUpdateRemoteUserRequest = operations['federation___update-remote-user']['requestBody']['content']['application/json']; export type FederationUsersRequest = operations['federation___users']['requestBody']['content']['application/json']; export type FederationUsersResponse = operations['federation___users']['responses']['200']['content']['application/json']; -export type FederationStatsRequest = operations['federation___stats']['requestBody']['content']['application/json']; -export type FederationStatsResponse = operations['federation___stats']['responses']['200']['content']['application/json']; +export type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json']; +export type FetchExternalResourcesResponse = operations['fetch-external-resources']['responses']['200']['content']['application/json']; +export type FetchRssRequest = operations['fetch-rss']['requestBody']['content']['application/json']; +export type FetchRssResponse = operations['fetch-rss']['responses']['200']['content']['application/json']; +export type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json']; +export type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json']; +export type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json']; +export type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json']; +export type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json']; +export type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json']; +export type FlashMyRequest = operations['flash___my']['requestBody']['content']['application/json']; +export type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json']; +export type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json']; +export type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json']; +export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json']; +export type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json']; +export type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json']; +export type FlashUpdateRequest = operations['flash___update']['requestBody']['content']['application/json']; export type FollowingCreateRequest = operations['following___create']['requestBody']['content']['application/json']; export type FollowingCreateResponse = operations['following___create']['responses']['200']['content']['application/json']; export type FollowingDeleteRequest = operations['following___delete']['requestBody']['content']['application/json']; export type FollowingDeleteResponse = operations['following___delete']['responses']['200']['content']['application/json']; -export type FollowingUpdateRequest = operations['following___update']['requestBody']['content']['application/json']; -export type FollowingUpdateResponse = operations['following___update']['responses']['200']['content']['application/json']; -export type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json']; export type FollowingInvalidateRequest = operations['following___invalidate']['requestBody']['content']['application/json']; export type FollowingInvalidateResponse = operations['following___invalidate']['responses']['200']['content']['application/json']; export type FollowingRequestsAcceptRequest = operations['following___requests___accept']['requestBody']['content']['application/json']; @@ -286,9 +305,12 @@ export type FollowingRequestsCancelRequest = operations['following___requests___ export type FollowingRequestsCancelResponse = operations['following___requests___cancel']['responses']['200']['content']['application/json']; export type FollowingRequestsListRequest = operations['following___requests___list']['requestBody']['content']['application/json']; export type FollowingRequestsListResponse = operations['following___requests___list']['responses']['200']['content']['application/json']; +export type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json']; export type FollowingRequestsSentRequest = operations['following___requests___sent']['requestBody']['content']['application/json']; export type FollowingRequestsSentResponse = operations['following___requests___sent']['responses']['200']['content']['application/json']; -export type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json']; +export type FollowingUpdateRequest = operations['following___update']['requestBody']['content']['application/json']; +export type FollowingUpdateResponse = operations['following___update']['responses']['200']['content']['application/json']; +export type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json']; export type GalleryFeaturedRequest = operations['gallery___featured']['requestBody']['content']['application/json']; export type GalleryFeaturedResponse = operations['gallery___featured']['responses']['200']['content']['application/json']; export type GalleryPopularResponse = operations['gallery___popular']['responses']['200']['content']['application/json']; @@ -303,8 +325,8 @@ export type GalleryPostsShowResponse = operations['gallery___posts___show']['res export type GalleryPostsUnlikeRequest = operations['gallery___posts___unlike']['requestBody']['content']['application/json']; export type GalleryPostsUpdateRequest = operations['gallery___posts___update']['requestBody']['content']['application/json']; export type GalleryPostsUpdateResponse = operations['gallery___posts___update']['responses']['200']['content']['application/json']; -export type GetOnlineUsersCountResponse = operations['get-online-users-count']['responses']['200']['content']['application/json']; export type GetAvatarDecorationsResponse = operations['get-avatar-decorations']['responses']['200']['content']['application/json']; +export type GetOnlineUsersCountResponse = operations['get-online-users-count']['responses']['200']['content']['application/json']; export type HashtagsListRequest = operations['hashtags___list']['requestBody']['content']['application/json']; export type HashtagsListResponse = operations['hashtags___list']['responses']['200']['content']['application/json']; export type HashtagsSearchRequest = operations['hashtags___search']['requestBody']['content']['application/json']; @@ -320,19 +342,19 @@ export type I2faDoneResponse = operations['i___2fa___done']['responses']['200'][ export type I2faKeyDoneRequest = operations['i___2fa___key-done']['requestBody']['content']['application/json']; export type I2faKeyDoneResponse = operations['i___2fa___key-done']['responses']['200']['content']['application/json']; export type I2faPasswordLessRequest = operations['i___2fa___password-less']['requestBody']['content']['application/json']; -export type I2faRegisterKeyRequest = operations['i___2fa___register-key']['requestBody']['content']['application/json']; -export type I2faRegisterKeyResponse = operations['i___2fa___register-key']['responses']['200']['content']['application/json']; export type I2faRegisterRequest = operations['i___2fa___register']['requestBody']['content']['application/json']; export type I2faRegisterResponse = operations['i___2fa___register']['responses']['200']['content']['application/json']; -export type I2faUpdateKeyRequest = operations['i___2fa___update-key']['requestBody']['content']['application/json']; +export type I2faRegisterKeyRequest = operations['i___2fa___register-key']['requestBody']['content']['application/json']; +export type I2faRegisterKeyResponse = operations['i___2fa___register-key']['responses']['200']['content']['application/json']; export type I2faRemoveKeyRequest = operations['i___2fa___remove-key']['requestBody']['content']['application/json']; export type I2faUnregisterRequest = operations['i___2fa___unregister']['requestBody']['content']['application/json']; +export type I2faUpdateKeyRequest = operations['i___2fa___update-key']['requestBody']['content']['application/json']; export type IAppsRequest = operations['i___apps']['requestBody']['content']['application/json']; export type IAppsResponse = operations['i___apps']['responses']['200']['content']['application/json']; export type IAuthorizedAppsRequest = operations['i___authorized-apps']['requestBody']['content']['application/json']; export type IAuthorizedAppsResponse = operations['i___authorized-apps']['responses']['200']['content']['application/json']; -export type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json']; export type IChangePasswordRequest = operations['i___change-password']['requestBody']['content']['application/json']; +export type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json']; export type IDeleteAccountRequest = operations['i___delete-account']['requestBody']['content']['application/json']; export type IExportFollowingRequest = operations['i___export-following']['requestBody']['content']['application/json']; export type IFavoritesRequest = operations['i___favorites']['requestBody']['content']['application/json']; @@ -341,11 +363,13 @@ export type IGalleryLikesRequest = operations['i___gallery___likes']['requestBod export type IGalleryLikesResponse = operations['i___gallery___likes']['responses']['200']['content']['application/json']; export type IGalleryPostsRequest = operations['i___gallery___posts']['requestBody']['content']['application/json']; export type IGalleryPostsResponse = operations['i___gallery___posts']['responses']['200']['content']['application/json']; +export type IImportAntennasRequest = operations['i___import-antennas']['requestBody']['content']['application/json']; export type IImportBlockingRequest = operations['i___import-blocking']['requestBody']['content']['application/json']; export type IImportFollowingRequest = operations['i___import-following']['requestBody']['content']['application/json']; export type IImportMutingRequest = operations['i___import-muting']['requestBody']['content']['application/json']; export type IImportUserListsRequest = operations['i___import-user-lists']['requestBody']['content']['application/json']; -export type IImportAntennasRequest = operations['i___import-antennas']['requestBody']['content']['application/json']; +export type IMoveRequest = operations['i___move']['requestBody']['content']['application/json']; +export type IMoveResponse = operations['i___move']['responses']['200']['content']['application/json']; export type INotificationsRequest = operations['i___notifications']['requestBody']['content']['application/json']; export type INotificationsResponse = operations['i___notifications']['responses']['200']['content']['application/json']; export type INotificationsGroupedRequest = operations['i___notifications-grouped']['requestBody']['content']['application/json']; @@ -358,16 +382,16 @@ export type IPinRequest = operations['i___pin']['requestBody']['content']['appli export type IPinResponse = operations['i___pin']['responses']['200']['content']['application/json']; export type IReadAnnouncementRequest = operations['i___read-announcement']['requestBody']['content']['application/json']; export type IRegenerateTokenRequest = operations['i___regenerate-token']['requestBody']['content']['application/json']; +export type IRegistryGetRequest = operations['i___registry___get']['requestBody']['content']['application/json']; +export type IRegistryGetResponse = operations['i___registry___get']['responses']['200']['content']['application/json']; export type IRegistryGetAllRequest = operations['i___registry___get-all']['requestBody']['content']['application/json']; export type IRegistryGetAllResponse = operations['i___registry___get-all']['responses']['200']['content']['application/json']; export type IRegistryGetDetailRequest = operations['i___registry___get-detail']['requestBody']['content']['application/json']; export type IRegistryGetDetailResponse = operations['i___registry___get-detail']['responses']['200']['content']['application/json']; -export type IRegistryGetRequest = operations['i___registry___get']['requestBody']['content']['application/json']; -export type IRegistryGetResponse = operations['i___registry___get']['responses']['200']['content']['application/json']; -export type IRegistryKeysWithTypeRequest = operations['i___registry___keys-with-type']['requestBody']['content']['application/json']; -export type IRegistryKeysWithTypeResponse = operations['i___registry___keys-with-type']['responses']['200']['content']['application/json']; export type IRegistryKeysRequest = operations['i___registry___keys']['requestBody']['content']['application/json']; export type IRegistryKeysResponse = operations['i___registry___keys']['responses']['200']['content']['application/json']; +export type IRegistryKeysWithTypeRequest = operations['i___registry___keys-with-type']['requestBody']['content']['application/json']; +export type IRegistryKeysWithTypeResponse = operations['i___registry___keys-with-type']['responses']['200']['content']['application/json']; export type IRegistryRemoveRequest = operations['i___registry___remove']['requestBody']['content']['application/json']; export type IRegistryScopesWithDomainResponse = operations['i___registry___scopes-with-domain']['responses']['200']['content']['application/json']; export type IRegistrySetRequest = operations['i___registry___set']['requestBody']['content']['application/json']; @@ -376,40 +400,31 @@ export type ISigninHistoryRequest = operations['i___signin-history']['requestBod export type ISigninHistoryResponse = operations['i___signin-history']['responses']['200']['content']['application/json']; export type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json']; export type IUnpinResponse = operations['i___unpin']['responses']['200']['content']['application/json']; -export type IUpdateEmailRequest = operations['i___update-email']['requestBody']['content']['application/json']; -export type IUpdateEmailResponse = operations['i___update-email']['responses']['200']['content']['application/json']; export type IUpdateRequest = operations['i___update']['requestBody']['content']['application/json']; export type IUpdateResponse = operations['i___update']['responses']['200']['content']['application/json']; -export type IMoveRequest = operations['i___move']['requestBody']['content']['application/json']; -export type IMoveResponse = operations['i___move']['responses']['200']['content']['application/json']; +export type IUpdateEmailRequest = operations['i___update-email']['requestBody']['content']['application/json']; +export type IUpdateEmailResponse = operations['i___update-email']['responses']['200']['content']['application/json']; export type IWebhooksCreateRequest = operations['i___webhooks___create']['requestBody']['content']['application/json']; export type IWebhooksCreateResponse = operations['i___webhooks___create']['responses']['200']['content']['application/json']; +export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json']; export type IWebhooksListResponse = operations['i___webhooks___list']['responses']['200']['content']['application/json']; export type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['content']['application/json']; export type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json']; -export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json']; -export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json']; export type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json']; +export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json']; export type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json']; export type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json']; +export type InviteLimitResponse = operations['invite___limit']['responses']['200']['content']['application/json']; export type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json']; export type InviteListResponse = operations['invite___list']['responses']['200']['content']['application/json']; -export type InviteLimitResponse = operations['invite___limit']['responses']['200']['content']['application/json']; export type MetaRequest = operations['meta']['requestBody']['content']['application/json']; export type MetaResponse = operations['meta']['responses']['200']['content']['application/json']; -export type EmojisResponse = operations['emojis']['responses']['200']['content']['application/json']; -export type EmojiRequest = operations['emoji']['requestBody']['content']['application/json']; -export type EmojiResponse = operations['emoji']['responses']['200']['content']['application/json']; export type MiauthGenTokenRequest = operations['miauth___gen-token']['requestBody']['content']['application/json']; export type MiauthGenTokenResponse = operations['miauth___gen-token']['responses']['200']['content']['application/json']; export type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json']; export type MuteDeleteRequest = operations['mute___delete']['requestBody']['content']['application/json']; export type MuteListRequest = operations['mute___list']['requestBody']['content']['application/json']; export type MuteListResponse = operations['mute___list']['responses']['200']['content']['application/json']; -export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json']; -export type RenoteMuteDeleteRequest = operations['renote-mute___delete']['requestBody']['content']['application/json']; -export type RenoteMuteListRequest = operations['renote-mute___list']['requestBody']['content']['application/json']; -export type RenoteMuteListResponse = operations['renote-mute___list']['responses']['200']['content']['application/json']; export type MyAppsRequest = operations['my___apps']['requestBody']['content']['application/json']; export type MyAppsResponse = operations['my___apps']['responses']['200']['content']['application/json']; export type NotesRequest = operations['notes']['requestBody']['content']['application/json']; @@ -446,10 +461,10 @@ export type NotesRenotesRequest = operations['notes___renotes']['requestBody'][' export type NotesRenotesResponse = operations['notes___renotes']['responses']['200']['content']['application/json']; export type NotesRepliesRequest = operations['notes___replies']['requestBody']['content']['application/json']; export type NotesRepliesResponse = operations['notes___replies']['responses']['200']['content']['application/json']; -export type NotesSearchByTagRequest = operations['notes___search-by-tag']['requestBody']['content']['application/json']; -export type NotesSearchByTagResponse = operations['notes___search-by-tag']['responses']['200']['content']['application/json']; export type NotesSearchRequest = operations['notes___search']['requestBody']['content']['application/json']; export type NotesSearchResponse = operations['notes___search']['responses']['200']['content']['application/json']; +export type NotesSearchByTagRequest = operations['notes___search-by-tag']['requestBody']['content']['application/json']; +export type NotesSearchByTagResponse = operations['notes___search-by-tag']['responses']['200']['content']['application/json']; export type NotesShowRequest = operations['notes___show']['requestBody']['content']['application/json']; export type NotesShowResponse = operations['notes___show']['responses']['200']['content']['application/json']; export type NotesStateRequest = operations['notes___state']['requestBody']['content']['application/json']; @@ -474,49 +489,57 @@ export type PagesShowRequest = operations['pages___show']['requestBody']['conten export type PagesShowResponse = operations['pages___show']['responses']['200']['content']['application/json']; export type PagesUnlikeRequest = operations['pages___unlike']['requestBody']['content']['application/json']; export type PagesUpdateRequest = operations['pages___update']['requestBody']['content']['application/json']; -export type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json']; -export type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json']; -export type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json']; -export type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json']; -export type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json']; -export type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json']; -export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json']; -export type FlashShowResponse = operations['flash___show']['responses']['200']['content']['application/json']; -export type FlashUnlikeRequest = operations['flash___unlike']['requestBody']['content']['application/json']; -export type FlashUpdateRequest = operations['flash___update']['requestBody']['content']['application/json']; -export type FlashMyRequest = operations['flash___my']['requestBody']['content']['application/json']; -export type FlashMyResponse = operations['flash___my']['responses']['200']['content']['application/json']; -export type FlashMyLikesRequest = operations['flash___my-likes']['requestBody']['content']['application/json']; -export type FlashMyLikesResponse = operations['flash___my-likes']['responses']['200']['content']['application/json']; export type PingResponse = operations['ping']['responses']['200']['content']['application/json']; export type PinnedUsersResponse = operations['pinned-users']['responses']['200']['content']['application/json']; export type PromoReadRequest = operations['promo___read']['requestBody']['content']['application/json']; +export type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json']; +export type RenoteMuteDeleteRequest = operations['renote-mute___delete']['requestBody']['content']['application/json']; +export type RenoteMuteListRequest = operations['renote-mute___list']['requestBody']['content']['application/json']; +export type RenoteMuteListResponse = operations['renote-mute___list']['responses']['200']['content']['application/json']; +export type RequestResetPasswordRequest = operations['request-reset-password']['requestBody']['content']['application/json']; +export type ResetPasswordRequest = operations['reset-password']['requestBody']['content']['application/json']; +export type RetentionResponse = operations['retention']['responses']['200']['content']['application/json']; +export type ReversiCancelMatchRequest = operations['reversi___cancel-match']['requestBody']['content']['application/json']; +export type ReversiGamesRequest = operations['reversi___games']['requestBody']['content']['application/json']; +export type ReversiGamesResponse = operations['reversi___games']['responses']['200']['content']['application/json']; +export type ReversiInvitationsResponse = operations['reversi___invitations']['responses']['200']['content']['application/json']; +export type ReversiMatchRequest = operations['reversi___match']['requestBody']['content']['application/json']; +export type ReversiMatchResponse = operations['reversi___match']['responses']['200']['content']['application/json']; +export type ReversiShowGameRequest = operations['reversi___show-game']['requestBody']['content']['application/json']; +export type ReversiShowGameResponse = operations['reversi___show-game']['responses']['200']['content']['application/json']; +export type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json']; +export type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json']; +export type ReversiVerifyResponse = operations['reversi___verify']['responses']['200']['content']['application/json']; export type RolesListResponse = operations['roles___list']['responses']['200']['content']['application/json']; +export type RolesNotesRequest = operations['roles___notes']['requestBody']['content']['application/json']; +export type RolesNotesResponse = operations['roles___notes']['responses']['200']['content']['application/json']; export type RolesShowRequest = operations['roles___show']['requestBody']['content']['application/json']; export type RolesShowResponse = operations['roles___show']['responses']['200']['content']['application/json']; export type RolesUsersRequest = operations['roles___users']['requestBody']['content']['application/json']; export type RolesUsersResponse = operations['roles___users']['responses']['200']['content']['application/json']; -export type RolesNotesRequest = operations['roles___notes']['requestBody']['content']['application/json']; -export type RolesNotesResponse = operations['roles___notes']['responses']['200']['content']['application/json']; -export type RequestResetPasswordRequest = operations['request-reset-password']['requestBody']['content']['application/json']; -export type ResetPasswordRequest = operations['reset-password']['requestBody']['content']['application/json']; export type ServerInfoResponse = operations['server-info']['responses']['200']['content']['application/json']; export type StatsResponse = operations['stats']['responses']['200']['content']['application/json']; +export type SwRegisterRequest = operations['sw___register']['requestBody']['content']['application/json']; +export type SwRegisterResponse = operations['sw___register']['responses']['200']['content']['application/json']; export type SwShowRegistrationRequest = operations['sw___show-registration']['requestBody']['content']['application/json']; export type SwShowRegistrationResponse = operations['sw___show-registration']['responses']['200']['content']['application/json']; +export type SwUnregisterRequest = operations['sw___unregister']['requestBody']['content']['application/json']; export type SwUpdateRegistrationRequest = operations['sw___update-registration']['requestBody']['content']['application/json']; export type SwUpdateRegistrationResponse = operations['sw___update-registration']['responses']['200']['content']['application/json']; -export type SwRegisterRequest = operations['sw___register']['requestBody']['content']['application/json']; -export type SwRegisterResponse = operations['sw___register']['responses']['200']['content']['application/json']; -export type SwUnregisterRequest = operations['sw___unregister']['requestBody']['content']['application/json']; export type TestRequest = operations['test']['requestBody']['content']['application/json']; export type TestResponse = operations['test']['responses']['200']['content']['application/json']; export type UsernameAvailableRequest = operations['username___available']['requestBody']['content']['application/json']; export type UsernameAvailableResponse = operations['username___available']['responses']['200']['content']['application/json']; export type UsersRequest = operations['users']['requestBody']['content']['application/json']; export type UsersResponse = operations['users']['responses']['200']['content']['application/json']; +export type UsersAchievementsRequest = operations['users___achievements']['requestBody']['content']['application/json']; +export type UsersAchievementsResponse = operations['users___achievements']['responses']['200']['content']['application/json']; export type UsersClipsRequest = operations['users___clips']['requestBody']['content']['application/json']; export type UsersClipsResponse = operations['users___clips']['responses']['200']['content']['application/json']; +export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json']; +export type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json']; +export type UsersFlashsRequest = operations['users___flashs']['requestBody']['content']['application/json']; +export type UsersFlashsResponse = operations['users___flashs']['responses']['200']['content']['application/json']; export type UsersFollowersRequest = operations['users___followers']['requestBody']['content']['application/json']; export type UsersFollowersResponse = operations['users___followers']['responses']['200']['content']['application/json']; export type UsersFollowingRequest = operations['users___following']['requestBody']['content']['application/json']; @@ -525,32 +548,28 @@ export type UsersGalleryPostsRequest = operations['users___gallery___posts']['re export type UsersGalleryPostsResponse = operations['users___gallery___posts']['responses']['200']['content']['application/json']; export type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json']; export type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json']; -export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json']; -export type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json']; export type UsersListsCreateRequest = operations['users___lists___create']['requestBody']['content']['application/json']; export type UsersListsCreateResponse = operations['users___lists___create']['responses']['200']['content']['application/json']; +export type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json']; +export type UsersListsCreateFromPublicResponse = operations['users___lists___create-from-public']['responses']['200']['content']['application/json']; export type UsersListsDeleteRequest = operations['users___lists___delete']['requestBody']['content']['application/json']; +export type UsersListsFavoriteRequest = operations['users___lists___favorite']['requestBody']['content']['application/json']; +export type UsersListsGetMembershipsRequest = operations['users___lists___get-memberships']['requestBody']['content']['application/json']; +export type UsersListsGetMembershipsResponse = operations['users___lists___get-memberships']['responses']['200']['content']['application/json']; export type UsersListsListRequest = operations['users___lists___list']['requestBody']['content']['application/json']; export type UsersListsListResponse = operations['users___lists___list']['responses']['200']['content']['application/json']; export type UsersListsPullRequest = operations['users___lists___pull']['requestBody']['content']['application/json']; export type UsersListsPushRequest = operations['users___lists___push']['requestBody']['content']['application/json']; export type UsersListsShowRequest = operations['users___lists___show']['requestBody']['content']['application/json']; export type UsersListsShowResponse = operations['users___lists___show']['responses']['200']['content']['application/json']; -export type UsersListsFavoriteRequest = operations['users___lists___favorite']['requestBody']['content']['application/json']; export type UsersListsUnfavoriteRequest = operations['users___lists___unfavorite']['requestBody']['content']['application/json']; export type UsersListsUpdateRequest = operations['users___lists___update']['requestBody']['content']['application/json']; export type UsersListsUpdateResponse = operations['users___lists___update']['responses']['200']['content']['application/json']; -export type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json']; -export type UsersListsCreateFromPublicResponse = operations['users___lists___create-from-public']['responses']['200']['content']['application/json']; export type UsersListsUpdateMembershipRequest = operations['users___lists___update-membership']['requestBody']['content']['application/json']; -export type UsersListsGetMembershipsRequest = operations['users___lists___get-memberships']['requestBody']['content']['application/json']; -export type UsersListsGetMembershipsResponse = operations['users___lists___get-memberships']['responses']['200']['content']['application/json']; export type UsersNotesRequest = operations['users___notes']['requestBody']['content']['application/json']; export type UsersNotesResponse = operations['users___notes']['responses']['200']['content']['application/json']; export type UsersPagesRequest = operations['users___pages']['requestBody']['content']['application/json']; export type UsersPagesResponse = operations['users___pages']['responses']['200']['content']['application/json']; -export type UsersFlashsRequest = operations['users___flashs']['requestBody']['content']['application/json']; -export type UsersFlashsResponse = operations['users___flashs']['responses']['200']['content']['application/json']; export type UsersReactionsRequest = operations['users___reactions']['requestBody']['content']['application/json']; export type UsersReactionsResponse = operations['users___reactions']['responses']['200']['content']['application/json']; export type UsersRecommendationRequest = operations['users___recommendation']['requestBody']['content']['application/json']; @@ -558,31 +577,12 @@ export type UsersRecommendationResponse = operations['users___recommendation'][' export type UsersRelationRequest = operations['users___relation']['requestBody']['content']['application/json']; export type UsersRelationResponse = operations['users___relation']['responses']['200']['content']['application/json']; export type UsersReportAbuseRequest = operations['users___report-abuse']['requestBody']['content']['application/json']; -export type UsersSearchByUsernameAndHostRequest = operations['users___search-by-username-and-host']['requestBody']['content']['application/json']; -export type UsersSearchByUsernameAndHostResponse = operations['users___search-by-username-and-host']['responses']['200']['content']['application/json']; export type UsersSearchRequest = operations['users___search']['requestBody']['content']['application/json']; export type UsersSearchResponse = operations['users___search']['responses']['200']['content']['application/json']; +export type UsersSearchByUsernameAndHostRequest = operations['users___search-by-username-and-host']['requestBody']['content']['application/json']; +export type UsersSearchByUsernameAndHostResponse = operations['users___search-by-username-and-host']['responses']['200']['content']['application/json']; export type UsersShowRequest = operations['users___show']['requestBody']['content']['application/json']; export type UsersShowResponse = operations['users___show']['responses']['200']['content']['application/json']; -export type UsersAchievementsRequest = operations['users___achievements']['requestBody']['content']['application/json']; -export type UsersAchievementsResponse = operations['users___achievements']['responses']['200']['content']['application/json']; export type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['content']['application/json']; -export type FetchRssRequest = operations['fetch-rss']['requestBody']['content']['application/json']; -export type FetchRssResponse = operations['fetch-rss']['responses']['200']['content']['application/json']; -export type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json']; -export type FetchExternalResourcesResponse = operations['fetch-external-resources']['responses']['200']['content']['application/json']; -export type RetentionResponse = operations['retention']['responses']['200']['content']['application/json']; -export type BubbleGameRegisterRequest = operations['bubble-game___register']['requestBody']['content']['application/json']; -export type BubbleGameRankingRequest = operations['bubble-game___ranking']['requestBody']['content']['application/json']; -export type BubbleGameRankingResponse = operations['bubble-game___ranking']['responses']['200']['content']['application/json']; -export type ReversiCancelMatchRequest = operations['reversi___cancel-match']['requestBody']['content']['application/json']; -export type ReversiGamesRequest = operations['reversi___games']['requestBody']['content']['application/json']; -export type ReversiGamesResponse = operations['reversi___games']['responses']['200']['content']['application/json']; -export type ReversiMatchRequest = operations['reversi___match']['requestBody']['content']['application/json']; -export type ReversiMatchResponse = operations['reversi___match']['responses']['200']['content']['application/json']; -export type ReversiInvitationsResponse = operations['reversi___invitations']['responses']['200']['content']['application/json']; -export type ReversiShowGameRequest = operations['reversi___show-game']['requestBody']['content']['application/json']; -export type ReversiShowGameResponse = operations['reversi___show-game']['responses']['200']['content']['application/json']; -export type ReversiSurrenderRequest = operations['reversi___surrender']['requestBody']['content']['application/json']; -export type ReversiVerifyRequest = operations['reversi___verify']['requestBody']['content']['application/json']; -export type ReversiVerifyResponse = operations['reversi___verify']['responses']['200']['content']['application/json']; +export type V2AdminEmojiListRequest = operations['v2___admin___emoji___list']['requestBody']['content']['application/json']; +export type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 75a99263d0..e42a163288 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -12,23 +12,25 @@ type XOR = (T | U) extends object ? (Without & U) | (Without & type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; export type paths = { - '/admin/meta': { + '/admin/abuse-report/notification-recipient/create': { /** - * admin/meta + * admin/abuse-report/notification-recipient/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ - post: operations['admin___meta']; + post: operations['admin___abuse-report___notification-recipient___create']; }; - '/admin/abuse-user-reports': { + '/admin/abuse-report/notification-recipient/delete': { /** - * admin/abuse-user-reports + * admin/abuse-report/notification-recipient/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ - post: operations['admin___abuse-user-reports']; + post: operations['admin___abuse-report___notification-recipient___delete']; }; '/admin/abuse-report/notification-recipient/list': { /** @@ -50,16 +52,6 @@ export type paths = { */ post: operations['admin___abuse-report___notification-recipient___show']; }; - '/admin/abuse-report/notification-recipient/create': { - /** - * admin/abuse-report/notification-recipient/create - * @description No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* - */ - post: operations['admin___abuse-report___notification-recipient___create']; - }; '/admin/abuse-report/notification-recipient/update': { /** * admin/abuse-report/notification-recipient/update @@ -70,15 +62,14 @@ export type paths = { */ post: operations['admin___abuse-report___notification-recipient___update']; }; - '/admin/abuse-report/notification-recipient/delete': { + '/admin/abuse-user-reports': { /** - * admin/abuse-report/notification-recipient/delete + * admin/abuse-user-reports * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* + * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* */ - post: operations['admin___abuse-report___notification-recipient___delete']; + post: operations['admin___abuse-user-reports']; }; '/admin/accounts/create': { /** @@ -233,32 +224,23 @@ export type paths = { */ post: operations['admin___captcha___save']; }; - '/admin/delete-all-files-of-a-user': { - /** - * admin/delete-all-files-of-a-user - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* - */ - post: operations['admin___delete-all-files-of-a-user']; - }; - '/admin/unset-user-avatar': { + '/admin/delete-account': { /** - * admin/unset-user-avatar + * admin/delete-account * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* + * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* */ - post: operations['admin___unset-user-avatar']; + post: operations['admin___delete-account']; }; - '/admin/unset-user-banner': { + '/admin/delete-all-files-of-a-user': { /** - * admin/unset-user-banner + * admin/delete-all-files-of-a-user * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* + * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* */ - post: operations['admin___unset-user-banner']; + post: operations['admin___delete-all-files-of-a-user']; }; '/admin/drive/clean-remote-files': { /** @@ -296,23 +278,23 @@ export type paths = { */ post: operations['admin___drive___show-file']; }; - '/admin/emoji/add-aliases-bulk': { + '/admin/emoji/add': { /** - * admin/emoji/add-aliases-bulk + * admin/emoji/add * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin___emoji___add-aliases-bulk']; + post: operations['admin___emoji___add']; }; - '/admin/emoji/add': { + '/admin/emoji/add-aliases-bulk': { /** - * admin/emoji/add + * admin/emoji/add-aliases-bulk * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin___emoji___add']; + post: operations['admin___emoji___add-aliases-bulk']; }; '/admin/emoji/copy': { /** @@ -323,23 +305,23 @@ export type paths = { */ post: operations['admin___emoji___copy']; }; - '/admin/emoji/delete-bulk': { + '/admin/emoji/delete': { /** - * admin/emoji/delete-bulk + * admin/emoji/delete * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin___emoji___delete-bulk']; + post: operations['admin___emoji___delete']; }; - '/admin/emoji/delete': { + '/admin/emoji/delete-bulk': { /** - * admin/emoji/delete + * admin/emoji/delete-bulk * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - post: operations['admin___emoji___delete']; + post: operations['admin___emoji___delete-bulk']; }; '/admin/emoji/import-zip': { /** @@ -351,23 +333,23 @@ export type paths = { */ post: operations['admin___emoji___import-zip']; }; - '/admin/emoji/list-remote': { + '/admin/emoji/list': { /** - * admin/emoji/list-remote + * admin/emoji/list * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - post: operations['admin___emoji___list-remote']; + post: operations['admin___emoji___list']; }; - '/admin/emoji/list': { + '/admin/emoji/list-remote': { /** - * admin/emoji/list + * admin/emoji/list-remote * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - post: operations['admin___emoji___list']; + post: operations['admin___emoji___list-remote']; }; '/admin/emoji/remove-aliases-bulk': { /** @@ -414,15 +396,6 @@ export type paths = { */ post: operations['admin___emoji___update']; }; - '/v2/admin/emoji/list': { - /** - * v2/admin/emoji/list - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* - */ - post: operations['v2___admin___emoji___list']; - }; '/admin/federation/delete-all-files': { /** * admin/federation/delete-all-files @@ -459,6 +432,15 @@ export type paths = { */ post: operations['admin___federation___update-instance']; }; + '/admin/forward-abuse-user-report': { + /** + * admin/forward-abuse-user-report + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + */ + post: operations['admin___forward-abuse-user-report']; + }; '/admin/get-index-stats': { /** * admin/get-index-stats @@ -504,6 +486,15 @@ export type paths = { */ post: operations['admin___invite___list']; }; + '/admin/meta': { + /** + * admin/meta + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + post: operations['admin___meta']; + }; '/admin/promo/create': { /** * admin/promo/create @@ -603,23 +594,86 @@ export type paths = { */ post: operations['admin___resolve-abuse-user-report']; }; - '/admin/forward-abuse-user-report': { + '/admin/roles/assign': { /** - * admin/forward-abuse-user-report + * admin/roles/assign * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin___forward-abuse-user-report']; + post: operations['admin___roles___assign']; }; - '/admin/update-abuse-user-report': { + '/admin/roles/create': { /** - * admin/update-abuse-user-report + * admin/roles/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - post: operations['admin___update-abuse-user-report']; + post: operations['admin___roles___create']; + }; + '/admin/roles/delete': { + /** + * admin/roles/delete + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + */ + post: operations['admin___roles___delete']; + }; + '/admin/roles/list': { + /** + * admin/roles/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:roles* + */ + post: operations['admin___roles___list']; + }; + '/admin/roles/show': { + /** + * admin/roles/show + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:roles* + */ + post: operations['admin___roles___show']; + }; + '/admin/roles/unassign': { + /** + * admin/roles/unassign + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + */ + post: operations['admin___roles___unassign']; + }; + '/admin/roles/update': { + /** + * admin/roles/update + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + */ + post: operations['admin___roles___update']; + }; + '/admin/roles/update-default-policies': { + /** + * admin/roles/update-default-policies + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + */ + post: operations['admin___roles___update-default-policies']; + }; + '/admin/roles/users': { + /** + * admin/roles/users + * @description No description provided. + * + * **Credential required**: *No* / **Permission**: *read:admin:roles* + */ + post: operations['admin___roles___users']; }; '/admin/send-email': { /** @@ -675,182 +729,119 @@ export type paths = { */ post: operations['admin___suspend-user']; }; - '/admin/unsuspend-user': { + '/admin/system-webhook/create': { /** - * admin/unsuspend-user + * admin/system-webhook/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - post: operations['admin___unsuspend-user']; + post: operations['admin___system-webhook___create']; }; - '/admin/update-meta': { + '/admin/system-webhook/delete': { /** - * admin/update-meta + * admin/system-webhook/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - post: operations['admin___update-meta']; + post: operations['admin___system-webhook___delete']; }; - '/admin/delete-account': { + '/admin/system-webhook/list': { /** - * admin/delete-account + * admin/system-webhook/list * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - post: operations['admin___delete-account']; + post: operations['admin___system-webhook___list']; }; - '/admin/update-user-note': { + '/admin/system-webhook/show': { /** - * admin/update-user-note + * admin/system-webhook/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - post: operations['admin___update-user-note']; + post: operations['admin___system-webhook___show']; }; - '/admin/roles/create': { + '/admin/system-webhook/test': { /** - * admin/roles/create + * admin/system-webhook/test * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* */ - post: operations['admin___roles___create']; + post: operations['admin___system-webhook___test']; }; - '/admin/roles/delete': { + '/admin/system-webhook/update': { /** - * admin/roles/delete + * admin/system-webhook/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - post: operations['admin___roles___delete']; - }; - '/admin/roles/list': { - /** - * admin/roles/list - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:admin:roles* - */ - post: operations['admin___roles___list']; - }; - '/admin/roles/show': { - /** - * admin/roles/show - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:admin:roles* - */ - post: operations['admin___roles___show']; - }; - '/admin/roles/update': { - /** - * admin/roles/update - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* - */ - post: operations['admin___roles___update']; - }; - '/admin/roles/assign': { - /** - * admin/roles/assign - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* - */ - post: operations['admin___roles___assign']; - }; - '/admin/roles/unassign': { - /** - * admin/roles/unassign - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* - */ - post: operations['admin___roles___unassign']; - }; - '/admin/roles/update-default-policies': { - /** - * admin/roles/update-default-policies - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* - */ - post: operations['admin___roles___update-default-policies']; - }; - '/admin/roles/users': { - /** - * admin/roles/users - * @description No description provided. - * - * **Credential required**: *No* / **Permission**: *read:admin:roles* - */ - post: operations['admin___roles___users']; + post: operations['admin___system-webhook___update']; }; - '/admin/system-webhook/create': { + '/admin/unset-user-avatar': { /** - * admin/system-webhook/create + * admin/unset-user-avatar * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* */ - post: operations['admin___system-webhook___create']; + post: operations['admin___unset-user-avatar']; }; - '/admin/system-webhook/delete': { + '/admin/unset-user-banner': { /** - * admin/system-webhook/delete + * admin/unset-user-banner * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* */ - post: operations['admin___system-webhook___delete']; + post: operations['admin___unset-user-banner']; }; - '/admin/system-webhook/list': { + '/admin/unsuspend-user': { /** - * admin/system-webhook/list + * admin/unsuspend-user * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* */ - post: operations['admin___system-webhook___list']; + post: operations['admin___unsuspend-user']; }; - '/admin/system-webhook/show': { + '/admin/update-abuse-user-report': { /** - * admin/system-webhook/show + * admin/update-abuse-user-report * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ - post: operations['admin___system-webhook___show']; + post: operations['admin___update-abuse-user-report']; }; - '/admin/system-webhook/update': { + '/admin/update-meta': { /** - * admin/system-webhook/update + * admin/update-meta * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ - post: operations['admin___system-webhook___update']; + post: operations['admin___update-meta']; }; - '/admin/system-webhook/test': { + '/admin/update-user-note': { /** - * admin/system-webhook/test + * admin/update-user-note * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* */ - post: operations['admin___system-webhook___test']; + post: operations['admin___update-user-note']; }; '/announcements': { /** @@ -1024,6 +1015,31 @@ export type paths = { */ post: operations['blocking___list']; }; + '/bubble-game/ranking': { + /** + * bubble-game/ranking + * @description No description provided. + * + * **Credential required**: *No* + */ + get: operations['bubble-game___ranking']; + /** + * bubble-game/ranking + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['bubble-game___ranking']; + }; + '/bubble-game/register': { + /** + * bubble-game/register + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['bubble-game___register']; + }; '/channels/create': { /** * channels/create @@ -1033,6 +1049,15 @@ export type paths = { */ post: operations['channels___create']; }; + '/channels/favorite': { + /** + * channels/favorite + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:channels* + */ + post: operations['channels___favorite']; + }; '/channels/featured': { /** * channels/featured @@ -1060,59 +1085,50 @@ export type paths = { */ post: operations['channels___followed']; }; - '/channels/owned': { + '/channels/my-favorites': { /** - * channels/owned + * channels/my-favorites * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - post: operations['channels___owned']; + post: operations['channels___my-favorites']; }; - '/channels/show': { + '/channels/owned': { /** - * channels/show + * channels/owned * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:channels* */ - post: operations['channels___show']; + post: operations['channels___owned']; }; - '/channels/timeline': { + '/channels/search': { /** - * channels/timeline + * channels/search * @description No description provided. * * **Credential required**: *No* */ - post: operations['channels___timeline']; - }; - '/channels/unfollow': { - /** - * channels/unfollow - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:channels* - */ - post: operations['channels___unfollow']; + post: operations['channels___search']; }; - '/channels/update': { + '/channels/show': { /** - * channels/update + * channels/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - post: operations['channels___update']; + post: operations['channels___show']; }; - '/channels/favorite': { + '/channels/timeline': { /** - * channels/favorite + * channels/timeline * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - post: operations['channels___favorite']; + post: operations['channels___timeline']; }; '/channels/unfavorite': { /** @@ -1123,23 +1139,23 @@ export type paths = { */ post: operations['channels___unfavorite']; }; - '/channels/my-favorites': { + '/channels/unfollow': { /** - * channels/my-favorites + * channels/unfollow * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:channels* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels___my-favorites']; + post: operations['channels___unfollow']; }; - '/channels/search': { + '/channels/update': { /** - * channels/search + * channels/update * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - post: operations['channels___search']; + post: operations['channels___update']; }; '/charts/active-users': { /** @@ -1342,15 +1358,6 @@ export type paths = { */ post: operations['clips___add-note']; }; - '/clips/remove-note': { - /** - * clips/remove-note - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['clips___remove-note']; - }; '/clips/create': { /** * clips/create @@ -1369,6 +1376,15 @@ export type paths = { */ post: operations['clips___delete']; }; + '/clips/favorite': { + /** + * clips/favorite + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* + */ + post: operations['clips___favorite']; + }; '/clips/list': { /** * clips/list @@ -1378,6 +1394,15 @@ export type paths = { */ post: operations['clips___list']; }; + '/clips/my-favorites': { + /** + * clips/my-favorites + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* + */ + post: operations['clips___my-favorites']; + }; '/clips/notes': { /** * clips/notes @@ -1387,6 +1412,15 @@ export type paths = { */ post: operations['clips___notes']; }; + '/clips/remove-note': { + /** + * clips/remove-note + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['clips___remove-note']; + }; '/clips/show': { /** * clips/show @@ -1396,41 +1430,23 @@ export type paths = { */ post: operations['clips___show']; }; - '/clips/update': { + '/clips/unfavorite': { /** - * clips/update + * clips/unfavorite * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['clips___update']; - }; - '/clips/favorite': { - /** - * clips/favorite - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* - */ - post: operations['clips___favorite']; - }; - '/clips/unfavorite': { - /** - * clips/unfavorite - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* + * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ post: operations['clips___unfavorite']; }; - '/clips/my-favorites': { + '/clips/update': { /** - * clips/my-favorites + * clips/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['clips___my-favorites']; + post: operations['clips___update']; }; '/drive': { /** @@ -1486,23 +1502,23 @@ export type paths = { */ post: operations['drive___files___delete']; }; - '/drive/files/find-by-hash': { + '/drive/files/find': { /** - * drive/files/find-by-hash - * @description Search for a drive file by a hash of the contents. + * drive/files/find + * @description Search for a drive file by the given parameters. * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive___files___find-by-hash']; + post: operations['drive___files___find']; }; - '/drive/files/find': { + '/drive/files/find-by-hash': { /** - * drive/files/find - * @description Search for a drive file by the given parameters. + * drive/files/find-by-hash + * @description Search for a drive file by a hash of the contents. * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - post: operations['drive___files___find']; + post: operations['drive___files___find-by-hash']; }; '/drive/files/show': { /** @@ -1603,6 +1619,38 @@ export type paths = { */ post: operations['email-address___available']; }; + '/emoji': { + /** + * emoji + * @description No description provided. + * + * **Credential required**: *No* + */ + get: operations['emoji']; + /** + * emoji + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['emoji']; + }; + '/emojis': { + /** + * emojis + * @description No description provided. + * + * **Credential required**: *No* + */ + get: operations['emojis']; + /** + * emojis + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['emojis']; + }; '/endpoint': { /** * endpoint @@ -1674,6 +1722,22 @@ export type paths = { */ post: operations['federation___show-instance']; }; + '/federation/stats': { + /** + * federation/stats + * @description No description provided. + * + * **Credential required**: *No* + */ + get: operations['federation___stats']; + /** + * federation/stats + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['federation___stats']; + }; '/federation/update-remote-user': { /** * federation/update-remote-user @@ -1692,57 +1756,130 @@ export type paths = { */ post: operations['federation___users']; }; - '/federation/stats': { + '/fetch-external-resources': { /** - * federation/stats + * fetch-external-resources + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* + */ + post: operations['fetch-external-resources']; + }; + '/fetch-rss': { + /** + * fetch-rss * @description No description provided. * * **Credential required**: *No* */ - get: operations['federation___stats']; + get: operations['fetch-rss']; /** - * federation/stats + * fetch-rss * @description No description provided. * * **Credential required**: *No* */ - post: operations['federation___stats']; + post: operations['fetch-rss']; }; - '/following/create': { + '/flash/create': { /** - * following/create + * flash/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *Yes* / **Permission**: *write:flash* */ - post: operations['following___create']; + post: operations['flash___create']; }; - '/following/delete': { + '/flash/delete': { /** - * following/delete + * flash/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *Yes* / **Permission**: *write:flash* */ - post: operations['following___delete']; + post: operations['flash___delete']; }; - '/following/update': { + '/flash/featured': { /** - * following/update + * flash/featured + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['flash___featured']; + }; + '/flash/like': { + /** + * flash/like + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + */ + post: operations['flash___like']; + }; + '/flash/my': { + /** + * flash/my + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:flash* + */ + post: operations['flash___my']; + }; + '/flash/my-likes': { + /** + * flash/my-likes + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:flash-likes* + */ + post: operations['flash___my-likes']; + }; + '/flash/show': { + /** + * flash/show + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['flash___show']; + }; + '/flash/unlike': { + /** + * flash/unlike + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + */ + post: operations['flash___unlike']; + }; + '/flash/update': { + /** + * flash/update + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:flash* + */ + post: operations['flash___update']; + }; + '/following/create': { + /** + * following/create * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following___update']; + post: operations['following___create']; }; - '/following/update-all': { + '/following/delete': { /** - * following/update-all + * following/delete * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following___update-all']; + post: operations['following___delete']; }; '/following/invalidate': { /** @@ -1780,6 +1917,15 @@ export type paths = { */ post: operations['following___requests___list']; }; + '/following/requests/reject': { + /** + * following/requests/reject + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:following* + */ + post: operations['following___requests___reject']; + }; '/following/requests/sent': { /** * following/requests/sent @@ -1789,14 +1935,23 @@ export type paths = { */ post: operations['following___requests___sent']; }; - '/following/requests/reject': { + '/following/update': { /** - * following/requests/reject + * following/update * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:following* */ - post: operations['following___requests___reject']; + post: operations['following___update']; + }; + '/following/update-all': { + /** + * following/update-all + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:following* + */ + post: operations['following___update-all']; }; '/gallery/featured': { /** @@ -1879,30 +2034,30 @@ export type paths = { */ post: operations['gallery___posts___update']; }; - '/get-online-users-count': { + '/get-avatar-decorations': { /** - * get-online-users-count + * get-avatar-decorations * @description No description provided. * * **Credential required**: *No* */ - get: operations['get-online-users-count']; + post: operations['get-avatar-decorations']; + }; + '/get-online-users-count': { /** * get-online-users-count * @description No description provided. * * **Credential required**: *No* */ - post: operations['get-online-users-count']; - }; - '/get-avatar-decorations': { + get: operations['get-online-users-count']; /** - * get-avatar-decorations + * get-online-users-count * @description No description provided. * * **Credential required**: *No* */ - post: operations['get-avatar-decorations']; + post: operations['get-online-users-count']; }; '/hashtags/list': { /** @@ -1995,16 +2150,6 @@ export type paths = { */ post: operations['i___2fa___password-less']; }; - '/i/2fa/register-key': { - /** - * i/2fa/register-key - * @description No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - post: operations['i___2fa___register-key']; - }; '/i/2fa/register': { /** * i/2fa/register @@ -2015,15 +2160,15 @@ export type paths = { */ post: operations['i___2fa___register']; }; - '/i/2fa/update-key': { + '/i/2fa/register-key': { /** - * i/2fa/update-key + * i/2fa/register-key * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___2fa___update-key']; + post: operations['i___2fa___register-key']; }; '/i/2fa/remove-key': { /** @@ -2045,6 +2190,16 @@ export type paths = { */ post: operations['i___2fa___unregister']; }; + '/i/2fa/update-key': { + /** + * i/2fa/update-key + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* + */ + post: operations['i___2fa___update-key']; + }; '/i/apps': { /** * i/apps @@ -2065,6 +2220,16 @@ export type paths = { */ post: operations['i___authorized-apps']; }; + '/i/change-password': { + /** + * i/change-password + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* + */ + post: operations['i___change-password']; + }; '/i/claim-achievement': { /** * i/claim-achievement @@ -2074,25 +2239,25 @@ export type paths = { */ post: operations['i___claim-achievement']; }; - '/i/change-password': { + '/i/delete-account': { /** - * i/change-password + * i/delete-account * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___change-password']; + post: operations['i___delete-account']; }; - '/i/delete-account': { + '/i/export-antennas': { /** - * i/delete-account + * i/export-antennas * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___delete-account']; + post: operations['i___export-antennas']; }; '/i/export-blocking': { /** @@ -2104,55 +2269,55 @@ export type paths = { */ post: operations['i___export-blocking']; }; - '/i/export-following': { + '/i/export-clips': { /** - * i/export-following + * i/export-clips * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___export-following']; + post: operations['i___export-clips']; }; - '/i/export-mute': { + '/i/export-favorites': { /** - * i/export-mute + * i/export-favorites * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___export-mute']; + post: operations['i___export-favorites']; }; - '/i/export-notes': { + '/i/export-following': { /** - * i/export-notes + * i/export-following * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___export-notes']; + post: operations['i___export-following']; }; - '/i/export-clips': { + '/i/export-mute': { /** - * i/export-clips + * i/export-mute * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___export-clips']; + post: operations['i___export-mute']; }; - '/i/export-favorites': { + '/i/export-notes': { /** - * i/export-favorites + * i/export-notes * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___export-favorites']; + post: operations['i___export-notes']; }; '/i/export-user-lists': { /** @@ -2164,16 +2329,6 @@ export type paths = { */ post: operations['i___export-user-lists']; }; - '/i/export-antennas': { - /** - * i/export-antennas - * @description No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - post: operations['i___export-antennas']; - }; '/i/favorites': { /** * i/favorites @@ -2201,6 +2356,16 @@ export type paths = { */ post: operations['i___gallery___posts']; }; + '/i/import-antennas': { + /** + * i/import-antennas + * @description No description provided. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* + */ + post: operations['i___import-antennas']; + }; '/i/import-blocking': { /** * i/import-blocking @@ -2241,15 +2406,15 @@ export type paths = { */ post: operations['i___import-user-lists']; }; - '/i/import-antennas': { + '/i/move': { /** - * i/import-antennas + * i/move * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___import-antennas']; + post: operations['i___move']; }; '/i/notifications': { /** @@ -2324,6 +2489,15 @@ export type paths = { */ post: operations['i___regenerate-token']; }; + '/i/registry/get': { + /** + * i/registry/get + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + post: operations['i___registry___get']; + }; '/i/registry/get-all': { /** * i/registry/get-all @@ -2342,14 +2516,14 @@ export type paths = { */ post: operations['i___registry___get-detail']; }; - '/i/registry/get': { + '/i/registry/keys': { /** - * i/registry/get + * i/registry/keys * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i___registry___get']; + post: operations['i___registry___keys']; }; '/i/registry/keys-with-type': { /** @@ -2360,15 +2534,6 @@ export type paths = { */ post: operations['i___registry___keys-with-type']; }; - '/i/registry/keys': { - /** - * i/registry/keys - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:account* - */ - post: operations['i___registry___keys']; - }; '/i/registry/remove': { /** * i/registry/remove @@ -2426,16 +2591,6 @@ export type paths = { */ post: operations['i___unpin']; }; - '/i/update-email': { - /** - * i/update-email - * @description No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - post: operations['i___update-email']; - }; '/i/update': { /** * i/update @@ -2445,15 +2600,15 @@ export type paths = { */ post: operations['i___update']; }; - '/i/move': { + '/i/update-email': { /** - * i/move + * i/update-email * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['i___move']; + post: operations['i___update-email']; }; '/i/webhooks/create': { /** @@ -2464,6 +2619,15 @@ export type paths = { */ post: operations['i___webhooks___create']; }; + '/i/webhooks/delete': { + /** + * i/webhooks/delete + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['i___webhooks___delete']; + }; '/i/webhooks/list': { /** * i/webhooks/list @@ -2482,33 +2646,24 @@ export type paths = { */ post: operations['i___webhooks___show']; }; - '/i/webhooks/update': { + '/i/webhooks/test': { /** - * i/webhooks/update + * i/webhooks/test * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['i___webhooks___update']; + post: operations['i___webhooks___test']; }; - '/i/webhooks/delete': { + '/i/webhooks/update': { /** - * i/webhooks/delete + * i/webhooks/update * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['i___webhooks___delete']; - }; - '/i/webhooks/test': { - /** - * i/webhooks/test - * @description No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *read:account* - */ - post: operations['i___webhooks___test']; + post: operations['i___webhooks___update']; }; '/invite/create': { /** @@ -2528,23 +2683,23 @@ export type paths = { */ post: operations['invite___delete']; }; - '/invite/list': { + '/invite/limit': { /** - * invite/list + * invite/limit * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - post: operations['invite___list']; + post: operations['invite___limit']; }; - '/invite/limit': { + '/invite/list': { /** - * invite/limit + * invite/list * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - post: operations['invite___limit']; + post: operations['invite___list']; }; '/meta': { /** @@ -2555,38 +2710,6 @@ export type paths = { */ post: operations['meta']; }; - '/emojis': { - /** - * emojis - * @description No description provided. - * - * **Credential required**: *No* - */ - get: operations['emojis']; - /** - * emojis - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['emojis']; - }; - '/emoji': { - /** - * emoji - * @description No description provided. - * - * **Credential required**: *No* - */ - get: operations['emoji']; - /** - * emoji - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['emoji']; - }; '/miauth/gen-token': { /** * miauth/gen-token @@ -2624,33 +2747,6 @@ export type paths = { */ post: operations['mute___list']; }; - '/renote-mute/create': { - /** - * renote-mute/create - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:mutes* - */ - post: operations['renote-mute___create']; - }; - '/renote-mute/delete': { - /** - * renote-mute/delete - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:mutes* - */ - post: operations['renote-mute___delete']; - }; - '/renote-mute/list': { - /** - * renote-mute/list - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:mutes* - */ - post: operations['renote-mute___list']; - }; '/my/apps': { /** * my/apps @@ -2854,23 +2950,23 @@ export type paths = { */ post: operations['notes___replies']; }; - '/notes/search-by-tag': { + '/notes/search': { /** - * notes/search-by-tag + * notes/search * @description No description provided. * * **Credential required**: *No* */ - post: operations['notes___search-by-tag']; + post: operations['notes___search']; }; - '/notes/search': { + '/notes/search-by-tag': { /** - * notes/search + * notes/search-by-tag * @description No description provided. * * **Credential required**: *No* */ - post: operations['notes___search']; + post: operations['notes___search-by-tag']; }; '/notes/show': { /** @@ -3053,176 +3149,201 @@ export type paths = { */ post: operations['pages___update']; }; - '/flash/create': { + '/ping': { /** - * flash/create + * ping * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *No* */ - post: operations['flash___create']; + post: operations['ping']; }; - '/flash/delete': { + '/pinned-users': { /** - * flash/delete + * pinned-users * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *No* */ - post: operations['flash___delete']; + post: operations['pinned-users']; }; - '/flash/featured': { + '/promo/read': { /** - * flash/featured + * promo/read * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['flash___featured']; + post: operations['promo___read']; }; - '/flash/like': { + '/renote-mute/create': { /** - * flash/like + * renote-mute/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - post: operations['flash___like']; + post: operations['renote-mute___create']; }; - '/flash/show': { + '/renote-mute/delete': { /** - * flash/show + * renote-mute/delete * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - post: operations['flash___show']; + post: operations['renote-mute___delete']; }; - '/flash/unlike': { + '/renote-mute/list': { /** - * flash/unlike + * renote-mute/list * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - post: operations['flash___unlike']; + post: operations['renote-mute___list']; }; - '/flash/update': { + '/request-reset-password': { /** - * flash/update - * @description No description provided. + * request-reset-password + * @description Request a users password to be reset. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *No* */ - post: operations['flash___update']; + post: operations['request-reset-password']; }; - '/flash/my': { + '/reset-db': { /** - * flash/my - * @description No description provided. + * reset-db + * @description Only available when running with NODE_ENV=testing. Reset the database and flush Redis. * - * **Credential required**: *Yes* / **Permission**: *read:flash* + * **Credential required**: *No* */ - post: operations['flash___my']; + post: operations['reset-db']; }; - '/flash/my-likes': { + '/reset-password': { /** - * flash/my-likes - * @description No description provided. + * reset-password + * @description Complete the password reset that was previously requested. * - * **Credential required**: *Yes* / **Permission**: *read:flash-likes* + * **Credential required**: *No* */ - post: operations['flash___my-likes']; + post: operations['reset-password']; }; - '/ping': { + '/retention': { /** - * ping + * retention * @description No description provided. * * **Credential required**: *No* */ - post: operations['ping']; - }; - '/pinned-users': { + get: operations['retention']; /** - * pinned-users + * retention * @description No description provided. * * **Credential required**: *No* */ - post: operations['pinned-users']; + post: operations['retention']; }; - '/promo/read': { + '/reversi/cancel-match': { /** - * promo/read + * reversi/cancel-match * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['promo___read']; + post: operations['reversi___cancel-match']; }; - '/roles/list': { + '/reversi/games': { /** - * roles/list + * reversi/games + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['reversi___games']; + }; + '/reversi/invitations': { + /** + * reversi/invitations * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['roles___list']; + post: operations['reversi___invitations']; }; - '/roles/show': { + '/reversi/match': { /** - * roles/show + * reversi/match + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi___match']; + }; + '/reversi/show-game': { + /** + * reversi/show-game * @description No description provided. * * **Credential required**: *No* */ - post: operations['roles___show']; + post: operations['reversi___show-game']; }; - '/roles/users': { + '/reversi/surrender': { /** - * roles/users + * reversi/surrender + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['reversi___surrender']; + }; + '/reversi/verify': { + /** + * reversi/verify * @description No description provided. * * **Credential required**: *No* */ - post: operations['roles___users']; + post: operations['reversi___verify']; }; - '/roles/notes': { + '/roles/list': { /** - * roles/notes + * roles/list * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['roles___notes']; + post: operations['roles___list']; }; - '/request-reset-password': { + '/roles/notes': { /** - * request-reset-password - * @description Request a users password to be reset. + * roles/notes + * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - post: operations['request-reset-password']; + post: operations['roles___notes']; }; - '/reset-db': { + '/roles/show': { /** - * reset-db - * @description Only available when running with NODE_ENV=testing. Reset the database and flush Redis. + * roles/show + * @description No description provided. * * **Credential required**: *No* */ - post: operations['reset-db']; + post: operations['roles___show']; }; - '/reset-password': { + '/roles/users': { /** - * reset-password - * @description Complete the password reset that was previously requested. + * roles/users + * @description No description provided. * * **Credential required**: *No* */ - post: operations['reset-password']; + post: operations['roles___users']; }; '/server-info': { /** @@ -3249,35 +3370,25 @@ export type paths = { */ post: operations['stats']; }; - '/sw/show-registration': { - /** - * sw/show-registration - * @description Check push notification registration exists. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - post: operations['sw___show-registration']; - }; - '/sw/update-registration': { + '/sw/register': { /** - * sw/update-registration - * @description Update push notification registration. + * sw/register + * @description Register to receive push notifications. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['sw___update-registration']; + post: operations['sw___register']; }; - '/sw/register': { + '/sw/show-registration': { /** - * sw/register - * @description Register to receive push notifications. + * sw/show-registration + * @description Check push notification registration exists. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - post: operations['sw___register']; + post: operations['sw___show-registration']; }; '/sw/unregister': { /** @@ -3288,6 +3399,16 @@ export type paths = { */ post: operations['sw___unregister']; }; + '/sw/update-registration': { + /** + * sw/update-registration + * @description Update push notification registration. + * + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* + */ + post: operations['sw___update-registration']; + }; '/test': { /** * test @@ -3315,6 +3436,15 @@ export type paths = { */ post: operations['users']; }; + '/users/achievements': { + /** + * users/achievements + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['users___achievements']; + }; '/users/clips': { /** * users/clips @@ -3324,6 +3454,31 @@ export type paths = { */ post: operations['users___clips']; }; + '/users/featured-notes': { + /** + * users/featured-notes + * @description No description provided. + * + * **Credential required**: *No* + */ + get: operations['users___featured-notes']; + /** + * users/featured-notes + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['users___featured-notes']; + }; + '/users/flashs': { + /** + * users/flashs + * @description Show all flashs this user created. + * + * **Credential required**: *No* + */ + post: operations['users___flashs']; + }; '/users/followers': { /** * users/followers @@ -3360,22 +3515,6 @@ export type paths = { */ post: operations['users___get-frequently-replied-users']; }; - '/users/featured-notes': { - /** - * users/featured-notes - * @description No description provided. - * - * **Credential required**: *No* - */ - get: operations['users___featured-notes']; - /** - * users/featured-notes - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['users___featured-notes']; - }; '/users/lists/create': { /** * users/lists/create @@ -3385,14 +3524,41 @@ export type paths = { */ post: operations['users___lists___create']; }; - '/users/lists/delete': { + '/users/lists/create-from-public': { /** - * users/lists/delete - * @description Delete an existing list of users. + * users/lists/create-from-public + * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - post: operations['users___lists___delete']; + post: operations['users___lists___create-from-public']; + }; + '/users/lists/delete': { + /** + * users/lists/delete + * @description Delete an existing list of users. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['users___lists___delete']; + }; + '/users/lists/favorite': { + /** + * users/lists/favorite + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + post: operations['users___lists___favorite']; + }; + '/users/lists/get-memberships': { + /** + * users/lists/get-memberships + * @description No description provided. + * + * **Credential required**: *No* / **Permission**: *read:account* + */ + post: operations['users___lists___get-memberships']; }; '/users/lists/list': { /** @@ -3430,15 +3596,6 @@ export type paths = { */ post: operations['users___lists___show']; }; - '/users/lists/favorite': { - /** - * users/lists/favorite - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['users___lists___favorite']; - }; '/users/lists/unfavorite': { /** * users/lists/unfavorite @@ -3457,15 +3614,6 @@ export type paths = { */ post: operations['users___lists___update']; }; - '/users/lists/create-from-public': { - /** - * users/lists/create-from-public - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['users___lists___create-from-public']; - }; '/users/lists/update-membership': { /** * users/lists/update-membership @@ -3475,15 +3623,6 @@ export type paths = { */ post: operations['users___lists___update-membership']; }; - '/users/lists/get-memberships': { - /** - * users/lists/get-memberships - * @description No description provided. - * - * **Credential required**: *No* / **Permission**: *read:account* - */ - post: operations['users___lists___get-memberships']; - }; '/users/notes': { /** * users/notes @@ -3502,15 +3641,6 @@ export type paths = { */ post: operations['users___pages']; }; - '/users/flashs': { - /** - * users/flashs - * @description Show all flashs this user created. - * - * **Credential required**: *No* - */ - post: operations['users___flashs']; - }; '/users/reactions': { /** * users/reactions @@ -3547,15 +3677,6 @@ export type paths = { */ post: operations['users___report-abuse']; }; - '/users/search-by-username-and-host': { - /** - * users/search-by-username-and-host - * @description Search for a user by username and/or host. - * - * **Credential required**: *No* - */ - post: operations['users___search-by-username-and-host']; - }; '/users/search': { /** * users/search @@ -3565,23 +3686,23 @@ export type paths = { */ post: operations['users___search']; }; - '/users/show': { + '/users/search-by-username-and-host': { /** - * users/show - * @description Show the properties of a user. + * users/search-by-username-and-host + * @description Search for a user by username and/or host. * * **Credential required**: *No* */ - post: operations['users___show']; + post: operations['users___search-by-username-and-host']; }; - '/users/achievements': { + '/users/show': { /** - * users/achievements - * @description No description provided. + * users/show + * @description Show the properties of a user. * * **Credential required**: *No* */ - post: operations['users___achievements']; + post: operations['users___show']; }; '/users/update-memo': { /** @@ -3592,135 +3713,14 @@ export type paths = { */ post: operations['users___update-memo']; }; - '/fetch-rss': { - /** - * fetch-rss - * @description No description provided. - * - * **Credential required**: *No* - */ - get: operations['fetch-rss']; - /** - * fetch-rss - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['fetch-rss']; - }; - '/fetch-external-resources': { - /** - * fetch-external-resources - * @description No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - post: operations['fetch-external-resources']; - }; - '/retention': { - /** - * retention - * @description No description provided. - * - * **Credential required**: *No* - */ - get: operations['retention']; - /** - * retention - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['retention']; - }; - '/bubble-game/register': { - /** - * bubble-game/register - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['bubble-game___register']; - }; - '/bubble-game/ranking': { - /** - * bubble-game/ranking - * @description No description provided. - * - * **Credential required**: *No* - */ - get: operations['bubble-game___ranking']; - /** - * bubble-game/ranking - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['bubble-game___ranking']; - }; - '/reversi/cancel-match': { - /** - * reversi/cancel-match - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['reversi___cancel-match']; - }; - '/reversi/games': { - /** - * reversi/games - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['reversi___games']; - }; - '/reversi/match': { - /** - * reversi/match - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['reversi___match']; - }; - '/reversi/invitations': { - /** - * reversi/invitations - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:account* - */ - post: operations['reversi___invitations']; - }; - '/reversi/show-game': { - /** - * reversi/show-game - * @description No description provided. - * - * **Credential required**: *No* - */ - post: operations['reversi___show-game']; - }; - '/reversi/surrender': { - /** - * reversi/surrender - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - post: operations['reversi___surrender']; - }; - '/reversi/verify': { + '/v2/admin/emoji/list': { /** - * reversi/verify + * v2/admin/emoji/list * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - post: operations['reversi___verify']; + post: operations['v2___admin___emoji___list']; }; }; @@ -5144,138 +5144,32 @@ export type external = Record; export type operations = { /** - * admin/meta + * admin/abuse-report/notification-recipient/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ - admin___meta: { + 'admin___abuse-report___notification-recipient___create': { + requestBody: { + content: { + 'application/json': { + isActive: boolean; + name: string; + /** @enum {string} */ + method: 'email' | 'webhook'; + /** Format: misskey:id */ + userId?: string; + /** Format: misskey:id */ + systemWebhookId?: string; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': { - cacheRemoteFiles: boolean; - cacheRemoteSensitiveFiles: boolean; - emailRequiredForSignup: boolean; - enableHcaptcha: boolean; - hcaptchaSiteKey: string | null; - enableMcaptcha: boolean; - mcaptchaSiteKey: string | null; - mcaptchaInstanceUrl: string | null; - enableRecaptcha: boolean; - recaptchaSiteKey: string | null; - enableTurnstile: boolean; - turnstileSiteKey: string | null; - enableTestcaptcha: boolean; - swPublickey: string | null; - /** @default /assets/ai.png */ - mascotImageUrl: string | null; - bannerUrl: string | null; - serverErrorImageUrl: string | null; - infoImageUrl: string | null; - notFoundImageUrl: string | null; - iconUrl: string | null; - app192IconUrl: string | null; - app512IconUrl: string | null; - enableEmail: boolean; - enableServiceWorker: boolean; - translatorAvailable: boolean; - silencedHosts?: string[]; - mediaSilencedHosts: string[]; - pinnedUsers: string[]; - hiddenTags: string[]; - blockedHosts: string[]; - sensitiveWords: string[]; - prohibitedWords: string[]; - prohibitedWordsForNameOfUser: string[]; - bannedEmailDomains?: string[]; - preservedUsernames: string[]; - hcaptchaSecretKey: string | null; - mcaptchaSecretKey: string | null; - recaptchaSecretKey: string | null; - turnstileSecretKey: string | null; - sensitiveMediaDetection: string; - sensitiveMediaDetectionSensitivity: string; - setSensitiveFlagAutomatically: boolean; - enableSensitiveMediaDetectionForVideos: boolean; - /** Format: id */ - proxyAccountId: string | null; - email: string | null; - smtpSecure: boolean; - smtpHost: string | null; - smtpPort: number | null; - smtpUser: string | null; - smtpPass: string | null; - swPrivateKey: string | null; - useObjectStorage: boolean; - objectStorageBaseUrl: string | null; - objectStorageBucket: string | null; - objectStoragePrefix: string | null; - objectStorageEndpoint: string | null; - objectStorageRegion: string | null; - objectStoragePort: number | null; - objectStorageAccessKey: string | null; - objectStorageSecretKey: string | null; - objectStorageUseSSL: boolean; - objectStorageUseProxy: boolean; - objectStorageSetPublicRead: boolean; - enableIpLogging: boolean; - enableActiveEmailValidation: boolean; - enableVerifymailApi: boolean; - verifymailAuthKey: string | null; - enableTruemailApi: boolean; - truemailInstance: string | null; - truemailAuthKey: string | null; - enableChartsForRemoteUser: boolean; - enableChartsForFederatedInstances: boolean; - enableStatsForFederatedInstances: boolean; - enableServerMachineStats: boolean; - enableIdenticonGeneration: boolean; - manifestJsonOverride: string; - policies: Record; - enableFanoutTimeline: boolean; - enableFanoutTimelineDbFallback: boolean; - perLocalUserUserTimelineCacheMax: number; - perRemoteUserUserTimelineCacheMax: number; - perUserHomeTimelineCacheMax: number; - perUserListTimelineCacheMax: number; - enableReactionsBuffering: boolean; - notesPerOneAd: number; - backgroundImageUrl: string | null; - deeplAuthKey: string | null; - deeplIsPro: boolean; - defaultDarkTheme: string | null; - defaultLightTheme: string | null; - description: string | null; - disableRegistration: boolean; - impressumUrl: string | null; - maintainerEmail: string | null; - maintainerName: string | null; - name: string | null; - shortName: string | null; - objectStorageS3ForcePathStyle: boolean; - privacyPolicyUrl: string | null; - inquiryUrl: string | null; - repositoryUrl: string | null; - /** - * @deprecated - * @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. - */ - summalyProxy: string | null; - themeColor: string | null; - tosUrl: string | null; - uri: string; - version: string; - urlPreviewEnabled: boolean; - urlPreviewTimeout: number; - urlPreviewMaximumContentLength: number; - urlPreviewRequireContentLength: boolean; - urlPreviewUserAgent: string | null; - urlPreviewSummaryProxyUrl: string | null; - federation: string; - federationHosts: string[]; - }; + 'application/json': components['schemas']['AbuseReportNotificationRecipient']; }; }; /** @description Client error */ @@ -5311,66 +5205,25 @@ export type operations = { }; }; /** - * admin/abuse-user-reports + * admin/abuse-report/notification-recipient/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ - 'admin___abuse-user-reports': { + 'admin___abuse-report___notification-recipient___delete': { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; /** Format: misskey:id */ - untilId?: string; - /** @default null */ - state?: string | null; - /** - * @default combined - * @enum {string} - */ - reporterOrigin?: 'combined' | 'local' | 'remote'; - /** - * @default combined - * @enum {string} - */ - targetUserOrigin?: 'combined' | 'local' | 'remote'; + id: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': ({ - /** - * Format: id - * @example xxxxxxxxxx - */ - id: string; - /** Format: date-time */ - createdAt: string; - comment: string; - /** @example false */ - resolved: boolean; - /** Format: id */ - reporterId: string; - /** Format: id */ - targetUserId: string; - /** Format: id */ - assigneeId: string | null; - reporter: components['schemas']['UserDetailedNotMe']; - targetUser: components['schemas']['UserDetailedNotMe']; - assignee: components['schemas']['UserDetailedNotMe'] | null; - forwarded: boolean; - /** @enum {string|null} */ - resolvedAs: 'accept' | 'reject' | null; - moderationNote: string; - })[]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -5514,16 +5367,18 @@ export type operations = { }; }; /** - * admin/abuse-report/notification-recipient/create + * admin/abuse-report/notification-recipient/update * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ - 'admin___abuse-report___notification-recipient___create': { + 'admin___abuse-report___notification-recipient___update': { requestBody: { content: { 'application/json': { + /** Format: misskey:id */ + id: string; isActive: boolean; name: string; /** @enum {string} */ @@ -5575,26 +5430,33 @@ export type operations = { }; }; /** - * admin/abuse-report/notification-recipient/update + * admin/abuse-user-reports * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* + * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* */ - 'admin___abuse-report___notification-recipient___update': { + 'admin___abuse-user-reports': { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - id: string; - isActive: boolean; - name: string; - /** @enum {string} */ - method: 'email' | 'webhook'; - /** Format: misskey:id */ - userId?: string; + sinceId?: string; /** Format: misskey:id */ - systemWebhookId?: string; + untilId?: string; + /** @default null */ + state?: string | null; + /** + * @default combined + * @enum {string} + */ + reporterOrigin?: 'combined' | 'local' | 'remote'; + /** + * @default combined + * @enum {string} + */ + targetUserOrigin?: 'combined' | 'local' | 'remote'; }; }; }; @@ -5602,7 +5464,31 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['AbuseReportNotificationRecipient']; + 'application/json': ({ + /** + * Format: id + * @example xxxxxxxxxx + */ + id: string; + /** Format: date-time */ + createdAt: string; + comment: string; + /** @example false */ + resolved: boolean; + /** Format: id */ + reporterId: string; + /** Format: id */ + targetUserId: string; + /** Format: id */ + assigneeId: string | null; + reporter: components['schemas']['UserDetailedNotMe']; + targetUser: components['schemas']['UserDetailedNotMe']; + assignee: components['schemas']['UserDetailedNotMe'] | null; + forwarded: boolean; + /** @enum {string|null} */ + resolvedAs: 'accept' | 'reject' | null; + moderationNote: string; + })[]; }; }; /** @description Client error */ @@ -5638,25 +5524,27 @@ export type operations = { }; }; /** - * admin/abuse-report/notification-recipient/delete + * admin/accounts/create * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* + * **Credential required**: *No* */ - 'admin___abuse-report___notification-recipient___delete': { + admin___accounts___create: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - id: string; + username: string; + password: string; + setupPassword?: string | null; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['MeDetailed']; + }; }; /** @description Client error */ 400: { @@ -5691,67 +5579,12 @@ export type operations = { }; }; /** - * admin/accounts/create + * admin/accounts/delete * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:admin:account* */ - admin___accounts___create: { - requestBody: { - content: { - 'application/json': { - username: string; - password: string; - setupPassword?: string | null; - }; - }; - }; - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['MeDetailed']; - }; - }; - /** @description Client error */ - 400: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Authentication error */ - 401: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Forbidden error */ - 403: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - }; - }; - /** - * admin/accounts/delete - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:account* - */ - admin___accounts___delete: { + admin___accounts___delete: { requestBody: { content: { 'application/json': { @@ -6739,64 +6572,12 @@ export type operations = { }; }; /** - * admin/delete-all-files-of-a-user - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* - */ - 'admin___delete-all-files-of-a-user': { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - userId: string; - }; - }; - }; - responses: { - /** @description OK (without any results) */ - 204: { - content: never; - }; - /** @description Client error */ - 400: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Authentication error */ - 401: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Forbidden error */ - 403: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - }; - }; - /** - * admin/unset-user-avatar + * admin/delete-account * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* + * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* */ - 'admin___unset-user-avatar': { + 'admin___delete-account': { requestBody: { content: { 'application/json': { @@ -6843,12 +6624,12 @@ export type operations = { }; }; /** - * admin/unset-user-banner + * admin/delete-all-files-of-a-user * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* + * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* */ - 'admin___unset-user-banner': { + 'admin___delete-all-files-of-a-user': { requestBody: { content: { 'application/json': { @@ -7163,24 +6944,34 @@ export type operations = { }; }; /** - * admin/emoji/add-aliases-bulk + * admin/emoji/add * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin___emoji___add-aliases-bulk': { + admin___emoji___add: { requestBody: { content: { 'application/json': { - ids: string[]; - aliases: string[]; + name: string; + /** Format: misskey:id */ + fileId: string; + /** @description Use `null` to reset the category. */ + category?: string | null; + aliases?: string[]; + license?: string | null; + isSensitive?: boolean; + localOnly?: boolean; + roleIdsThatCanBeUsedThisEmojiAsReaction?: string[]; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['EmojiDetailed']; + }; }; /** @description Client error */ 400: { @@ -7215,34 +7006,24 @@ export type operations = { }; }; /** - * admin/emoji/add + * admin/emoji/add-aliases-bulk * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - admin___emoji___add: { + 'admin___emoji___add-aliases-bulk': { requestBody: { content: { 'application/json': { - name: string; - /** Format: misskey:id */ - fileId: string; - /** @description Use `null` to reset the category. */ - category?: string | null; - aliases?: string[]; - license?: string | null; - isSensitive?: boolean; - localOnly?: boolean; - roleIdsThatCanBeUsedThisEmojiAsReaction?: string[]; + ids: string[]; + aliases: string[]; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['EmojiDetailed']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -7334,16 +7115,17 @@ export type operations = { }; }; /** - * admin/emoji/delete-bulk + * admin/emoji/delete * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - 'admin___emoji___delete-bulk': { + admin___emoji___delete: { requestBody: { content: { 'application/json': { - ids: string[]; + /** Format: misskey:id */ + id: string; }; }; }; @@ -7385,17 +7167,16 @@ export type operations = { }; }; /** - * admin/emoji/delete + * admin/emoji/delete-bulk * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ - admin___emoji___delete: { + 'admin___emoji___delete-bulk': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - id: string; + ids: string[]; }; }; }; @@ -7490,22 +7271,17 @@ export type operations = { }; }; /** - * admin/emoji/list-remote + * admin/emoji/list * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - 'admin___emoji___list-remote': { + admin___emoji___list: { requestBody: { content: { 'application/json': { /** @default null */ query?: string | null; - /** - * @description Use `null` to represent the local host. - * @default null - */ - host?: string | null; /** @default 10 */ limit?: number; /** Format: misskey:id */ @@ -7525,7 +7301,7 @@ export type operations = { aliases: string[]; name: string; category: string | null; - /** @description The local host is represented with `null`. */ + /** @description The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files. */ host: string | null; url: string; })[]; @@ -7564,17 +7340,22 @@ export type operations = { }; }; /** - * admin/emoji/list + * admin/emoji/list-remote * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - admin___emoji___list: { + 'admin___emoji___list-remote': { requestBody: { content: { 'application/json': { /** @default null */ query?: string | null; + /** + * @description Use `null` to represent the local host. + * @default null + */ + host?: string | null; /** @default 10 */ limit?: number; /** Format: misskey:id */ @@ -7594,7 +7375,7 @@ export type operations = { aliases: string[]; name: string; category: string | null; - /** @description The local host is represented with `null`. The field exists for compatibility with other API endpoints that return files. */ + /** @description The local host is represented with `null`. */ host: string | null; url: string; })[]; @@ -7905,63 +7686,23 @@ export type operations = { }; }; /** - * v2/admin/emoji/list + * admin/federation/delete-all-files * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* + * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - v2___admin___emoji___list: { + 'admin___federation___delete-all-files': { requestBody: { content: { 'application/json': { - query?: ({ - updatedAtFrom?: string; - updatedAtTo?: string; - name?: string; - host?: string; - uri?: string; - publicUrl?: string; - originalUrl?: string; - type?: string; - aliases?: string; - category?: string; - license?: string; - isSensitive?: boolean; - localOnly?: boolean; - /** - * @default all - * @enum {string} - */ - hostType?: 'local' | 'remote' | 'all'; - roleIds?: string[]; - }) | null; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ - limit?: number; - page?: number; - /** - * @default [ - * "-id" - * ] - */ - sortKeys?: ('+id' | '-id' | '+updatedAt' | '-updatedAt' | '+name' | '-name' | '+host' | '-host' | '+uri' | '-uri' | '+publicUrl' | '-publicUrl' | '+type' | '-type' | '+aliases' | '-aliases' | '+category' | '-category' | '+license' | '-license' | '+isSensitive' | '-isSensitive' | '+localOnly' | '-localOnly' | '+roleIdsThatCanBeUsedThisEmojiAsReaction' | '-roleIdsThatCanBeUsedThisEmojiAsReaction')[]; + host: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - emojis: components['schemas']['EmojiDetailedAdmin'][]; - count: number; - allCount: number; - allPages: number; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -7996,12 +7737,12 @@ export type operations = { }; }; /** - * admin/federation/delete-all-files + * admin/federation/refresh-remote-instance-metadata * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - 'admin___federation___delete-all-files': { + 'admin___federation___refresh-remote-instance-metadata': { requestBody: { content: { 'application/json': { @@ -8047,12 +7788,12 @@ export type operations = { }; }; /** - * admin/federation/refresh-remote-instance-metadata + * admin/federation/remove-all-following * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - 'admin___federation___refresh-remote-instance-metadata': { + 'admin___federation___remove-all-following': { requestBody: { content: { 'application/json': { @@ -8098,16 +7839,18 @@ export type operations = { }; }; /** - * admin/federation/remove-all-following + * admin/federation/update-instance * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ - 'admin___federation___remove-all-following': { + 'admin___federation___update-instance': { requestBody: { content: { 'application/json': { host: string; + isSuspended?: boolean; + moderationNote?: string; }; }; }; @@ -8149,18 +7892,17 @@ export type operations = { }; }; /** - * admin/federation/update-instance + * admin/forward-abuse-user-report * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:federation* + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ - 'admin___federation___update-instance': { + 'admin___forward-abuse-user-report': { requestBody: { content: { 'application/json': { - host: string; - isSuspended?: boolean; - moderationNote?: string; + /** Format: misskey:id */ + reportId: string; }; }; }; @@ -8477,6 +8219,173 @@ export type operations = { }; }; }; + /** + * admin/meta + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:meta* + */ + admin___meta: { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + cacheRemoteFiles: boolean; + cacheRemoteSensitiveFiles: boolean; + emailRequiredForSignup: boolean; + enableHcaptcha: boolean; + hcaptchaSiteKey: string | null; + enableMcaptcha: boolean; + mcaptchaSiteKey: string | null; + mcaptchaInstanceUrl: string | null; + enableRecaptcha: boolean; + recaptchaSiteKey: string | null; + enableTurnstile: boolean; + turnstileSiteKey: string | null; + enableTestcaptcha: boolean; + swPublickey: string | null; + /** @default /assets/ai.png */ + mascotImageUrl: string | null; + bannerUrl: string | null; + serverErrorImageUrl: string | null; + infoImageUrl: string | null; + notFoundImageUrl: string | null; + iconUrl: string | null; + app192IconUrl: string | null; + app512IconUrl: string | null; + enableEmail: boolean; + enableServiceWorker: boolean; + translatorAvailable: boolean; + silencedHosts?: string[]; + mediaSilencedHosts: string[]; + pinnedUsers: string[]; + hiddenTags: string[]; + blockedHosts: string[]; + sensitiveWords: string[]; + prohibitedWords: string[]; + prohibitedWordsForNameOfUser: string[]; + bannedEmailDomains?: string[]; + preservedUsernames: string[]; + hcaptchaSecretKey: string | null; + mcaptchaSecretKey: string | null; + recaptchaSecretKey: string | null; + turnstileSecretKey: string | null; + sensitiveMediaDetection: string; + sensitiveMediaDetectionSensitivity: string; + setSensitiveFlagAutomatically: boolean; + enableSensitiveMediaDetectionForVideos: boolean; + /** Format: id */ + proxyAccountId: string | null; + email: string | null; + smtpSecure: boolean; + smtpHost: string | null; + smtpPort: number | null; + smtpUser: string | null; + smtpPass: string | null; + swPrivateKey: string | null; + useObjectStorage: boolean; + objectStorageBaseUrl: string | null; + objectStorageBucket: string | null; + objectStoragePrefix: string | null; + objectStorageEndpoint: string | null; + objectStorageRegion: string | null; + objectStoragePort: number | null; + objectStorageAccessKey: string | null; + objectStorageSecretKey: string | null; + objectStorageUseSSL: boolean; + objectStorageUseProxy: boolean; + objectStorageSetPublicRead: boolean; + enableIpLogging: boolean; + enableActiveEmailValidation: boolean; + enableVerifymailApi: boolean; + verifymailAuthKey: string | null; + enableTruemailApi: boolean; + truemailInstance: string | null; + truemailAuthKey: string | null; + enableChartsForRemoteUser: boolean; + enableChartsForFederatedInstances: boolean; + enableStatsForFederatedInstances: boolean; + enableServerMachineStats: boolean; + enableIdenticonGeneration: boolean; + manifestJsonOverride: string; + policies: Record; + enableFanoutTimeline: boolean; + enableFanoutTimelineDbFallback: boolean; + perLocalUserUserTimelineCacheMax: number; + perRemoteUserUserTimelineCacheMax: number; + perUserHomeTimelineCacheMax: number; + perUserListTimelineCacheMax: number; + enableReactionsBuffering: boolean; + notesPerOneAd: number; + backgroundImageUrl: string | null; + deeplAuthKey: string | null; + deeplIsPro: boolean; + defaultDarkTheme: string | null; + defaultLightTheme: string | null; + description: string | null; + disableRegistration: boolean; + impressumUrl: string | null; + maintainerEmail: string | null; + maintainerName: string | null; + name: string | null; + shortName: string | null; + objectStorageS3ForcePathStyle: boolean; + privacyPolicyUrl: string | null; + inquiryUrl: string | null; + repositoryUrl: string | null; + /** + * @deprecated + * @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. + */ + summalyProxy: string | null; + themeColor: string | null; + tosUrl: string | null; + uri: string; + version: string; + urlPreviewEnabled: boolean; + urlPreviewTimeout: number; + urlPreviewMaximumContentLength: number; + urlPreviewRequireContentLength: boolean; + urlPreviewUserAgent: string | null; + urlPreviewSummaryProxyUrl: string | null; + federation: string; + federationHosts: string[]; + }; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * admin/promo/create * @description No description provided. @@ -9050,17 +8959,20 @@ export type operations = { }; }; /** - * admin/forward-abuse-user-report + * admin/roles/assign * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin___forward-abuse-user-report': { + admin___roles___assign: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - reportId: string; + roleId: string; + /** Format: misskey:id */ + userId: string; + expiresAt?: number | null; }; }; }; @@ -9102,25 +9014,40 @@ export type operations = { }; }; /** - * admin/update-abuse-user-report + * admin/roles/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin___update-abuse-user-report': { + admin___roles___create: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - reportId: string; - moderationNote?: string; + name: string; + description: string; + color: string | null; + iconUrl: string | null; + /** @enum {string} */ + target: 'manual' | 'conditional'; + condFormula: Record; + isPublic: boolean; + isModerator: boolean; + isAdministrator: boolean; + /** @default false */ + isExplorable?: boolean; + asBadge: boolean; + canEditMembersByModerator: boolean; + displayOrder: number; + policies: Record; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Role']; + }; }; /** @description Client error */ 400: { @@ -9155,18 +9082,17 @@ export type operations = { }; }; /** - * admin/send-email + * admin/roles/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin___send-email': { + admin___roles___delete: { requestBody: { content: { 'application/json': { - to: string; - subject: string; - text: string; + /** Format: misskey:id */ + roleId: string; }; }; }; @@ -9208,41 +9134,17 @@ export type operations = { }; }; /** - * admin/server-info + * admin/roles/list * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* + * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - 'admin___server-info': { + admin___roles___list: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': { - machine: string; - /** @example linux */ - os: string; - node: string; - psql: string; - cpu: { - model: string; - cores: number; - }; - mem: { - /** Format: bytes */ - total: number; - }; - fs: { - /** Format: bytes */ - total: number; - /** Format: bytes */ - used: number; - }; - net: { - /** @example eth0 */ - interface: string; - }; - }; + 'application/json': components['schemas']['Role'][]; }; }; /** @description Client error */ @@ -9278,24 +9180,17 @@ export type operations = { }; }; /** - * admin/show-moderation-logs + * admin/roles/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* + * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ - 'admin___show-moderation-logs': { + admin___roles___show: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - type?: string | null; /** Format: misskey:id */ - userId?: string | null; + roleId: string; }; }; }; @@ -9303,17 +9198,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: id */ - id: string; - /** Format: date-time */ - createdAt: string; - type: string; - info: Record; - /** Format: id */ - userId: string; - user: components['schemas']['UserDetailedNotMe']; - }[]; + 'application/json': components['schemas']['Role']; }; }; /** @description Client error */ @@ -9349,182 +9234,26 @@ export type operations = { }; }; /** - * admin/show-user + * admin/roles/unassign * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin___show-user': { + admin___roles___unassign: { requestBody: { content: { 'application/json': { + /** Format: misskey:id */ + roleId: string; /** Format: misskey:id */ userId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - email: string | null; - emailVerified: boolean; - followedMessage: string | null; - autoAcceptFollowed: boolean; - noCrawle: boolean; - preventAiLearning: boolean; - alwaysMarkNsfw: boolean; - autoSensitive: boolean; - carefulBot: boolean; - injectFeaturedNote: boolean; - receiveAnnouncementEmail: boolean; - mutedWords: (string | string[])[]; - mutedInstances: string[]; - notificationRecieveConfig: { - note?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - follow?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - mention?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - reply?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - renote?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - quote?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - reaction?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - pollEnded?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - receiveFollowRequest?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - followRequestAccepted?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - roleAssigned?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - achievementEarned?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - app?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - test?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - }; - isModerator: boolean; - isSilenced: boolean; - isSuspended: boolean; - isHibernated: boolean; - lastActiveDate: string | null; - moderationNote: string; - signins: components['schemas']['Signin'][]; - policies: components['schemas']['RolePolicies']; - roles: components['schemas']['Role'][]; - roleAssigns: ({ - createdAt: string; - expiresAt: string | null; - roleId: string; - })[]; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -9559,48 +9288,40 @@ export type operations = { }; }; /** - * admin/show-users + * admin/roles/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin___show-users': { + admin___roles___update: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** @default 0 */ - offset?: number; + /** Format: misskey:id */ + roleId: string; + name?: string; + description?: string; + color?: string | null; + iconUrl?: string | null; /** @enum {string} */ - sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt' | '+lastActiveDate' | '-lastActiveDate'; - /** - * @default all - * @enum {string} - */ - state?: 'all' | 'alive' | 'available' | 'admin' | 'moderator' | 'adminOrModerator' | 'suspended'; - /** - * @default combined - * @enum {string} - */ - origin?: 'combined' | 'local' | 'remote'; - /** @default null */ - username?: string | null; - /** - * @description The local host is represented with `null`. - * @default null - */ - hostname?: string | null; - }; - }; - }; - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['UserDetailed'][]; + target?: 'manual' | 'conditional'; + condFormula?: Record; + isPublic?: boolean; + isModerator?: boolean; + isAdministrator?: boolean; + isExplorable?: boolean; + asBadge?: boolean; + canEditMembersByModerator?: boolean; + displayOrder?: number; + policies?: Record; }; }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -9634,17 +9355,16 @@ export type operations = { }; }; /** - * admin/suspend-user + * admin/roles/update-default-policies * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* + * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ - 'admin___suspend-user': { + 'admin___roles___update-default-policies': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - userId: string; + policies: Record; }; }; }; @@ -9686,24 +9406,40 @@ export type operations = { }; }; /** - * admin/unsuspend-user + * admin/roles/users * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* + * **Credential required**: *No* / **Permission**: *read:admin:roles* */ - 'admin___unsuspend-user': { + admin___roles___users: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; + roleId: string; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': ({ + /** Format: misskey:id */ + id: string; + /** Format: date-time */ + createdAt: string; + user: components['schemas']['UserDetailed']; + /** Format: date-time */ + expiresAt: string | null; + })[]; + }; }; /** @description Client error */ 400: { @@ -9738,134 +9474,18 @@ export type operations = { }; }; /** - * admin/update-meta + * admin/send-email * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* */ - 'admin___update-meta': { + 'admin___send-email': { requestBody: { content: { 'application/json': { - disableRegistration?: boolean | null; - pinnedUsers?: string[] | null; - hiddenTags?: string[] | null; - blockedHosts?: string[] | null; - sensitiveWords?: string[] | null; - prohibitedWords?: string[] | null; - prohibitedWordsForNameOfUser?: string[] | null; - themeColor?: string | null; - mascotImageUrl?: string | null; - bannerUrl?: string | null; - serverErrorImageUrl?: string | null; - infoImageUrl?: string | null; - notFoundImageUrl?: string | null; - iconUrl?: string | null; - app192IconUrl?: string | null; - app512IconUrl?: string | null; - backgroundImageUrl?: string | null; - logoImageUrl?: string | null; - name?: string | null; - shortName?: string | null; - description?: string | null; - defaultLightTheme?: string | null; - defaultDarkTheme?: string | null; - cacheRemoteFiles?: boolean; - cacheRemoteSensitiveFiles?: boolean; - emailRequiredForSignup?: boolean; - enableHcaptcha?: boolean; - hcaptchaSiteKey?: string | null; - hcaptchaSecretKey?: string | null; - enableMcaptcha?: boolean; - mcaptchaSiteKey?: string | null; - mcaptchaInstanceUrl?: string | null; - mcaptchaSecretKey?: string | null; - enableRecaptcha?: boolean; - recaptchaSiteKey?: string | null; - recaptchaSecretKey?: string | null; - enableTurnstile?: boolean; - turnstileSiteKey?: string | null; - turnstileSecretKey?: string | null; - enableTestcaptcha?: boolean; - /** @enum {string} */ - sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote'; - /** @enum {string} */ - sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh'; - setSensitiveFlagAutomatically?: boolean; - enableSensitiveMediaDetectionForVideos?: boolean; - /** Format: misskey:id */ - proxyAccountId?: string | null; - maintainerName?: string | null; - maintainerEmail?: string | null; - langs?: string[]; - deeplAuthKey?: string | null; - deeplIsPro?: boolean; - enableEmail?: boolean; - email?: string | null; - smtpSecure?: boolean; - smtpHost?: string | null; - smtpPort?: number | null; - smtpUser?: string | null; - smtpPass?: string | null; - enableServiceWorker?: boolean; - swPublicKey?: string | null; - swPrivateKey?: string | null; - tosUrl?: string | null; - repositoryUrl?: string | null; - feedbackUrl?: string | null; - impressumUrl?: string | null; - privacyPolicyUrl?: string | null; - inquiryUrl?: string | null; - useObjectStorage?: boolean; - objectStorageBaseUrl?: string | null; - objectStorageBucket?: string | null; - objectStoragePrefix?: string | null; - objectStorageEndpoint?: string | null; - objectStorageRegion?: string | null; - objectStoragePort?: number | null; - objectStorageAccessKey?: string | null; - objectStorageSecretKey?: string | null; - objectStorageUseSSL?: boolean; - objectStorageUseProxy?: boolean; - objectStorageSetPublicRead?: boolean; - objectStorageS3ForcePathStyle?: boolean; - enableIpLogging?: boolean; - enableActiveEmailValidation?: boolean; - enableVerifymailApi?: boolean; - verifymailAuthKey?: string | null; - enableTruemailApi?: boolean; - truemailInstance?: string | null; - truemailAuthKey?: string | null; - enableChartsForRemoteUser?: boolean; - enableChartsForFederatedInstances?: boolean; - enableStatsForFederatedInstances?: boolean; - enableServerMachineStats?: boolean; - enableIdenticonGeneration?: boolean; - serverRules?: string[]; - bannedEmailDomains?: string[]; - preservedUsernames?: string[]; - manifestJsonOverride?: string; - enableFanoutTimeline?: boolean; - enableFanoutTimelineDbFallback?: boolean; - perLocalUserUserTimelineCacheMax?: number; - perRemoteUserUserTimelineCacheMax?: number; - perUserHomeTimelineCacheMax?: number; - perUserListTimelineCacheMax?: number; - enableReactionsBuffering?: boolean; - notesPerOneAd?: number; - silencedHosts?: string[] | null; - mediaSilencedHosts?: string[] | null; - /** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */ - summalyProxy?: string | null; - urlPreviewEnabled?: boolean; - urlPreviewTimeout?: number; - urlPreviewMaximumContentLength?: number; - urlPreviewRequireContentLength?: boolean; - urlPreviewUserAgent?: string | null; - urlPreviewSummaryProxyUrl?: string | null; - /** @enum {string} */ - federation?: 'all' | 'none' | 'specified'; - federationHosts?: string[]; + to: string; + subject: string; + text: string; }; }; }; @@ -9907,24 +9527,113 @@ export type operations = { }; }; /** - * admin/delete-account + * admin/server-info * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* + * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* */ - 'admin___delete-account': { + 'admin___server-info': { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + machine: string; + /** @example linux */ + os: string; + node: string; + psql: string; + cpu: { + model: string; + cores: number; + }; + mem: { + /** Format: bytes */ + total: number; + }; + fs: { + /** Format: bytes */ + total: number; + /** Format: bytes */ + used: number; + }; + net: { + /** @example eth0 */ + interface: string; + }; + }; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * admin/show-moderation-logs + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* + */ + 'admin___show-moderation-logs': { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - userId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + type?: string | null; + /** Format: misskey:id */ + userId?: string | null; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + type: string; + info: Record; + /** Format: id */ + userId: string; + user: components['schemas']['UserDetailedNotMe']; + }[]; + }; }; /** @description Client error */ 400: { @@ -9959,26 +9668,183 @@ export type operations = { }; }; /** - * admin/update-user-note + * admin/show-user * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* + * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ - 'admin___update-user-note': { + 'admin___show-user': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ userId: string; - text: string; }; }; - }; - responses: { - /** @description OK (without any results) */ - 204: { - content: never; - }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + email: string | null; + emailVerified: boolean; + followedMessage: string | null; + autoAcceptFollowed: boolean; + noCrawle: boolean; + preventAiLearning: boolean; + alwaysMarkNsfw: boolean; + autoSensitive: boolean; + carefulBot: boolean; + injectFeaturedNote: boolean; + receiveAnnouncementEmail: boolean; + mutedWords: (string | string[])[]; + mutedInstances: string[]; + notificationRecieveConfig: { + note?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + follow?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + mention?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + reply?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + renote?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + quote?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + reaction?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + pollEnded?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + receiveFollowRequest?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + followRequestAccepted?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + roleAssigned?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + achievementEarned?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + app?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + test?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + }; + isModerator: boolean; + isSilenced: boolean; + isSuspended: boolean; + isHibernated: boolean; + lastActiveDate: string | null; + moderationNote: string; + signins: components['schemas']['Signin'][]; + policies: components['schemas']['RolePolicies']; + roles: components['schemas']['Role'][]; + roleAssigns: ({ + createdAt: string; + expiresAt: string | null; + roleId: string; + })[]; + }; + }; + }; /** @description Client error */ 400: { content: { @@ -10012,31 +9878,38 @@ export type operations = { }; }; /** - * admin/roles/create + * admin/show-users * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ - admin___roles___create: { + 'admin___show-users': { requestBody: { content: { 'application/json': { - name: string; - description: string; - color: string | null; - iconUrl: string | null; + /** @default 10 */ + limit?: number; + /** @default 0 */ + offset?: number; /** @enum {string} */ - target: 'manual' | 'conditional'; - condFormula: Record; - isPublic: boolean; - isModerator: boolean; - isAdministrator: boolean; - /** @default false */ - isExplorable?: boolean; - asBadge: boolean; - canEditMembersByModerator: boolean; - displayOrder: number; - policies: Record; + sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt' | '+lastActiveDate' | '-lastActiveDate'; + /** + * @default all + * @enum {string} + */ + state?: 'all' | 'alive' | 'available' | 'admin' | 'moderator' | 'adminOrModerator' | 'suspended'; + /** + * @default combined + * @enum {string} + */ + origin?: 'combined' | 'local' | 'remote'; + /** @default null */ + username?: string | null; + /** + * @description The local host is represented with `null`. + * @default null + */ + hostname?: string | null; }; }; }; @@ -10044,7 +9917,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Role']; + 'application/json': components['schemas']['UserDetailed'][]; }; }; /** @description Client error */ @@ -10080,17 +9953,17 @@ export type operations = { }; }; /** - * admin/roles/delete + * admin/suspend-user * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* */ - admin___roles___delete: { + 'admin___suspend-user': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - roleId: string; + userId: string; }; }; }; @@ -10132,63 +10005,21 @@ export type operations = { }; }; /** - * admin/roles/list - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:admin:roles* - */ - admin___roles___list: { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Role'][]; - }; - }; - /** @description Client error */ - 400: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Authentication error */ - 401: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Forbidden error */ - 403: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - }; - }; - /** - * admin/roles/show + * admin/system-webhook/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - admin___roles___show: { + 'admin___system-webhook___create': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - roleId: string; + isActive: boolean; + name: string; + on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; + url: string; + secret: string; }; }; }; @@ -10196,7 +10027,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Role']; + 'application/json': components['schemas']['SystemWebhook']; }; }; /** @description Client error */ @@ -10232,32 +10063,18 @@ export type operations = { }; }; /** - * admin/roles/update + * admin/system-webhook/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - admin___roles___update: { + 'admin___system-webhook___delete': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - roleId: string; - name?: string; - description?: string; - color?: string | null; - iconUrl?: string | null; - /** @enum {string} */ - target?: 'manual' | 'conditional'; - condFormula?: Record; - isPublic?: boolean; - isModerator?: boolean; - isAdministrator?: boolean; - isExplorable?: boolean; - asBadge?: boolean; - canEditMembersByModerator?: boolean; - displayOrder?: number; - policies?: Record; + id: string; }; }; }; @@ -10299,27 +10116,27 @@ export type operations = { }; }; /** - * admin/roles/assign + * admin/system-webhook/list * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - admin___roles___assign: { + 'admin___system-webhook___list': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - roleId: string; - /** Format: misskey:id */ - userId: string; - expiresAt?: number | null; + isActive?: boolean; + on?: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['SystemWebhook'][]; + }; }; /** @description Client error */ 400: { @@ -10354,27 +10171,28 @@ export type operations = { }; }; /** - * admin/roles/unassign + * admin/system-webhook/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - admin___roles___unassign: { + 'admin___system-webhook___show': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - roleId: string; - /** Format: misskey:id */ - userId: string; + id: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['SystemWebhook']; }; }; - }; - responses: { - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -10408,16 +10226,24 @@ export type operations = { }; }; /** - * admin/roles/update-default-policies + * admin/system-webhook/test * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* */ - 'admin___roles___update-default-policies': { + 'admin___system-webhook___test': { requestBody: { content: { 'application/json': { - policies: Record; + /** Format: misskey:id */ + webhookId: string; + /** @enum {string} */ + type: 'abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged'; + override?: { + url?: string; + secret?: string; + }; }; }; }; @@ -10450,6 +10276,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -10459,23 +10291,23 @@ export type operations = { }; }; /** - * admin/roles/users + * admin/system-webhook/update * @description No description provided. * - * **Credential required**: *No* / **Permission**: *read:admin:roles* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ - admin___roles___users: { + 'admin___system-webhook___update': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - roleId: string; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ - limit?: number; + id: string; + isActive: boolean; + name: string; + on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; + url: string; + secret: string; }; }; }; @@ -10483,15 +10315,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': ({ - /** Format: misskey:id */ - id: string; - /** Format: date-time */ - createdAt: string; - user: components['schemas']['UserDetailed']; - /** Format: date-time */ - expiresAt: string | null; - })[]; + 'application/json': components['schemas']['SystemWebhook']; }; }; /** @description Client error */ @@ -10527,30 +10351,24 @@ export type operations = { }; }; /** - * admin/system-webhook/create + * admin/unset-user-avatar * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* */ - 'admin___system-webhook___create': { + 'admin___unset-user-avatar': { requestBody: { content: { 'application/json': { - isActive: boolean; - name: string; - on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; - url: string; - secret: string; + /** Format: misskey:id */ + userId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['SystemWebhook']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -10585,18 +10403,17 @@ export type operations = { }; }; /** - * admin/system-webhook/delete + * admin/unset-user-banner * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* */ - 'admin___system-webhook___delete': { + 'admin___unset-user-banner': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - id: string; + userId: string; }; }; }; @@ -10638,27 +10455,24 @@ export type operations = { }; }; /** - * admin/system-webhook/list + * admin/unsuspend-user * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* */ - 'admin___system-webhook___list': { + 'admin___unsuspend-user': { requestBody: { content: { 'application/json': { - isActive?: boolean; - on?: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; + /** Format: misskey:id */ + userId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['SystemWebhook'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -10693,27 +10507,25 @@ export type operations = { }; }; /** - * admin/system-webhook/show + * admin/update-abuse-user-report * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ - 'admin___system-webhook___show': { + 'admin___update-abuse-user-report': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - id: string; + reportId: string; + moderationNote?: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['SystemWebhook']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -10748,32 +10560,141 @@ export type operations = { }; }; /** - * admin/system-webhook/update + * admin/update-meta * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ - 'admin___system-webhook___update': { + 'admin___update-meta': { requestBody: { content: { 'application/json': { + disableRegistration?: boolean | null; + pinnedUsers?: string[] | null; + hiddenTags?: string[] | null; + blockedHosts?: string[] | null; + sensitiveWords?: string[] | null; + prohibitedWords?: string[] | null; + prohibitedWordsForNameOfUser?: string[] | null; + themeColor?: string | null; + mascotImageUrl?: string | null; + bannerUrl?: string | null; + serverErrorImageUrl?: string | null; + infoImageUrl?: string | null; + notFoundImageUrl?: string | null; + iconUrl?: string | null; + app192IconUrl?: string | null; + app512IconUrl?: string | null; + backgroundImageUrl?: string | null; + logoImageUrl?: string | null; + name?: string | null; + shortName?: string | null; + description?: string | null; + defaultLightTheme?: string | null; + defaultDarkTheme?: string | null; + cacheRemoteFiles?: boolean; + cacheRemoteSensitiveFiles?: boolean; + emailRequiredForSignup?: boolean; + enableHcaptcha?: boolean; + hcaptchaSiteKey?: string | null; + hcaptchaSecretKey?: string | null; + enableMcaptcha?: boolean; + mcaptchaSiteKey?: string | null; + mcaptchaInstanceUrl?: string | null; + mcaptchaSecretKey?: string | null; + enableRecaptcha?: boolean; + recaptchaSiteKey?: string | null; + recaptchaSecretKey?: string | null; + enableTurnstile?: boolean; + turnstileSiteKey?: string | null; + turnstileSecretKey?: string | null; + enableTestcaptcha?: boolean; + /** @enum {string} */ + sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote'; + /** @enum {string} */ + sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh'; + setSensitiveFlagAutomatically?: boolean; + enableSensitiveMediaDetectionForVideos?: boolean; /** Format: misskey:id */ - id: string; - isActive: boolean; - name: string; - on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; - url: string; - secret: string; + proxyAccountId?: string | null; + maintainerName?: string | null; + maintainerEmail?: string | null; + langs?: string[]; + deeplAuthKey?: string | null; + deeplIsPro?: boolean; + enableEmail?: boolean; + email?: string | null; + smtpSecure?: boolean; + smtpHost?: string | null; + smtpPort?: number | null; + smtpUser?: string | null; + smtpPass?: string | null; + enableServiceWorker?: boolean; + swPublicKey?: string | null; + swPrivateKey?: string | null; + tosUrl?: string | null; + repositoryUrl?: string | null; + feedbackUrl?: string | null; + impressumUrl?: string | null; + privacyPolicyUrl?: string | null; + inquiryUrl?: string | null; + useObjectStorage?: boolean; + objectStorageBaseUrl?: string | null; + objectStorageBucket?: string | null; + objectStoragePrefix?: string | null; + objectStorageEndpoint?: string | null; + objectStorageRegion?: string | null; + objectStoragePort?: number | null; + objectStorageAccessKey?: string | null; + objectStorageSecretKey?: string | null; + objectStorageUseSSL?: boolean; + objectStorageUseProxy?: boolean; + objectStorageSetPublicRead?: boolean; + objectStorageS3ForcePathStyle?: boolean; + enableIpLogging?: boolean; + enableActiveEmailValidation?: boolean; + enableVerifymailApi?: boolean; + verifymailAuthKey?: string | null; + enableTruemailApi?: boolean; + truemailInstance?: string | null; + truemailAuthKey?: string | null; + enableChartsForRemoteUser?: boolean; + enableChartsForFederatedInstances?: boolean; + enableStatsForFederatedInstances?: boolean; + enableServerMachineStats?: boolean; + enableIdenticonGeneration?: boolean; + serverRules?: string[]; + bannedEmailDomains?: string[]; + preservedUsernames?: string[]; + manifestJsonOverride?: string; + enableFanoutTimeline?: boolean; + enableFanoutTimelineDbFallback?: boolean; + perLocalUserUserTimelineCacheMax?: number; + perRemoteUserUserTimelineCacheMax?: number; + perUserHomeTimelineCacheMax?: number; + perUserListTimelineCacheMax?: number; + enableReactionsBuffering?: boolean; + notesPerOneAd?: number; + silencedHosts?: string[] | null; + mediaSilencedHosts?: string[] | null; + /** @description [Deprecated] Use "urlPreviewSummaryProxyUrl" instead. */ + summalyProxy?: string | null; + urlPreviewEnabled?: boolean; + urlPreviewTimeout?: number; + urlPreviewMaximumContentLength?: number; + urlPreviewRequireContentLength?: boolean; + urlPreviewUserAgent?: string | null; + urlPreviewSummaryProxyUrl?: string | null; + /** @enum {string} */ + federation?: 'all' | 'none' | 'specified'; + federationHosts?: string[]; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['SystemWebhook']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -10808,24 +10729,18 @@ export type operations = { }; }; /** - * admin/system-webhook/test + * admin/update-user-note * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* + * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* */ - 'admin___system-webhook___test': { + 'admin___update-user-note': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - webhookId: string; - /** @enum {string} */ - type: 'abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged'; - override?: { - url?: string; - secret?: string; - }; + userId: string; + text: string; }; }; }; @@ -10858,12 +10773,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -11971,22 +11880,16 @@ export type operations = { }; }; /** - * channels/create + * bubble-game/ranking * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - channels___create: { + 'bubble-game___ranking': { requestBody: { content: { 'application/json': { - name: string; - description?: string | null; - /** Format: misskey:id */ - bannerId?: string | null; - color?: string; - isSensitive?: boolean | null; - allowRenoteToExternal?: boolean | null; + gameMode: string; }; }; }; @@ -11994,9 +11897,69 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Channel']; + 'application/json': { + /** Format: misskey:id */ + id: string; + score: number; + user?: components['schemas']['UserLite']; + }[]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; }; }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * bubble-game/register + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'bubble-game___register': { + requestBody: { + content: { + 'application/json': { + score: number; + seed: string; + logs: number[][]; + gameMode: string; + gameVersion: number; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -12036,17 +11999,30 @@ export type operations = { }; }; /** - * channels/featured + * channels/create * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - channels___featured: { + channels___create: { + requestBody: { + content: { + 'application/json': { + name: string; + description?: string | null; + /** Format: misskey:id */ + bannerId?: string | null; + color?: string; + isSensitive?: boolean | null; + allowRenoteToExternal?: boolean | null; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Channel'][]; + 'application/json': components['schemas']['Channel']; }; }; /** @description Client error */ @@ -12073,6 +12049,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -12082,12 +12064,12 @@ export type operations = { }; }; /** - * channels/follow + * channels/favorite * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:channels* */ - channels___follow: { + channels___favorite: { requestBody: { content: { 'application/json': { @@ -12134,30 +12116,70 @@ export type operations = { }; }; /** - * channels/followed + * channels/featured * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:channels* + * **Credential required**: *No* */ - channels___followed: { + channels___featured: { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Channel'][]; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * channels/follow + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:channels* + */ + channels___follow: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 5 */ - limit?: number; + channelId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Channel'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -12192,12 +12214,12 @@ export type operations = { }; }; /** - * channels/owned + * channels/followed * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:channels* */ - channels___owned: { + channels___followed: { requestBody: { content: { 'application/json': { @@ -12250,25 +12272,17 @@ export type operations = { }; }; /** - * channels/show + * channels/my-favorites * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:channels* */ - channels___show: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - channelId: string; - }; - }; - }; + 'channels___my-favorites': { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Channel']; + 'application/json': components['schemas']['Channel'][]; }; }; /** @description Client error */ @@ -12304,27 +12318,21 @@ export type operations = { }; }; /** - * channels/timeline + * channels/owned * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:channels* */ - channels___timeline: { + channels___owned: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - channelId: string; - /** @default 10 */ - limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - sinceDate?: number; - untilDate?: number; - /** @default false */ - allowPartial?: boolean; + /** @default 5 */ + limit?: number; }; }; }; @@ -12332,7 +12340,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': components['schemas']['Channel'][]; }; }; /** @description Client error */ @@ -12368,24 +12376,36 @@ export type operations = { }; }; /** - * channels/unfollow + * channels/search * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - channels___unfollow: { + channels___search: { requestBody: { content: { 'application/json': { + query: string; + /** + * @default nameAndDescription + * @enum {string} + */ + type?: 'nameAndDescription' | 'nameOnly'; + /** Format: misskey:id */ + sinceId?: string; /** Format: misskey:id */ - channelId: string; + untilId?: string; + /** @default 5 */ + limit?: number; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Channel'][]; + }; }; /** @description Client error */ 400: { @@ -12420,26 +12440,17 @@ export type operations = { }; }; /** - * channels/update + * channels/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - channels___update: { + channels___show: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ channelId: string; - name?: string; - description?: string | null; - /** Format: misskey:id */ - bannerId?: string | null; - isArchived?: boolean | null; - pinnedNoteIds?: string[]; - color?: string; - isSensitive?: boolean | null; - allowRenoteToExternal?: boolean | null; }; }; }; @@ -12483,24 +12494,36 @@ export type operations = { }; }; /** - * channels/favorite + * channels/timeline * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:channels* + * **Credential required**: *No* */ - channels___favorite: { + channels___timeline: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ channelId: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; + /** @default false */ + allowPartial?: boolean; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Note'][]; + }; }; /** @description Client error */ 400: { @@ -12587,19 +12610,25 @@ export type operations = { }; }; /** - * channels/my-favorites + * channels/unfollow * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:channels* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - 'channels___my-favorites': { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Channel'][]; + channels___unfollow: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + channelId: string; }; }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -12633,27 +12662,26 @@ export type operations = { }; }; /** - * channels/search + * channels/update * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:channels* */ - channels___search: { + channels___update: { requestBody: { content: { 'application/json': { - query: string; - /** - * @default nameAndDescription - * @enum {string} - */ - type?: 'nameAndDescription' | 'nameOnly'; /** Format: misskey:id */ - sinceId?: string; + channelId: string; + name?: string; + description?: string | null; /** Format: misskey:id */ - untilId?: string; - /** @default 5 */ - limit?: number; + bannerId?: string | null; + isArchived?: boolean | null; + pinnedNoteIds?: string[]; + color?: string; + isSensitive?: boolean | null; + allowRenoteToExternal?: boolean | null; }; }; }; @@ -12661,7 +12689,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Channel'][]; + 'application/json': components['schemas']['Channel']; }; }; /** @description Client error */ @@ -13631,26 +13659,28 @@ export type operations = { }; }; /** - * clips/remove-note + * clips/create * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'clips___remove-note': { + clips___create: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - clipId: string; - /** Format: misskey:id */ - noteId: string; + name: string; + /** @default false */ + isPublic?: boolean; + description?: string | null; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Clip']; + }; }; /** @description Client error */ 400: { @@ -13685,28 +13715,24 @@ export type operations = { }; }; /** - * clips/create + * clips/delete * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - clips___create: { + clips___delete: { requestBody: { content: { 'application/json': { - name: string; - /** @default false */ - isPublic?: boolean; - description?: string | null; + /** Format: misskey:id */ + clipId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Clip']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -13741,12 +13767,12 @@ export type operations = { }; }; /** - * clips/delete + * clips/favorite * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ - clips___delete: { + clips___favorite: { requestBody: { content: { 'application/json': { @@ -13839,31 +13865,17 @@ export type operations = { }; }; /** - * clips/notes + * clips/my-favorites * @description No description provided. * - * **Credential required**: *No* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* */ - clips___notes: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - clipId: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - }; - }; - }; + 'clips___my-favorites': { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': components['schemas']['Clip'][]; }; }; /** @description Client error */ @@ -13899,17 +13911,23 @@ export type operations = { }; }; /** - * clips/show + * clips/notes * @description No description provided. * * **Credential required**: *No* / **Permission**: *read:account* */ - clips___show: { + clips___notes: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ clipId: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; @@ -13917,7 +13935,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Clip']; + 'application/json': components['schemas']['Note'][]; }; }; /** @description Client error */ @@ -13953,29 +13971,26 @@ export type operations = { }; }; /** - * clips/update + * clips/remove-note * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - clips___update: { + 'clips___remove-note': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ clipId: string; - name?: string; - isPublic?: boolean; - description?: string | null; + /** Format: misskey:id */ + noteId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Clip']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -14010,12 +14025,12 @@ export type operations = { }; }; /** - * clips/favorite + * clips/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* + * **Credential required**: *No* / **Permission**: *read:account* */ - clips___favorite: { + clips___show: { requestBody: { content: { 'application/json': { @@ -14025,9 +14040,11 @@ export type operations = { }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Clip']; + }; }; /** @description Client error */ 400: { @@ -14114,17 +14131,28 @@ export type operations = { }; }; /** - * clips/my-favorites + * clips/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'clips___my-favorites': { + clips___update: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + clipId: string; + name?: string; + isPublic?: boolean; + description?: string | null; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Clip'][]; + 'application/json': components['schemas']['Clip']; }; }; /** @description Client error */ @@ -14516,16 +14544,21 @@ export type operations = { }; }; /** - * drive/files/find-by-hash - * @description Search for a drive file by a hash of the contents. + * drive/files/find + * @description Search for a drive file by the given parameters. * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - 'drive___files___find-by-hash': { + drive___files___find: { requestBody: { content: { 'application/json': { - md5: string; + name: string; + /** + * Format: misskey:id + * @default null + */ + folderId?: string | null; }; }; }; @@ -14569,21 +14602,16 @@ export type operations = { }; }; /** - * drive/files/find - * @description Search for a drive file by the given parameters. + * drive/files/find-by-hash + * @description Search for a drive file by a hash of the contents. * * **Credential required**: *Yes* / **Permission**: *read:drive* */ - drive___files___find: { - requestBody: { - content: { - 'application/json': { - name: string; - /** - * Format: misskey:id - * @default null - */ - folderId?: string | null; + 'drive___files___find-by-hash': { + requestBody: { + content: { + 'application/json': { + md5: string; }; }; }; @@ -15272,16 +15300,16 @@ export type operations = { }; }; /** - * endpoint + * emoji * @description No description provided. * * **Credential required**: *No* */ - endpoint: { + emoji: { requestBody: { content: { 'application/json': { - endpoint: string; + name: string; }; }; }; @@ -15289,18 +15317,9 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - params: { - name: string; - type: string; - }[]; - } | null; + 'application/json': components['schemas']['EmojiDetailed']; }; }; - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -15334,64 +15353,21 @@ export type operations = { }; }; /** - * endpoints + * emojis * @description No description provided. * * **Credential required**: *No* */ - endpoints: { + emojis: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': string[]; - }; - }; - /** @description Client error */ - 400: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Authentication error */ - 401: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Forbidden error */ - 403: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; + 'application/json': { + emojis: components['schemas']['EmojiSimple'][]; + }; }; }; - }; - }; - /** - * export-custom-emojis - * @description No description provided. - * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* - */ - 'export-custom-emojis': { - responses: { - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -15416,12 +15392,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -15431,22 +15401,16 @@ export type operations = { }; }; /** - * federation/followers + * endpoint * @description No description provided. * * **Credential required**: *No* */ - federation___followers: { + endpoint: { requestBody: { content: { 'application/json': { - host: string; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ - limit?: number; + endpoint: string; }; }; }; @@ -15454,9 +15418,18 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Following'][]; + 'application/json': { + params: { + name: string; + type: string; + }[]; + } | null; }; }; + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -15490,30 +15463,17 @@ export type operations = { }; }; /** - * federation/following + * endpoints * @description No description provided. * * **Credential required**: *No* */ - federation___following: { - requestBody: { - content: { - 'application/json': { - host: string; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ - limit?: number; - }; - }; - }; + endpoints: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Following'][]; + 'application/json': string[]; }; }; /** @description Client error */ @@ -15549,39 +15509,17 @@ export type operations = { }; }; /** - * federation/instances + * export-custom-emojis * @description No description provided. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - federation___instances: { - requestBody: { - content: { - 'application/json': { - /** @description Omit or use `null` to not filter by host. */ - host?: string | null; - blocked?: boolean | null; - notResponding?: boolean | null; - suspended?: boolean | null; - silenced?: boolean | null; - federating?: boolean | null; - subscribing?: boolean | null; - publishing?: boolean | null; - /** @default 30 */ - limit?: number; - /** @default 0 */ - offset?: number; - /** @enum {string|null} */ - sort?: '+pubSub' | '-pubSub' | '+notes' | '-notes' | '+users' | '-users' | '+following' | '-following' | '+followers' | '-followers' | '+firstRetrievedAt' | '-firstRetrievedAt' | '+latestRequestReceivedAt' | '-latestRequestReceivedAt' | null; - }; - }; - }; + 'export-custom-emojis': { responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['FederationInstance'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -15607,6 +15545,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -15616,16 +15560,22 @@ export type operations = { }; }; /** - * federation/show-instance + * federation/followers * @description No description provided. * * **Credential required**: *No* */ - 'federation___show-instance': { + federation___followers: { requestBody: { content: { 'application/json': { host: string; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; }; }; }; @@ -15633,13 +15583,9 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['FederationInstance'] | null; + 'application/json': components['schemas']['Following'][]; }; }; - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -15673,24 +15619,31 @@ export type operations = { }; }; /** - * federation/update-remote-user + * federation/following * @description No description provided. * * **Credential required**: *No* */ - 'federation___update-remote-user': { + federation___following: { requestBody: { content: { 'application/json': { + host: string; /** Format: misskey:id */ - userId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Following'][]; + }; }; /** @description Client error */ 400: { @@ -15725,22 +15678,30 @@ export type operations = { }; }; /** - * federation/users + * federation/instances * @description No description provided. * * **Credential required**: *No* */ - federation___users: { + federation___instances: { requestBody: { content: { 'application/json': { - host: string; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ + /** @description Omit or use `null` to not filter by host. */ + host?: string | null; + blocked?: boolean | null; + notResponding?: boolean | null; + suspended?: boolean | null; + silenced?: boolean | null; + federating?: boolean | null; + subscribing?: boolean | null; + publishing?: boolean | null; + /** @default 30 */ limit?: number; + /** @default 0 */ + offset?: number; + /** @enum {string|null} */ + sort?: '+pubSub' | '-pubSub' | '+notes' | '-notes' | '+users' | '-users' | '+following' | '-following' | '+followers' | '-followers' | '+firstRetrievedAt' | '-firstRetrievedAt' | '+latestRequestReceivedAt' | '-latestRequestReceivedAt' | null; }; }; }; @@ -15748,7 +15709,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserDetailedNotMe'][]; + 'application/json': components['schemas']['FederationInstance'][]; }; }; /** @description Client error */ @@ -15784,17 +15745,16 @@ export type operations = { }; }; /** - * federation/stats + * federation/show-instance * @description No description provided. * * **Credential required**: *No* */ - federation___stats: { + 'federation___show-instance': { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; + host: string; }; }; }; @@ -15802,14 +15762,13 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - topSubInstances: components['schemas']['FederationInstance'][]; - otherFollowersCount: number; - topPubInstances: components['schemas']['FederationInstance'][]; - otherFollowingCount: number; - }; + 'application/json': components['schemas']['FederationInstance'] | null; }; }; + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -15843,18 +15802,17 @@ export type operations = { }; }; /** - * following/create + * federation/stats * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *No* */ - following___create: { + federation___stats: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - userId: string; - withReplies?: boolean; + /** @default 10 */ + limit?: number; }; }; }; @@ -15862,7 +15820,12 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserLite']; + 'application/json': { + topSubInstances: components['schemas']['FederationInstance'][]; + otherFollowersCount: number; + topPubInstances: components['schemas']['FederationInstance'][]; + otherFollowingCount: number; + }; }; }; /** @description Client error */ @@ -15889,12 +15852,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -15904,12 +15861,12 @@ export type operations = { }; }; /** - * following/delete + * federation/update-remote-user * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *No* */ - following___delete: { + 'federation___update-remote-user': { requestBody: { content: { 'application/json': { @@ -15919,11 +15876,9 @@ export type operations = { }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['UserLite']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -15949,12 +15904,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -15964,20 +15913,22 @@ export type operations = { }; }; /** - * following/update + * federation/users * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *No* */ - following___update: { + federation___users: { requestBody: { content: { 'application/json': { + host: string; /** Format: misskey:id */ - userId: string; - /** @enum {string} */ - notify?: 'normal' | 'none'; - withReplies?: boolean; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; }; }; }; @@ -15985,7 +15936,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserLite']; + 'application/json': components['schemas']['UserDetailedNotMe'][]; }; }; /** @description Client error */ @@ -16012,12 +15963,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -16027,25 +15972,30 @@ export type operations = { }; }; /** - * following/update-all + * fetch-external-resources * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'following___update-all': { + 'fetch-external-resources': { requestBody: { content: { 'application/json': { - /** @enum {string} */ - notify?: 'normal' | 'none'; - withReplies?: boolean; + url: string; + hash: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + type: string; + data: string; + }; + }; }; /** @description Client error */ 400: { @@ -16086,17 +16036,16 @@ export type operations = { }; }; /** - * following/invalidate + * fetch-rss * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *No* */ - following___invalidate: { + 'fetch-rss': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - userId: string; + url: string; }; }; }; @@ -16104,7 +16053,54 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserLite']; + 'application/json': { + image?: { + link?: string; + url: string; + title?: string; + }; + paginationLinks?: { + self?: string; + first?: string; + next?: string; + last?: string; + prev?: string; + }; + link?: string; + title?: string; + items: { + link?: string; + guid?: string; + title?: string; + pubDate?: string; + creator?: string; + summary?: string; + content?: string; + isoDate?: string; + categories?: string[]; + contentSnippet?: string; + enclosure?: { + url: string; + length?: number; + type?: string; + }; + }[]; + feedUrl?: string; + description?: string; + itunes?: { + image?: string; + owner?: { + name?: string; + email?: string; + }; + author?: string; + summary?: string; + explicit?: string; + categories?: string[]; + keywords?: string[]; + [key: string]: unknown; + }; + }; }; }; /** @description Client error */ @@ -16131,12 +16127,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -16146,24 +16136,33 @@ export type operations = { }; }; /** - * following/requests/accept + * flash/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *Yes* / **Permission**: *write:flash* */ - following___requests___accept: { + flash___create: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - userId: string; + title: string; + summary: string; + script: string; + permissions: string[]; + /** + * @default public + * @enum {string} + */ + visibility?: 'public' | 'private'; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Flash']; + }; }; /** @description Client error */ 400: { @@ -16189,6 +16188,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -16198,26 +16203,24 @@ export type operations = { }; }; /** - * following/requests/cancel + * flash/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *Yes* / **Permission**: *write:flash* */ - following___requests___cancel: { + flash___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; + flashId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['UserLite']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -16252,19 +16255,17 @@ export type operations = { }; }; /** - * following/requests/list + * flash/featured * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:following* + * **Credential required**: *No* */ - following___requests___list: { + flash___featured: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + /** @default 0 */ + offset?: number; /** @default 10 */ limit?: number; }; @@ -16274,12 +16275,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: id */ - id: string; - follower: components['schemas']['UserLite']; - followee: components['schemas']['UserLite']; - }[]; + 'application/json': components['schemas']['Flash'][]; }; }; /** @description Client error */ @@ -16315,35 +16311,24 @@ export type operations = { }; }; /** - * following/requests/sent + * flash/like * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:following* + * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ - following___requests___sent: { + flash___like: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ - limit?: number; + flashId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - /** Format: id */ - id: string; - follower: components['schemas']['UserLite']; - followee: components['schemas']['UserLite']; - }[]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -16378,24 +16363,30 @@ export type operations = { }; }; /** - * following/requests/reject + * flash/my * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:following* + * **Credential required**: *Yes* / **Permission**: *read:flash* */ - following___requests___reject: { + flash___my: { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - userId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Flash'][]; + }; }; /** @description Client error */ 400: { @@ -16430,18 +16421,20 @@ export type operations = { }; }; /** - * gallery/featured + * flash/my-likes * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:flash-likes* */ - gallery___featured: { + 'flash___my-likes': { requestBody: { content: { 'application/json': { /** @default 10 */ limit?: number; /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ untilId?: string; }; }; @@ -16450,7 +16443,11 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['GalleryPost'][]; + 'application/json': { + /** Format: id */ + id: string; + flash: components['schemas']['Flash']; + }[]; }; }; /** @description Client error */ @@ -16486,17 +16483,25 @@ export type operations = { }; }; /** - * gallery/popular + * flash/show * @description No description provided. * * **Credential required**: *No* */ - gallery___popular: { + flash___show: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + flashId: string; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['GalleryPost'][]; + 'application/json': components['schemas']['Flash']; }; }; /** @description Client error */ @@ -16532,30 +16537,24 @@ export type operations = { }; }; /** - * gallery/posts + * flash/unlike * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ - gallery___posts: { + flash___unlike: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; /** Format: misskey:id */ - untilId?: string; + flashId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['GalleryPost'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -16590,29 +16589,30 @@ export type operations = { }; }; /** - * gallery/posts/create + * flash/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:gallery* + * **Credential required**: *Yes* / **Permission**: *write:flash* */ - gallery___posts___create: { + flash___update: { requestBody: { content: { 'application/json': { - title: string; - description?: string | null; - fileIds: string[]; - /** @default false */ - isSensitive?: boolean; + /** Format: misskey:id */ + flashId: string; + title?: string; + summary?: string; + script?: string; + permissions?: string[]; + /** @enum {string} */ + visibility?: 'public' | 'private'; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['GalleryPost']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -16653,24 +16653,27 @@ export type operations = { }; }; /** - * gallery/posts/delete + * following/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:gallery* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - gallery___posts___delete: { + following___create: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - postId: string; + userId: string; + withReplies?: boolean; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserLite']; + }; }; /** @description Client error */ 400: { @@ -16696,6 +16699,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -16705,24 +16714,26 @@ export type operations = { }; }; /** - * gallery/posts/like + * following/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - gallery___posts___like: { + following___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - postId: string; + userId: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserLite']; + }; }; /** @description Client error */ 400: { @@ -16748,6 +16759,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -16757,17 +16774,17 @@ export type operations = { }; }; /** - * gallery/posts/show + * following/invalidate * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - gallery___posts___show: { + following___invalidate: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - postId: string; + userId: string; }; }; }; @@ -16775,7 +16792,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['GalleryPost']; + 'application/json': components['schemas']['UserLite']; }; }; /** @description Client error */ @@ -16802,6 +16819,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -16811,17 +16834,17 @@ export type operations = { }; }; /** - * gallery/posts/unlike + * following/requests/accept * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - gallery___posts___unlike: { + following___requests___accept: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - postId: string; + userId: string; }; }; }; @@ -16863,22 +16886,17 @@ export type operations = { }; }; /** - * gallery/posts/update + * following/requests/cancel * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:gallery* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - gallery___posts___update: { + following___requests___cancel: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - postId: string; - title?: string; - description?: string | null; - fileIds?: string[]; - /** @default false */ - isSensitive?: boolean; + userId: string; }; }; }; @@ -16886,7 +16904,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['GalleryPost']; + 'application/json': components['schemas']['UserLite']; }; }; /** @description Client error */ @@ -16913,12 +16931,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -16928,19 +16940,34 @@ export type operations = { }; }; /** - * get-online-users-count + * following/requests/list * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:following* */ - 'get-online-users-count': { + following___requests___list: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { 'application/json': { - count: number; - }; + /** Format: id */ + id: string; + follower: components['schemas']['UserLite']; + followee: components['schemas']['UserLite']; + }[]; }; }; /** @description Client error */ @@ -16976,29 +17003,25 @@ export type operations = { }; }; /** - * get-avatar-decorations + * following/requests/reject * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - 'get-avatar-decorations': { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - /** - * Format: id - * @example xxxxxxxxxx - */ - id: string; - name: string; - description: string; - url: string; - roleIdsThatCanBeUsedThisDecoration: string[]; - }[]; + following___requests___reject: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId: string; }; }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -17032,25 +17055,21 @@ export type operations = { }; }; /** - * hashtags/list + * following/requests/sent * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:following* */ - hashtags___list: { + following___requests___sent: { requestBody: { content: { 'application/json': { + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; /** @default 10 */ limit?: number; - /** @default false */ - attachedToUserOnly?: boolean; - /** @default false */ - attachedToLocalUserOnly?: boolean; - /** @default false */ - attachedToRemoteUserOnly?: boolean; - /** @enum {string} */ - sort: '+mentionedUsers' | '-mentionedUsers' | '+mentionedLocalUsers' | '-mentionedLocalUsers' | '+mentionedRemoteUsers' | '-mentionedRemoteUsers' | '+attachedUsers' | '-attachedUsers' | '+attachedLocalUsers' | '-attachedLocalUsers' | '+attachedRemoteUsers' | '-attachedRemoteUsers'; }; }; }; @@ -17058,7 +17077,12 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Hashtag'][]; + 'application/json': { + /** Format: id */ + id: string; + follower: components['schemas']['UserLite']; + followee: components['schemas']['UserLite']; + }[]; }; }; /** @description Client error */ @@ -17094,20 +17118,20 @@ export type operations = { }; }; /** - * hashtags/search + * following/update * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - hashtags___search: { + following___update: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - query: string; - /** @default 0 */ - offset?: number; + /** Format: misskey:id */ + userId: string; + /** @enum {string} */ + notify?: 'normal' | 'none'; + withReplies?: boolean; }; }; }; @@ -17115,7 +17139,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': string[]; + 'application/json': components['schemas']['UserLite']; }; }; /** @description Client error */ @@ -17142,6 +17166,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -17151,25 +17181,25 @@ export type operations = { }; }; /** - * hashtags/show + * following/update-all * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:following* */ - hashtags___show: { + 'following___update-all': { requestBody: { content: { 'application/json': { - tag: string; + /** @enum {string} */ + notify?: 'normal' | 'none'; + withReplies?: boolean; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Hashtag']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -17195,6 +17225,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -17204,21 +17240,27 @@ export type operations = { }; }; /** - * hashtags/trend + * gallery/featured * @description No description provided. * * **Credential required**: *No* */ - hashtags___trend: { + gallery___featured: { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + untilId?: string; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': { - tag: string; - chart: number[]; - usersCount: number; - }[]; + 'application/json': components['schemas']['GalleryPost'][]; }; }; /** @description Client error */ @@ -17254,38 +17296,17 @@ export type operations = { }; }; /** - * hashtags/users + * gallery/popular * @description No description provided. * * **Credential required**: *No* */ - hashtags___users: { - requestBody: { - content: { - 'application/json': { - tag: string; - /** @default 10 */ - limit?: number; - /** @enum {string} */ - sort: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; - /** - * @default all - * @enum {string} - */ - state?: 'all' | 'alive'; - /** - * @default local - * @enum {string} - */ - origin?: 'combined' | 'local' | 'remote'; - }; - }; - }; + gallery___popular: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserDetailed'][]; + 'application/json': components['schemas']['GalleryPost'][]; }; }; /** @description Client error */ @@ -17321,17 +17342,29 @@ export type operations = { }; }; /** - * i + * gallery/posts * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* */ - i: { + gallery___posts: { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['MeDetailed']; + 'application/json': components['schemas']['GalleryPost'][]; }; }; /** @description Client error */ @@ -17367,17 +17400,20 @@ export type operations = { }; }; /** - * i/2fa/done + * gallery/posts/create * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - i___2fa___done: { + gallery___posts___create: { requestBody: { content: { 'application/json': { - token: string; + title: string; + description?: string | null; + fileIds: string[]; + /** @default false */ + isSensitive?: boolean; }; }; }; @@ -17385,9 +17421,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - backupCodes: string[]; - }; + 'application/json': components['schemas']['GalleryPost']; }; }; /** @description Client error */ @@ -17414,6 +17448,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -17423,32 +17463,24 @@ export type operations = { }; }; /** - * i/2fa/key-done + * gallery/posts/delete * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - 'i___2fa___key-done': { + gallery___posts___delete: { requestBody: { content: { 'application/json': { - password: string; - token?: string | null; - name: string; - credential: Record; + /** Format: misskey:id */ + postId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - id: string; - name: string; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -17483,17 +17515,17 @@ export type operations = { }; }; /** - * i/2fa/password-less + * gallery/posts/like * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ - 'i___2fa___password-less': { + gallery___posts___like: { requestBody: { content: { 'application/json': { - value: boolean; + /** Format: misskey:id */ + postId: string; }; }; }; @@ -17535,18 +17567,17 @@ export type operations = { }; }; /** - * i/2fa/register-key + * gallery/posts/show * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - 'i___2fa___register-key': { + gallery___posts___show: { requestBody: { content: { 'application/json': { - password: string; - token?: string | null; + /** Format: misskey:id */ + postId: string; }; }; }; @@ -17554,41 +17585,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - rp: { - id?: string; - }; - user: { - id: string; - name: string; - displayName: string; - }; - challenge: string; - pubKeyCredParams: { - type: string; - alg: number; - }[]; - timeout: number | null; - excludeCredentials: (({ - id: string; - type: string; - transports: ('ble' | 'cable' | 'hybrid' | 'internal' | 'nfc' | 'smart-card' | 'usb')[]; - })[]) | null; - authenticatorSelection: ({ - /** @enum {string} */ - authenticatorAttachment: 'cross-platform' | 'platform'; - requireResidentKey: boolean; - /** @enum {string} */ - userVerification: 'discouraged' | 'preferred' | 'required'; - }) | null; - /** @enum {string|null} */ - attestation: 'direct' | 'enterprise' | 'indirect' | 'none' | null; - extensions: ({ - appid: string | null; - credProps: boolean | null; - hmacCreateSecret: boolean | null; - }) | null; - }; + 'application/json': components['schemas']['GalleryPost']; }; }; /** @description Client error */ @@ -17624,33 +17621,24 @@ export type operations = { }; }; /** - * i/2fa/register + * gallery/posts/unlike * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ - i___2fa___register: { + gallery___posts___unlike: { requestBody: { content: { 'application/json': { - password: string; - token?: string | null; + /** Format: misskey:id */ + postId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - qr: string; - url: string; - secret: string; - label: string; - issuer: string; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -17685,26 +17673,32 @@ export type operations = { }; }; /** - * i/2fa/update-key + * gallery/posts/update * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:gallery* */ - 'i___2fa___update-key': { + gallery___posts___update: { requestBody: { content: { 'application/json': { - name: string; - credentialId: string; + /** Format: misskey:id */ + postId: string; + title?: string; + description?: string | null; + fileIds?: string[]; + /** @default false */ + isSensitive?: boolean; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['GalleryPost']; }; }; - }; - responses: { - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -17729,6 +17723,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -17738,26 +17738,28 @@ export type operations = { }; }; /** - * i/2fa/remove-key + * get-avatar-decorations * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - 'i___2fa___remove-key': { - requestBody: { - content: { - 'application/json': { - password: string; - token?: string | null; - credentialId: string; - }; - }; - }; + 'get-avatar-decorations': { responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** + * Format: id + * @example xxxxxxxxxx + */ + id: string; + name: string; + description: string; + url: string; + roleIdsThatCanBeUsedThisDecoration: string[]; + }[]; + }; }; /** @description Client error */ 400: { @@ -17792,25 +17794,20 @@ export type operations = { }; }; /** - * i/2fa/unregister + * get-online-users-count * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - i___2fa___unregister: { - requestBody: { - content: { - 'application/json': { - password: string; - token?: string | null; - }; - }; - }; + 'get-online-users-count': { responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + count: number; + }; + }; }; /** @description Client error */ 400: { @@ -17845,18 +17842,25 @@ export type operations = { }; }; /** - * i/apps + * hashtags/list * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - i___apps: { + hashtags___list: { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; + /** @default false */ + attachedToUserOnly?: boolean; + /** @default false */ + attachedToLocalUserOnly?: boolean; + /** @default false */ + attachedToRemoteUserOnly?: boolean; /** @enum {string} */ - sort?: '+createdAt' | '-createdAt' | '+lastUsedAt' | '-lastUsedAt'; + sort: '+mentionedUsers' | '-mentionedUsers' | '+mentionedLocalUsers' | '-mentionedLocalUsers' | '+mentionedRemoteUsers' | '-mentionedRemoteUsers' | '+attachedUsers' | '-attachedUsers' | '+attachedLocalUsers' | '-attachedLocalUsers' | '+attachedRemoteUsers' | '-attachedRemoteUsers'; }; }; }; @@ -17864,16 +17868,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: misskey:id */ - id: string; - name?: string; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - lastUsedAt?: string; - permission: string[]; - }[]; + 'application/json': components['schemas']['Hashtag'][]; }; }; /** @description Client error */ @@ -17909,25 +17904,20 @@ export type operations = { }; }; /** - * i/authorized-apps + * hashtags/search * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - 'i___authorized-apps': { + hashtags___search: { requestBody: { content: { 'application/json': { /** @default 10 */ limit?: number; + query: string; /** @default 0 */ offset?: number; - /** - * @default desc - * @enum {string} - */ - sort?: 'desc' | 'asc'; }; }; }; @@ -17935,14 +17925,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': ({ - /** Format: misskey:id */ - id: string; - name: string; - callbackUrl: string | null; - permission: string[]; - isAuthorized?: boolean; - })[]; + 'application/json': string[]; }; }; /** @description Client error */ @@ -17978,24 +17961,25 @@ export type operations = { }; }; /** - * i/claim-achievement + * hashtags/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - 'i___claim-achievement': { + hashtags___show: { requestBody: { content: { 'application/json': { - /** @enum {string} */ - name: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead'; + tag: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Hashtag']; + }; }; /** @description Client error */ 400: { @@ -18030,26 +18014,22 @@ export type operations = { }; }; /** - * i/change-password + * hashtags/trend * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - 'i___change-password': { - requestBody: { - content: { - 'application/json': { - currentPassword: string; - newPassword: string; - token?: string | null; - }; - }; - }; + hashtags___trend: { responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + tag: string; + chart: number[]; + usersCount: number; + }[]; + }; }; /** @description Client error */ 400: { @@ -18084,25 +18064,39 @@ export type operations = { }; }; /** - * i/delete-account + * hashtags/users * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - 'i___delete-account': { + hashtags___users: { requestBody: { content: { 'application/json': { - password: string; - token?: string | null; + tag: string; + /** @default 10 */ + limit?: number; + /** @enum {string} */ + sort: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; + /** + * @default all + * @enum {string} + */ + state?: 'all' | 'alive'; + /** + * @default local + * @enum {string} + */ + origin?: 'combined' | 'local' | 'remote'; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserDetailed'][]; + }; }; /** @description Client error */ 400: { @@ -18137,17 +18131,18 @@ export type operations = { }; }; /** - * i/export-blocking + * i * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'i___export-blocking': { + i: { responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['MeDetailed']; + }; }; /** @description Client error */ 400: { @@ -18173,12 +18168,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18188,27 +18177,28 @@ export type operations = { }; }; /** - * i/export-following + * i/2fa/done * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___export-following': { + i___2fa___done: { requestBody: { content: { 'application/json': { - /** @default false */ - excludeMuting?: boolean; - /** @default false */ - excludeInactive?: boolean; + token: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + backupCodes: string[]; + }; + }; }; /** @description Client error */ 400: { @@ -18234,12 +18224,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18249,17 +18233,32 @@ export type operations = { }; }; /** - * i/export-mute + * i/2fa/key-done * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___export-mute': { + 'i___2fa___key-done': { + requestBody: { + content: { + 'application/json': { + password: string; + token?: string | null; + name: string; + credential: Record; + }; + }; + }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + id: string; + name: string; + }; + }; }; /** @description Client error */ 400: { @@ -18285,12 +18284,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18300,13 +18293,20 @@ export type operations = { }; }; /** - * i/export-notes + * i/2fa/password-less * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___export-notes': { + 'i___2fa___password-less': { + requestBody: { + content: { + 'application/json': { + value: boolean; + }; + }; + }; responses: { /** @description OK (without any results) */ 204: { @@ -18336,12 +18336,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18351,17 +18345,33 @@ export type operations = { }; }; /** - * i/export-clips + * i/2fa/register * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___export-clips': { + i___2fa___register: { + requestBody: { + content: { + 'application/json': { + password: string; + token?: string | null; + }; + }; + }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + qr: string; + url: string; + secret: string; + label: string; + issuer: string; + }; + }; }; /** @description Client error */ 400: { @@ -18387,12 +18397,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18402,17 +18406,61 @@ export type operations = { }; }; /** - * i/export-favorites + * i/2fa/register-key * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___export-favorites': { + 'i___2fa___register-key': { + requestBody: { + content: { + 'application/json': { + password: string; + token?: string | null; + }; + }; + }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + rp: { + id?: string; + }; + user: { + id: string; + name: string; + displayName: string; + }; + challenge: string; + pubKeyCredParams: { + type: string; + alg: number; + }[]; + timeout: number | null; + excludeCredentials: (({ + id: string; + type: string; + transports: ('ble' | 'cable' | 'hybrid' | 'internal' | 'nfc' | 'smart-card' | 'usb')[]; + })[]) | null; + authenticatorSelection: ({ + /** @enum {string} */ + authenticatorAttachment: 'cross-platform' | 'platform'; + requireResidentKey: boolean; + /** @enum {string} */ + userVerification: 'discouraged' | 'preferred' | 'required'; + }) | null; + /** @enum {string|null} */ + attestation: 'direct' | 'enterprise' | 'indirect' | 'none' | null; + extensions: ({ + appid: string | null; + credProps: boolean | null; + hmacCreateSecret: boolean | null; + }) | null; + }; + }; }; /** @description Client error */ 400: { @@ -18438,12 +18486,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18453,13 +18495,22 @@ export type operations = { }; }; /** - * i/export-user-lists + * i/2fa/remove-key * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___export-user-lists': { + 'i___2fa___remove-key': { + requestBody: { + content: { + 'application/json': { + password: string; + token?: string | null; + credentialId: string; + }; + }; + }; responses: { /** @description OK (without any results) */ 204: { @@ -18489,12 +18540,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18504,13 +18549,21 @@ export type operations = { }; }; /** - * i/export-antennas + * i/2fa/unregister * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___export-antennas': { + i___2fa___unregister: { + requestBody: { + content: { + 'application/json': { + password: string; + token?: string | null; + }; + }; + }; responses: { /** @description OK (without any results) */ 204: { @@ -18540,12 +18593,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18555,30 +18602,25 @@ export type operations = { }; }; /** - * i/favorites + * i/2fa/update-key * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:favorites* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___favorites: { + 'i___2fa___update-key': { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + name: string; + credentialId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['NoteFavorite'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -18613,21 +18655,18 @@ export type operations = { }; }; /** - * i/gallery/likes + * i/apps * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:gallery-likes* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___gallery___likes: { + i___apps: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + /** @enum {string} */ + sort?: '+createdAt' | '-createdAt' | '+lastUsedAt' | '-lastUsedAt'; }; }; }; @@ -18636,9 +18675,14 @@ export type operations = { 200: { content: { 'application/json': { - /** Format: id */ + /** Format: misskey:id */ id: string; - post: components['schemas']['GalleryPost']; + name?: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + lastUsedAt?: string; + permission: string[]; }[]; }; }; @@ -18675,21 +18719,25 @@ export type operations = { }; }; /** - * i/gallery/posts + * i/authorized-apps * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:gallery* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___gallery___posts: { + 'i___authorized-apps': { requestBody: { content: { 'application/json': { /** @default 10 */ limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + /** @default 0 */ + offset?: number; + /** + * @default desc + * @enum {string} + */ + sort?: 'desc' | 'asc'; }; }; }; @@ -18697,7 +18745,14 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['GalleryPost'][]; + 'application/json': ({ + /** Format: misskey:id */ + id: string; + name: string; + callbackUrl: string | null; + permission: string[]; + isAuthorized?: boolean; + })[]; }; }; /** @description Client error */ @@ -18733,18 +18788,19 @@ export type operations = { }; }; /** - * i/import-blocking + * i/change-password * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___import-blocking': { + 'i___change-password': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - fileId: string; + currentPassword: string; + newPassword: string; + token?: string | null; }; }; }; @@ -18771,14 +18827,8 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Too many requests */ - 429: { + /** @description I'm Ai */ + 418: { content: { 'application/json': components['schemas']['Error']; }; @@ -18792,19 +18842,17 @@ export type operations = { }; }; /** - * i/import-following + * i/claim-achievement * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i___import-following': { + 'i___claim-achievement': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - fileId: string; - withReplies?: boolean; + /** @enum {string} */ + name: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead'; }; }; }; @@ -18837,12 +18885,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18852,18 +18894,18 @@ export type operations = { }; }; /** - * i/import-muting + * i/delete-account * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___import-muting': { + 'i___delete-account': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - fileId: string; + password: string; + token?: string | null; }; }; }; @@ -18896,12 +18938,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -18911,21 +18947,13 @@ export type operations = { }; }; /** - * i/import-user-lists + * i/export-antennas * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___import-user-lists': { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - fileId: string; - }; - }; - }; + 'i___export-antennas': { responses: { /** @description OK (without any results) */ 204: { @@ -18970,21 +18998,13 @@ export type operations = { }; }; /** - * i/import-antennas + * i/export-blocking * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___import-antennas': { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - fileId: string; - }; - }; - }; + 'i___export-blocking': { responses: { /** @description OK (without any results) */ 204: { @@ -19029,34 +19049,17 @@ export type operations = { }; }; /** - * i/notifications + * i/export-clips * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:notifications* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___notifications: { - requestBody: { - content: { - 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default true */ - markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; - }; - }; - }; + 'i___export-clips': { responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Notification'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19097,34 +19100,17 @@ export type operations = { }; }; /** - * i/notifications-grouped + * i/export-favorites * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:notifications* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'i___notifications-grouped': { - requestBody: { - content: { - 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default true */ - markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; - }; - }; - }; + 'i___export-favorites': { responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Notification'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19165,34 +19151,27 @@ export type operations = { }; }; /** - * i/page-likes + * i/export-following * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:page-likes* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'i___page-likes': { + 'i___export-following': { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + /** @default false */ + excludeMuting?: boolean; + /** @default false */ + excludeInactive?: boolean; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - /** Format: id */ - id: string; - page: components['schemas']['Page']; - }[]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19218,6 +19197,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19227,30 +19212,17 @@ export type operations = { }; }; /** - * i/pages + * i/export-mute * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:pages* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___pages: { - requestBody: { - content: { - 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - }; - }; - }; + 'i___export-mute': { responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Page'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19276,6 +19248,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19285,26 +19263,17 @@ export type operations = { }; }; /** - * i/pin + * i/export-notes * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___pin: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - noteId: string; - }; - }; - }; + 'i___export-notes': { responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['MeDetailed']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19330,6 +19299,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19339,12 +19314,13 @@ export type operations = { }; }; /** - * i/read-all-unread-notes + * i/export-user-lists * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'i___read-all-unread-notes': { + 'i___export-user-lists': { responses: { /** @description OK (without any results) */ 204: { @@ -19374,6 +19350,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19383,24 +19365,30 @@ export type operations = { }; }; /** - * i/read-announcement + * i/favorites * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *read:favorites* */ - 'i___read-announcement': { + i___favorites: { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - announcementId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['NoteFavorite'][]; + }; }; /** @description Client error */ 400: { @@ -19435,24 +19423,34 @@ export type operations = { }; }; /** - * i/regenerate-token + * i/gallery/likes * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *read:gallery-likes* */ - 'i___regenerate-token': { + i___gallery___likes: { requestBody: { content: { 'application/json': { - password: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** Format: id */ + id: string; + post: components['schemas']['GalleryPost']; + }[]; + }; }; /** @description Client error */ 400: { @@ -19487,18 +19485,21 @@ export type operations = { }; }; /** - * i/registry/get-all + * i/gallery/posts * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *read:gallery* */ - 'i___registry___get-all': { + i___gallery___posts: { requestBody: { content: { 'application/json': { - /** @default [] */ - scope: string[]; - domain?: string | null; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; @@ -19506,7 +19507,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': Record; + 'application/json': components['schemas']['GalleryPost'][]; }; }; /** @description Client error */ @@ -19542,31 +19543,25 @@ export type operations = { }; }; /** - * i/registry/get-detail + * i/import-antennas * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'i___registry___get-detail': { + 'i___import-antennas': { requestBody: { content: { 'application/json': { - key: string; - /** @default [] */ - scope: string[]; - domain?: string | null; + /** Format: misskey:id */ + fileId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - updatedAt: string; - value: unknown; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19592,6 +19587,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19601,28 +19602,25 @@ export type operations = { }; }; /** - * i/registry/get + * i/import-blocking * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___registry___get: { + 'i___import-blocking': { requestBody: { content: { 'application/json': { - key: string; - /** @default [] */ - scope: string[]; - domain?: string | null; + /** Format: misskey:id */ + fileId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': Record; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19648,6 +19646,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19657,29 +19661,26 @@ export type operations = { }; }; /** - * i/registry/keys-with-type + * i/import-following * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'i___registry___keys-with-type': { + 'i___import-following': { requestBody: { content: { 'application/json': { - /** @default [] */ - scope: string[]; - domain?: string | null; + /** Format: misskey:id */ + fileId: string; + withReplies?: boolean; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - [key: string]: string; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19705,6 +19706,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19714,27 +19721,25 @@ export type operations = { }; }; /** - * i/registry/keys + * i/import-muting * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___registry___keys: { + 'i___import-muting': { requestBody: { content: { 'application/json': { - /** @default [] */ - scope: string[]; - domain?: string | null; + /** Format: misskey:id */ + fileId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': string[]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -19760,6 +19765,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19769,19 +19780,18 @@ export type operations = { }; }; /** - * i/registry/remove + * i/import-user-lists * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - i___registry___remove: { + 'i___import-user-lists': { requestBody: { content: { 'application/json': { - key: string; - /** @default [] */ - scope: string[]; - domain?: string | null; + /** Format: misskey:id */ + fileId: string; }; }; }; @@ -19814,6 +19824,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19823,21 +19839,25 @@ export type operations = { }; }; /** - * i/registry/scopes-with-domain + * i/move * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - 'i___registry___scopes-with-domain': { + i___move: { + requestBody: { + content: { + 'application/json': { + moveToAccount: string; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': ({ - scopes: string[][]; - domain: string | null; - })[]; + 'application/json': Record; }; }; /** @description Client error */ @@ -19864,6 +19884,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19873,27 +19899,34 @@ export type operations = { }; }; /** - * i/registry/set + * i/notifications * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *read:notifications* */ - i___registry___set: { + i___notifications: { requestBody: { content: { 'application/json': { - key: string; - value: unknown; - /** @default [] */ - scope: string[]; - domain?: string | null; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default true */ + markAsRead?: boolean; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Notification'][]; + }; }; /** @description Client error */ 400: { @@ -19919,6 +19952,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -19928,26 +19967,34 @@ export type operations = { }; }; /** - * i/revoke-token + * i/notifications-grouped * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *read:notifications* */ - 'i___revoke-token': { + 'i___notifications-grouped': { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - tokenId?: string; - token?: string | null; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default true */ + markAsRead?: boolean; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Notification'][]; + }; }; /** @description Client error */ 400: { @@ -19967,8 +20014,14 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description I'm Ai */ - 418: { + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Too many requests */ + 429: { content: { 'application/json': components['schemas']['Error']; }; @@ -19982,13 +20035,12 @@ export type operations = { }; }; /** - * i/signin-history + * i/page-likes * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *read:page-likes* */ - 'i___signin-history': { + 'i___page-likes': { requestBody: { content: { 'application/json': { @@ -20005,7 +20057,11 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Signin'][]; + 'application/json': { + /** Format: id */ + id: string; + page: components['schemas']['Page']; + }[]; }; }; /** @description Client error */ @@ -20041,17 +20097,21 @@ export type operations = { }; }; /** - * i/unpin + * i/pages * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *read:pages* */ - i___unpin: { + i___pages: { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - noteId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; @@ -20059,7 +20119,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['MeDetailed']; + 'application/json': components['schemas']['Page'][]; }; }; /** @description Client error */ @@ -20095,19 +20155,17 @@ export type operations = { }; }; /** - * i/update-email + * i/pin * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'i___update-email': { + i___pin: { requestBody: { content: { 'application/json': { - password: string; - email?: string | null; - token?: string | null; + /** Format: misskey:id */ + noteId: string; }; }; }; @@ -20133,228 +20191,35 @@ export type operations = { /** @description Forbidden error */ 403: { content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - }; - }; - /** - * i/update - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:account* - */ - i___update: { - requestBody: { - content: { - 'application/json': { - name?: string | null; - description?: string | null; - followedMessage?: string | null; - location?: string | null; - birthday?: string | null; - /** @enum {string|null} */ - lang?: null | 'ach' | 'ady' | 'af' | 'af-NA' | 'af-ZA' | 'ak' | 'ar' | 'ar-AR' | 'ar-MA' | 'ar-SA' | 'ay-BO' | 'az' | 'az-AZ' | 'be-BY' | 'bg' | 'bg-BG' | 'bn' | 'bn-IN' | 'bn-BD' | 'br' | 'bs-BA' | 'ca' | 'ca-ES' | 'cak' | 'ck-US' | 'cs' | 'cs-CZ' | 'cy' | 'cy-GB' | 'da' | 'da-DK' | 'de' | 'de-AT' | 'de-DE' | 'de-CH' | 'dsb' | 'el' | 'el-GR' | 'en' | 'en-GB' | 'en-AU' | 'en-CA' | 'en-IE' | 'en-IN' | 'en-PI' | 'en-SG' | 'en-UD' | 'en-US' | 'en-ZA' | 'en@pirate' | 'eo' | 'eo-EO' | 'es' | 'es-AR' | 'es-419' | 'es-CL' | 'es-CO' | 'es-EC' | 'es-ES' | 'es-LA' | 'es-NI' | 'es-MX' | 'es-US' | 'es-VE' | 'et' | 'et-EE' | 'eu' | 'eu-ES' | 'fa' | 'fa-IR' | 'fb-LT' | 'ff' | 'fi' | 'fi-FI' | 'fo' | 'fo-FO' | 'fr' | 'fr-CA' | 'fr-FR' | 'fr-BE' | 'fr-CH' | 'fy-NL' | 'ga' | 'ga-IE' | 'gd' | 'gl' | 'gl-ES' | 'gn-PY' | 'gu-IN' | 'gv' | 'gx-GR' | 'he' | 'he-IL' | 'hi' | 'hi-IN' | 'hr' | 'hr-HR' | 'hsb' | 'ht' | 'hu' | 'hu-HU' | 'hy' | 'hy-AM' | 'id' | 'id-ID' | 'is' | 'is-IS' | 'it' | 'it-IT' | 'ja' | 'ja-JP' | 'jv-ID' | 'ka-GE' | 'kk-KZ' | 'km' | 'kl' | 'km-KH' | 'kab' | 'kn' | 'kn-IN' | 'ko' | 'ko-KR' | 'ku-TR' | 'kw' | 'la' | 'la-VA' | 'lb' | 'li-NL' | 'lt' | 'lt-LT' | 'lv' | 'lv-LV' | 'mai' | 'mg-MG' | 'mk' | 'mk-MK' | 'ml' | 'ml-IN' | 'mn-MN' | 'mr' | 'mr-IN' | 'ms' | 'ms-MY' | 'mt' | 'mt-MT' | 'my' | 'no' | 'nb' | 'nb-NO' | 'ne' | 'ne-NP' | 'nl' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'oc' | 'or-IN' | 'pa' | 'pa-IN' | 'pl' | 'pl-PL' | 'ps-AF' | 'pt' | 'pt-BR' | 'pt-PT' | 'qu-PE' | 'rm-CH' | 'ro' | 'ro-RO' | 'ru' | 'ru-RU' | 'sa-IN' | 'se-NO' | 'sh' | 'si-LK' | 'sk' | 'sk-SK' | 'sl' | 'sl-SI' | 'so-SO' | 'sq' | 'sq-AL' | 'sr' | 'sr-RS' | 'su' | 'sv' | 'sv-SE' | 'sw' | 'sw-KE' | 'ta' | 'ta-IN' | 'te' | 'te-IN' | 'tg' | 'tg-TJ' | 'th' | 'th-TH' | 'fil' | 'tlh' | 'tr' | 'tr-TR' | 'tt-RU' | 'uk' | 'uk-UA' | 'ur' | 'ur-PK' | 'uz' | 'uz-UZ' | 'vi' | 'vi-VN' | 'xh-ZA' | 'yi' | 'yi-DE' | 'zh' | 'zh-Hans' | 'zh-Hant' | 'zh-CN' | 'zh-HK' | 'zh-SG' | 'zh-TW' | 'zu-ZA'; - /** Format: misskey:id */ - avatarId?: string | null; - avatarDecorations?: ({ - /** Format: misskey:id */ - id: string; - angle?: number | null; - flipH?: boolean | null; - offsetX?: number | null; - offsetY?: number | null; - })[]; - /** Format: misskey:id */ - bannerId?: string | null; - fields?: { - name: string; - value: string; - }[]; - isLocked?: boolean; - isExplorable?: boolean; - hideOnlineStatus?: boolean; - publicReactions?: boolean; - carefulBot?: boolean; - autoAcceptFollowed?: boolean; - noCrawle?: boolean; - preventAiLearning?: boolean; - requireSigninToViewContents?: boolean; - makeNotesFollowersOnlyBefore?: number | null; - makeNotesHiddenBefore?: number | null; - isBot?: boolean; - isCat?: boolean; - injectFeaturedNote?: boolean; - receiveAnnouncementEmail?: boolean; - alwaysMarkNsfw?: boolean; - autoSensitive?: boolean; - /** @enum {string} */ - followingVisibility?: 'public' | 'followers' | 'private'; - /** @enum {string} */ - followersVisibility?: 'public' | 'followers' | 'private'; - /** Format: misskey:id */ - pinnedPageId?: string | null; - mutedWords?: (string[] | string)[]; - hardMutedWords?: (string[] | string)[]; - mutedInstances?: string[]; - notificationRecieveConfig?: { - note?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - follow?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - mention?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - reply?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - renote?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - quote?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - reaction?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - pollEnded?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - receiveFollowRequest?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - followRequestAccepted?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - roleAssigned?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - achievementEarned?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - app?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - test?: OneOf<[{ - /** @enum {string} */ - type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; - }, { - /** @enum {string} */ - type: 'list'; - /** Format: misskey:id */ - userListId: string; - }]>; - }; - emailNotificationTypes?: string[]; - alsoKnownAs?: string[]; + 'application/json': components['schemas']['Error']; }; }; - }; - responses: { - /** @description OK (with results) */ - 200: { + /** @description I'm Ai */ + 418: { content: { - 'application/json': components['schemas']['MeDetailed']; + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; }; }; + }; + }; + /** + * i/read-all-unread-notes + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'i___read-all-unread-notes': { + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -20379,8 +20244,54 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * i/read-announcement + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:account* + */ + 'i___read-announcement': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + announcementId: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { content: { 'application/json': components['schemas']['Error']; }; @@ -20394,17 +20305,71 @@ export type operations = { }; }; /** - * i/move + * i/regenerate-token * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ - i___move: { + 'i___regenerate-token': { requestBody: { content: { 'application/json': { - moveToAccount: string; + password: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * i/registry/get + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + i___registry___get: { + requestBody: { + content: { + 'application/json': { + key: string; + /** @default [] */ + scope: string[]; + domain?: string | null; }; }; }; @@ -20439,12 +20404,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -20454,20 +20413,18 @@ export type operations = { }; }; /** - * i/webhooks/create + * i/registry/get-all * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - i___webhooks___create: { + 'i___registry___get-all': { requestBody: { content: { 'application/json': { - name: string; - url: string; - /** @default */ - secret?: string; - on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; + /** @default [] */ + scope: string[]; + domain?: string | null; }; }; }; @@ -20475,20 +20432,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: misskey:id */ - id: string; - /** Format: misskey:id */ - userId: string; - name: string; - on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; - url: string; - secret: string; - active: boolean; - /** Format: date-time */ - latestSentAt: string | null; - latestStatus: number | null; - }; + 'application/json': Record; }; }; /** @description Client error */ @@ -20524,30 +20468,30 @@ export type operations = { }; }; /** - * i/webhooks/list + * i/registry/get-detail * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:account* */ - i___webhooks___list: { + 'i___registry___get-detail': { + requestBody: { + content: { + 'application/json': { + key: string; + /** @default [] */ + scope: string[]; + domain?: string | null; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': ({ - /** Format: misskey:id */ - id: string; - /** Format: misskey:id */ - userId: string; - name: string; - on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; - url: string; - secret: string; - active: boolean; - /** Format: date-time */ - latestSentAt: string | null; - latestStatus: number | null; - })[]; + 'application/json': { + updatedAt: string; + value: unknown; + }; }; }; /** @description Client error */ @@ -20583,17 +20527,18 @@ export type operations = { }; }; /** - * i/webhooks/show + * i/registry/keys * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *read:account* */ - i___webhooks___show: { + i___registry___keys: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - webhookId: string; + /** @default [] */ + scope: string[]; + domain?: string | null; }; }; }; @@ -20601,20 +20546,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: misskey:id */ - id: string; - /** Format: misskey:id */ - userId: string; - name: string; - on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; - url: string; - secret: string; - active: boolean; - /** Format: date-time */ - latestSentAt: string | null; - latestStatus: number | null; - }; + 'application/json': string[]; }; }; /** @description Client error */ @@ -20650,29 +20582,29 @@ export type operations = { }; }; /** - * i/webhooks/update + * i/registry/keys-with-type * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - i___webhooks___update: { + 'i___registry___keys-with-type': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - webhookId: string; - name?: string; - url?: string; - secret?: string | null; - on?: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; - active?: boolean; + /** @default [] */ + scope: string[]; + domain?: string | null; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + [key: string]: string; + }; + }; }; /** @description Client error */ 400: { @@ -20707,17 +20639,19 @@ export type operations = { }; }; /** - * i/webhooks/delete + * i/registry/remove * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - i___webhooks___delete: { + i___registry___remove: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - webhookId: string; + key: string; + /** @default [] */ + scope: string[]; + domain?: string | null; }; }; }; @@ -20759,31 +20693,22 @@ export type operations = { }; }; /** - * i/webhooks/test + * i/registry/scopes-with-domain * @description No description provided. * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* */ - i___webhooks___test: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - webhookId: string; - /** @enum {string} */ - type: 'mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction'; - override?: { - url?: string; - secret?: string; - }; - }; - }; - }; + 'i___registry___scopes-with-domain': { responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': ({ + scopes: string[][]; + domain: string | null; + })[]; + }; }; /** @description Client error */ 400: { @@ -20809,12 +20734,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -20824,19 +20743,28 @@ export type operations = { }; }; /** - * invite/create + * i/registry/set * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:invite-codes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - invite___create: { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['InviteCode']; + i___registry___set: { + requestBody: { + content: { + 'application/json': { + key: string; + value: unknown; + /** @default [] */ + scope: string[]; + domain?: string | null; }; }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -20870,17 +20798,19 @@ export type operations = { }; }; /** - * invite/delete + * i/revoke-token * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:invite-codes* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - invite___delete: { + 'i___revoke-token': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - inviteId: string; + tokenId?: string; + token?: string | null; }; }; }; @@ -20922,16 +20852,17 @@ export type operations = { }; }; /** - * invite/list + * i/signin-history * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:invite-codes* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - invite___list: { + 'i___signin-history': { requestBody: { content: { 'application/json': { - /** @default 30 */ + /** @default 10 */ limit?: number; /** Format: misskey:id */ sinceId?: string; @@ -20944,7 +20875,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['InviteCode'][]; + 'application/json': components['schemas']['Signin'][]; }; }; /** @description Client error */ @@ -20980,19 +20911,25 @@ export type operations = { }; }; /** - * invite/limit + * i/unpin * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:invite-codes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - invite___limit: { + i___unpin: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + noteId: string; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': { - remaining: number | null; - }; + 'application/json': components['schemas']['MeDetailed']; }; }; /** @description Client error */ @@ -21028,17 +20965,194 @@ export type operations = { }; }; /** - * meta + * i/update * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - meta: { + i___update: { requestBody: { content: { 'application/json': { - /** @default true */ - detail?: boolean; + name?: string | null; + description?: string | null; + followedMessage?: string | null; + location?: string | null; + birthday?: string | null; + /** @enum {string|null} */ + lang?: null | 'ach' | 'ady' | 'af' | 'af-NA' | 'af-ZA' | 'ak' | 'ar' | 'ar-AR' | 'ar-MA' | 'ar-SA' | 'ay-BO' | 'az' | 'az-AZ' | 'be-BY' | 'bg' | 'bg-BG' | 'bn' | 'bn-IN' | 'bn-BD' | 'br' | 'bs-BA' | 'ca' | 'ca-ES' | 'cak' | 'ck-US' | 'cs' | 'cs-CZ' | 'cy' | 'cy-GB' | 'da' | 'da-DK' | 'de' | 'de-AT' | 'de-DE' | 'de-CH' | 'dsb' | 'el' | 'el-GR' | 'en' | 'en-GB' | 'en-AU' | 'en-CA' | 'en-IE' | 'en-IN' | 'en-PI' | 'en-SG' | 'en-UD' | 'en-US' | 'en-ZA' | 'en@pirate' | 'eo' | 'eo-EO' | 'es' | 'es-AR' | 'es-419' | 'es-CL' | 'es-CO' | 'es-EC' | 'es-ES' | 'es-LA' | 'es-NI' | 'es-MX' | 'es-US' | 'es-VE' | 'et' | 'et-EE' | 'eu' | 'eu-ES' | 'fa' | 'fa-IR' | 'fb-LT' | 'ff' | 'fi' | 'fi-FI' | 'fo' | 'fo-FO' | 'fr' | 'fr-CA' | 'fr-FR' | 'fr-BE' | 'fr-CH' | 'fy-NL' | 'ga' | 'ga-IE' | 'gd' | 'gl' | 'gl-ES' | 'gn-PY' | 'gu-IN' | 'gv' | 'gx-GR' | 'he' | 'he-IL' | 'hi' | 'hi-IN' | 'hr' | 'hr-HR' | 'hsb' | 'ht' | 'hu' | 'hu-HU' | 'hy' | 'hy-AM' | 'id' | 'id-ID' | 'is' | 'is-IS' | 'it' | 'it-IT' | 'ja' | 'ja-JP' | 'jv-ID' | 'ka-GE' | 'kk-KZ' | 'km' | 'kl' | 'km-KH' | 'kab' | 'kn' | 'kn-IN' | 'ko' | 'ko-KR' | 'ku-TR' | 'kw' | 'la' | 'la-VA' | 'lb' | 'li-NL' | 'lt' | 'lt-LT' | 'lv' | 'lv-LV' | 'mai' | 'mg-MG' | 'mk' | 'mk-MK' | 'ml' | 'ml-IN' | 'mn-MN' | 'mr' | 'mr-IN' | 'ms' | 'ms-MY' | 'mt' | 'mt-MT' | 'my' | 'no' | 'nb' | 'nb-NO' | 'ne' | 'ne-NP' | 'nl' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'oc' | 'or-IN' | 'pa' | 'pa-IN' | 'pl' | 'pl-PL' | 'ps-AF' | 'pt' | 'pt-BR' | 'pt-PT' | 'qu-PE' | 'rm-CH' | 'ro' | 'ro-RO' | 'ru' | 'ru-RU' | 'sa-IN' | 'se-NO' | 'sh' | 'si-LK' | 'sk' | 'sk-SK' | 'sl' | 'sl-SI' | 'so-SO' | 'sq' | 'sq-AL' | 'sr' | 'sr-RS' | 'su' | 'sv' | 'sv-SE' | 'sw' | 'sw-KE' | 'ta' | 'ta-IN' | 'te' | 'te-IN' | 'tg' | 'tg-TJ' | 'th' | 'th-TH' | 'fil' | 'tlh' | 'tr' | 'tr-TR' | 'tt-RU' | 'uk' | 'uk-UA' | 'ur' | 'ur-PK' | 'uz' | 'uz-UZ' | 'vi' | 'vi-VN' | 'xh-ZA' | 'yi' | 'yi-DE' | 'zh' | 'zh-Hans' | 'zh-Hant' | 'zh-CN' | 'zh-HK' | 'zh-SG' | 'zh-TW' | 'zu-ZA'; + /** Format: misskey:id */ + avatarId?: string | null; + avatarDecorations?: ({ + /** Format: misskey:id */ + id: string; + angle?: number | null; + flipH?: boolean | null; + offsetX?: number | null; + offsetY?: number | null; + })[]; + /** Format: misskey:id */ + bannerId?: string | null; + fields?: { + name: string; + value: string; + }[]; + isLocked?: boolean; + isExplorable?: boolean; + hideOnlineStatus?: boolean; + publicReactions?: boolean; + carefulBot?: boolean; + autoAcceptFollowed?: boolean; + noCrawle?: boolean; + preventAiLearning?: boolean; + requireSigninToViewContents?: boolean; + makeNotesFollowersOnlyBefore?: number | null; + makeNotesHiddenBefore?: number | null; + isBot?: boolean; + isCat?: boolean; + injectFeaturedNote?: boolean; + receiveAnnouncementEmail?: boolean; + alwaysMarkNsfw?: boolean; + autoSensitive?: boolean; + /** @enum {string} */ + followingVisibility?: 'public' | 'followers' | 'private'; + /** @enum {string} */ + followersVisibility?: 'public' | 'followers' | 'private'; + /** Format: misskey:id */ + pinnedPageId?: string | null; + mutedWords?: (string[] | string)[]; + hardMutedWords?: (string[] | string)[]; + mutedInstances?: string[]; + notificationRecieveConfig?: { + note?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + follow?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + mention?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + reply?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + renote?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + quote?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + reaction?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + pollEnded?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + receiveFollowRequest?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + followRequestAccepted?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + roleAssigned?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + achievementEarned?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + app?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + test?: OneOf<[{ + /** @enum {string} */ + type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never'; + }, { + /** @enum {string} */ + type: 'list'; + /** Format: misskey:id */ + userListId: string; + }]>; + }; + emailNotificationTypes?: string[]; + alsoKnownAs?: string[]; }; }; }; @@ -21046,7 +21160,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['MetaLite'] | components['schemas']['MetaDetailed']; + 'application/json': components['schemas']['MeDetailed']; }; }; /** @description Client error */ @@ -21073,50 +21187,8 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - }; - }; - /** - * emojis - * @description No description provided. - * - * **Credential required**: *No* - */ - emojis: { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - emojis: components['schemas']['EmojiSimple'][]; - }; - }; - }; - /** @description Client error */ - 400: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Authentication error */ - 401: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Forbidden error */ - 403: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { + /** @description Too many requests */ + 429: { content: { 'application/json': components['schemas']['Error']; }; @@ -21130,16 +21202,19 @@ export type operations = { }; }; /** - * emoji + * i/update-email * @description No description provided. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - emoji: { + 'i___update-email': { requestBody: { content: { 'application/json': { - name: string; + password: string; + email?: string | null; + token?: string | null; }; }; }; @@ -21147,7 +21222,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['EmojiDetailed']; + 'application/json': components['schemas']['MeDetailed']; }; }; /** @description Client error */ @@ -21174,6 +21249,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -21183,21 +21264,20 @@ export type operations = { }; }; /** - * miauth/gen-token + * i/webhooks/create * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'miauth___gen-token': { + i___webhooks___create: { requestBody: { content: { 'application/json': { - session: string | null; - name?: string | null; - description?: string | null; - iconUrl?: string | null; - permission: string[]; + name: string; + url: string; + /** @default */ + secret?: string; + on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; }; }; }; @@ -21206,7 +21286,18 @@ export type operations = { 200: { content: { 'application/json': { - token: string; + /** Format: misskey:id */ + id: string; + /** Format: misskey:id */ + userId: string; + name: string; + on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; + url: string; + secret: string; + active: boolean; + /** Format: date-time */ + latestSentAt: string | null; + latestStatus: number | null; }; }; }; @@ -21243,19 +21334,17 @@ export type operations = { }; }; /** - * mute/create + * i/webhooks/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:mutes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - mute___create: { + i___webhooks___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; - /** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */ - expiresAt?: number | null; + webhookId: string; }; }; }; @@ -21288,12 +21377,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -21303,24 +21386,31 @@ export type operations = { }; }; /** - * mute/delete + * i/webhooks/list * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:mutes* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - mute___delete: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - userId: string; - }; - }; - }; + i___webhooks___list: { responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': ({ + /** Format: misskey:id */ + id: string; + /** Format: misskey:id */ + userId: string; + name: string; + on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; + url: string; + secret: string; + active: boolean; + /** Format: date-time */ + latestSentAt: string | null; + latestStatus: number | null; + })[]; + }; }; /** @description Client error */ 400: { @@ -21355,21 +21445,17 @@ export type operations = { }; }; /** - * mute/list + * i/webhooks/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:mutes* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - mute___list: { + i___webhooks___show: { requestBody: { content: { 'application/json': { - /** @default 30 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; /** Format: misskey:id */ - untilId?: string; + webhookId: string; }; }; }; @@ -21377,61 +21463,22 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Muting'][]; - }; - }; - /** @description Client error */ - 400: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Authentication error */ - 401: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Forbidden error */ - 403: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - }; - }; - /** - * renote-mute/create - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *write:mutes* - */ - 'renote-mute___create': { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - userId: string; + 'application/json': { + /** Format: misskey:id */ + id: string; + /** Format: misskey:id */ + userId: string; + name: string; + on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; + url: string; + secret: string; + active: boolean; + /** Format: date-time */ + latestSentAt: string | null; + latestStatus: number | null; + }; }; }; - }; - responses: { - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -21456,12 +21503,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -21471,17 +21512,24 @@ export type operations = { }; }; /** - * renote-mute/delete + * i/webhooks/test * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:mutes* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'renote-mute___delete': { + i___webhooks___test: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; + webhookId: string; + /** @enum {string} */ + type: 'mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction'; + override?: { + url?: string; + secret?: string; + }; }; }; }; @@ -21514,6 +21562,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -21523,30 +21577,29 @@ export type operations = { }; }; /** - * renote-mute/list + * i/webhooks/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:mutes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'renote-mute___list': { + i___webhooks___update: { requestBody: { content: { 'application/json': { - /** @default 30 */ - limit?: number; /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + webhookId: string; + name?: string; + url?: string; + secret?: string | null; + on?: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[]; + active?: boolean; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['RenoteMuting'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -21581,27 +21634,17 @@ export type operations = { }; }; /** - * my/apps + * invite/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ - my___apps: { - requestBody: { - content: { - 'application/json': { - /** @default 10 */ - limit?: number; - /** @default 0 */ - offset?: number; - }; - }; - }; + invite___create: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['App'][]; + 'application/json': components['schemas']['InviteCode']; }; }; /** @description Client error */ @@ -21637,36 +21680,24 @@ export type operations = { }; }; /** - * notes + * invite/delete * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ - notes: { + invite___delete: { requestBody: { content: { 'application/json': { - /** @default false */ - local?: boolean; - reply?: boolean; - renote?: boolean; - withFiles?: boolean; - poll?: boolean; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; /** Format: misskey:id */ - untilId?: string; + inviteId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Note'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -21701,31 +21732,19 @@ export type operations = { }; }; /** - * notes/children + * invite/limit * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - notes___children: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - noteId: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - }; - }; - }; + invite___limit: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': { + remaining: number | null; + }; }; }; /** @description Client error */ @@ -21761,17 +21780,21 @@ export type operations = { }; }; /** - * notes/clips + * invite/list * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ - notes___clips: { + invite___list: { requestBody: { content: { 'application/json': { + /** @default 30 */ + limit?: number; /** Format: misskey:id */ - noteId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; @@ -21779,7 +21802,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Clip'][]; + 'application/json': components['schemas']['InviteCode'][]; }; }; /** @description Client error */ @@ -21815,21 +21838,17 @@ export type operations = { }; }; /** - * notes/conversation + * meta * @description No description provided. * * **Credential required**: *No* */ - notes___conversation: { + meta: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - noteId: string; - /** @default 10 */ - limit?: number; - /** @default 0 */ - offset?: number; + /** @default true */ + detail?: boolean; }; }; }; @@ -21837,7 +21856,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': components['schemas']['MetaLite'] | components['schemas']['MetaDetailed']; }; }; /** @description Client error */ @@ -21873,50 +21892,21 @@ export type operations = { }; }; /** - * notes/create + * miauth/gen-token * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:notes* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - notes___create: { + 'miauth___gen-token': { requestBody: { content: { 'application/json': { - /** - * @default public - * @enum {string} - */ - visibility?: 'public' | 'home' | 'followers' | 'specified'; - visibleUserIds?: string[]; - cw?: string | null; - /** @default false */ - localOnly?: boolean; - /** - * @default null - * @enum {string|null} - */ - reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote'; - /** @default false */ - noExtractMentions?: boolean; - /** @default false */ - noExtractHashtags?: boolean; - /** @default false */ - noExtractEmojis?: boolean; - /** Format: misskey:id */ - replyId?: string | null; - /** Format: misskey:id */ - renoteId?: string | null; - /** Format: misskey:id */ - channelId?: string | null; - text?: string | null; - fileIds?: string[]; - mediaIds?: string[]; - poll?: ({ - choices: string[]; - multiple?: boolean; - expiresAt?: number | null; - expiredAfter?: number | null; - }) | null; + session: string | null; + name?: string | null; + description?: string | null; + iconUrl?: string | null; + permission: string[]; }; }; }; @@ -21925,7 +21915,7 @@ export type operations = { 200: { content: { 'application/json': { - createdNote: components['schemas']['Note']; + token: string; }; }; }; @@ -21953,12 +21943,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -21968,17 +21952,19 @@ export type operations = { }; }; /** - * notes/delete + * mute/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:notes* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - notes___delete: { + mute___create: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - noteId: string; + userId: string; + /** @description A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute. */ + expiresAt?: number | null; }; }; }; @@ -22026,17 +22012,17 @@ export type operations = { }; }; /** - * notes/favorites/create + * mute/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:favorites* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - notes___favorites___create: { + mute___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - noteId: string; + userId: string; }; }; }; @@ -22069,12 +22055,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -22084,24 +22064,30 @@ export type operations = { }; }; /** - * notes/favorites/delete + * mute/list * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:favorites* + * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - notes___favorites___delete: { + mute___list: { requestBody: { content: { 'application/json': { + /** @default 30 */ + limit?: number; /** Format: misskey:id */ - noteId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Muting'][]; + }; }; /** @description Client error */ 400: { @@ -22136,21 +22122,19 @@ export type operations = { }; }; /** - * notes/featured + * my/apps * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - notes___featured: { + my___apps: { requestBody: { content: { 'application/json': { /** @default 10 */ limit?: number; - /** Format: misskey:id */ - untilId?: string; - /** Format: misskey:id */ - channelId?: string | null; + /** @default 0 */ + offset?: number; }; }; }; @@ -22158,7 +22142,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': components['schemas']['App'][]; }; }; /** @description Client error */ @@ -22194,27 +22178,27 @@ export type operations = { }; }; /** - * notes/global-timeline + * notes * @description No description provided. * * **Credential required**: *No* */ - 'notes___global-timeline': { + notes: { requestBody: { content: { 'application/json': { /** @default false */ + local?: boolean; + reply?: boolean; + renote?: boolean; withFiles?: boolean; - /** @default true */ - withRenotes?: boolean; + poll?: boolean; /** @default 10 */ limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - sinceDate?: number; - untilDate?: number; }; }; }; @@ -22258,37 +22242,23 @@ export type operations = { }; }; /** - * notes/hybrid-timeline + * notes/children * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* */ - 'notes___hybrid-timeline': { + notes___children: { requestBody: { content: { 'application/json': { + /** Format: misskey:id */ + noteId: string; /** @default 10 */ limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - sinceDate?: number; - untilDate?: number; - /** @default false */ - allowPartial?: boolean; - /** @default true */ - includeMyRenotes?: boolean; - /** @default true */ - includeRenotedMyNotes?: boolean; - /** @default true */ - includeLocalRenotes?: boolean; - /** @default false */ - withFiles?: boolean; - /** @default true */ - withRenotes?: boolean; - /** @default false */ - withReplies?: boolean; }; }; }; @@ -22332,31 +22302,17 @@ export type operations = { }; }; /** - * notes/local-timeline + * notes/clips * @description No description provided. * * **Credential required**: *No* */ - 'notes___local-timeline': { + notes___clips: { requestBody: { content: { 'application/json': { - /** @default false */ - withFiles?: boolean; - /** @default true */ - withRenotes?: boolean; - /** @default false */ - withReplies?: boolean; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; /** Format: misskey:id */ - untilId?: string; - /** @default false */ - allowPartial?: boolean; - sinceDate?: number; - untilDate?: number; + noteId: string; }; }; }; @@ -22364,7 +22320,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': components['schemas']['Clip'][]; }; }; /** @description Client error */ @@ -22400,24 +22356,21 @@ export type operations = { }; }; /** - * notes/mentions + * notes/conversation * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* */ - notes___mentions: { + notes___conversation: { requestBody: { content: { 'application/json': { - /** @default false */ - following?: boolean; + /** Format: misskey:id */ + noteId: string; /** @default 10 */ limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - visibility?: string; + /** @default 0 */ + offset?: number; }; }; }; @@ -22461,21 +22414,50 @@ export type operations = { }; }; /** - * notes/polls/recommendation + * notes/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:notes* */ - notes___polls___recommendation: { + notes___create: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** @default 0 */ - offset?: number; + /** + * @default public + * @enum {string} + */ + visibility?: 'public' | 'home' | 'followers' | 'specified'; + visibleUserIds?: string[]; + cw?: string | null; /** @default false */ - excludeChannels?: boolean; + localOnly?: boolean; + /** + * @default null + * @enum {string|null} + */ + reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote'; + /** @default false */ + noExtractMentions?: boolean; + /** @default false */ + noExtractHashtags?: boolean; + /** @default false */ + noExtractEmojis?: boolean; + /** Format: misskey:id */ + replyId?: string | null; + /** Format: misskey:id */ + renoteId?: string | null; + /** Format: misskey:id */ + channelId?: string | null; + text?: string | null; + fileIds?: string[]; + mediaIds?: string[]; + poll?: ({ + choices: string[]; + multiple?: boolean; + expiresAt?: number | null; + expiredAfter?: number | null; + }) | null; }; }; }; @@ -22483,7 +22465,9 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': { + createdNote: components['schemas']['Note']; + }; }; }; /** @description Client error */ @@ -22510,6 +22494,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -22519,18 +22509,17 @@ export type operations = { }; }; /** - * notes/polls/vote + * notes/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:votes* + * **Credential required**: *Yes* / **Permission**: *write:notes* */ - notes___polls___vote: { + notes___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; - choice: number; }; }; }; @@ -22563,6 +22552,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -22572,33 +22567,24 @@ export type operations = { }; }; /** - * notes/reactions + * notes/favorites/create * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:favorites* */ - notes___reactions: { + notes___favorites___create: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; - type?: string | null; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['NoteReaction'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -22624,6 +22610,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -22633,18 +22625,17 @@ export type operations = { }; }; /** - * notes/reactions/create + * notes/favorites/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:reactions* + * **Credential required**: *Yes* / **Permission**: *write:favorites* */ - notes___reactions___create: { + notes___favorites___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; - reaction: string; }; }; }; @@ -22686,24 +22677,30 @@ export type operations = { }; }; /** - * notes/reactions/delete + * notes/featured * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:reactions* + * **Credential required**: *No* */ - notes___reactions___delete: { + notes___featured: { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - noteId: string; + untilId?: string; + /** Format: misskey:id */ + channelId?: string | null; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Note'][]; + }; }; /** @description Client error */ 400: { @@ -22729,12 +22726,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -22744,23 +22735,27 @@ export type operations = { }; }; /** - * notes/renotes + * notes/global-timeline * @description No description provided. * * **Credential required**: *No* */ - notes___renotes: { + 'notes___global-timeline': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - noteId: string; + /** @default false */ + withFiles?: boolean; + /** @default true */ + withRenotes?: boolean; /** @default 10 */ limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; + sinceDate?: number; + untilDate?: number; }; }; }; @@ -22804,23 +22799,37 @@ export type operations = { }; }; /** - * notes/replies + * notes/hybrid-timeline * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - notes___replies: { + 'notes___hybrid-timeline': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - noteId: string; + /** @default 10 */ + limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - /** @default 10 */ - limit?: number; + sinceDate?: number; + untilDate?: number; + /** @default false */ + allowPartial?: boolean; + /** @default true */ + includeMyRenotes?: boolean; + /** @default true */ + includeRenotedMyNotes?: boolean; + /** @default true */ + includeLocalRenotes?: boolean; + /** @default false */ + withFiles?: boolean; + /** @default true */ + withRenotes?: boolean; + /** @default false */ + withReplies?: boolean; }; }; }; @@ -22864,35 +22873,31 @@ export type operations = { }; }; /** - * notes/search-by-tag + * notes/local-timeline * @description No description provided. * * **Credential required**: *No* */ - 'notes___search-by-tag': { + 'notes___local-timeline': { requestBody: { content: { 'application/json': { - /** @default null */ - reply?: boolean | null; - /** @default null */ - renote?: boolean | null; - /** - * @description Only show notes that have attached files. - * @default false - */ + /** @default false */ withFiles?: boolean; - /** @default null */ - poll?: boolean | null; + /** @default true */ + withRenotes?: boolean; + /** @default false */ + withReplies?: boolean; + /** @default 10 */ + limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - /** @default 10 */ - limit?: number; - tag?: string; - /** @description The outer arrays are chained with OR, the inner arrays are chained with AND. */ - query?: string[][]; + /** @default false */ + allowPartial?: boolean; + sinceDate?: number; + untilDate?: number; }; }; }; @@ -22936,36 +22941,24 @@ export type operations = { }; }; /** - * notes/search + * notes/mentions * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - notes___search: { + notes___mentions: { requestBody: { content: { 'application/json': { - query: string; + /** @default false */ + following?: boolean; + /** @default 10 */ + limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - /** @default 10 */ - limit?: number; - /** @default 0 */ - offset?: number; - /** @description The local host is represented with `.`. */ - host?: string; - /** - * Format: misskey:id - * @default null - */ - userId?: string | null; - /** - * Format: misskey:id - * @default null - */ - channelId?: string | null; + visibility?: string; }; }; }; @@ -23009,17 +23002,21 @@ export type operations = { }; }; /** - * notes/show + * notes/polls/recommendation * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - notes___show: { + notes___polls___recommendation: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - noteId: string; + /** @default 10 */ + limit?: number; + /** @default 0 */ + offset?: number; + /** @default false */ + excludeChannels?: boolean; }; }; }; @@ -23027,7 +23024,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note']; + 'application/json': components['schemas']['Note'][]; }; }; /** @description Client error */ @@ -23063,29 +23060,25 @@ export type operations = { }; }; /** - * notes/state + * notes/polls/vote * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:votes* */ - notes___state: { + notes___polls___vote: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; + choice: number; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - isFavorited: boolean; - isMutedThread: boolean; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -23120,24 +23113,33 @@ export type operations = { }; }; /** - * notes/thread-muting/create + * notes/reactions * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - 'notes___thread-muting___create': { + notes___reactions: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; + type?: string | null; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['NoteReaction'][]; + }; }; /** @description Client error */ 400: { @@ -23163,12 +23165,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -23178,17 +23174,18 @@ export type operations = { }; }; /** - * notes/thread-muting/delete + * notes/reactions/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *write:reactions* */ - 'notes___thread-muting___delete': { + notes___reactions___create: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; + reaction: string; }; }; }; @@ -23230,44 +23227,24 @@ export type operations = { }; }; /** - * notes/timeline + * notes/reactions/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:reactions* */ - notes___timeline: { + notes___reactions___delete: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; /** Format: misskey:id */ - untilId?: string; - sinceDate?: number; - untilDate?: number; - /** @default false */ - allowPartial?: boolean; - /** @default true */ - includeMyRenotes?: boolean; - /** @default true */ - includeRenotedMyNotes?: boolean; - /** @default true */ - includeLocalRenotes?: boolean; - /** @default false */ - withFiles?: boolean; - /** @default true */ - withRenotes?: boolean; + noteId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Note'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -23293,6 +23270,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -23302,18 +23285,23 @@ export type operations = { }; }; /** - * notes/translate + * notes/renotes * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* */ - notes___translate: { + notes___renotes: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; - targetLang: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; @@ -23321,16 +23309,9 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - sourceLang: string; - text: string; - }; + 'application/json': components['schemas']['Note'][]; }; }; - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -23364,24 +23345,32 @@ export type operations = { }; }; /** - * notes/unrenote + * notes/replies * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:notes* + * **Credential required**: *No* */ - notes___unrenote: { + notes___replies: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ noteId: string; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Note'][]; + }; }; /** @description Client error */ 400: { @@ -23407,12 +23396,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -23422,40 +23405,36 @@ export type operations = { }; }; /** - * notes/user-list-timeline + * notes/search * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* */ - 'notes___user-list-timeline': { + notes___search: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - listId: string; - /** @default 10 */ - limit?: number; + query: string; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - sinceDate?: number; - untilDate?: number; - /** @default false */ - allowPartial?: boolean; - /** @default true */ - includeMyRenotes?: boolean; - /** @default true */ - includeRenotedMyNotes?: boolean; - /** @default true */ - includeLocalRenotes?: boolean; - /** @default true */ - withRenotes?: boolean; + /** @default 10 */ + limit?: number; + /** @default 0 */ + offset?: number; + /** @description The local host is represented with `.`. */ + host?: string; /** - * @description Only show notes that have attached files. - * @default false + * Format: misskey:id + * @default null + */ + userId?: string | null; + /** + * Format: misskey:id + * @default null */ - withFiles?: boolean; + channelId?: string | null; }; }; }; @@ -23499,25 +23478,44 @@ export type operations = { }; }; /** - * notifications/create + * notes/search-by-tag * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:notifications* + * **Credential required**: *No* */ - notifications___create: { + 'notes___search-by-tag': { requestBody: { content: { 'application/json': { - body: string; - header?: string | null; - icon?: string | null; + /** @default null */ + reply?: boolean | null; + /** @default null */ + renote?: boolean | null; + /** + * @description Only show notes that have attached files. + * @default false + */ + withFiles?: boolean; + /** @default null */ + poll?: boolean | null; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; + tag?: string; + /** @description The outer arrays are chained with OR, the inner arrays are chained with AND. */ + query?: string[][]; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Note'][]; + }; }; /** @description Client error */ 400: { @@ -23543,12 +23541,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -23558,16 +23550,26 @@ export type operations = { }; }; /** - * notifications/flush + * notes/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:notifications* + * **Credential required**: *No* */ - notifications___flush: { + notes___show: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + noteId: string; + }; + }; + }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Note']; + }; }; /** @description Client error */ 400: { @@ -23602,16 +23604,29 @@ export type operations = { }; }; /** - * notifications/mark-all-as-read + * notes/state * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:notifications* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'notifications___mark-all-as-read': { + notes___state: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + noteId: string; + }; + }; + }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + isFavorited: boolean; + isMutedThread: boolean; + }; + }; }; /** @description Client error */ 400: { @@ -23646,12 +23661,20 @@ export type operations = { }; }; /** - * notifications/test-notification + * notes/thread-muting/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:notifications* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'notifications___test-notification': { + 'notes___thread-muting___create': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + noteId: string; + }; + }; + }; responses: { /** @description OK (without any results) */ 204: { @@ -23696,20 +23719,17 @@ export type operations = { }; }; /** - * page-push + * notes/thread-muting/delete * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'page-push': { + 'notes___thread-muting___delete': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - pageId: string; - event: string; - var?: unknown; + noteId: string; }; }; }; @@ -23751,36 +23771,35 @@ export type operations = { }; }; /** - * pages/create + * notes/timeline * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:pages* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - pages___create: { + notes___timeline: { requestBody: { content: { 'application/json': { - title: string; - name: string; - summary?: string | null; - content: { - [key: string]: unknown; - }[]; - variables: { - [key: string]: unknown; - }[]; - script: string; + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - eyeCatchingImageId?: string | null; - /** - * @default sans-serif - * @enum {string} - */ - font?: 'serif' | 'sans-serif'; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; /** @default false */ - alignCenter?: boolean; + allowPartial?: boolean; + /** @default true */ + includeMyRenotes?: boolean; + /** @default true */ + includeRenotedMyNotes?: boolean; + /** @default true */ + includeLocalRenotes?: boolean; /** @default false */ - hideTitleWhenPinned?: boolean; + withFiles?: boolean; + /** @default true */ + withRenotes?: boolean; }; }; }; @@ -23788,7 +23807,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Page']; + 'application/json': components['schemas']['Note'][]; }; }; /** @description Client error */ @@ -23815,12 +23834,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -23830,21 +23843,31 @@ export type operations = { }; }; /** - * pages/delete + * notes/translate * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:pages* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - pages___delete: { + notes___translate: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - pageId: string; + noteId: string; + targetLang: string; }; }; }; responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + sourceLang: string; + text: string; + }; + }; + }; /** @description OK (without any results) */ 204: { content: never; @@ -23882,19 +23905,25 @@ export type operations = { }; }; /** - * pages/featured + * notes/unrenote * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:notes* */ - pages___featured: { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Page'][]; + notes___unrenote: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + noteId: string; }; }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -23919,6 +23948,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -23928,24 +23963,49 @@ export type operations = { }; }; /** - * pages/like + * notes/user-list-timeline * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:page-likes* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - pages___like: { + 'notes___user-list-timeline': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - pageId: string; + listId: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; + /** @default false */ + allowPartial?: boolean; + /** @default true */ + includeMyRenotes?: boolean; + /** @default true */ + includeRenotedMyNotes?: boolean; + /** @default true */ + includeLocalRenotes?: boolean; + /** @default true */ + withRenotes?: boolean; + /** + * @description Only show notes that have attached files. + * @default false + */ + withFiles?: boolean; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Note'][]; + }; }; /** @description Client error */ 400: { @@ -23980,28 +24040,25 @@ export type operations = { }; }; /** - * pages/show + * notifications/create * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - pages___show: { + notifications___create: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - pageId?: string; - name?: string; - username?: string; + body: string; + header?: string | null; + icon?: string | null; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Page']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -24027,6 +24084,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -24036,20 +24099,12 @@ export type operations = { }; }; /** - * pages/unlike + * notifications/flush * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:page-likes* + * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - pages___unlike: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - pageId: string; - }; - }; - }; + notifications___flush: { responses: { /** @description OK (without any results) */ 204: { @@ -24088,36 +24143,12 @@ export type operations = { }; }; /** - * pages/update + * notifications/mark-all-as-read * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:pages* + * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - pages___update: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - pageId: string; - title?: string; - name?: string; - summary?: string | null; - content?: { - [key: string]: unknown; - }[]; - variables?: { - [key: string]: unknown; - }[]; - script?: string; - /** Format: misskey:id */ - eyeCatchingImageId?: string | null; - /** @enum {string} */ - font?: 'serif' | 'sans-serif'; - alignCenter?: boolean; - hideTitleWhenPinned?: boolean; - }; - }; - }; + 'notifications___mark-all-as-read': { responses: { /** @description OK (without any results) */ 204: { @@ -24147,12 +24178,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -24162,33 +24187,16 @@ export type operations = { }; }; /** - * flash/create + * notifications/test-notification * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *Yes* / **Permission**: *write:notifications* */ - flash___create: { - requestBody: { - content: { - 'application/json': { - title: string; - summary: string; - script: string; - permissions: string[]; - /** - * @default public - * @enum {string} - */ - visibility?: 'public' | 'private'; - }; - }; - }; + 'notifications___test-notification': { responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Flash']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -24229,17 +24237,20 @@ export type operations = { }; }; /** - * flash/delete + * page-push * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - flash___delete: { + 'page-push': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - flashId: string; + pageId: string; + event: string; + var?: unknown; }; }; }; @@ -24281,19 +24292,36 @@ export type operations = { }; }; /** - * flash/featured + * pages/create * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:pages* */ - flash___featured: { + pages___create: { requestBody: { content: { 'application/json': { - /** @default 0 */ - offset?: number; - /** @default 10 */ - limit?: number; + title: string; + name: string; + summary?: string | null; + content: { + [key: string]: unknown; + }[]; + variables: { + [key: string]: unknown; + }[]; + script: string; + /** Format: misskey:id */ + eyeCatchingImageId?: string | null; + /** + * @default sans-serif + * @enum {string} + */ + font?: 'serif' | 'sans-serif'; + /** @default false */ + alignCenter?: boolean; + /** @default false */ + hideTitleWhenPinned?: boolean; }; }; }; @@ -24301,7 +24329,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Flash'][]; + 'application/json': components['schemas']['Page']; }; }; /** @description Client error */ @@ -24328,6 +24356,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -24337,17 +24371,17 @@ export type operations = { }; }; /** - * flash/like + * pages/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + * **Credential required**: *Yes* / **Permission**: *write:pages* */ - flash___like: { + pages___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - flashId: string; + pageId: string; }; }; }; @@ -24389,25 +24423,17 @@ export type operations = { }; }; /** - * flash/show + * pages/featured * @description No description provided. * * **Credential required**: *No* */ - flash___show: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - flashId: string; - }; - }; - }; + pages___featured: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Flash']; + 'application/json': components['schemas']['Page'][]; }; }; /** @description Client error */ @@ -24443,17 +24469,17 @@ export type operations = { }; }; /** - * flash/unlike + * pages/like * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash-likes* + * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ - flash___unlike: { + pages___like: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - flashId: string; + pageId: string; }; }; }; @@ -24495,30 +24521,28 @@ export type operations = { }; }; /** - * flash/update + * pages/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:flash* + * **Credential required**: *No* */ - flash___update: { + pages___show: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - flashId: string; - title?: string; - summary?: string; - script?: string; - permissions?: string[]; - /** @enum {string} */ - visibility?: 'public' | 'private'; + pageId?: string; + name?: string; + username?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Page']; + }; }; /** @description Client error */ 400: { @@ -24544,12 +24568,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -24559,30 +24577,24 @@ export type operations = { }; }; /** - * flash/my + * pages/unlike * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:flash* + * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ - flash___my: { + pages___unlike: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + pageId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Flash'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -24617,34 +24629,40 @@ export type operations = { }; }; /** - * flash/my-likes + * pages/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:flash-likes* + * **Credential required**: *Yes* / **Permission**: *write:pages* */ - 'flash___my-likes': { + pages___update: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; /** Format: misskey:id */ - sinceId?: string; + pageId: string; + title?: string; + name?: string; + summary?: string | null; + content?: { + [key: string]: unknown; + }[]; + variables?: { + [key: string]: unknown; + }[]; + script?: string; /** Format: misskey:id */ - untilId?: string; + eyeCatchingImageId?: string | null; + /** @enum {string} */ + font?: 'serif' | 'sans-serif'; + alignCenter?: boolean; + hideTitleWhenPinned?: boolean; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - /** Format: id */ - id: string; - flash: components['schemas']['Flash']; - }[]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -24670,6 +24688,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -24825,72 +24849,24 @@ export type operations = { }; }; /** - * roles/list - * @description No description provided. - * - * **Credential required**: *Yes* / **Permission**: *read:account* - */ - roles___list: { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Role'][]; - }; - }; - /** @description Client error */ - 400: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Authentication error */ - 401: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Forbidden error */ - 403: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description I'm Ai */ - 418: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - /** @description Internal server error */ - 500: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; - }; - }; - /** - * roles/show + * renote-mute/create * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - roles___show: { + 'renote-mute___create': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - roleId: string; + userId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['Role']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -24916,6 +24892,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -24925,36 +24907,24 @@ export type operations = { }; }; /** - * roles/users + * renote-mute/delete * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:mutes* */ - roles___users: { + 'renote-mute___delete': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - roleId: string; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ - limit?: number; + userId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - /** Format: misskey:id */ - id: string; - user: components['schemas']['UserDetailed']; - }[]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -24989,25 +24959,21 @@ export type operations = { }; }; /** - * roles/notes + * renote-mute/list * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *read:mutes* */ - roles___notes: { + 'renote-mute___list': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - roleId: string; - /** @default 10 */ + /** @default 30 */ limit?: number; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; - sinceDate?: number; - untilDate?: number; }; }; }; @@ -25015,7 +24981,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': components['schemas']['RenoteMuting'][]; }; }; /** @description Client error */ @@ -25205,30 +25171,24 @@ export type operations = { }; }; /** - * server-info + * retention * @description No description provided. * * **Credential required**: *No* */ - 'server-info': { + retention: { responses: { /** @description OK (with results) */ 200: { content: { 'application/json': { - machine: string; - cpu: { - model: string; - cores: number; - }; - mem: { - total: number; - }; - fs: { - total: number; - used: number; - }; - }; + /** Format: date-time */ + createdAt: string; + users: number; + data: { + [key: string]: number; + }; + }[]; }; }; /** @description Client error */ @@ -25264,27 +25224,25 @@ export type operations = { }; }; /** - * stats + * reversi/cancel-match * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - stats: { - responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - notesCount: number; - originalNotesCount: number; - usersCount: number; - originalUsersCount: number; - instances: number; - driveUsageLocal: number; - driveUsageRemote: number; - }; + 'reversi___cancel-match': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId?: string | null; }; }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -25318,17 +25276,23 @@ export type operations = { }; }; /** - * sw/show-registration - * @description Check push notification registration exists. + * reversi/games + * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - 'sw___show-registration': { + reversi___games: { requestBody: { content: { 'application/json': { - endpoint: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default false */ + my?: boolean; }; }; }; @@ -25336,16 +25300,54 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - userId: string; - endpoint: string; - sendReadMessage: boolean; - } | null; + 'application/json': components['schemas']['ReversiGameLite'][]; }; }; - /** @description OK (without any results) */ - 204: { - content: never; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * reversi/invitations + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:account* + */ + reversi___invitations: { + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserLite'][]; + }; }; /** @description Client error */ 400: { @@ -25380,18 +25382,21 @@ export type operations = { }; }; /** - * sw/update-registration - * @description Update push notification registration. + * reversi/match + * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'sw___update-registration': { + reversi___match: { requestBody: { content: { 'application/json': { - endpoint: string; - sendReadMessage?: boolean; + /** Format: misskey:id */ + userId?: string | null; + /** @default false */ + noIrregularRules?: boolean; + /** @default false */ + multiple?: boolean; }; }; }; @@ -25399,13 +25404,13 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - userId: string; - endpoint: string; - sendReadMessage: boolean; - }; + 'application/json': components['schemas']['ReversiGameDetailed']; }; }; + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -25439,21 +25444,17 @@ export type operations = { }; }; /** - * sw/register - * @description Register to receive push notifications. + * reversi/show-game + * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - sw___register: { + 'reversi___show-game': { requestBody: { content: { 'application/json': { - endpoint: string; - auth: string; - publickey: string; - /** @default false */ - sendReadMessage?: boolean; + /** Format: misskey:id */ + gameId: string; }; }; }; @@ -25461,14 +25462,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** @enum {string} */ - state?: 'already-subscribed' | 'subscribed'; - key: string | null; - userId: string; - endpoint: string; - sendReadMessage: boolean; - }; + 'application/json': components['schemas']['ReversiGameDetailed']; }; }; /** @description Client error */ @@ -25504,16 +25498,17 @@ export type operations = { }; }; /** - * sw/unregister - * @description Unregister from receiving push notifications. + * reversi/surrender + * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - sw___unregister: { + reversi___surrender: { requestBody: { content: { 'application/json': { - endpoint: string; + /** Format: misskey:id */ + gameId: string; }; }; }; @@ -25555,23 +25550,18 @@ export type operations = { }; }; /** - * test - * @description Endpoint for testing input validation. + * reversi/verify + * @description No description provided. * * **Credential required**: *No* */ - test: { + reversi___verify: { requestBody: { content: { 'application/json': { - required: boolean; - string?: string; - /** @default hello */ - default?: string; - /** @default hello */ - nullableDefault?: string | null; /** Format: misskey:id */ - id?: string; + gameId: string; + crc32: string; }; }; }; @@ -25580,13 +25570,8 @@ export type operations = { 200: { content: { 'application/json': { - /** Format: misskey:id */ - id?: string; - required: boolean; - string?: string; - default?: string; - /** @default hello */ - nullableDefault?: string | null; + desynced: boolean; + game?: components['schemas']['ReversiGameDetailed'] | null; }; }; }; @@ -25623,26 +25608,17 @@ export type operations = { }; }; /** - * username/available + * roles/list * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - username___available: { - requestBody: { - content: { - 'application/json': { - username: string; - }; - }; - }; + roles___list: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': { - available: boolean; - }; + 'application/json': components['schemas']['Role'][]; }; }; /** @description Client error */ @@ -25678,36 +25654,25 @@ export type operations = { }; }; /** - * users + * roles/notes * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - users: { + roles___notes: { requestBody: { content: { 'application/json': { + /** Format: misskey:id */ + roleId: string; /** @default 10 */ limit?: number; - /** @default 0 */ - offset?: number; - /** @enum {string} */ - sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; - /** - * @default all - * @enum {string} - */ - state?: 'all' | 'alive'; - /** - * @default local - * @enum {string} - */ - origin?: 'combined' | 'local' | 'remote'; - /** - * @description The local host is represented with `null`. - * @default null - */ - hostname?: string | null; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; }; }; }; @@ -25715,7 +25680,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserDetailed'][]; + 'application/json': components['schemas']['Note'][]; }; }; /** @description Client error */ @@ -25751,23 +25716,17 @@ export type operations = { }; }; /** - * users/clips - * @description Show all clips this user owns. + * roles/show + * @description No description provided. * * **Credential required**: *No* */ - users___clips: { + roles___show: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + roleId: string; }; }; }; @@ -25775,7 +25734,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Clip'][]; + 'application/json': components['schemas']['Role']; }; }; /** @description Client error */ @@ -25811,26 +25770,23 @@ export type operations = { }; }; /** - * users/followers - * @description Show everyone that follows this user. + * roles/users + * @description No description provided. * * **Credential required**: *No* */ - users___followers: { + roles___users: { requestBody: { content: { 'application/json': { + /** Format: misskey:id */ + roleId: string; /** Format: misskey:id */ sinceId?: string; /** Format: misskey:id */ untilId?: string; /** @default 10 */ limit?: number; - /** Format: misskey:id */ - userId?: string; - username?: string; - /** @description The local host is represented with `null`. */ - host?: string | null; }; }; }; @@ -25838,7 +25794,11 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Following'][]; + 'application/json': { + /** Format: misskey:id */ + id: string; + user: components['schemas']['UserDetailed']; + }[]; }; }; /** @description Client error */ @@ -25874,35 +25834,30 @@ export type operations = { }; }; /** - * users/following - * @description Show everyone that this user is following. + * server-info + * @description No description provided. * * **Credential required**: *No* */ - users___following: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - userId?: string; - username?: string; - /** @description The local host is represented with `null`. */ - host?: string | null; - birthday?: string | null; - }; - }; - }; + 'server-info': { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Following'][]; + 'application/json': { + machine: string; + cpu: { + model: string; + cores: number; + }; + mem: { + total: number; + }; + fs: { + total: number; + used: number; + }; + }; }; }; /** @description Client error */ @@ -25938,31 +25893,25 @@ export type operations = { }; }; /** - * users/gallery/posts - * @description Show all gallery posts by the given user. + * stats + * @description No description provided. * * **Credential required**: *No* */ - users___gallery___posts: { - requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - userId: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - }; - }; - }; + stats: { responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['GalleryPost'][]; + 'application/json': { + notesCount: number; + originalNotesCount: number; + usersCount: number; + originalUsersCount: number; + instances: number; + driveUsageLocal: number; + driveUsageRemote: number; + }; }; }; /** @description Client error */ @@ -25998,19 +25947,21 @@ export type operations = { }; }; /** - * users/get-frequently-replied-users - * @description Get a list of other users that the specified user frequently replies to. + * sw/register + * @description Register to receive push notifications. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'users___get-frequently-replied-users': { + sw___register: { requestBody: { - content: { - 'application/json': { - /** Format: misskey:id */ - userId: string; - /** @default 10 */ - limit?: number; + content: { + 'application/json': { + endpoint: string; + auth: string; + publickey: string; + /** @default false */ + sendReadMessage?: boolean; }; }; }; @@ -26019,9 +25970,13 @@ export type operations = { 200: { content: { 'application/json': { - user: components['schemas']['UserDetailed']; - weight: number; - }[]; + /** @enum {string} */ + state?: 'already-subscribed' | 'subscribed'; + key: string | null; + userId: string; + endpoint: string; + sendReadMessage: boolean; + }; }; }; /** @description Client error */ @@ -26057,21 +26012,17 @@ export type operations = { }; }; /** - * users/featured-notes - * @description No description provided. + * sw/show-registration + * @description Check push notification registration exists. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - 'users___featured-notes': { + 'sw___show-registration': { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - untilId?: string; - /** Format: misskey:id */ - userId: string; + endpoint: string; }; }; }; @@ -26079,9 +26030,17 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': { + userId: string; + endpoint: string; + sendReadMessage: boolean; + } | null; }; }; + /** @description OK (without any results) */ + 204: { + content: never; + }; /** @description Client error */ 400: { content: { @@ -26115,25 +26074,23 @@ export type operations = { }; }; /** - * users/lists/create - * @description Create a new list of users. + * sw/unregister + * @description Unregister from receiving push notifications. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - users___lists___create: { + sw___unregister: { requestBody: { content: { 'application/json': { - name: string; + endpoint: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['UserList']; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -26168,24 +26125,31 @@ export type operations = { }; }; /** - * users/lists/delete - * @description Delete an existing list of users. + * sw/update-registration + * @description Update push notification registration. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* */ - users___lists___delete: { + 'sw___update-registration': { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - listId: string; + endpoint: string; + sendReadMessage?: boolean; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + userId: string; + endpoint: string; + sendReadMessage: boolean; + }; + }; }; /** @description Client error */ 400: { @@ -26220,17 +26184,23 @@ export type operations = { }; }; /** - * users/lists/list - * @description Show all lists that the authenticated user has created. + * test + * @description Endpoint for testing input validation. * - * **Credential required**: *No* / **Permission**: *read:account* + * **Credential required**: *No* */ - users___lists___list: { + test: { requestBody: { content: { 'application/json': { + required: boolean; + string?: string; + /** @default hello */ + default?: string; + /** @default hello */ + nullableDefault?: string | null; /** Format: misskey:id */ - userId?: string; + id?: string; }; }; }; @@ -26238,7 +26208,15 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserList'][]; + 'application/json': { + /** Format: misskey:id */ + id?: string; + required: boolean; + string?: string; + default?: string; + /** @default hello */ + nullableDefault?: string | null; + }; }; }; /** @description Client error */ @@ -26274,26 +26252,27 @@ export type operations = { }; }; /** - * users/lists/pull - * @description Remove a user from a list. + * username/available + * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - users___lists___pull: { + username___available: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - listId: string; - /** Format: misskey:id */ - userId: string; + username: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + available: boolean; + }; + }; }; /** @description Client error */ 400: { @@ -26328,26 +26307,45 @@ export type operations = { }; }; /** - * users/lists/push - * @description Add a user to an existing list. + * users + * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - users___lists___push: { + users: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - listId: string; - /** Format: misskey:id */ - userId: string; + /** @default 10 */ + limit?: number; + /** @default 0 */ + offset?: number; + /** @enum {string} */ + sort?: '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt'; + /** + * @default all + * @enum {string} + */ + state?: 'all' | 'alive'; + /** + * @default local + * @enum {string} + */ + origin?: 'combined' | 'local' | 'remote'; + /** + * @description The local host is represented with `null`. + * @default null + */ + hostname?: string | null; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserDetailed'][]; + }; }; /** @description Client error */ 400: { @@ -26373,12 +26371,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -26388,19 +26380,17 @@ export type operations = { }; }; /** - * users/lists/show - * @description Show the properties of a list. + * users/achievements + * @description No description provided. * - * **Credential required**: *No* / **Permission**: *read:account* + * **Credential required**: *No* */ - users___lists___show: { + users___achievements: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - listId: string; - /** @default false */ - forPublic?: boolean; + userId: string; }; }; }; @@ -26408,7 +26398,10 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserList']; + 'application/json': { + name: string; + unlockedAt: number; + }[]; }; }; /** @description Client error */ @@ -26444,24 +26437,32 @@ export type operations = { }; }; /** - * users/lists/favorite - * @description No description provided. + * users/clips + * @description Show all clips this user owns. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - users___lists___favorite: { + users___clips: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - listId: string; + userId: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Clip'][]; + }; }; /** @description Client error */ 400: { @@ -26496,24 +26497,30 @@ export type operations = { }; }; /** - * users/lists/unfavorite + * users/featured-notes * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - users___lists___unfavorite: { + 'users___featured-notes': { requestBody: { content: { 'application/json': { + /** @default 10 */ + limit?: number; /** Format: misskey:id */ - listId: string; + untilId?: string; + /** Format: misskey:id */ + userId: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Note'][]; + }; }; /** @description Client error */ 400: { @@ -26548,19 +26555,23 @@ export type operations = { }; }; /** - * users/lists/update - * @description Update the properties of a list. + * users/flashs + * @description Show all flashs this user created. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - users___lists___update: { + users___flashs: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - listId: string; - name?: string; - isPublic?: boolean; + userId: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; @@ -26568,7 +26579,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserList']; + 'application/json': components['schemas']['Flash'][]; }; }; /** @description Client error */ @@ -26604,18 +26615,26 @@ export type operations = { }; }; /** - * users/lists/create-from-public - * @description No description provided. + * users/followers + * @description Show everyone that follows this user. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - 'users___lists___create-from-public': { + users___followers: { requestBody: { content: { 'application/json': { - name: string; /** Format: misskey:id */ - listId: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + userId?: string; + username?: string; + /** @description The local host is represented with `null`. */ + host?: string | null; }; }; }; @@ -26623,7 +26642,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserList']; + 'application/json': components['schemas']['Following'][]; }; }; /** @description Client error */ @@ -26659,27 +26678,36 @@ export type operations = { }; }; /** - * users/lists/update-membership - * @description No description provided. + * users/following + * @description Show everyone that this user is following. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - 'users___lists___update-membership': { + users___following: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - listId: string; + sinceId?: string; /** Format: misskey:id */ - userId: string; - withReplies?: boolean; + untilId?: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + userId?: string; + username?: string; + /** @description The local host is represented with `null`. */ + host?: string | null; + birthday?: string | null; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Following'][]; + }; }; /** @description Client error */ 400: { @@ -26714,20 +26742,18 @@ export type operations = { }; }; /** - * users/lists/get-memberships - * @description No description provided. + * users/gallery/posts + * @description Show all gallery posts by the given user. * - * **Credential required**: *No* / **Permission**: *read:account* + * **Credential required**: *No* */ - 'users___lists___get-memberships': { + users___gallery___posts: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - listId: string; - /** @default false */ - forPublic?: boolean; - /** @default 30 */ + userId: string; + /** @default 10 */ limit?: number; /** Format: misskey:id */ sinceId?: string; @@ -26740,16 +26766,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: misskey:id */ - id: string; - /** Format: date-time */ - createdAt: string; - /** Format: misskey:id */ - userId: string; - user: components['schemas']['UserLite']; - withReplies: boolean; - }[]; + 'application/json': components['schemas']['GalleryPost'][]; }; }; /** @description Client error */ @@ -26785,35 +26802,19 @@ export type operations = { }; }; /** - * users/notes - * @description No description provided. + * users/get-frequently-replied-users + * @description Get a list of other users that the specified user frequently replies to. * * **Credential required**: *No* */ - users___notes: { + 'users___get-frequently-replied-users': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ userId: string; - /** @default false */ - withReplies?: boolean; - /** @default true */ - withRenotes?: boolean; - /** @default false */ - withChannelNotes?: boolean; /** @default 10 */ limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - sinceDate?: number; - untilDate?: number; - /** @default false */ - allowPartial?: boolean; - /** @default false */ - withFiles?: boolean; }; }; }; @@ -26821,7 +26822,10 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Note'][]; + 'application/json': { + user: components['schemas']['UserDetailed']; + weight: number; + }[]; }; }; /** @description Client error */ @@ -26857,23 +26861,16 @@ export type operations = { }; }; /** - * users/pages - * @description Show all pages this user created. + * users/lists/create + * @description Create a new list of users. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - users___pages: { + users___lists___create: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - userId: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + name: string; }; }; }; @@ -26881,7 +26878,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Page'][]; + 'application/json': components['schemas']['UserList']; }; }; /** @description Client error */ @@ -26917,23 +26914,18 @@ export type operations = { }; }; /** - * users/flashs - * @description Show all flashs this user created. + * users/lists/create-from-public + * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - users___flashs: { + 'users___lists___create-from-public': { requestBody: { content: { 'application/json': { + name: string; /** Format: misskey:id */ - userId: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; + listId: string; }; }; }; @@ -26941,7 +26933,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Flash'][]; + 'application/json': components['schemas']['UserList']; }; }; /** @description Client error */ @@ -26977,34 +26969,24 @@ export type operations = { }; }; /** - * users/reactions - * @description Show all reactions this user made. + * users/lists/delete + * @description Delete an existing list of users. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - users___reactions: { + users___lists___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - sinceDate?: number; - untilDate?: number; + listId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['NoteReaction'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -27039,28 +27021,24 @@ export type operations = { }; }; /** - * users/recommendation - * @description Show users that the authenticated user might be interested to follow. + * users/lists/favorite + * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - users___recommendation: { + users___lists___favorite: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** @default 0 */ - offset?: number; + /** Format: misskey:id */ + listId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['UserDetailed'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -27095,16 +27073,25 @@ export type operations = { }; }; /** - * users/relation - * @description Show the different kinds of relations between the authenticated user and the specified user(s). + * users/lists/get-memberships + * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* / **Permission**: *read:account* */ - users___relation: { + 'users___lists___get-memberships': { requestBody: { content: { 'application/json': { - userId: string | string[]; + /** Format: misskey:id */ + listId: string; + /** @default false */ + forPublic?: boolean; + /** @default 30 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; }; }; }; @@ -27112,29 +27099,16 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': OneOf<[{ - /** Format: id */ - id: string; - isFollowing: boolean; - hasPendingFollowRequestFromYou: boolean; - hasPendingFollowRequestToYou: boolean; - isFollowed: boolean; - isBlocking: boolean; - isBlocked: boolean; - isMuted: boolean; - isRenoteMuted: boolean; - }, { - /** Format: id */ + 'application/json': { + /** Format: misskey:id */ id: string; - isFollowing: boolean; - hasPendingFollowRequestFromYou: boolean; - hasPendingFollowRequestToYou: boolean; - isFollowed: boolean; - isBlocking: boolean; - isBlocked: boolean; - isMuted: boolean; - isRenoteMuted: boolean; - }[]]>; + /** Format: date-time */ + createdAt: string; + /** Format: misskey:id */ + userId: string; + user: components['schemas']['UserLite']; + withReplies: boolean; + }[]; }; }; /** @description Client error */ @@ -27170,25 +27144,26 @@ export type operations = { }; }; /** - * users/report-abuse - * @description File a report. + * users/lists/list + * @description Show all lists that the authenticated user has created. * - * **Credential required**: *Yes* / **Permission**: *write:report-abuse* + * **Credential required**: *No* / **Permission**: *read:account* */ - 'users___report-abuse': { + users___lists___list: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; - comment: string; + userId?: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserList'][]; + }; }; /** @description Client error */ 400: { @@ -27223,30 +27198,26 @@ export type operations = { }; }; /** - * users/search-by-username-and-host - * @description Search for a user by username and/or host. + * users/lists/pull + * @description Remove a user from a list. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users___search-by-username-and-host': { + users___lists___pull: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** @default true */ - detail?: boolean; - username?: string | null; - host?: string | null; + /** Format: misskey:id */ + listId: string; + /** Format: misskey:id */ + userId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['User'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -27281,36 +27252,26 @@ export type operations = { }; }; /** - * users/search - * @description Search for users. + * users/lists/push + * @description Add a user to an existing list. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - users___search: { + users___lists___push: { requestBody: { content: { 'application/json': { - query: string; - /** @default 0 */ - offset?: number; - /** @default 10 */ - limit?: number; - /** - * @default combined - * @enum {string} - */ - origin?: 'local' | 'remote' | 'combined'; - /** @default true */ - detail?: boolean; + /** Format: misskey:id */ + listId: string; + /** Format: misskey:id */ + userId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['User'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -27336,6 +27297,12 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; + /** @description Too many requests */ + 429: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; /** @description Internal server error */ 500: { content: { @@ -27345,21 +27312,19 @@ export type operations = { }; }; /** - * users/show - * @description Show the properties of a user. + * users/lists/show + * @description Show the properties of a list. * - * **Credential required**: *No* + * **Credential required**: *No* / **Permission**: *read:account* */ - users___show: { + users___lists___show: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId?: string; - userIds?: string[]; - username?: string; - /** @description The local host is represented with `null`. */ - host?: string | null; + listId: string; + /** @default false */ + forPublic?: boolean; }; }; }; @@ -27367,7 +27332,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserDetailed'] | components['schemas']['UserDetailed'][]; + 'application/json': components['schemas']['UserList']; }; }; /** @description Client error */ @@ -27403,29 +27368,24 @@ export type operations = { }; }; /** - * users/achievements + * users/lists/unfavorite * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - users___achievements: { + users___lists___unfavorite: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; + listId: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - name: string; - unlockedAt: number; - }[]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -27460,26 +27420,28 @@ export type operations = { }; }; /** - * users/update-memo - * @description No description provided. + * users/lists/update + * @description Update the properties of a list. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'users___update-memo': { + users___lists___update: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; - /** @description A personal memo for the target user. If null or empty, delete the memo. */ - memo: string | null; + listId: string; + name?: string; + isPublic?: boolean; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['UserList']; + }; }; /** @description Client error */ 400: { @@ -27514,72 +27476,27 @@ export type operations = { }; }; /** - * fetch-rss + * users/lists/update-membership * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:account* */ - 'fetch-rss': { + 'users___lists___update-membership': { requestBody: { content: { 'application/json': { - url: string; + /** Format: misskey:id */ + listId: string; + /** Format: misskey:id */ + userId: string; + withReplies?: boolean; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': { - image?: { - link?: string; - url: string; - title?: string; - }; - paginationLinks?: { - self?: string; - first?: string; - next?: string; - last?: string; - prev?: string; - }; - link?: string; - title?: string; - items: { - link?: string; - guid?: string; - title?: string; - pubDate?: string; - creator?: string; - summary?: string; - content?: string; - isoDate?: string; - categories?: string[]; - contentSnippet?: string; - enclosure?: { - url: string; - length?: number; - type?: string; - }; - }[]; - feedUrl?: string; - description?: string; - itunes?: { - image?: string; - owner?: { - name?: string; - email?: string; - }; - author?: string; - summary?: string; - explicit?: string; - categories?: string[]; - keywords?: string[]; - [key: string]: unknown; - }; - }; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -27614,18 +27531,35 @@ export type operations = { }; }; /** - * fetch-external-resources + * users/notes * @description No description provided. * - * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. - * **Credential required**: *Yes* + * **Credential required**: *No* */ - 'fetch-external-resources': { + users___notes: { requestBody: { content: { 'application/json': { - url: string; - hash: string; + /** Format: misskey:id */ + userId: string; + /** @default false */ + withReplies?: boolean; + /** @default true */ + withRenotes?: boolean; + /** @default false */ + withChannelNotes?: boolean; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; + /** @default false */ + allowPartial?: boolean; + /** @default false */ + withFiles?: boolean; }; }; }; @@ -27633,10 +27567,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - type: string; - data: string; - }; + 'application/json': components['schemas']['Note'][]; }; }; /** @description Client error */ @@ -27663,12 +27594,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -27678,24 +27603,31 @@ export type operations = { }; }; /** - * retention - * @description No description provided. + * users/pages + * @description Show all pages this user created. * * **Credential required**: *No* */ - retention: { + users___pages: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: date-time */ - createdAt: string; - users: number; - data: { - [key: string]: number; - }; - }[]; + 'application/json': components['schemas']['Page'][]; }; }; /** @description Client error */ @@ -27731,27 +27663,34 @@ export type operations = { }; }; /** - * bubble-game/register - * @description No description provided. + * users/reactions + * @description Show all reactions this user made. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - 'bubble-game___register': { + users___reactions: { requestBody: { content: { 'application/json': { - score: number; - seed: string; - logs: number[][]; - gameMode: string; - gameVersion: number; + /** Format: misskey:id */ + userId: string; + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + sinceDate?: number; + untilDate?: number; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['NoteReaction'][]; + }; }; /** @description Client error */ 400: { @@ -27777,12 +27716,6 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description Too many requests */ - 429: { - content: { - 'application/json': components['schemas']['Error']; - }; - }; /** @description Internal server error */ 500: { content: { @@ -27792,16 +27725,19 @@ export type operations = { }; }; /** - * bubble-game/ranking - * @description No description provided. + * users/recommendation + * @description Show users that the authenticated user might be interested to follow. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'bubble-game___ranking': { + users___recommendation: { requestBody: { content: { 'application/json': { - gameMode: string; + /** @default 10 */ + limit?: number; + /** @default 0 */ + offset?: number; }; }; }; @@ -27809,12 +27745,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** Format: misskey:id */ - id: string; - score: number; - user?: components['schemas']['UserLite']; - }[]; + 'application/json': components['schemas']['UserDetailed'][]; }; }; /** @description Client error */ @@ -27850,24 +27781,47 @@ export type operations = { }; }; /** - * reversi/cancel-match - * @description No description provided. + * users/relation + * @description Show the different kinds of relations between the authenticated user and the specified user(s). * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *Yes* / **Permission**: *read:account* */ - 'reversi___cancel-match': { + users___relation: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - userId?: string | null; + userId: string | string[]; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': OneOf<[{ + /** Format: id */ + id: string; + isFollowing: boolean; + hasPendingFollowRequestFromYou: boolean; + hasPendingFollowRequestToYou: boolean; + isFollowed: boolean; + isBlocking: boolean; + isBlocked: boolean; + isMuted: boolean; + isRenoteMuted: boolean; + }, { + /** Format: id */ + id: string; + isFollowing: boolean; + hasPendingFollowRequestFromYou: boolean; + hasPendingFollowRequestToYou: boolean; + isFollowed: boolean; + isBlocking: boolean; + isBlocked: boolean; + isMuted: boolean; + isRenoteMuted: boolean; + }[]]>; + }; }; /** @description Client error */ 400: { @@ -27902,32 +27856,25 @@ export type operations = { }; }; /** - * reversi/games - * @description No description provided. + * users/report-abuse + * @description File a report. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *write:report-abuse* */ - reversi___games: { + 'users___report-abuse': { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; /** Format: misskey:id */ - untilId?: string; - /** @default false */ - my?: boolean; + userId: string; + comment: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': components['schemas']['ReversiGameLite'][]; - }; + /** @description OK (without any results) */ + 204: { + content: never; }; /** @description Client error */ 400: { @@ -27962,21 +27909,27 @@ export type operations = { }; }; /** - * reversi/match - * @description No description provided. + * users/search + * @description Search for users. * - * **Credential required**: *Yes* / **Permission**: *write:account* + * **Credential required**: *No* */ - reversi___match: { + users___search: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - userId?: string | null; - /** @default false */ - noIrregularRules?: boolean; - /** @default false */ - multiple?: boolean; + query: string; + /** @default 0 */ + offset?: number; + /** @default 10 */ + limit?: number; + /** + * @default combined + * @enum {string} + */ + origin?: 'local' | 'remote' | 'combined'; + /** @default true */ + detail?: boolean; }; }; }; @@ -27984,13 +27937,9 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['ReversiGameDetailed']; + 'application/json': components['schemas']['User'][]; }; }; - /** @description OK (without any results) */ - 204: { - content: never; - }; /** @description Client error */ 400: { content: { @@ -28024,17 +27973,29 @@ export type operations = { }; }; /** - * reversi/invitations - * @description No description provided. + * users/search-by-username-and-host + * @description Search for a user by username and/or host. * - * **Credential required**: *Yes* / **Permission**: *read:account* + * **Credential required**: *No* */ - reversi___invitations: { + 'users___search-by-username-and-host': { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** @default true */ + detail?: boolean; + username?: string | null; + host?: string | null; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserLite'][]; + 'application/json': components['schemas']['User'][]; }; }; /** @description Client error */ @@ -28070,17 +28031,21 @@ export type operations = { }; }; /** - * reversi/show-game - * @description No description provided. + * users/show + * @description Show the properties of a user. * * **Credential required**: *No* */ - 'reversi___show-game': { + users___show: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - gameId: string; + userId?: string; + userIds?: string[]; + username?: string; + /** @description The local host is represented with `null`. */ + host?: string | null; }; }; }; @@ -28088,7 +28053,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['ReversiGameDetailed']; + 'application/json': components['schemas']['UserDetailed'] | components['schemas']['UserDetailed'][]; }; }; /** @description Client error */ @@ -28124,17 +28089,19 @@ export type operations = { }; }; /** - * reversi/surrender + * users/update-memo * @description No description provided. * * **Credential required**: *Yes* / **Permission**: *write:account* */ - reversi___surrender: { + 'users___update-memo': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - gameId: string; + userId: string; + /** @description A personal memo for the target user. If null or empty, delete the memo. */ + memo: string | null; }; }; }; @@ -28176,18 +28143,49 @@ export type operations = { }; }; /** - * reversi/verify + * v2/admin/emoji/list * @description No description provided. * - * **Credential required**: *No* + * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ - reversi___verify: { + v2___admin___emoji___list: { requestBody: { content: { 'application/json': { + query?: ({ + updatedAtFrom?: string; + updatedAtTo?: string; + name?: string; + host?: string; + uri?: string; + publicUrl?: string; + originalUrl?: string; + type?: string; + aliases?: string; + category?: string; + license?: string; + isSensitive?: boolean; + localOnly?: boolean; + /** + * @default all + * @enum {string} + */ + hostType?: 'local' | 'remote' | 'all'; + roleIds?: string[]; + }) | null; /** Format: misskey:id */ - gameId: string; - crc32: string; + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default 10 */ + limit?: number; + page?: number; + /** + * @default [ + * "-id" + * ] + */ + sortKeys?: ('+id' | '-id' | '+updatedAt' | '-updatedAt' | '+name' | '-name' | '+host' | '-host' | '+uri' | '-uri' | '+publicUrl' | '-publicUrl' | '+type' | '-type' | '+aliases' | '-aliases' | '+category' | '-category' | '+license' | '-license' | '+isSensitive' | '-isSensitive' | '+localOnly' | '-localOnly' | '+roleIdsThatCanBeUsedThisEmojiAsReaction' | '-roleIdsThatCanBeUsedThisEmojiAsReaction')[]; }; }; }; @@ -28196,8 +28194,10 @@ export type operations = { 200: { content: { 'application/json': { - desynced: boolean; - game?: components['schemas']['ReversiGameDetailed'] | null; + emojis: components['schemas']['EmojiDetailedAdmin'][]; + count: number; + allCount: number; + allPages: number; }; }; }; -- cgit v1.2.3-freya From b92a3255394a727feb4a9aae1eef3c2048e2bea4 Mon Sep 17 00:00:00 2001 From: piuvas Date: Mon, 20 Jan 2025 17:18:56 -0300 Subject: use addOrderBy as not to override orderBy --- packages/backend/src/server/api/endpoints/emojis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts index 3cc7f89ab9..f8095dd00e 100644 --- a/packages/backend/src/server/api/endpoints/emojis.ts +++ b/packages/backend/src/server/api/endpoints/emojis.ts @@ -59,7 +59,7 @@ export default class extends Endpoint { // eslint- const emojis = await this.emojisRepository.createQueryBuilder() .where('host IS NULL') .orderBy('LOWER(category)', 'ASC') - .orderBy('LOWER(name)', 'ASC') + .addOrderBy('LOWER(name)', 'ASC') .getMany(); return { emojis: await this.emojiEntityService.packSimpleMany(emojis), -- cgit v1.2.3-freya From 62964c45f8bd2fce818b76faaba88f94c71db74d Mon Sep 17 00:00:00 2001 From: piuvas Date: Mon, 20 Jan 2025 17:26:39 -0300 Subject: cleanup for linter to be happy --- packages/backend/src/server/api/endpoints/emojis.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts index f8095dd00e..84f2eebe88 100644 --- a/packages/backend/src/server/api/endpoints/emojis.ts +++ b/packages/backend/src/server/api/endpoints/emojis.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { IsNull } from 'typeorm'; import { Inject, Injectable } from '@nestjs/common'; import type { EmojisRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; @@ -55,7 +54,7 @@ export default class extends Endpoint { // eslint- private emojiEntityService: EmojiEntityService, ) { - super(meta, paramDef, async (ps, me) => { + super(meta, paramDef, async () => { const emojis = await this.emojisRepository.createQueryBuilder() .where('host IS NULL') .orderBy('LOWER(category)', 'ASC') -- cgit v1.2.3-freya From d7fdcbc7339966c2f074261d9c99d5000e3fb60c Mon Sep 17 00:00:00 2001 From: Takeshi Umeda Date: Tue, 21 Jan 2025 09:30:39 +0900 Subject: fix: 絵文字のライセンスのActivityPub contextを追加 (#15318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/activitypub/misc/contexts.ts | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts index 94cb0785cb..6611e4b7f9 100644 --- a/packages/backend/src/core/activitypub/misc/contexts.ts +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -558,6 +558,11 @@ const extension_context_definition = { '_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents', '_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore', '_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore', + '_misskey_license': 'misskey:_misskey_license', + 'freeText': { + '@id': 'misskey:freeText', + '@type': 'schema:text', + }, 'isCat': 'misskey:isCat', // vcard vcard: 'http://www.w3.org/2006/vcard/ns#', -- cgit v1.2.3-freya From b3d2cd65aab57508dd28050f0b288b3bd95c144a Mon Sep 17 00:00:00 2001 From: dakkar Date: Thu, 23 Jan 2025 12:24:06 +0000 Subject: use `import` configuration for downloads in emoji importer --- .../src/queue/processors/ImportCustomEmojisProcessorService.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts index 17ba71df3d..666a709ab9 100644 --- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts @@ -17,6 +17,7 @@ import { bindThis } from '@/decorators.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserImportJobData } from '../types.js'; +import type { Config } from '@/config.js'; // TODO: 名前衝突時の動作を選べるようにする @Injectable() @@ -24,6 +25,9 @@ export class ImportCustomEmojisProcessorService { private logger: Logger; constructor( + @Inject(DI.config) + private config: Config, + @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, @@ -57,7 +61,7 @@ export class ImportCustomEmojisProcessorService { try { fs.writeFileSync(destPath, '', 'binary'); - await this.downloadService.downloadUrl(file.url, destPath); + await this.downloadService.downloadUrl(file.url, destPath, { operationTimeout: this.config.import?.downloadTimeout, maxSize: this.config.import?.maxFileSize }); } catch (e) { // TODO: 何度か再試行 if (e instanceof Error || typeof e === 'string') { this.logger.error(e); -- cgit v1.2.3-freya From eef8a9e24c79b9142475c0dafbce4c6c10e87ff5 Mon Sep 17 00:00:00 2001 From: dakkar Date: Thu, 23 Jan 2025 12:26:51 +0000 Subject: use `import` configuration for media downloads in notes importer --- packages/backend/src/queue/processors/ImportNotesProcessorService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/queue/processors/ImportNotesProcessorService.ts b/packages/backend/src/queue/processors/ImportNotesProcessorService.ts index f89dc46722..ee9819b29f 100644 --- a/packages/backend/src/queue/processors/ImportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportNotesProcessorService.ts @@ -626,7 +626,7 @@ export class ImportNotesProcessorService { if (!exists) { try { - await this.downloadService.downloadUrl(videos[0].url, filePath); + await this.downloadUrl(videos[0].url, filePath); } catch (e) { // TODO: 何度か再試行 this.logger.error(e instanceof Error ? e : new Error(e as string)); } @@ -651,7 +651,7 @@ export class ImportNotesProcessorService { if (!exists) { try { - await this.downloadService.downloadUrl(file.media_url_https, filePath); + await this.downloadUrl(file.media_url_https, filePath); } catch (e) { // TODO: 何度か再試行 this.logger.error(e instanceof Error ? e : new Error(e as string)); } -- cgit v1.2.3-freya From 716f203171361d6279104b36b868a237f6abae56 Mon Sep 17 00:00:00 2001 From: piuvas Date: Thu, 23 Jan 2025 22:33:19 -0300 Subject: requested changes --- packages/backend/src/server/api/endpoints/emojis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts index 84f2eebe88..4909c948e3 100644 --- a/packages/backend/src/server/api/endpoints/emojis.ts +++ b/packages/backend/src/server/api/endpoints/emojis.ts @@ -54,7 +54,7 @@ export default class extends Endpoint { // eslint- private emojiEntityService: EmojiEntityService, ) { - super(meta, paramDef, async () => { + super(meta, paramDef, async (ps, me) => { const emojis = await this.emojisRepository.createQueryBuilder() .where('host IS NULL') .orderBy('LOWER(category)', 'ASC') -- cgit v1.2.3-freya From 1080f19e99ffccdebb09c39035d93d9b561ef0e6 Mon Sep 17 00:00:00 2001 From: piuvas Date: Fri, 24 Jan 2025 18:37:18 -0300 Subject: generalize current language so we match more broadly on fallback --- packages/backend/src/server/web/boot.embed.js | 2 +- packages/backend/src/server/web/boot.js | 2 +- packages/frontend/src/_dev_boot_.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js index b07dce3ac4..1af1dc545b 100644 --- a/packages/backend/src/server/web/boot.embed.js +++ b/packages/backend/src/server/web/boot.embed.js @@ -48,7 +48,7 @@ if (supportedLangs.includes(navigator.language)) { lang = navigator.language; } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language.split('-')[0]); // Fallback if (lang == null) lang = 'en-US'; diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index bf83340bde..54750e26e5 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -39,7 +39,7 @@ if (supportedLangs.includes(navigator.language)) { lang = navigator.language; } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language.split('-')[0]); // Fallback if (lang == null) lang = 'en-US'; diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts index f312765dcf..ebce7e735f 100644 --- a/packages/frontend/src/_dev_boot_.ts +++ b/packages/frontend/src/_dev_boot_.ts @@ -25,7 +25,7 @@ async function main() { if (supportedLangs.includes(navigator.language)) { lang = navigator.language; } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + lang = supportedLangs.find(x => x.split('-')[0] === navigator.language.split('-')[0]); // Fallback if (lang == null) lang = 'en-US'; -- cgit v1.2.3-freya From 35104d87d5174a080143d3604e50bbef974ab04e Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:58:39 +0900 Subject: revert(dev): フロントエンド・バックエンドを分離する開発モードを廃止 (#15284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "chore: 開発モードでフロントエンドとバックエンドを独立して起動するようにする(再) (#12593)" This reverts commit b0039f0946b02777ad99ad8c92f6555792aa8996. * revert dev command * revert embed dev * 消しすぎた * filesをプロキシするように * fix chromatic ci * Revert "filesをプロキシするように" This reverts commit 41be2548ce82ba408588c5f0dee007c97d026e55. * fix: configのhostnameでサーバーを起動するように * fix * lint * Update Changelog * fix --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + CONTRIBUTING.md | 19 +--- .../backend/src/server/web/ClientServerService.ts | 7 +- packages/frontend-embed/package.json | 1 - packages/frontend-embed/src/index.html | 36 -------- packages/frontend-embed/vite.config.local-dev.ts | 96 -------------------- packages/frontend-embed/vite.config.ts | 6 ++ packages/frontend/package.json | 1 - packages/frontend/src/_dev_boot_.ts | 90 ------------------ packages/frontend/src/index.html | 37 -------- packages/frontend/src/pages/welcome.entrance.a.vue | 1 + packages/frontend/vite.config.local-dev.ts | 101 --------------------- packages/frontend/vite.config.ts | 6 ++ scripts/dev.mjs | 4 +- 14 files changed, 23 insertions(+), 383 deletions(-) delete mode 100644 packages/frontend-embed/src/index.html delete mode 100644 packages/frontend-embed/vite.config.local-dev.ts delete mode 100644 packages/frontend/src/_dev_boot_.ts delete mode 100644 packages/frontend/src/index.html delete mode 100644 packages/frontend/vite.config.local-dev.ts (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7ae08215..15578d0da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - 新しい設定項目"fulltextSearch.provider"が追加されました. sqlLike, sqlPgroonga, meilisearchのいずれかを設定出来ます. - すでにMeilisearchをお使いの場合、 **"fulltextSearch.provider"を"meilisearch"に設定する必要** があります. - 詳細は #14730 および `.config/example.yml` または `.config/docker_example.yml`の'Fulltext search configuration'をご参照願います. +- 【開発者向け】従来の開発モードでHMRが機能しない問題が修正されたため、バックエンド・フロントエンド分離型の開発モードが削除されました。開発環境においてconfigの変更が必要となる可能性があります。 ### General - Feat: カスタム絵文字管理画面をリニューアル #10996 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d721ae60d8..0c63f69cf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -197,25 +197,10 @@ pnpm dev command. - Server-side source files and automatically builds them if they are modified. Automatically start the server process(es). -- Vite HMR (just the `vite` command) is available. The behavior may be different from production. - Service Worker is watched by esbuild. -- The front end can be viewed by accessing `http://localhost:5173`. -- The backend listens on the port configured with `port` in .config/default.yml. -If you have not changed it from the default, it will be "http://localhost:3000". -If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts. - -### `MK_DEV_PREFER=backend pnpm dev` -pnpm dev has another mode with `MK_DEV_PREFER=backend`. - -``` -MK_DEV_PREFER=backend pnpm dev -``` - -- This mode is closer to the production environment than the default mode. -- Vite runs behind the backend (the backend will proxy Vite at /vite). +- Vite HMR (just the `vite` command) is available. The behavior may be different from production. +- Vite runs behind the backend (the backend will proxy Vite at /vite and /embed_vite except for websocket used for HMR). - You can see Misskey by accessing `http://localhost:3000` (Replace `3000` with the port configured with `port` in .config/default.yml). -- To change the port of Vite, specify with `VITE_PORT` environment variable. -- HMR may not work in some environments such as Windows. ## Testing You can run non-backend tests by executing following commands: diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 1a75096c4e..d450c3fb01 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -317,16 +317,19 @@ export class ClientServerService { done(); }); } else { + const configUrl = new URL(this.config.url); + const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); + const port = (process.env.VITE_PORT ?? '5173'); fastify.register(fastifyProxy, { - upstream: 'http://localhost:' + port, + upstream: urlOriginWithoutPort + ':' + port, prefix: '/vite', rewritePrefix: '/vite', }); const embedPort = (process.env.EMBED_VITE_PORT ?? '5174'); fastify.register(fastifyProxy, { - upstream: 'http://localhost:' + embedPort, + upstream: urlOriginWithoutPort + ':' + embedPort, prefix: '/embed_vite', rewritePrefix: '/embed_vite', }); diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 3d04c566b6..a0353b4c7f 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -4,7 +4,6 @@ "type": "module", "scripts": { "watch": "vite", - "dev": "vite --config vite.config.local-dev.ts --debug hmr", "build": "vite build", "typecheck": "vue-tsc --noEmit", "eslint": "eslint --quiet \"src/**/*.{ts,vue}\"", diff --git a/packages/frontend-embed/src/index.html b/packages/frontend-embed/src/index.html deleted file mode 100644 index 47b0b0e84e..0000000000 --- a/packages/frontend-embed/src/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - [DEV] Loading... - - - - - - - -
- - - diff --git a/packages/frontend-embed/vite.config.local-dev.ts b/packages/frontend-embed/vite.config.local-dev.ts deleted file mode 100644 index bf2f478887..0000000000 --- a/packages/frontend-embed/vite.config.local-dev.ts +++ /dev/null @@ -1,96 +0,0 @@ -import dns from 'dns'; -import { readFile } from 'node:fs/promises'; -import type { IncomingMessage } from 'node:http'; -import { defineConfig } from 'vite'; -import type { UserConfig } from 'vite'; -import * as yaml from 'js-yaml'; -import locales from '../../locales/index.js'; -import { getConfig } from './vite.config.js'; - -dns.setDefaultResultOrder('ipv4first'); - -const defaultConfig = getConfig(); - -const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8')); - -const httpUrl = `http://localhost:${port}/`; -const websocketUrl = `ws://localhost:${port}/`; - -// activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す -function varyHandler(req: IncomingMessage) { - if (req.headers.accept?.includes('application/activity+json')) { - return null; - } - return '/index.html'; -} - -const devConfig: UserConfig = { - // 基本の設定は vite.config.js から引き継ぐ - ...defaultConfig, - root: 'src', - publicDir: '../assets', - base: '/embed', - server: { - host: 'localhost', - port: 5174, - proxy: { - '/api': { - changeOrigin: true, - target: httpUrl, - }, - '/assets': httpUrl, - '/static-assets': httpUrl, - '/client-assets': httpUrl, - '/files': httpUrl, - '/twemoji': httpUrl, - '/fluent-emoji': httpUrl, - '/sw.js': httpUrl, - '/streaming': { - target: websocketUrl, - ws: true, - }, - '/favicon.ico': httpUrl, - '/robots.txt': httpUrl, - '/embed.js': httpUrl, - '/identicon': { - target: httpUrl, - rewrite(path) { - return path.replace('@localhost:5173', ''); - }, - }, - '/url': httpUrl, - '/proxy': httpUrl, - '/_info_card_': httpUrl, - '/bios': httpUrl, - '/cli': httpUrl, - '/inbox': httpUrl, - '/emoji/': httpUrl, - '/notes': { - target: httpUrl, - bypass: varyHandler, - }, - '/users': { - target: httpUrl, - bypass: varyHandler, - }, - '/.well-known': { - target: httpUrl, - }, - }, - }, - build: { - ...defaultConfig.build, - rollupOptions: { - ...defaultConfig.build?.rollupOptions, - input: 'index.html', - }, - }, - - define: { - ...defaultConfig.define, - _LANGS_FULL_: JSON.stringify(Object.entries(locales)), - }, -}; - -export default defineConfig(({ command, mode }) => devConfig); - diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index 151d316190..3d628c800e 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -1,12 +1,17 @@ import path from 'path'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; +import * as yaml from 'js-yaml'; +import { promises as fsp } from 'fs'; import locales from '../../locales/index.js'; import meta from '../../package.json'; import packageInfo from './package.json' with { type: 'json' }; import pluginJson5 from './vite.json5.js'; +const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null; +const host = url ? (new URL(url)).hostname : undefined; + const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; /** @@ -62,6 +67,7 @@ export function getConfig(): UserConfig { base: '/embed_vite/', server: { + host, port: 5174, hmr: { // バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8b20980d63..9ec8bb0a83 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,7 +4,6 @@ "type": "module", "scripts": { "watch": "vite", - "dev": "vite --config vite.config.local-dev.ts --debug hmr", "build": "vite build", "storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"", "build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js", diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts deleted file mode 100644 index f312765dcf..0000000000 --- a/packages/frontend/src/_dev_boot_.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -await main(); - -import('@/_boot_.js'); - -/** - * backend/src/server/web/boot.jsで差し込まれている起動処理のうち、最低限必要なものを模倣するための処理 - */ -async function main() { - const forceError = localStorage.getItem('forceError'); - if (forceError != null) { - renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.'); - } - - //#region Detect language & fetch translations - - // dev-modeの場合は常に取り直す - const supportedLangs = _LANGS_.map(it => it[0]); - let lang: string | null | undefined = localStorage.getItem('lang'); - if (lang == null || !supportedLangs.includes(lang)) { - if (supportedLangs.includes(navigator.language)) { - lang = navigator.language; - } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); - - // Fallback - if (lang == null) lang = 'en-US'; - } - } - - // TODO:今のままだと言語ファイル変更後はpnpm devをリスタートする必要があるので、chokidarを使ったり等で対応できるようにする - const locale = _LANGS_FULL_.find(it => it[0] === lang); - localStorage.setItem('lang', lang); - localStorage.setItem('locale', JSON.stringify(locale[1])); - localStorage.setItem('localeVersion', _VERSION_); - //#endregion - - //#region Theme - const theme = localStorage.getItem('theme'); - if (theme) { - for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); - - // HTMLの theme-color 適用 - if (k === 'htmlThemeColor') { - for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', v); - break; - } - } - } - } - } - const colorScheme = localStorage.getItem('colorScheme'); - if (colorScheme) { - document.documentElement.style.setProperty('color-scheme', colorScheme); - } - //#endregion - - const fontSize = localStorage.getItem('fontSize'); - if (fontSize) { - document.documentElement.classList.add('f-' + fontSize); - } - - const useSystemFont = localStorage.getItem('useSystemFont'); - if (useSystemFont) { - document.documentElement.classList.add('useSystemFont'); - } - - const wallpaper = localStorage.getItem('wallpaper'); - if (wallpaper) { - document.documentElement.style.backgroundImage = `url(${wallpaper})`; - } - - const customCss = localStorage.getItem('customCss'); - if (customCss && customCss.length > 0) { - const style = document.createElement('style'); - style.innerHTML = customCss; - document.head.appendChild(style); - } -} - -function renderError(code: string, details?: string) { - console.log(code, details); -} diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html deleted file mode 100644 index 84ba9dfabc..0000000000 --- a/packages/frontend/src/index.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - [DEV] Loading... - - - - - - - -
- - - diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue index 68938f0bbf..34c5c3ce6c 100644 --- a/packages/frontend/src/pages/welcome.entrance.a.vue +++ b/packages/frontend/src/pages/welcome.entrance.a.vue @@ -53,6 +53,7 @@ function getInstanceIcon(instance: Misskey.entities.FederationInstance): string if (!instance.iconUrl) { return ''; } + return getProxiedImageUrl(instance.iconUrl, 'preview'); } diff --git a/packages/frontend/vite.config.local-dev.ts b/packages/frontend/vite.config.local-dev.ts deleted file mode 100644 index 922fb45995..0000000000 --- a/packages/frontend/vite.config.local-dev.ts +++ /dev/null @@ -1,101 +0,0 @@ -import dns from 'dns'; -import { readFile } from 'node:fs/promises'; -import type { IncomingMessage } from 'node:http'; -import { defineConfig } from 'vite'; -import type { UserConfig } from 'vite'; -import * as yaml from 'js-yaml'; -import locales from '../../locales/index.js'; -import { getConfig } from './vite.config.js'; - -dns.setDefaultResultOrder('ipv4first'); - -const defaultConfig = getConfig(); - -const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8')); - -const httpUrl = `http://localhost:${port}/`; -const websocketUrl = `ws://localhost:${port}/`; -const embedUrl = `http://localhost:5174/`; - -// activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す -function varyHandler(req: IncomingMessage) { - if (req.headers.accept?.includes('application/activity+json')) { - return null; - } - return '/index.html'; -} - -const devConfig: UserConfig = { - // 基本の設定は vite.config.js から引き継ぐ - ...defaultConfig, - root: 'src', - publicDir: '../assets', - base: './', - server: { - host: 'localhost', - port: 5173, - proxy: { - '/api': { - changeOrigin: true, - target: httpUrl, - }, - '/assets': httpUrl, - '/static-assets': httpUrl, - '/client-assets': httpUrl, - '/files': httpUrl, - '/twemoji': httpUrl, - '/fluent-emoji': httpUrl, - '/sw.js': httpUrl, - '/streaming': { - target: websocketUrl, - ws: true, - }, - '/favicon.ico': httpUrl, - '/robots.txt': httpUrl, - '/embed.js': httpUrl, - '/embed': { - target: embedUrl, - ws: true, - }, - '/identicon': { - target: httpUrl, - rewrite(path) { - return path.replace('@localhost:5173', ''); - }, - }, - '/url': httpUrl, - '/proxy': httpUrl, - '/_info_card_': httpUrl, - '/bios': httpUrl, - '/cli': httpUrl, - '/inbox': httpUrl, - '/emoji/': httpUrl, - '/notes': { - target: httpUrl, - bypass: varyHandler, - }, - '/users': { - target: httpUrl, - bypass: varyHandler, - }, - '/.well-known': { - target: httpUrl, - }, - }, - }, - build: { - ...defaultConfig.build, - rollupOptions: { - ...defaultConfig.build?.rollupOptions, - input: 'index.html', - }, - }, - - define: { - ...defaultConfig.define, - _LANGS_FULL_: JSON.stringify(Object.entries(locales)), - }, -}; - -export default defineConfig(({ command, mode }) => devConfig); - diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 3c4b19a571..d1b7c410dc 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -2,6 +2,8 @@ import path from 'path'; import pluginReplace from '@rollup/plugin-replace'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; +import * as yaml from 'js-yaml'; +import { promises as fsp } from 'fs'; import locales from '../../locales/index.js'; import meta from '../../package.json'; @@ -9,6 +11,9 @@ import packageInfo from './package.json' with { type: 'json' }; import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js'; import pluginJson5 from './vite.json5.js'; +const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null; +const host = url ? (new URL(url)).hostname : undefined; + const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; /** @@ -64,6 +69,7 @@ export function getConfig(): UserConfig { base: '/vite/', server: { + host, port: 5173, hmr: { // バックエンド経由での起動時、Viteは5173経由でアセットを参照していると思い込んでいるが実際は3000から配信される diff --git a/scripts/dev.mjs b/scripts/dev.mjs index a4c82d46e1..ede77554d2 100644 --- a/scripts/dev.mjs +++ b/scripts/dev.mjs @@ -71,13 +71,13 @@ execa('pnpm', ['--filter', 'frontend-shared', 'watch'], { stderr: process.stderr, }); -execa('pnpm', ['--filter', 'frontend', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], { +execa('pnpm', ['--filter', 'frontend', 'watch'], { cwd: _dirname + '/../', stdout: process.stdout, stderr: process.stderr, }); -execa('pnpm', ['--filter', 'frontend-embed', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], { +execa('pnpm', ['--filter', 'frontend-embed', 'watch'], { cwd: _dirname + '/../', stdout: process.stdout, stderr: process.stderr, -- cgit v1.2.3-freya From ef29130057e08e3c2b51625671d8cdfd495f6d08 Mon Sep 17 00:00:00 2001 From: "饺子w (Yumechi)" <35571479+eternal-flame-AD@users.noreply.github.com> Date: Sun, 26 Jan 2025 06:03:42 +0000 Subject: fix(backend): ノートの閲覧にログイン必須にしてもFeedでノートが表示されてしまう問題を修正 (#15083) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: eternal-flame-AD Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + packages/backend/src/server/web/ClientServerService.ts | 1 + 2 files changed, 2 insertions(+) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 7339dbd3ff..8536c1a4ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) +- Fix: ノートの閲覧にログイン必須にしてもFeedでノートが表示されてしまう問題を修正 - Fix: 絵文字の連合でライセンス欄を相互にやり取りするように ( #10859, #14109 ) - Fix: ロックダウンされた期間指定のノートがStreaming経由でLTLに出現するのを修正 ( #15200 ) - Fix: disableClustering設定時の初期化ロジックを調整( #15223 ) diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index d450c3fb01..4c884dd314 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -512,6 +512,7 @@ export class ClientServerService { usernameLower: username.toLowerCase(), host: host ?? IsNull(), isSuspended: false, + requireSigninToViewContents: false, }); return user && await this.feedService.packFeed(user); -- cgit v1.2.3-freya From 8232ea6956c74bb13c214debb9edcfb255faf8fd Mon Sep 17 00:00:00 2001 From: おさむのひと <46447427+samunohito@users.noreply.github.com> Date: Mon, 27 Jan 2025 22:10:00 +0900 Subject: fix(backend): デフォルト起動時のメインプロセスはHTTPサーバモジュールのみ読み込む (#15355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/boot/master.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 2b181af675..d1fb3858db 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -92,7 +92,7 @@ export async function masterMain() { } bootLogger.info( - `mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]` + `mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]`, ); if (!envOption.disableClustering) { @@ -107,7 +107,6 @@ export async function masterMain() { await jobQueue(); } else { await server(); - await jobQueue(); } await spawnWorkers(config.clusterLimit); -- cgit v1.2.3-freya From 59ee7adc897d3e0778916595561c58b35d88a122 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 27 Jan 2025 18:13:16 -0500 Subject: Correct type definition of `Actor.discoverable` property --- packages/backend/src/core/activitypub/type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index d67f8cf62e..119a9d8ccb 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -202,7 +202,7 @@ export interface IActor extends IObject { manuallyApprovesFollowers?: boolean; movedTo?: string; alsoKnownAs?: string[]; - discoverable?: boolean; + discoverable?: boolean | null; inbox: string; sharedInbox?: string; // 後方互換性のため publicKey?: { -- cgit v1.2.3-freya From 4ec2ef29216981bc120a25722fb31d2311d5d1e0 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 27 Jan 2025 18:13:49 -0500 Subject: handle null values for `discoverable` property --- packages/backend/src/core/activitypub/models/ApPersonService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 5c71dbc626..9d326e2737 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -398,7 +398,7 @@ export class ApPersonService implements OnModuleInit { alsoKnownAs: person.alsoKnownAs, // We use "!== false" to handle incorrect types, missing / null values, and "default to true" logic. hideOnlineStatus: person.hideOnlineStatus !== false, - isExplorable: person.discoverable, + isExplorable: person.discoverable !== false, username: person.preferredUsername, approved: true, usernameLower: person.preferredUsername?.toLowerCase(), @@ -602,7 +602,7 @@ export class ApPersonService implements OnModuleInit { alsoKnownAs: person.alsoKnownAs ?? null, // We use "!== false" to handle incorrect types, missing / null values, and "default to true" logic. hideOnlineStatus: person.hideOnlineStatus !== false, - isExplorable: person.discoverable, + isExplorable: person.discoverable !== false, ...(await this.resolveAvatarAndBanner(exist, person.icon, person.image, person.backgroundUrl).catch(() => ({}))), } as Partial & Pick; -- cgit v1.2.3-freya From 993532bc1fe0f67d84e16a99ee916f7fff9b0935 Mon Sep 17 00:00:00 2001 From: Kinetix Date: Tue, 28 Jan 2025 15:57:45 -0800 Subject: Adding robots.txt override via admin control panel This is a requested low priority feature in #418 - I created the changes to follow similarly to how the Instance Description is handled. --- locales/index.d.ts | 8 ++++++++ packages/backend/migration/1738098171990-robotsTxt.js | 16 ++++++++++++++++ packages/backend/src/core/entities/MetaEntityService.ts | 1 + packages/backend/src/models/Meta.ts | 5 +++++ packages/backend/src/models/json-schema/meta.ts | 4 ++++ packages/backend/src/server/api/endpoints/admin/meta.ts | 5 +++++ .../src/server/api/endpoints/admin/update-meta.ts | 5 +++++ packages/backend/src/server/web/ClientServerService.ts | 9 ++++++++- packages/frontend/src/pages/admin/settings.vue | 7 +++++++ sharkey-locales/en-US.yml | 3 +++ 10 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1738098171990-robotsTxt.js (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index 3a3b94b89d..70eba52ea0 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -11626,6 +11626,14 @@ export interface Locale extends ILocale { * Scheduled Notes */ "scheduledNotes": string; + /** + * Custom robots.txt + */ + "robotsTxt": string; + /** + * Adding entries here will override the default robots.txt packaged with Sharkey. Maximum 2048 characters. + */ + "robotsTxtDescription": string; } declare const locales: { [lang: string]: Locale; diff --git a/packages/backend/migration/1738098171990-robotsTxt.js b/packages/backend/migration/1738098171990-robotsTxt.js new file mode 100644 index 0000000000..947f21cc46 --- /dev/null +++ b/packages/backend/migration/1738098171990-robotsTxt.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: marie and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RobotsTxt1738098171990 { + name = 'RobotsTxt1738098171990' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "robotsTxt" character varying(2048)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "robotsTxt"`); + } +} diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 7d7b4cbd81..857e8f5a7b 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -95,6 +95,7 @@ export class MetaEntityService { mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl, enableRecaptcha: instance.enableRecaptcha, enableAchievements: instance.enableAchievements, + robotsTxt: instance.robotsTxt, recaptchaSiteKey: instance.recaptchaSiteKey, enableTurnstile: instance.enableTurnstile, turnstileSiteKey: instance.turnstileSiteKey, diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 3fc3f273dd..a224117676 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -599,6 +599,11 @@ export class MiMeta { }) public enableAchievements: boolean; + @Column('varchar', { + length: 2048, nullable: true, + }) + public robotsTxt: string | null; + @Column('jsonb', { default: { }, }) diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index 5179e5d51c..29fdb4f6be 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -139,6 +139,10 @@ export const packedMetaLiteSchema = { type: 'boolean', optional: false, nullable: true, }, + robotsTxt: { + type: 'string', + optional: false, nullable: true, + }, enableTestcaptcha: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 6495e3b7da..436dcf27cb 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -391,6 +391,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + robotsTxt: { + type: 'string', + optional: false, nullable: true, + }, enableIdenticonGeneration: { type: 'boolean', optional: false, nullable: false, @@ -708,6 +712,7 @@ export default class extends Endpoint { // eslint- enableStatsForFederatedInstances: instance.enableStatsForFederatedInstances, enableServerMachineStats: instance.enableServerMachineStats, enableAchievements: instance.enableAchievements, + robotsTxt: instance.robotsTxt, enableIdenticonGeneration: instance.enableIdenticonGeneration, bannedEmailDomains: instance.bannedEmailDomains, policies: { ...DEFAULT_POLICIES, ...instance.policies }, diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 72f428d85f..b3733d3d39 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -149,6 +149,7 @@ export const paramDef = { enableStatsForFederatedInstances: { type: 'boolean' }, enableServerMachineStats: { type: 'boolean' }, enableAchievements: { type: 'boolean' }, + robotsTxt: { type: 'string', nullable: true }, enableIdenticonGeneration: { type: 'boolean' }, serverRules: { type: 'array', items: { type: 'string' } }, bannedEmailDomains: { type: 'array', items: { type: 'string' } }, @@ -636,6 +637,10 @@ export default class extends Endpoint { // eslint- set.enableAchievements = ps.enableAchievements; } + if (ps.robotsTxt !== undefined) { + set.robotsTxt = ps.robotsTxt; + } + if (ps.enableIdenticonGeneration !== undefined) { set.enableIdenticonGeneration = ps.enableIdenticonGeneration; } diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index e59314bf55..e93900b358 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -488,7 +488,14 @@ export class ClientServerService { }); fastify.get('/robots.txt', async (request, reply) => { - return await reply.sendFile('/robots.txt', staticAssets); + if (this.meta.robotsTxt) { + let content = ''; + content += this.meta.robotsTxt; + reply.header('Content-Type', 'text/plain'); + return await reply.send(content); + } else { + return await reply.sendFile('/robots.txt', staticAssets); + } }); // OpenSearch XML diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 68f211de5c..cd05b43be8 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -159,6 +159,11 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + +
@@ -369,10 +374,12 @@ const serviceWorkerForm = useForm({ const otherForm = useForm({ enableAchievements: meta.enableAchievements, enableBotTrending: meta.enableBotTrending, + robotsTxt: meta.robotsTxt, }, async (state) => { await os.apiWithDialog('admin/update-meta', { enableAchievements: state.enableAchievements, enableBotTrending: state.enableBotTrending, + robotsTxt: state.robotsTxt, }); fetchInstance(true); }); diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index 6b3c099411..e0430b10ea 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -434,3 +434,6 @@ scheduledNotes: "Scheduled Notes" _permissions: "read:notes-schedule": "View your list of scheduled notes" "write:notes-schedule": "Compose or delete scheduled notes" + +robotsTxt: "Custom robots.txt" +robotsTxtDescription: "Adding entries here will override the default robots.txt packaged with Sharkey. Maximum 2048 characters." -- cgit v1.2.3-freya From 4d91baaa13a8c4e8b9df221ce710d7005102c5a3 Mon Sep 17 00:00:00 2001 From: Kinetix Date: Fri, 31 Jan 2025 10:22:13 -0800 Subject: Adjust as per suggestions in !877 --- packages/backend/migration/1738098171990-robotsTxt.js | 16 ---------------- packages/backend/migration/1738346484187-robotsTxt.js | 16 ++++++++++++++++ packages/backend/src/server/web/ClientServerService.ts | 4 +--- 3 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 packages/backend/migration/1738098171990-robotsTxt.js create mode 100644 packages/backend/migration/1738346484187-robotsTxt.js (limited to 'packages/backend/src') diff --git a/packages/backend/migration/1738098171990-robotsTxt.js b/packages/backend/migration/1738098171990-robotsTxt.js deleted file mode 100644 index 947f21cc46..0000000000 --- a/packages/backend/migration/1738098171990-robotsTxt.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-FileCopyrightText: marie and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -export class RobotsTxt1738098171990 { - name = 'RobotsTxt1738098171990' - - async up(queryRunner) { - await queryRunner.query(`ALTER TABLE "meta" ADD "robotsTxt" character varying(2048)`); - } - - async down(queryRunner) { - await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "robotsTxt"`); - } -} diff --git a/packages/backend/migration/1738346484187-robotsTxt.js b/packages/backend/migration/1738346484187-robotsTxt.js new file mode 100644 index 0000000000..00ea1fb030 --- /dev/null +++ b/packages/backend/migration/1738346484187-robotsTxt.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: marie and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RobotsTxt1738346484187 { + name = 'RobotsTxt1738346484187' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "robotsTxt" text`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "robotsTxt"`); + } +} diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index e93900b358..ae923ada1f 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -489,10 +489,8 @@ export class ClientServerService { fastify.get('/robots.txt', async (request, reply) => { if (this.meta.robotsTxt) { - let content = ''; - content += this.meta.robotsTxt; reply.header('Content-Type', 'text/plain'); - return await reply.send(content); + return await reply.send(this.meta.robotsTxt); } else { return await reply.sendFile('/robots.txt', staticAssets); } -- cgit v1.2.3-freya From 40bfb1be0962c3e7762f3f777034c0d2da137b39 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 1 Feb 2025 13:36:10 +0900 Subject: fix(backend): お知らせのmetaタグ出力の条件が間違っていたのを修正 (#15377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): お知らせのmetaタグ出力の条件が間違っていたのを修正 * Update Changelog --- CHANGELOG.md | 2 +- packages/backend/src/server/web/ClientServerService.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index a39aa3c8fb..4b4c0e3924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - ### Server -- +- Fix: 個別お知らせページのmetaタグ出力の条件が間違っていたのを修正 ## 2025.1.0 diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 4c884dd314..f8b3843cac 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -817,6 +817,7 @@ export class ClientServerService { fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => { const announcement = await this.announcementsRepository.findOneBy({ id: request.params.announcementId, + userId: IsNull(), }); if (announcement) { -- cgit v1.2.3-freya From 83f2d93d30100f38df2ed34807ec5d8d80a19c4b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 1 Feb 2025 18:53:32 -0500 Subject: increase rate limit on federation/update-remote-user --- .../src/server/api/endpoints/federation/update-remote-user.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 3ec9522c44..5217f79065 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -13,10 +13,11 @@ export const meta = { requireCredential: false, - // 2 calls per second + // Up to 10 calls, then 4 / second. + // This allows for reliable automation. limit: { - duration: 1000, - max: 2, + max: 10, + dripRate: 250, }, } as const; -- cgit v1.2.3-freya From bd716ed8377ab160abf23f60ccc43affebc87882 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 1 Feb 2025 23:56:41 -0500 Subject: increase the rate limit for `/api/i` endpoint, preventing some 429 errors if multiple tabs reload simultaneously --- packages/backend/src/server/api/endpoints/i.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 9347c9ca27..48a2e3b40a 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -31,10 +31,12 @@ export const meta = { }, }, - // 3 calls per second + // up to 20 calls, then 1 per second. + // This handles bursty traffic when all tabs reload as a group limit: { - duration: 1000, - max: 3, + max: 20, + dripSize: 1, + dripRate: 1000, }, } as const; -- cgit v1.2.3-freya From 70d8ea846447ae3755467a9df79b970e0f63dcf1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 14:41:16 -0500 Subject: copy changes to NoteEditService.ts --- packages/backend/src/core/NoteEditService.ts | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index f1c7bcbea5..453ad5d9d0 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -664,14 +664,7 @@ export class NoteEditService implements OnApplicationShutdown { this.roleService.addNoteToRoleTimeline(noteObj); - this.webhookService.getActiveWebhooks().then(webhooks => { - webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note')); - for (const webhook of webhooks) { - this.queueService.userWebhookDeliver(webhook, 'note', { - note: noteObj, - }); - } - }); + this.webhookService.enqueueUserWebhook(user.id, 'note', { note: noteObj }); const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note); @@ -700,12 +693,7 @@ export class NoteEditService implements OnApplicationShutdown { nm.push(data.reply.userId, 'edited'); this.globalEventService.publishMainStream(data.reply.userId, 'edited', noteObj); - const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('edited')); - for (const webhook of webhooks) { - this.queueService.userWebhookDeliver(webhook, 'edited', { - note: noteObj, - }); - } + this.webhookService.enqueueUserWebhook(data.reply.userId, 'reply', { note: noteObj }); } } } @@ -713,7 +701,7 @@ export class NoteEditService implements OnApplicationShutdown { nm.notify(); //#region AP deliver - if (this.userEntityService.isLocalUser(user)) { + if (!data.localOnly && this.userEntityService.isLocalUser(user)) { (async () => { const noteActivity = await this.renderNoteOrRenoteActivity(data, note); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); @@ -810,6 +798,7 @@ export class NoteEditService implements OnApplicationShutdown { (note.files != null && note.files.length > 0); } + // TODO why is this unused? @bindThis private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { @@ -837,13 +826,7 @@ export class NoteEditService implements OnApplicationShutdown { }); this.globalEventService.publishMainStream(u.id, 'edited', detailPackedNote); - - const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('edited')); - for (const webhook of webhooks) { - this.queueService.userWebhookDeliver(webhook, 'edited', { - note: detailPackedNote, - }); - } + this.webhookService.enqueueUserWebhook(u.id, 'edited', { note: detailPackedNote }); // Create notification nm.push(u.id, 'edited'); -- cgit v1.2.3-freya From 3f80138c7095fa29d247adf4822333f8ca7e289a Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 14:50:00 -0500 Subject: fix lint error in ApRendererService.ts --- packages/backend/src/core/activitypub/ApRendererService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index a71c0edcf5..721cb77b2f 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -193,7 +193,7 @@ export class ApRendererService { url: emoji.publicUrl || emoji.originalUrl, }, _misskey_license: { - freeText: emoji.license + freeText: emoji.license, }, }; } -- cgit v1.2.3-freya From 3391c2414b0fc89d897fc3ecf66ca1dffaf6dfe9 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 15:03:42 -0500 Subject: add IdentifiableError.isRetryable to ensure that Identifiable Errors can still terminate a batch process --- packages/backend/src/core/DriveService.ts | 2 +- packages/backend/src/misc/identifiable-error.ts | 8 +++++++- packages/backend/src/queue/processors/InboxProcessorService.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 734ce6b88f..a65059b417 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -520,7 +520,7 @@ export class DriveService { // If usage limit exceeded if (driveCapacity < usage + info.size) { if (isLocalUser) { - throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); + throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.', true); } await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as MiRemoteUser, driveCapacity - info.size); } diff --git a/packages/backend/src/misc/identifiable-error.ts b/packages/backend/src/misc/identifiable-error.ts index 13c41f1e3b..f5c3fcd6cb 100644 --- a/packages/backend/src/misc/identifiable-error.ts +++ b/packages/backend/src/misc/identifiable-error.ts @@ -10,9 +10,15 @@ export class IdentifiableError extends Error { public message: string; public id: string; - constructor(id: string, message?: string) { + /** + * Indicates that this is a temporary error that may be cleared by retrying + */ + public readonly isRetryable: boolean; + + constructor(id: string, message?: string, isRetryable = false) { super(message); this.message = message ?? ''; this.id = id; + this.isRetryable = isRetryable; } } diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 7727a3e985..87d4bf52fa 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -248,6 +248,14 @@ export class InboxProcessorService implements OnApplicationShutdown { return `skip: permanent error ${e.statusCode}`; } + if (e instanceof IdentifiableError && !e.isRetryable) { + if (e.message) { + return `skip: permanent error ${e.id}: ${e.message}`; + } else { + return `skip: permanent error ${e.id}`; + } + } + throw e; } return 'ok'; -- cgit v1.2.3-freya From b5ff784b1c5ddd4a6f5054f53d5bc9a5dc36ccf1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 15:08:47 -0500 Subject: remove unused AiService.ts --- packages/backend/src/core/AiService.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 packages/backend/src/core/AiService.ts (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/AiService.ts b/packages/backend/src/core/AiService.ts deleted file mode 100644 index e69de29bb2..0000000000 -- cgit v1.2.3-freya From 5781e9986123205ad87f8197e172495988b936a1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 18:17:12 -0500 Subject: port FriendlyCaptcha to the new captcha infrastructure --- packages/backend/src/core/CaptchaService.ts | 37 ++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 79aa722fe5..d17101ac97 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -11,7 +11,7 @@ import { MiMeta } from '@/models/Meta.js'; import Logger from '@/logger.js'; import { LoggerService } from './LoggerService.js'; -export const supportedCaptchaProviders = ['none', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile', 'testcaptcha'] as const; +export const supportedCaptchaProviders = ['none', 'hcaptcha', 'mcaptcha', 'recaptcha', 'turnstile', 'fc', 'testcaptcha'] as const; export type CaptchaProvider = typeof supportedCaptchaProviders[number]; export const captchaErrorCodes = { @@ -43,6 +43,10 @@ export type CaptchaSetting = { siteKey: string | null; secretKey: string | null; } + fc: { + siteKey: string | null; + secretKey: string | null; + } } export class CaptchaError extends Error { @@ -141,7 +145,7 @@ export class CaptchaService { @bindThis public async verifyFriendlyCaptcha(secret: string, response: string | null | undefined): Promise { if (response == null) { - throw new Error('frc-failed: no response provided'); + throw new CaptchaError(captchaErrorCodes.noResponseProvided, 'frc-failed: no response provided'); } const result = await this.httpRequestService.send('https://api.friendlycaptcha.com/api/v1/siteverify', { @@ -153,17 +157,17 @@ export class CaptchaService { headers: { 'Content-Type': 'application/json', }, - }); + }, { throwErrorWhenResponseNotOk: false }); if (result.status !== 200) { - throw new Error('frc-failed: frc didn\'t return 200 OK'); + throw new CaptchaError(captchaErrorCodes.requestFailed, `frc-request-failed: ${result.status}`); } const resp = await result.json() as CaptchaResponse; if (resp.success !== true) { const errorCodes = resp['errors'] ? resp['errors'].join(', ') : ''; - throw new Error(`frc-failed: ${errorCodes}`); + throw new CaptchaError(captchaErrorCodes.verificationFailed, `frc-failed: ${errorCodes}`); } } @@ -253,6 +257,10 @@ export class CaptchaService { provider = 'testcaptcha'; break; } + case meta.enableFC: { + provider = 'fc'; + break; + } default: { provider = 'none'; break; @@ -278,6 +286,10 @@ export class CaptchaService { siteKey: meta.turnstileSiteKey, secretKey: meta.turnstileSecretKey, }, + fc: { + siteKey: meta.fcSiteKey, + secretKey: meta.fcSecretKey, + }, }; } @@ -358,6 +370,14 @@ export class CaptchaService { await this.verifyTestcaptcha(params.captchaResult); await this.updateMeta(provider, params); }, + fc: async () => { + if (!params?.secret || !params.captchaResult) { + throw new CaptchaError(captchaErrorCodes.invalidParameters, 'frc-failed: secret and captureResult are required'); + } + + await this.verifyFriendlyCaptcha(params.captchaResult, params.captchaResult); + await this.updateMeta(provider, params); + }, }[provider]; return operation() @@ -390,7 +410,7 @@ export class CaptchaService { ('enableMcaptcha' | 'mcaptchaSitekey' | 'mcaptchaSecretKey' | 'mcaptchaInstanceUrl') | ('enableRecaptcha' | 'recaptchaSiteKey' | 'recaptchaSecretKey') | ('enableTurnstile' | 'turnstileSiteKey' | 'turnstileSecretKey') | - ('enableTestcaptcha') + ('enableTestcaptcha' | 'enableFC' | 'fcSiteKey' | 'fcSecretKey') > > = { enableHcaptcha: provider === 'hcaptcha', @@ -398,6 +418,7 @@ export class CaptchaService { enableRecaptcha: provider === 'recaptcha', enableTurnstile: provider === 'turnstile', enableTestcaptcha: provider === 'testcaptcha', + enableFC: provider === 'fc', }; const updateIfNotUndefined = (key: K, value: typeof metaPartial[K]) => { @@ -427,6 +448,10 @@ export class CaptchaService { updateIfNotUndefined('turnstileSecretKey', params?.secret); break; } + case 'fc': { + updateIfNotUndefined('fcSiteKey', params?.sitekey); + updateIfNotUndefined('fcSecretKey', params?.secret); + } } await this.metaService.update(metaPartial); -- cgit v1.2.3-freya From 19d3cdfa3705cfcaacda49ad63cbd9b0b17c8d79 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 18:20:26 -0500 Subject: fix "delete file when updating emoji" logic --- packages/backend/src/core/CustomEmojiService.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 3f7ad5b947..165d58d1b2 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -147,7 +147,7 @@ export class CustomEmojiService implements OnApplicationShutdown { @bindThis public async update(data: ( { id: MiEmoji['id'], name?: string; } | { name: string; id?: MiEmoji['id'], } - ) & { + ) & { originalUrl?: string; publicUrl?: string; fileType?: string; @@ -175,17 +175,6 @@ export class CustomEmojiService implements OnApplicationShutdown { if (isDuplicate) return 'SAME_NAME_EMOJI_EXISTS'; } - // If we're changing the file, then we need to delete the old one - if (data.originalUrl != null && data.originalUrl !== emoji.originalUrl) { - const oldFile = await this.driveFilesRepository.findOneBy({ url: emoji.originalUrl, userHost: emoji.host ? emoji.host : IsNull() }); - const newFile = await this.driveFilesRepository.findOneBy({ url: data.originalUrl, userHost: emoji.host ? emoji.host : IsNull() }); - - // But DON'T delete if this is the same file reference, otherwise we'll break the emoji! - if (oldFile && newFile && oldFile.id !== newFile.id) { - await this.driveService.deleteFile(oldFile, false, moderator ? moderator : undefined); - } - } - await this.emojisRepository.update(emoji.id, { updatedAt: new Date(), name: data.name, @@ -202,6 +191,17 @@ export class CustomEmojiService implements OnApplicationShutdown { this.localEmojisCache.refresh(); + // If we're changing the file, then we need to delete the old one + if (data.originalUrl != null && data.originalUrl !== emoji.originalUrl) { + const oldFile = await this.driveFilesRepository.findOneBy({ url: emoji.originalUrl, userHost: emoji.host ? emoji.host : IsNull() }); + const newFile = await this.driveFilesRepository.findOneBy({ url: data.originalUrl, userHost: emoji.host ? emoji.host : IsNull() }); + + // But DON'T delete if this is the same file reference, otherwise we'll break the emoji! + if (oldFile && newFile && oldFile.id !== newFile.id) { + await this.driveService.deleteFile(oldFile, false, moderator ? moderator : undefined); + } + } + const packed = await this.emojiEntityService.packDetailed(emoji.id); if (!doNameUpdate) { -- cgit v1.2.3-freya From 8c1d5281a9d628318b9179a90a11091a4c178701 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 18:21:32 -0500 Subject: remove unused import in FileInfoService.ts --- packages/backend/src/core/FileInfoService.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts index dc4483ad3f..cc66e9fe3a 100644 --- a/packages/backend/src/core/FileInfoService.ts +++ b/packages/backend/src/core/FileInfoService.ts @@ -16,7 +16,6 @@ import * as blurhash from 'blurhash'; import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; -import type { PredictionType } from 'nsfwjs'; export type FileInfo = { size: number; -- cgit v1.2.3-freya From 47bf96988cbffabf55999e193337c840602a48d8 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 18:27:09 -0500 Subject: fix meilisearch merge --- packages/backend/src/core/SearchService.ts | 42 ++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 431cc0234e..35c67400ba 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -175,7 +175,6 @@ export class SearchService { if (!['home', 'public'].includes(note.visibility)) return; await this.meilisearchNoteIndex?.deleteDocument(note.id); - await this.meilisearchNoteIndex?.deleteDocument(note.id); } @bindThis @@ -296,8 +295,47 @@ export class SearchService { } } + if (opts.filetype) { + if (opts.filetype === 'image') { + filter.qs.push({ op: 'or', qs: [ + { op: '=', k: 'attachedFileTypes', v: 'image/webp' }, + { op: '=', k: 'attachedFileTypes', v: 'image/png' }, + { op: '=', k: 'attachedFileTypes', v: 'image/jpeg' }, + { op: '=', k: 'attachedFileTypes', v: 'image/avif' }, + { op: '=', k: 'attachedFileTypes', v: 'image/apng' }, + { op: '=', k: 'attachedFileTypes', v: 'image/gif' }, + ] }); + } else if (opts.filetype === 'video') { + filter.qs.push({ op: 'or', qs: [ + { op: '=', k: 'attachedFileTypes', v: 'video/mp4' }, + { op: '=', k: 'attachedFileTypes', v: 'video/webm' }, + { op: '=', k: 'attachedFileTypes', v: 'video/mpeg' }, + { op: '=', k: 'attachedFileTypes', v: 'video/x-m4v' }, + ] }); + } else if (opts.filetype === 'audio') { + filter.qs.push({ op: 'or', qs: [ + { op: '=', k: 'attachedFileTypes', v: 'audio/mpeg' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/flac' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/wav' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/aac' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/webm' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/opus' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/ogg' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/x-m4a' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/mod' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/s3m' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/xm' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/it' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/x-mod' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/x-s3m' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/x-xm' }, + { op: '=', k: 'attachedFileTypes', v: 'audio/x-it' }, + ] }); + } + } + const res = await this.meilisearchNoteIndex.search(q, { - sort: ['createdAt:desc'], + sort: [`createdAt:${opts.order ? opts.order : 'desc'}`], matchingStrategy: 'all', attributesToRetrieve: ['id', 'createdAt'], filter: compileQuery(filter), -- cgit v1.2.3-freya From 4afe01909e1bb7def7221495f0d8f2c6ca1dabcc Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 18:36:11 -0500 Subject: add FriendlyCaptcha to new captcha admin endpoints --- packages/backend/src/server/api/endpoints/admin/captcha/current.ts | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/admin/captcha/current.ts b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts index 63ec740348..41192c1926 100644 --- a/packages/backend/src/server/api/endpoints/admin/captcha/current.ts +++ b/packages/backend/src/server/api/endpoints/admin/captcha/current.ts @@ -52,6 +52,13 @@ export const meta = { secretKey: { type: 'string', nullable: true }, }, }, + fc: { + type: 'object', + properties: { + siteKey: { type: 'string', nullable: true }, + secretKey: { type: 'string', nullable: true }, + }, + }, }, }, } as const; -- cgit v1.2.3-freya From 47815631e4e007fde96cb6a9abc68b85da2affcd Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 3 Feb 2025 19:20:19 -0500 Subject: add Sharkey endpoints to endpoint-list.ts --- packages/backend/src/server/api/endpoint-list.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index 28f7cfea04..1866ee5da8 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -27,12 +27,14 @@ export * as 'admin/announcements/create' from './endpoints/admin/announcements/c export * as 'admin/announcements/delete' from './endpoints/admin/announcements/delete.js'; export * as 'admin/announcements/list' from './endpoints/admin/announcements/list.js'; export * as 'admin/announcements/update' from './endpoints/admin/announcements/update.js'; +export * as 'admin/approve-user' from './endpoints/admin/approve-user.js'; export * as 'admin/avatar-decorations/create' from './endpoints/admin/avatar-decorations/create.js'; export * as 'admin/avatar-decorations/delete' from './endpoints/admin/avatar-decorations/delete.js'; export * as 'admin/avatar-decorations/list' from './endpoints/admin/avatar-decorations/list.js'; export * as 'admin/avatar-decorations/update' from './endpoints/admin/avatar-decorations/update.js'; export * as 'admin/captcha/current' from './endpoints/admin/captcha/current.js'; export * as 'admin/captcha/save' from './endpoints/admin/captcha/save.js'; +export * as 'admin/decline-user' from './endpoints/admin/decline-user.js'; export * as 'admin/delete-account' from './endpoints/admin/delete-account.js'; export * as 'admin/delete-all-files-of-a-user' from './endpoints/admin/delete-all-files-of-a-user.js'; export * as 'admin/drive/clean-remote-files' from './endpoints/admin/drive/clean-remote-files.js'; @@ -63,6 +65,7 @@ export * as 'admin/get-user-ips' from './endpoints/admin/get-user-ips.js'; export * as 'admin/invite/create' from './endpoints/admin/invite/create.js'; export * as 'admin/invite/list' from './endpoints/admin/invite/list.js'; export * as 'admin/meta' from './endpoints/admin/meta.js'; +export * as 'admin/nsfw-user' from './endpoints/admin/nsfw-user.js'; export * as 'admin/promo/create' from './endpoints/admin/promo/create.js'; export * as 'admin/queue/clear' from './endpoints/admin/queue/clear.js'; export * as 'admin/queue/deliver-delayed' from './endpoints/admin/queue/deliver-delayed.js'; @@ -88,6 +91,7 @@ export * as 'admin/server-info' from './endpoints/admin/server-info.js'; export * as 'admin/show-moderation-logs' from './endpoints/admin/show-moderation-logs.js'; export * as 'admin/show-user' from './endpoints/admin/show-user.js'; export * as 'admin/show-users' from './endpoints/admin/show-users.js'; +export * as 'admin/silence-user' from './endpoints/admin/silence-user.js'; export * as 'admin/suspend-user' from './endpoints/admin/suspend-user.js'; export * as 'admin/system-webhook/create' from './endpoints/admin/system-webhook/create.js'; export * as 'admin/system-webhook/delete' from './endpoints/admin/system-webhook/delete.js'; @@ -95,8 +99,10 @@ export * as 'admin/system-webhook/list' from './endpoints/admin/system-webhook/l export * as 'admin/system-webhook/show' from './endpoints/admin/system-webhook/show.js'; export * as 'admin/system-webhook/test' from './endpoints/admin/system-webhook/test.js'; export * as 'admin/system-webhook/update' from './endpoints/admin/system-webhook/update.js'; +export * as 'admin/unnsfw-user' from './endpoints/admin/unnsfw-user.js'; export * as 'admin/unset-user-avatar' from './endpoints/admin/unset-user-avatar.js'; export * as 'admin/unset-user-banner' from './endpoints/admin/unset-user-banner.js'; +export * as 'admin/unsilence-user' from './endpoints/admin/unsilence-user.js'; export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js'; export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js'; export * as 'admin/update-meta' from './endpoints/admin/update-meta.js'; @@ -243,6 +249,7 @@ export * as 'i/delete-account' from './endpoints/i/delete-account.js'; export * as 'i/export-antennas' from './endpoints/i/export-antennas.js'; export * as 'i/export-blocking' from './endpoints/i/export-blocking.js'; export * as 'i/export-clips' from './endpoints/i/export-clips.js'; +export * as 'i/export-data' from './endpoints/i/export-data.js'; export * as 'i/export-favorites' from './endpoints/i/export-favorites.js'; export * as 'i/export-following' from './endpoints/i/export-following.js'; export * as 'i/export-mute' from './endpoints/i/export-mute.js'; @@ -255,6 +262,7 @@ export * as 'i/import-antennas' from './endpoints/i/import-antennas.js'; export * as 'i/import-blocking' from './endpoints/i/import-blocking.js'; export * as 'i/import-following' from './endpoints/i/import-following.js'; export * as 'i/import-muting' from './endpoints/i/import-muting.js'; +export * as 'i/import-notes' from './endpoints/i/import-notes.js'; export * as 'i/import-user-lists' from './endpoints/i/import-user-lists.js'; export * as 'i/move' from './endpoints/i/move.js'; export * as 'i/notifications' from './endpoints/i/notifications.js'; @@ -268,6 +276,7 @@ export * as 'i/regenerate-token' from './endpoints/i/regenerate-token.js'; export * as 'i/registry/get' from './endpoints/i/registry/get.js'; export * as 'i/registry/get-all' from './endpoints/i/registry/get-all.js'; export * as 'i/registry/get-detail' from './endpoints/i/registry/get-detail.js'; +export * as 'i/registry/get-unsecure' from './endpoints/i/registry/get-unsecure.js'; export * as 'i/registry/keys' from './endpoints/i/registry/keys.js'; export * as 'i/registry/keys-with-type' from './endpoints/i/registry/keys-with-type.js'; export * as 'i/registry/remove' from './endpoints/i/registry/remove.js'; @@ -295,19 +304,24 @@ export * as 'mute/delete' from './endpoints/mute/delete.js'; export * as 'mute/list' from './endpoints/mute/list.js'; export * as 'my/apps' from './endpoints/my/apps.js'; export * as 'notes' from './endpoints/notes.js'; +export * as 'notes/bubble-timeline' from './endpoints/notes/bubble-timeline.js'; export * as 'notes/children' from './endpoints/notes/children.js'; export * as 'notes/clips' from './endpoints/notes/clips.js'; export * as 'notes/conversation' from './endpoints/notes/conversation.js'; export * as 'notes/create' from './endpoints/notes/create.js'; export * as 'notes/delete' from './endpoints/notes/delete.js'; +export * as 'notes/edit' from './endpoints/notes/edit.js'; export * as 'notes/favorites/create' from './endpoints/notes/favorites/create.js'; export * as 'notes/favorites/delete' from './endpoints/notes/favorites/delete.js'; export * as 'notes/featured' from './endpoints/notes/featured.js'; +export * as 'notes/following' from './endpoints/notes/following.js'; export * as 'notes/global-timeline' from './endpoints/notes/global-timeline.js'; export * as 'notes/hybrid-timeline' from './endpoints/notes/hybrid-timeline.js'; +export * as 'notes/like' from './endpoints/notes/like.js'; export * as 'notes/local-timeline' from './endpoints/notes/local-timeline.js'; export * as 'notes/mentions' from './endpoints/notes/mentions.js'; export * as 'notes/polls/recommendation' from './endpoints/notes/polls/recommendation.js'; +export * as 'notes/polls/refresh' from './endpoints/notes/polls/refresh.js'; export * as 'notes/polls/vote' from './endpoints/notes/polls/vote.js'; export * as 'notes/reactions' from './endpoints/notes/reactions.js'; export * as 'notes/reactions/create' from './endpoints/notes/reactions/create.js'; @@ -315,6 +329,9 @@ export * as 'notes/reactions/delete' from './endpoints/notes/reactions/delete.js export * as 'notes/renotes' from './endpoints/notes/renotes.js'; export * as 'notes/replies' from './endpoints/notes/replies.js'; export * as 'notes/search' from './endpoints/notes/search.js'; +export * as 'notes/schedule/create' from './endpoints/notes/schedule/create.js'; +export * as 'notes/schedule/delete' from './endpoints/notes/schedule/delete.js'; +export * as 'notes/schedule/list' from './endpoints/notes/schedule/list.js'; export * as 'notes/search-by-tag' from './endpoints/notes/search-by-tag.js'; export * as 'notes/show' from './endpoints/notes/show.js'; export * as 'notes/state' from './endpoints/notes/state.js'; @@ -324,6 +341,7 @@ export * as 'notes/timeline' from './endpoints/notes/timeline.js'; export * as 'notes/translate' from './endpoints/notes/translate.js'; export * as 'notes/unrenote' from './endpoints/notes/unrenote.js'; export * as 'notes/user-list-timeline' from './endpoints/notes/user-list-timeline.js'; +export * as 'notes/versions' from './endpoints/notes/versions.js'; export * as 'notifications/create' from './endpoints/notifications/create.js'; export * as 'notifications/flush' from './endpoints/notifications/flush.js'; export * as 'notifications/mark-all-as-read' from './endpoints/notifications/mark-all-as-read.js'; @@ -358,6 +376,7 @@ export * as 'roles/notes' from './endpoints/roles/notes.js'; export * as 'roles/show' from './endpoints/roles/show.js'; export * as 'roles/users' from './endpoints/roles/users.js'; export * as 'server-info' from './endpoints/server-info.js'; +export * as 'sponsors' from './endpoints/sponsors.js'; export * as 'stats' from './endpoints/stats.js'; export * as 'sw/register' from './endpoints/sw/register.js'; export * as 'sw/show-registration' from './endpoints/sw/show-registration.js'; -- cgit v1.2.3-freya From edfd9e54fbd660196fdaa058993b85b181906cb5 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 08:59:01 -0500 Subject: remove unused meta.version call --- packages/backend/src/boot/master.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 7b3f3395e6..44f2176e0a 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -32,7 +32,6 @@ const themeColor = chalk.hex('#86b300'); function greet() { if (!envOption.quiet) { //#region Misskey logo - const v = `v${meta.version}`; console.log(themeColor(' _____ _ _ ')); console.log(themeColor('/ ___| | | | ')); console.log(themeColor('\\ `--.| |__ __ _ _ __| | _____ _ _ ')); -- cgit v1.2.3-freya From 878b4d3899ccb70206b0b598314a14783496815d Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:10:52 -0500 Subject: remove spurious code style / formatting changes in ApRequestService.ts --- packages/backend/src/core/activitypub/ApRequestService.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 8036c9638f..bcd0ce9043 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -242,10 +242,8 @@ export class ApRequestService { const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]'); if (alternate) { const href = alternate.getAttribute('href'); - if (href) { - if (this.utilityService.punyHostPSLDomain(url) === this.utilityService.punyHostPSLDomain(href)) { - return await this.signedGet(href, user, false); - } + if (href && this.utilityService.punyHostPSLDomain(url) === this.utilityService.punyHostPSLDomain(href)) { + return await this.signedGet(href, user, false); } } } catch (e) { @@ -257,7 +255,6 @@ export class ApRequestService { //#endregion validateContentTypeSetAsActivityPub(res); - const finalUrl = res.url; // redirects may have been involved const activity = await res.json() as IObject; -- cgit v1.2.3-freya From 21667a0422baf1b2a3a58fc5c74d4852906ea025 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:13:03 -0500 Subject: convert private field to constructor param --- packages/backend/src/core/chart/ChartManagementService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts index ef65af2432..81495c8a6c 100644 --- a/packages/backend/src/core/chart/ChartManagementService.ts +++ b/packages/backend/src/core/chart/ChartManagementService.ts @@ -41,7 +41,7 @@ export class ChartManagementService implements OnApplicationShutdown { private perUserFollowingChart: PerUserFollowingChart, private perUserDriveChart: PerUserDriveChart, private apRequestChart: ApRequestChart, - private chartLoggerService: ChartLoggerService, + chartLoggerService: ChartLoggerService, ) { this.charts = [ this.federationChart, -- cgit v1.2.3-freya From ce8374bdc9812961d1f0ab23968e6de14d01e246 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:13:49 -0500 Subject: remove spurious newline --- packages/backend/src/core/entities/MetaEntityService.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index 4e127f17ca..84d591ce7a 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -186,3 +186,4 @@ export class MetaEntityService { return packDetailed; } } + -- cgit v1.2.3-freya From a8e4ad28b3500725bc14c03764807cbc66cf6033 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:15:14 -0500 Subject: remove unused DI parameter --- packages/backend/src/core/entities/NoteEntityService.ts | 4 ---- 1 file changed, 4 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index e5b575c219..c54c062fba 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -23,7 +23,6 @@ import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; import type { UserEntityService } from './UserEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; -import type { Config } from '@/config.js'; // is-renote.tsとよしなにリンク function isPureRenote(note: MiNote): note is MiNote & { renoteId: MiNote['id']; renote: MiNote } { @@ -69,9 +68,6 @@ export class NoteEntityService implements OnModuleInit { @Inject(DI.usersRepository) private usersRepository: UsersRepository, - @Inject(DI.config) - private config: Config, - @Inject(DI.notesRepository) private notesRepository: NotesRepository, -- cgit v1.2.3-freya From baff0605e0304784d8096ad99df7bf167fe83132 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:16:35 -0500 Subject: fix import order in UserEntityService.ts --- packages/backend/src/core/entities/UserEntityService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 6bfe865038..c818fa5603 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -49,11 +49,11 @@ import { IdService } from '@/core/IdService.js'; import type { AnnouncementService } from '@/core/AnnouncementService.js'; import type { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { isSystemAccount } from '@/misc/is-system-account.js'; import type { OnModuleInit } from '@nestjs/common'; import type { NoteEntityService } from './NoteEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; import type { PageEntityService } from './PageEntityService.js'; -import { isSystemAccount } from '@/misc/is-system-account.js'; const Ajv = _Ajv.default; const ajv = new Ajv(); -- cgit v1.2.3-freya From c1a8654f0d2ca8161de2ab055f3cc4e2463645a2 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:20:55 -0500 Subject: fix import order in FederatedInstanceService.ts --- packages/backend/src/core/FederatedInstanceService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index fca3ad847a..3f7ed99348 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import * as Redis from 'ioredis'; +import { QueryFailedError } from 'typeorm'; import type { InstancesRepository } from '@/models/_.js'; import type { MiInstance } from '@/models/Instance.js'; import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js'; @@ -12,7 +13,6 @@ import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; -import { QueryFailedError } from 'typeorm'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; @Injectable() -- cgit v1.2.3-freya From 176db52d885b2ea60e8450cb8ad4d8c4b0fd4c10 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:49:43 -0500 Subject: fix formatting in SignupService.ts --- packages/backend/src/core/SignupService.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 0ad448e95f..9fc0c2b34a 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -162,3 +162,4 @@ export class SignupService { return { account, secret }; } } + -- cgit v1.2.3-freya From f2fb408ffcf35a75c1434dc181054c31b924a6aa Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:57:02 -0500 Subject: fix import order in DeleteAccountProcessorService.ts --- packages/backend/src/queue/processors/DeleteAccountProcessorService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 0e604a0501..e350b97f53 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -15,10 +15,10 @@ import type { MiNoteReaction } from '@/models/NoteReaction.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; +import { ReactionService } from '@/core/ReactionService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserDeleteJobData } from '../types.js'; -import { ReactionService } from '@/core/ReactionService.js'; @Injectable() export class DeleteAccountProcessorService { -- cgit v1.2.3-freya From 5ff6814c74106d94a534f36902cdbfeeed513fe7 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 09:57:16 -0500 Subject: remove unused imports from accounts/create.ts --- packages/backend/src/server/api/endpoints/admin/accounts/create.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 53b1c4c4ec..5843457676 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -6,7 +6,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository } from '@/models/_.js'; -import { MiAccessToken, MiUser } from '@/models/_.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; -- cgit v1.2.3-freya From b84a1542fa01da41de6bf78ae45b85f0fb496c75 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:02:30 -0500 Subject: fix import order in channels/update.ts --- packages/backend/src/server/api/endpoints/channels/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index d2a75225ed..7cca688fda 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -4,13 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { DriveFilesRepository, ChannelsRepository } from '@/models/_.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; -import ms from 'ms'; export const meta = { tags: ['channels'], -- cgit v1.2.3-freya From 606d10f1097c0f28c074b66c354b8c102f0d878b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:03:42 -0500 Subject: remove unused import from drive/files/delete.ts --- packages/backend/src/server/api/endpoints/drive/files/delete.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 7a009b12a1..3065bb6711 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -11,7 +11,6 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../../error.js'; -import ms from 'ms'; export const meta = { tags: ['drive'], -- cgit v1.2.3-freya From ffd1c52213dfb1fbb834171d09583b55568c91d1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:04:19 -0500 Subject: remove unused import from drive/files/update.ts --- packages/backend/src/server/api/endpoints/drive/files/update.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 94a0e673a3..306a646785 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -11,7 +11,6 @@ import { RoleService } from '@/core/RoleService.js'; import { DriveService } from '@/core/DriveService.js'; import type { Config } from '@/config.js'; import { ApiError } from '../../../error.js'; -import ms from 'ms'; export const meta = { tags: ['drive'], -- cgit v1.2.3-freya From aeed479ea08e85a777bd135eaad13797cf91b678 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:04:40 -0500 Subject: remove unused import from drive/folders/update.ts --- packages/backend/src/server/api/endpoints/drive/folders/update.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts index cd47c0fc68..8d51d09ea6 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts @@ -10,7 +10,6 @@ import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityServi import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import ms from 'ms'; export const meta = { tags: ['drive'], -- cgit v1.2.3-freya From 6bb99734ad856a853510ddb9d3223568ee26b0e7 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:05:16 -0500 Subject: fix lint errors in drive/files.ts --- packages/backend/src/server/api/endpoints/drive/files.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index 8e821da0da..5df212415d 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -4,13 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import { Brackets } from 'typeorm'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { DriveFilesRepository } from '@/models/_.js'; import { QueryService } from '@/core/QueryService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DI } from '@/di-symbols.js'; import { sqlLikeEscape } from '@/misc/sql-like-escape.js'; -import { Brackets } from 'typeorm'; export const meta = { tags: ['drive'], @@ -45,7 +45,7 @@ export const paramDef = { folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) }, sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] }, - searchQuery: { type: 'string', default: '' } + searchQuery: { type: 'string', default: '' }, }, required: [], } as const; -- cgit v1.2.3-freya From 82345dc77ab558114fc0b6588c6d792505774e6b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:05:34 -0500 Subject: fix lint error in drive/folder.ts --- packages/backend/src/server/api/endpoints/drive/folders.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts index 1245706b0d..525cb8c5d6 100644 --- a/packages/backend/src/server/api/endpoints/drive/folders.ts +++ b/packages/backend/src/server/api/endpoints/drive/folders.ts @@ -42,7 +42,7 @@ export const paramDef = { sinceId: { type: 'string', format: 'misskey:id' }, untilId: { type: 'string', format: 'misskey:id' }, folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, - searchQuery: { type: 'string', default: '' } + searchQuery: { type: 'string', default: '' }, }, required: [], } as const; -- cgit v1.2.3-freya From f469a7b1de293bc89976aa90bfb3f167c014c384 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:06:25 -0500 Subject: fix import order in flash/delete.ts --- packages/backend/src/server/api/endpoints/flash/delete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/flash/delete.ts b/packages/backend/src/server/api/endpoints/flash/delete.ts index 1010567113..5b7d936b1c 100644 --- a/packages/backend/src/server/api/endpoints/flash/delete.ts +++ b/packages/backend/src/server/api/endpoints/flash/delete.ts @@ -4,13 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { FlashsRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; -import ms from 'ms'; export const meta = { tags: ['flashs'], -- cgit v1.2.3-freya From 294365591c547fa49e37c9efd2913b367ce8679d Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:07:03 -0500 Subject: fix import order in gallery/posts/delete.ts --- packages/backend/src/server/api/endpoints/gallery/posts/delete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts index 68478ba55c..28c8237761 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts @@ -4,13 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { GalleryPostsRepository, UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../../error.js'; -import ms from 'ms'; export const meta = { tags: ['gallery'], -- cgit v1.2.3-freya From ea35204c9e84e54cad5a5fb34586fb779aa2d371 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:07:34 -0500 Subject: fix import order in i/2fa/done.ts --- packages/backend/src/server/api/endpoints/i/2fa/done.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 34b6907338..12d5ef443d 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -5,12 +5,12 @@ import * as OTPAuth from 'otpauth'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UserProfilesRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 96334ef178e65e2e2c18e6dd0482590f10e3671c Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:08:42 -0500 Subject: fix lint errors in i/2fa/key-done.ts --- packages/backend/src/server/api/endpoints/i/2fa/key-done.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index 084d4af658..370d9915a3 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -6,6 +6,7 @@ //import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -14,7 +15,6 @@ import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/model import { WebAuthnService } from '@/core/WebAuthnService.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import ms from 'ms'; export const meta = { requireCredential: true, @@ -63,8 +63,8 @@ export const paramDef = { required: ['password', 'name', 'credential'], } as const; -// eslint-disable-next-line import/no-default-export @Injectable() +// eslint-disable-next-line import/no-default-export export default class extends Endpoint { constructor( @Inject(DI.userProfilesRepository) -- cgit v1.2.3-freya From 85fbd0edf9217be1e030a5ef23e5e2831bc0bca5 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:09:07 -0500 Subject: fix import order in i/2fa/password-less.ts --- packages/backend/src/server/api/endpoints/i/2fa/password-less.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts index a1c965f603..cd520cff0f 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -4,13 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 4c268e4a2bf9db5422ff7192d6ca0eccd4d508a1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:09:30 -0500 Subject: fix import order in i/2fa/register.ts --- packages/backend/src/server/api/endpoints/i/2fa/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index 888d0fc6ef..d27c14c69b 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -8,13 +8,13 @@ import * as argon2 from 'argon2'; import * as OTPAuth from 'otpauth'; import * as QRCode from 'qrcode'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 51232dc60f857b602012a4271e9283d74b5b8311 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:09:58 -0500 Subject: fix import order in i/2fa/register-key.ts --- packages/backend/src/server/api/endpoints/i/2fa/register-key.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts index 6ab50a57c9..893ea30391 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -6,13 +6,13 @@ //import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { WebAuthnService } from '@/core/WebAuthnService.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 3ae4356ac7d172f748277a94729a9144ab806643 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:10:16 -0500 Subject: fix import order in i/2fa/remove-key.ts --- packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts index 614fd0c498..b01e452056 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -6,6 +6,7 @@ //import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; @@ -13,7 +14,6 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From e04d38e1053a82bfac16c19474a8f47c80fdb6d3 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:10:43 -0500 Subject: fix import order in i/2fa/unregister.ts --- packages/backend/src/server/api/endpoints/i/2fa/unregister.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index 2773825373..2fe4fdc4c0 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -6,6 +6,7 @@ //import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import type { UserProfilesRepository } from '@/models/_.js'; @@ -13,7 +14,6 @@ import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '@/server/api/error.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 27a8ded246ebf0f059bc95cfe94f674e9293ca88 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:16:08 -0500 Subject: fix import order in i/2fa/update-key.ts --- packages/backend/src/server/api/endpoints/i/2fa/update-key.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts index eb8a63b3dc..4a41c7b984 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts @@ -5,13 +5,13 @@ //import bcrypt from 'bcryptjs'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserSecurityKeysRepository } from '@/models/_.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 6d12bc0e7fb7cf27243beecfb3ab5fcfb2178fad Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:17:18 -0500 Subject: fix import order in i/change-password.ts --- packages/backend/src/server/api/endpoints/i/change-password.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts index f131c7e9d1..4069683740 100644 --- a/packages/backend/src/server/api/endpoints/i/change-password.ts +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -6,11 +6,11 @@ //import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 980ea858f20bba92b932128c40f3336d727ac1cd Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:38:50 -0500 Subject: fix import order in ActivityPubServerService.ts --- packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index a4dddbd6c3..1f838ffdbe 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -34,10 +34,10 @@ import { bindThis } from '@/decorators.js'; import { IActivity } from '@/core/activitypub/type.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; import * as Acct from '@/misc/acct.js'; -import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify'; -import type { FindOptionsWhere } from 'typeorm'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify'; +import type { FindOptionsWhere } from 'typeorm'; const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; -- cgit v1.2.3-freya From 5942da15dae081e9e1d53968acef5445b0ffdc2f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:39:03 -0500 Subject: fix import order in SignupApiService.ts --- packages/backend/src/server/api/SignupApiService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 7aea6a0e56..42137d3298 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -19,10 +19,9 @@ import { MiLocalUser } from '@/models/User.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; +import { RoleService } from '@/core/RoleService.js'; import { SigninService } from './SigninService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; -import instance from './endpoints/charts/instance.js'; -import { RoleService } from '@/core/RoleService.js'; @Injectable() export class SignupApiService { -- cgit v1.2.3-freya From f65937446ac87399dd85d5d18531afd74173e5db Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:40:14 -0500 Subject: fix lint errors in Connection.ts --- packages/backend/src/server/api/stream/Connection.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts index f102cb42e1..e98e2a2f3f 100644 --- a/packages/backend/src/server/api/stream/Connection.ts +++ b/packages/backend/src/server/api/stream/Connection.ts @@ -16,11 +16,11 @@ import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { isJsonObject } from '@/misc/json-value.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import type Logger from '@/logger.js'; import type { ChannelsService } from './ChannelsService.js'; import type { EventEmitter } from 'events'; import type Channel from './channel.js'; -import { LoggerService } from '@/core/LoggerService.js'; -import type Logger from '@/logger.js'; const MAX_CHANNELS_PER_CONNECTION = 32; @@ -45,8 +45,8 @@ export default class Connection { public userIdsWhoMeMutingRenotes: Set = new Set(); public userMutedInstances: Set = new Set(); private fetchIntervalId: NodeJS.Timeout | null = null; - private activeRateLimitRequests: number = 0; - private closingConnection: boolean = false; + private activeRateLimitRequests = 0; + private closingConnection = false; private logger: Logger; constructor( -- cgit v1.2.3-freya From 8459d6283d01ce3f93d69354dcbfd06e5e9c8ce1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:40:47 -0500 Subject: fix import order in stream/channel.ts --- packages/backend/src/server/api/stream/channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 047dedd5ce..3c90908c1b 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -9,8 +9,8 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import type Connection from './Connection.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import type Connection from './Connection.js'; /** * Stream channel -- cgit v1.2.3-freya From 8ee315a82bfed23b44c4d7788b15d30a2bd5d45a Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:41:03 -0500 Subject: fix lint errors in stream/channel/bubble-timeline.ts --- packages/backend/src/server/api/stream/channels/bubble-timeline.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index 98ecf16a83..6b6aa80797 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -3,8 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable } from '@nestjs/common'; -import { DI } from '@/di-symbols.js'; +import { Injectable } from '@nestjs/common'; import type { Packed } from '@/misc/json-schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; @@ -33,7 +32,6 @@ class BubbleTimelineChannel extends Channel { connection: Channel['connection'], ) { super(id, connection); - //this.onNote = this.onNote.bind(this); } @bindThis -- cgit v1.2.3-freya From 1f03acb97a21f10c038d3f7e02aec0a27c17271a Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:41:15 -0500 Subject: fix import order in pages/delete.ts --- packages/backend/src/server/api/endpoints/pages/delete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index c2c3215f49..0ad7a3633a 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -4,13 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { PagesRepository, UsersRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; -import ms from 'ms'; export const meta = { tags: ['pages'], -- cgit v1.2.3-freya From 8e7cac3d778143a618c986e8fcd2f9c46fe38888 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:41:27 -0500 Subject: fix import order in notes/like.ts --- packages/backend/src/server/api/endpoints/notes/like.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/notes/like.ts b/packages/backend/src/server/api/endpoints/notes/like.ts index 9068de2865..d6511faf95 100644 --- a/packages/backend/src/server/api/endpoints/notes/like.ts +++ b/packages/backend/src/server/api/endpoints/notes/like.ts @@ -1,5 +1,5 @@ -import { DI } from '@/di-symbols.js'; import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { ReactionService } from '@/core/ReactionService.js'; -- cgit v1.2.3-freya From d879e5c03a8b81b14bb5d5cd9bce81503402e04f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:41:52 -0500 Subject: fix import order in notes/global-timeline.ts --- packages/backend/src/server/api/endpoints/notes/global-timeline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index c45fcd7c5c..0f2592bd78 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -12,8 +12,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; import { DI } from '@/di-symbols.js'; import { RoleService } from '@/core/RoleService.js'; -import { ApiError } from '../../error.js'; import { CacheService } from '@/core/CacheService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes'], -- cgit v1.2.3-freya From b2b6f2f1d664354cc2ceae525ca4ded14b15c8f6 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:42:08 -0500 Subject: fix lint errors in notes/schedule/create.ts --- packages/backend/src/server/api/endpoints/notes/schedule/create.ts | 5 ----- 1 file changed, 5 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts index c6032fbdae..0c2e00ee8d 100644 --- a/packages/backend/src/server/api/endpoints/notes/schedule/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/schedule/create.ts @@ -14,12 +14,10 @@ import type { NotesRepository, BlockingsRepository, DriveFilesRepository, - ChannelsRepository, NoteScheduleRepository, } from '@/models/_.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; import type { MiNote } from '@/models/Note.js'; -import type { MiChannel } from '@/models/Channel.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { QueueService } from '@/core/QueueService.js'; @@ -210,9 +208,6 @@ export default class extends Endpoint { // eslint- @Inject(DI.driveFilesRepository) private driveFilesRepository: DriveFilesRepository, - @Inject(DI.channelsRepository) - private channelsRepository: ChannelsRepository, - private queueService: QueueService, private roleService: RoleService, private idService: IdService, -- cgit v1.2.3-freya From 9086de16486a7dd3e5b3390cd5fac4646a0bcd55 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:42:23 -0500 Subject: fix import order in notes/favorite/delete.ts --- packages/backend/src/server/api/endpoints/notes/favorites/delete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index 19a6a5af54..742c872d45 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -4,12 +4,12 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GetterService } from '@/server/api/GetterService.js'; import { DI } from '@/di-symbols.js'; import type { NoteFavoritesRepository } from '@/models/_.js'; import { ApiError } from '../../../error.js'; -import ms from 'ms'; export const meta = { tags: ['notes', 'favorites'], -- cgit v1.2.3-freya From 6c33542fa7814017816dc625e43650f970f62b59 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:42:41 -0500 Subject: fix import order in mute/delete.ts --- packages/backend/src/server/api/endpoints/mute/delete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts index 1e14bafc87..ccc98c4b10 100644 --- a/packages/backend/src/server/api/endpoints/mute/delete.ts +++ b/packages/backend/src/server/api/endpoints/mute/delete.ts @@ -4,13 +4,13 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MutingsRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { UserMutingService } from '@/core/UserMutingService.js'; import { ApiError } from '../../error.js'; -import ms from 'ms'; export const meta = { tags: ['account'], -- cgit v1.2.3-freya From 4aed4d6cc289a6a7d688c87f80f0aa7e0a05f568 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:42:54 -0500 Subject: fix import order in i/regenerate-token.ts --- packages/backend/src/server/api/endpoints/i/regenerate-token.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts index 814ffb5488..38328bb7d4 100644 --- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -6,12 +6,12 @@ //import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import generateUserToken from '@/misc/generate-native-user-token.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { DI } from '@/di-symbols.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From c73de4ece0e9930992f1b15052e9a831e5dcd35d Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:43:09 -0500 Subject: fix import order in i/delete-account.ts --- packages/backend/src/server/api/endpoints/i/delete-account.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts index 565eaaafc0..10fb923d4f 100644 --- a/packages/backend/src/server/api/endpoints/i/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -6,12 +6,12 @@ //import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; import { Inject, Injectable } from '@nestjs/common'; +import ms from 'ms'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DeleteAccountService } from '@/core/DeleteAccountService.js'; import { DI } from '@/di-symbols.js'; import { UserAuthService } from '@/core/UserAuthService.js'; -import ms from 'ms'; export const meta = { requireCredential: true, -- cgit v1.2.3-freya From 5745b942e07518611ec5e6f3c2f648eb0adf3621 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 4 Feb 2025 10:43:51 -0500 Subject: fix import order in i/claim-achievement.ts --- packages/backend/src/server/api/endpoints/i/claim-achievement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts index ebb25ebf1c..52f96e5bbd 100644 --- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts +++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { DI } from '@/di-symbols.js'; import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; import type { MiMeta } from '@/models/_.js'; -- cgit v1.2.3-freya From fbc6d0de54031de840c39be3a2c7c63fe522c439 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:39:46 +0900 Subject: enhance: ページslugに使用可能な文字を限定 (#15395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * paramの正規表現で弾くように * apiWithDialogを使用するように * Update CHANGELOG.md --------- Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> --- CHANGELOG.md | 2 +- locales/index.d.ts | 14 +-- locales/ja-JP.yml | 5 +- packages/backend/src/models/Page.ts | 2 + .../src/server/api/endpoints/pages/create.ts | 4 +- .../src/server/api/endpoints/pages/update.ts | 5 +- .../frontend/src/pages/page-editor/page-editor.vue | 111 ++++++++++----------- 7 files changed, 59 insertions(+), 84 deletions(-) (limited to 'packages/backend/src') diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b9f91a38..7f48d1c532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ - Playが実装されたため、ページ機能の「ソースを見る」は削除されました ### Server +- Enhance: ページのURLに使用可能な文字を限定するように - Fix: 個別お知らせページのmetaタグ出力の条件が間違っていたのを修正 - ## 2025.1.0 ### Note diff --git a/locales/index.d.ts b/locales/index.d.ts index a0540fd228..4e26d5406b 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4195,7 +4195,7 @@ export interface Locale extends ILocale { */ "invalidParamError": string; /** - * リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる等の可能性もあります。 + * リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる・許可されていない文字を入力している等の可能性もあります。 */ "invalidParamErrorDescription": string; /** @@ -9180,18 +9180,6 @@ export interface Locale extends ILocale { * ソースを表示中 */ "readPage": string; - /** - * ページを作成しました - */ - "created": string; - /** - * ページを更新しました - */ - "updated": string; - /** - * ページを削除しました - */ - "deleted": string; /** * ページ設定 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a578704434..13d8aec9b8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1044,7 +1044,7 @@ youCannotCreateAnymore: "これ以上作成することはできません。" cannotPerformTemporary: "一時的に利用できません" cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。" invalidParamError: "パラメータエラー" -invalidParamErrorDescription: "リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる等の可能性もあります。" +invalidParamErrorDescription: "リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる・許可されていない文字を入力している等の可能性もあります。" permissionDeniedError: "操作が拒否されました" permissionDeniedErrorDescription: "このアカウントにはこの操作を行うための権限がありません。" preset: "プリセット" @@ -2422,9 +2422,6 @@ _pages: newPage: "ページの作成" editPage: "ページの編集" readPage: "ソースを表示中" - created: "ページを作成しました" - updated: "ページを更新しました" - deleted: "ページを削除しました" pageSetting: "ページ設定" nameAlreadyExists: "指定されたページURLは既に存在しています" invalidNameTitle: "不正なページURLです" diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts index 1695bf570e..0b59e7a92c 100644 --- a/packages/backend/src/models/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -118,3 +118,5 @@ export class MiPage { } } } + +export const pageNameSchema = { type: 'string', pattern: /^[^\s:\/?#\[\]@!$&'()*+,;=\\%\x00-\x20]{1,256}$/.source } as const; diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index fa03b0b457..6de5fe3d44 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -7,7 +7,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import type { DriveFilesRepository, PagesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import { MiPage } from '@/models/Page.js'; +import { MiPage, pageNameSchema } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -51,7 +51,7 @@ export const paramDef = { type: 'object', properties: { title: { type: 'string' }, - name: { type: 'string', minLength: 1 }, + name: { ...pageNameSchema, minLength: 1 }, summary: { type: 'string', nullable: true }, content: { type: 'array', items: { type: 'object', additionalProperties: true, diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index e52d9c32df..a6aeb6002e 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -10,6 +10,7 @@ import type { PagesRepository, DriveFilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { pageNameSchema } from '@/models/Page.js'; export const meta = { tags: ['pages'], @@ -31,13 +32,11 @@ export const meta = { code: 'NO_SUCH_PAGE', id: '21149b9e-3616-4778-9592-c4ce89f5a864', }, - accessDenied: { message: 'Access denied.', code: 'ACCESS_DENIED', id: '3c15cd52-3b4b-4274-967d-6456fc4f792b', }, - noSuchFile: { message: 'No such file.', code: 'NO_SUCH_FILE', @@ -56,7 +55,7 @@ export const paramDef = { properties: { pageId: { type: 'string', format: 'misskey:id' }, title: { type: 'string' }, - name: { type: 'string', minLength: 1 }, + name: { ...pageNameSchema, minLength: 1 }, summary: { type: 'string', nullable: true }, content: { type: 'array', items: { type: 'object', additionalProperties: true, diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index ddb808390c..c08cfebab3 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -96,7 +96,7 @@ const summary = ref(null); const name = ref(Date.now().toString()); const eyeCatchingImage = ref(null); const eyeCatchingImageId = ref(null); -const font = ref('sans-serif'); +const font = ref<'sans-serif' | 'serif'>('sans-serif'); const content = ref([]); const alignCenter = ref(false); const hideTitleWhenPinned = ref(false); @@ -113,7 +113,7 @@ watch(eyeCatchingImageId, async () => { } }); -function getSaveOptions() { +function getSaveOptions(): Misskey.entities.PagesCreateRequest { return { title: title.value.trim(), name: name.value.trim(), @@ -128,80 +128,69 @@ function getSaveOptions() { }; } -function save() { +async function save() { const options = getSaveOptions(); - const onError = err => { - if (err.id === '3d81ceae-475f-4600-b2a8-2bc116157532') { - if (err.info.param === 'name') { - os.alert({ - type: 'error', - title: i18n.ts._pages.invalidNameTitle, - text: i18n.ts._pages.invalidNameText, - }); - } - } else if (err.code === 'NAME_ALREADY_EXISTS') { - os.alert({ - type: 'error', + if (pageId.value) { + const updateOptions: Misskey.entities.PagesUpdateRequest = { + pageId: pageId.value, + ...options, + }; + + await os.apiWithDialog('pages/update', updateOptions, undefined, { + '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab': { + title: i18n.ts.somethingHappened, text: i18n.ts._pages.nameAlreadyExists, - }); - } - }; + }, + }); - if (pageId.value) { - options.pageId = pageId.value; - misskeyApi('pages/update', options) - .then(page => { - currentName.value = name.value.trim(); - os.alert({ - type: 'success', - text: i18n.ts._pages.updated, - }); - }).catch(onError); + currentName.value = name.value.trim(); } else { - misskeyApi('pages/create', options) - .then(created => { - pageId.value = created.id; - currentName.value = name.value.trim(); - os.alert({ - type: 'success', - text: i18n.ts._pages.created, - }); - mainRouter.push(`/pages/edit/${pageId.value}`); - }).catch(onError); + const created = await os.apiWithDialog('pages/create', options, undefined, { + '4650348e-301c-499a-83c9-6aa988c66bc1': { + title: i18n.ts.somethingHappened, + text: i18n.ts._pages.nameAlreadyExists, + }, + }); + + pageId.value = created.id; + currentName.value = name.value.trim(); + mainRouter.replace(`/pages/edit/${pageId.value}`); } } -function del() { - os.confirm({ +async function del() { + if (!pageId.value) return; + + const { canceled } = await os.confirm({ type: 'warning', text: i18n.tsx.removeAreYouSure({ x: title.value.trim() }), - }).then(({ canceled }) => { - if (canceled) return; - misskeyApi('pages/delete', { - pageId: pageId.value, - }).then(() => { - os.alert({ - type: 'success', - text: i18n.ts._pages.deleted, - }); - mainRouter.push('/pages'); - }); }); + + if (canceled) return; + + await os.apiWithDialog('pages/delete', { + pageId: pageId.value, + }); + + mainRouter.replace('/pages'); } -function duplicate() { +async function duplicate() { title.value = title.value + ' - copy'; name.value = name.value + '-copy'; - misskeyApi('pages/create', getSaveOptions()).then(created => { - pageId.value = created.id; - currentName.value = name.value.trim(); - os.alert({ - type: 'success', - text: i18n.ts._pages.created, - }); - mainRouter.push(`/pages/edit/${pageId.value}`); + + const created = await os.apiWithDialog('pages/create', getSaveOptions(), undefined, { + '4650348e-301c-499a-83c9-6aa988c66bc1': { + title: i18n.ts.somethingHappened, + text: i18n.ts._pages.nameAlreadyExists, + }, }); + + pageId.value = created.id; + currentName.value = name.value.trim(); + + mainRouter.push(`/pages/edit/${pageId.value}`); } async function add() { @@ -216,7 +205,7 @@ async function add() { content.value.push({ id, type }); } -function setEyeCatchingImage(img) { +function setEyeCatchingImage(img: Event) { selectFile(img.currentTarget ?? img.target, null).then(file => { eyeCatchingImageId.value = file.id; }); -- cgit v1.2.3-freya From 402933004a8094038e2fc9949626a7c2aa9ff9b1 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 2 Feb 2025 17:23:36 -0500 Subject: increase sign-in rate limit --- packages/backend/src/server/api/SigninApiService.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index fa9155d82d..25ea03a356 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -27,11 +27,18 @@ import { CaptchaService } from '@/core/CaptchaService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { isSystemAccount } from '@/misc/is-system-account.js'; import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; -import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; +import { Keyed, RateLimit, sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import { SigninService } from './SigninService.js'; import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; import type { FastifyReply, FastifyRequest } from 'fastify'; +// Up to 10 attempts, then 1 per minute +const signinRateLimit: Keyed = { + key: 'signin', + max: 10, + dripRate: 1000 * 60, +}; + @Injectable() export class SigninApiService { constructor( @@ -94,7 +101,7 @@ export class SigninApiService { } // not more than 1 attempt per second and not more than 10 attempts per hour - const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip)); + const rateLimit = await this.rateLimiterService.limit(signinRateLimit, getIpHash(request.ip)); sendRateLimitHeaders(reply, rateLimit); -- cgit v1.2.3-freya From 09669d72e7e2474141a2712a12c6dafe290ccf88 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 2 Feb 2025 22:02:08 -0500 Subject: lookup and cache rate limit factors directly within SkRateLimiterService --- packages/backend/src/core/CoreModule.ts | 6 ++ .../backend/src/server/ActivityPubServerService.ts | 4 +- packages/backend/src/server/FileServerService.ts | 21 +++---- packages/backend/src/server/api/ApiCallService.ts | 37 ++++++------ .../backend/src/server/api/SkRateLimiterService.ts | 45 ++++++++++----- .../src/server/api/StreamingApiServerService.ts | 16 ++---- .../unit/server/api/SkRateLimiterServiceTests.ts | 66 ++++++++++++++++++---- 7 files changed, 124 insertions(+), 71 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 141f905d7f..8c9f419c44 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -232,6 +232,8 @@ const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpo const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService }; const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService }; const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService }; +const $TimeService: Provider = { provide: 'TimeService', useExisting: TimeService }; +const $EnvService: Provider = { provide: 'EnvService', useExisting: EnvService }; const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService }; const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart }; @@ -538,6 +540,8 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp $ChannelFollowingService, $RegistryApiService, $ReversiService, + $TimeService, + $EnvService, $ChartLoggerService, $FederationChart, @@ -839,6 +843,8 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp $ChannelFollowingService, $RegistryApiService, $ReversiService, + $TimeService, + $EnvService, $FederationChart, $NotesChart, diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 815bf278c7..19049e528c 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -34,10 +34,10 @@ import { bindThis } from '@/decorators.js'; import { IActivity } from '@/core/activitypub/type.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; import * as Acct from '@/misc/acct.js'; -import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify'; -import type { FindOptionsWhere } from 'typeorm'; import type Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; +import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify'; +import type { FindOptionsWhere } from 'typeorm'; const ACTIVITY_JSON = 'application/activity+json; charset=utf-8'; const LD_JSON = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8'; diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 5293d529ad..18cdda5430 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -11,7 +11,7 @@ import rename from 'rename'; import sharp from 'sharp'; import { sharpBmp } from '@misskey-dev/sharp-read-bmp'; import type { Config } from '@/config.js'; -import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js'; +import type { MiDriveFile, DriveFilesRepository, MiUser } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { createTemp } from '@/misc/create-temp.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; @@ -30,7 +30,6 @@ import { correctFilename } from '@/misc/correct-filename.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import { AuthenticateService } from '@/server/api/AuthenticateService.js'; -import { RoleService } from '@/core/RoleService.js'; import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; import { Keyed, RateLimit, sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; @@ -59,7 +58,6 @@ export class FileServerService { private loggerService: LoggerService, private authenticateService: AuthenticateService, private rateLimiterService: SkRateLimiterService, - private roleService: RoleService, ) { this.logger = this.loggerService.getLogger('server', 'gray'); @@ -625,14 +623,13 @@ export class FileServerService { // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. const [user] = await this.authenticateService.authenticate(token); - const actor = user?.id ?? getIpHash(request.ip); - const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1; + const actor = user ?? getIpHash(request.ip); // Call both limits: the per-resource limit and the shared cross-resource limit - return await this.checkResourceLimit(reply, actor, group, resource, factor) && await this.checkSharedLimit(reply, actor, group, factor); + return await this.checkResourceLimit(reply, actor, group, resource) && await this.checkSharedLimit(reply, actor, group); } - private async checkResourceLimit(reply: FastifyReply, actor: string, group: string, resource: string, factor = 1): Promise { + private async checkResourceLimit(reply: FastifyReply, actor: string | MiUser, group: string, resource: string): Promise { const limit: Keyed = { // Group by resource key: `${group}${resource}`, @@ -643,10 +640,10 @@ export class FileServerService { dripRate: 1000 * 60, }; - return await this.checkLimit(reply, actor, limit, factor); + return await this.checkLimit(reply, actor, limit); } - private async checkSharedLimit(reply: FastifyReply, actor: string, group: string, factor = 1): Promise { + private async checkSharedLimit(reply: FastifyReply, actor: string | MiUser, group: string): Promise { const limit: Keyed = { key: group, type: 'bucket', @@ -655,11 +652,11 @@ export class FileServerService { size: 3600, }; - return await this.checkLimit(reply, actor, limit, factor); + return await this.checkLimit(reply, actor, limit); } - private async checkLimit(reply: FastifyReply, actor: string, limit: Keyed, factor = 1): Promise { - const info = await this.rateLimiterService.limit(limit, actor, factor); + private async checkLimit(reply: FastifyReply, actor: string | MiUser, limit: Keyed): Promise { + const info = await this.rateLimiterService.limit(limit, actor); sendRateLimitHeaders(reply, info); diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 03f25a51fe..9c3952d541 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -313,35 +313,30 @@ export class ApiCallService implements OnApplicationShutdown { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (endpointLimit) { // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. - let limitActor: string; + let limitActor: string | MiLocalUser; if (user) { - limitActor = user.id; + limitActor = user; } else { limitActor = getIpHash(request.ip); } - // TODO: 毎リクエスト計算するのもあれだしキャッシュしたい - const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1; + const limit = { + key: ep.name, + ...endpointLimit, + }; - if (factor > 0) { - const limit = { - key: ep.name, - ...endpointLimit, - }; + // Rate limit + const info = await this.rateLimiterService.limit(limit, limitActor); - // Rate limit - const info = await this.rateLimiterService.limit(limit, limitActor, factor); + sendRateLimitHeaders(reply, info); - sendRateLimitHeaders(reply, info); - - if (info.blocked) { - throw new ApiError({ - message: 'Rate limit exceeded. Please try again later.', - code: 'RATE_LIMIT_EXCEEDED', - id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', - httpStatusCode: 429, - }, info); - } + if (info.blocked) { + throw new ApiError({ + message: 'Rate limit exceeded. Please try again later.', + code: 'RATE_LIMIT_EXCEEDED', + id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', + httpStatusCode: 429, + }, info); } } diff --git a/packages/backend/src/server/api/SkRateLimiterService.ts b/packages/backend/src/server/api/SkRateLimiterService.ts index 38c97b63df..70103222f3 100644 --- a/packages/backend/src/server/api/SkRateLimiterService.ts +++ b/packages/backend/src/server/api/SkRateLimiterService.ts @@ -5,36 +5,59 @@ import { Inject, Injectable } from '@nestjs/common'; import Redis from 'ioredis'; -import { TimeService } from '@/core/TimeService.js'; -import { EnvService } from '@/core/EnvService.js'; +import type { TimeService } from '@/core/TimeService.js'; +import type { EnvService } from '@/core/EnvService.js'; import { BucketRateLimit, LegacyRateLimit, LimitInfo, RateLimit, hasMinLimit, isLegacyRateLimit, Keyed, hasMaxLimit, disabledLimitInfo, MaxLegacyLimit, MinLegacyLimit } from '@/misc/rate-limit-utils.js'; import { DI } from '@/di-symbols.js'; +import { MemoryKVCache } from '@/misc/cache.js'; +import type { MiUser } from '@/models/_.js'; +import type { RoleService } from '@/core/RoleService.js'; + +// Sentinel value used for caching the default role template. +// Required because MemoryKVCache doesn't support null keys. +const defaultUserKey = ''; @Injectable() export class SkRateLimiterService { + // 1-minute cache interval + private readonly factorCache = new MemoryKVCache(1000 * 60); private readonly disabled: boolean; constructor( - @Inject(TimeService) + @Inject('TimeService') private readonly timeService: TimeService, @Inject(DI.redis) private readonly redisClient: Redis.Redis, - @Inject(EnvService) + @Inject('RoleService') + private readonly roleService: RoleService, + + @Inject('EnvService') envService: EnvService, ) { this.disabled = envService.env.NODE_ENV === 'test'; } /** - * Check & increment a rate limit + * Check & increment a rate limit for a client * @param limit The limit definition - * @param actor Client who is calling this limit - * @param factor Scaling factor - smaller = larger limit (less restrictive) + * @param actorOrUser authenticated client user or IP hash */ - public async limit(limit: Keyed, actor: string, factor = 1): Promise { - if (this.disabled || factor === 0) { + public async limit(limit: Keyed, actorOrUser: string | MiUser): Promise { + if (this.disabled) { + return disabledLimitInfo; + } + + const actor = typeof(actorOrUser) === 'object' ? actorOrUser.id : actorOrUser; + const userCacheKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : defaultUserKey; + const userRoleKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : null; + const factor = this.factorCache.get(userCacheKey) ?? await this.factorCache.fetch(userCacheKey, async () => { + const role = await this.roleService.getUserPolicies(userRoleKey); + return role.rateLimitFactor; + }); + + if (factor === 0) { return disabledLimitInfo; } @@ -42,10 +65,6 @@ export class SkRateLimiterService { throw new Error(`Rate limit factor is zero or negative: ${factor}`); } - return await this.tryLimit(limit, actor, factor); - } - - private async tryLimit(limit: Keyed, actor: string, factor: number): Promise { if (isLegacyRateLimit(limit)) { return await this.limitLegacy(limit, actor, factor); } else { diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index e3fd1312ae..f30bbb928b 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -18,7 +18,6 @@ import { CacheService } from '@/core/CacheService.js'; import { MiLocalUser } from '@/models/User.js'; import { UserService } from '@/core/UserService.js'; import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; -import { RoleService } from '@/core/RoleService.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import { LoggerService } from '@/core/LoggerService.js'; import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; @@ -49,7 +48,6 @@ export class StreamingApiServerService { private usersService: UserService, private channelFollowingService: ChannelFollowingService, private rateLimiterService: SkRateLimiterService, - private roleService: RoleService, private loggerService: LoggerService, ) { } @@ -57,22 +55,18 @@ export class StreamingApiServerService { @bindThis private async rateLimitThis( user: MiLocalUser | null | undefined, - requestIp: string | undefined, + requestIp: string, limit: IEndpointMeta['limit'] & { key: NonNullable }, ) : Promise { - let limitActor: string; + let limitActor: string | MiLocalUser; if (user) { - limitActor = user.id; + limitActor = user; } else { - limitActor = getIpHash(requestIp || 'wtf'); + limitActor = getIpHash(requestIp); } - const factor = user ? (await this.roleService.getUserPolicies(user.id)).rateLimitFactor : 1; - - if (factor <= 0) return false; - // Rate limit - const rateLimit = await this.rateLimiterService.limit(limit, limitActor, factor); + const rateLimit = await this.rateLimiterService.limit(limit, limitActor); return rateLimit.blocked; } diff --git a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts index d13dbd2a71..6d4f117c87 100644 --- a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts +++ b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts @@ -4,6 +4,8 @@ */ import type Redis from 'ioredis'; +import type { MiUser } from '@/models/User.js'; +import type { RolePolicies, RoleService } from '@/core/RoleService.js'; import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; import { BucketRateLimit, Keyed, LegacyRateLimit } from '@/misc/rate-limit-utils.js'; @@ -15,6 +17,8 @@ describe(SkRateLimiterService, () => { let mockRedisExec: (batch: [string, ...unknown[]][]) => Promise<[Error | null, unknown][] | null> = null!; let mockEnvironment: Record = null!; let serviceUnderTest: () => SkRateLimiterService = null!; + let mockDefaultUserPolicies: Partial = null!; + let mockUserPolicies: Record> = null!; beforeEach(() => { mockTimeService = { @@ -69,9 +73,18 @@ describe(SkRateLimiterService, () => { env: mockEnvironment, }; + mockDefaultUserPolicies = { rateLimitFactor: 1 }; + mockUserPolicies = {}; + const mockRoleService = { + getUserPolicies(key: string | null) { + const policies = key != null ? mockUserPolicies[key] : null; + return Promise.resolve(policies ?? mockDefaultUserPolicies); + }, + } as unknown as RoleService; + let service: SkRateLimiterService | undefined = undefined; serviceUnderTest = () => { - return service ??= new SkRateLimiterService(mockTimeService, mockRedisClient, mockEnvService); + return service ??= new SkRateLimiterService(mockTimeService, mockRedisClient, mockRoleService, mockEnvService); }; }); @@ -284,11 +297,12 @@ describe(SkRateLimiterService, () => { }); it('should scale limit by factor', async () => { + mockDefaultUserPolicies.rateLimitFactor = 0.5; limitCounter = 1; limitTimestamp = 0; - const i1 = await serviceUnderTest().limit(limit, actor, 0.5); // 1 + 1 = 2 - const i2 = await serviceUnderTest().limit(limit, actor, 0.5); // 2 + 1 = 3 + const i1 = await serviceUnderTest().limit(limit, actor); // 1 + 1 = 2 + const i2 = await serviceUnderTest().limit(limit, actor); // 2 + 1 = 3 expect(i1.blocked).toBeFalsy(); expect(i2.blocked).toBeTruthy(); @@ -330,14 +344,18 @@ describe(SkRateLimiterService, () => { }); it('should skip if factor is zero', async () => { - const info = await serviceUnderTest().limit(limit, actor, 0); + mockDefaultUserPolicies.rateLimitFactor = 0; + + const info = await serviceUnderTest().limit(limit, actor); expect(info.blocked).toBeFalsy(); expect(info.remaining).toBe(Number.MAX_SAFE_INTEGER); }); it('should throw if factor is negative', async () => { - const promise = serviceUnderTest().limit(limit, actor, -1); + mockDefaultUserPolicies.rateLimitFactor = -1; + + const promise = serviceUnderTest().limit(limit, actor); await expect(promise).rejects.toThrow(/factor is zero or negative/); }); @@ -426,6 +444,19 @@ describe(SkRateLimiterService, () => { expect(info.fullResetMs).toBe(2000); expect(info.fullResetSec).toBe(2); }); + + it('should look up factor by user ID', async () => { + const userActor = { id: actor } as unknown as MiUser; + mockUserPolicies[actor] = { rateLimitFactor: 0.5 }; + limitCounter = 1; + limitTimestamp = 0; + + const i1 = await serviceUnderTest().limit(limit, userActor); // 1 + 1 = 2 + const i2 = await serviceUnderTest().limit(limit, userActor); // 2 + 1 = 3 + + expect(i1.blocked).toBeFalsy(); + expect(i2.blocked).toBeTruthy(); + }); }); describe('with min interval', () => { @@ -529,11 +560,12 @@ describe(SkRateLimiterService, () => { }); it('should scale interval by factor', async () => { + mockDefaultUserPolicies.rateLimitFactor = 0.5; limitCounter = 1; limitTimestamp = 0; mockTimeService.now += 500; - const info = await serviceUnderTest().limit(limit, actor, 0.5); + const info = await serviceUnderTest().limit(limit, actor); expect(info.blocked).toBeFalsy(); }); @@ -574,14 +606,18 @@ describe(SkRateLimiterService, () => { }); it('should skip if factor is zero', async () => { - const info = await serviceUnderTest().limit(limit, actor, 0); + mockDefaultUserPolicies.rateLimitFactor = 0; + + const info = await serviceUnderTest().limit(limit, actor); expect(info.blocked).toBeFalsy(); expect(info.remaining).toBe(Number.MAX_SAFE_INTEGER); }); it('should throw if factor is negative', async () => { - const promise = serviceUnderTest().limit(limit, actor, -1); + mockDefaultUserPolicies.rateLimitFactor = -1; + + const promise = serviceUnderTest().limit(limit, actor); await expect(promise).rejects.toThrow(/factor is zero or negative/); }); @@ -701,10 +737,11 @@ describe(SkRateLimiterService, () => { }); it('should scale limit by factor', async () => { + mockDefaultUserPolicies.rateLimitFactor = 0.5; limitCounter = 10; limitTimestamp = 0; - const info = await serviceUnderTest().limit(limit, actor, 0.5); // 10 + 1 = 11 + const info = await serviceUnderTest().limit(limit, actor); // 10 + 1 = 11 expect(info.blocked).toBeTruthy(); }); @@ -760,14 +797,18 @@ describe(SkRateLimiterService, () => { }); it('should skip if factor is zero', async () => { - const info = await serviceUnderTest().limit(limit, actor, 0); + mockDefaultUserPolicies.rateLimitFactor = 0; + + const info = await serviceUnderTest().limit(limit, actor); expect(info.blocked).toBeFalsy(); expect(info.remaining).toBe(Number.MAX_SAFE_INTEGER); }); it('should throw if factor is negative', async () => { - const promise = serviceUnderTest().limit(limit, actor, -1); + mockDefaultUserPolicies.rateLimitFactor = -1; + + const promise = serviceUnderTest().limit(limit, actor); await expect(promise).rejects.toThrow(/factor is zero or negative/); }); @@ -890,11 +931,12 @@ describe(SkRateLimiterService, () => { }); it('should scale limit and interval by factor', async () => { + mockDefaultUserPolicies.rateLimitFactor = 0.5; limitCounter = 5; limitTimestamp = 0; mockTimeService.now += 500; - const info = await serviceUnderTest().limit(limit, actor, 0.5); + const info = await serviceUnderTest().limit(limit, actor); expect(info.blocked).toBeFalsy(); }); -- cgit v1.2.3-freya From d4311ea041eb746f3fdc5b4d8dbba2adb2470380 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 5 Feb 2025 10:04:42 -0500 Subject: document new rate limit factor calculations --- packages/backend/src/server/SkRateLimiterService.md | 5 +++++ packages/backend/src/server/api/SkRateLimiterService.ts | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/SkRateLimiterService.md b/packages/backend/src/server/SkRateLimiterService.md index 762f8dfe14..fb007538fa 100644 --- a/packages/backend/src/server/SkRateLimiterService.md +++ b/packages/backend/src/server/SkRateLimiterService.md @@ -12,6 +12,11 @@ SkRateLimiterService is not quite plug-and-play compatible with existing call si Instead, the returned LimitInfo object will have `blocked` set to true. Callers are responsible for checking this property and taking any desired action, such as rejecting a request or returning limit details. +Rate limit factors are also handled differently. +Instead of providing an optional parameter for callers, SkRateLimiterServer accepts an `MiUser` parameter that is used to compute the factor directly. +If a user is not available (such as for unauthenticated callers), then the Role Template factor is used instead. +To avoid confusion, the `factor` parameter has been removed entirely and is now an implementation detail. + ## Headers LimitInfo objects (returned by `SkRateLimitService.limit()`) can be passed to `rate-limit-utils.sendRateLimitHeaders()` to send standard rate limit headers with an HTTP response. diff --git a/packages/backend/src/server/api/SkRateLimiterService.ts b/packages/backend/src/server/api/SkRateLimiterService.ts index 70103222f3..038f12cb25 100644 --- a/packages/backend/src/server/api/SkRateLimiterService.ts +++ b/packages/backend/src/server/api/SkRateLimiterService.ts @@ -40,7 +40,15 @@ export class SkRateLimiterService { } /** - * Check & increment a rate limit for a client + * Check & increment a rate limit for a client. + * + * If the client (actorOrUser) is passed as a string, then it uses the default rate limit factor from the role template. + * If the client (actorOrUser) is passed as an MiUser, then it queries the user's actual rate limit factor from their assigned roles. + * + * A factor of zero (0) will disable the limit, while any negative number will produce an error. + * A factor between zero (0) and one (1) will increase the limit from its default values (allowing more actions per time interval). + * A factor greater than one (1) will decrease the limit from its default values (allowing fewer actions per time interval). + * * @param limit The limit definition * @param actorOrUser authenticated client user or IP hash */ -- cgit v1.2.3-freya From f92fb3bb8c70b5ca35f4f61a437b101bd5bae7e8 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 5 Feb 2025 11:16:39 -0500 Subject: move SkRateLimiterService to correct directory --- packages/backend/src/server/FileServerService.ts | 2 +- packages/backend/src/server/ServerModule.ts | 2 +- .../backend/src/server/SkRateLimiterService.ts | 280 +++++++++++++++++++++ packages/backend/src/server/api/ApiCallService.ts | 2 +- .../backend/src/server/api/SigninApiService.ts | 2 +- .../src/server/api/SigninWithPasskeyApiService.ts | 2 +- .../backend/src/server/api/SkRateLimiterService.ts | 280 --------------------- .../src/server/api/StreamingApiServerService.ts | 2 +- .../test/unit/SigninWithPasskeyApiService.ts | 2 +- .../unit/server/api/SkRateLimiterServiceTests.ts | 2 +- 10 files changed, 288 insertions(+), 288 deletions(-) create mode 100644 packages/backend/src/server/SkRateLimiterService.ts delete mode 100644 packages/backend/src/server/api/SkRateLimiterService.ts (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 18cdda5430..a7e13a1b78 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -30,7 +30,7 @@ import { correctFilename } from '@/misc/correct-filename.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import { AuthenticateService } from '@/server/api/AuthenticateService.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { Keyed, RateLimit, sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify'; diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index c1d7c088f1..6b5156106a 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -6,7 +6,7 @@ import { Module } from '@nestjs/common'; import { EndpointsModule } from '@/server/api/EndpointsModule.js'; import { CoreModule } from '@/core/CoreModule.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { ApiCallService } from './api/ApiCallService.js'; import { FileServerService } from './FileServerService.js'; import { HealthServerService } from './HealthServerService.js'; diff --git a/packages/backend/src/server/SkRateLimiterService.ts b/packages/backend/src/server/SkRateLimiterService.ts new file mode 100644 index 0000000000..038f12cb25 --- /dev/null +++ b/packages/backend/src/server/SkRateLimiterService.ts @@ -0,0 +1,280 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import Redis from 'ioredis'; +import type { TimeService } from '@/core/TimeService.js'; +import type { EnvService } from '@/core/EnvService.js'; +import { BucketRateLimit, LegacyRateLimit, LimitInfo, RateLimit, hasMinLimit, isLegacyRateLimit, Keyed, hasMaxLimit, disabledLimitInfo, MaxLegacyLimit, MinLegacyLimit } from '@/misc/rate-limit-utils.js'; +import { DI } from '@/di-symbols.js'; +import { MemoryKVCache } from '@/misc/cache.js'; +import type { MiUser } from '@/models/_.js'; +import type { RoleService } from '@/core/RoleService.js'; + +// Sentinel value used for caching the default role template. +// Required because MemoryKVCache doesn't support null keys. +const defaultUserKey = ''; + +@Injectable() +export class SkRateLimiterService { + // 1-minute cache interval + private readonly factorCache = new MemoryKVCache(1000 * 60); + private readonly disabled: boolean; + + constructor( + @Inject('TimeService') + private readonly timeService: TimeService, + + @Inject(DI.redis) + private readonly redisClient: Redis.Redis, + + @Inject('RoleService') + private readonly roleService: RoleService, + + @Inject('EnvService') + envService: EnvService, + ) { + this.disabled = envService.env.NODE_ENV === 'test'; + } + + /** + * Check & increment a rate limit for a client. + * + * If the client (actorOrUser) is passed as a string, then it uses the default rate limit factor from the role template. + * If the client (actorOrUser) is passed as an MiUser, then it queries the user's actual rate limit factor from their assigned roles. + * + * A factor of zero (0) will disable the limit, while any negative number will produce an error. + * A factor between zero (0) and one (1) will increase the limit from its default values (allowing more actions per time interval). + * A factor greater than one (1) will decrease the limit from its default values (allowing fewer actions per time interval). + * + * @param limit The limit definition + * @param actorOrUser authenticated client user or IP hash + */ + public async limit(limit: Keyed, actorOrUser: string | MiUser): Promise { + if (this.disabled) { + return disabledLimitInfo; + } + + const actor = typeof(actorOrUser) === 'object' ? actorOrUser.id : actorOrUser; + const userCacheKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : defaultUserKey; + const userRoleKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : null; + const factor = this.factorCache.get(userCacheKey) ?? await this.factorCache.fetch(userCacheKey, async () => { + const role = await this.roleService.getUserPolicies(userRoleKey); + return role.rateLimitFactor; + }); + + if (factor === 0) { + return disabledLimitInfo; + } + + if (factor < 0) { + throw new Error(`Rate limit factor is zero or negative: ${factor}`); + } + + if (isLegacyRateLimit(limit)) { + return await this.limitLegacy(limit, actor, factor); + } else { + return await this.limitBucket(limit, actor, factor); + } + } + + private async limitLegacy(limit: Keyed, actor: string, factor: number): Promise { + if (hasMaxLimit(limit)) { + return await this.limitLegacyMinMax(limit, actor, factor); + } else if (hasMinLimit(limit)) { + return await this.limitLegacyMinOnly(limit, actor, factor); + } else { + return disabledLimitInfo; + } + } + + private async limitLegacyMinMax(limit: Keyed, actor: string, factor: number): Promise { + if (limit.duration === 0) return disabledLimitInfo; + if (limit.duration < 0) throw new Error(`Invalid rate limit ${limit.key}: duration is negative (${limit.duration})`); + if (limit.max < 1) throw new Error(`Invalid rate limit ${limit.key}: max is less than 1 (${limit.max})`); + + // Derive initial dripRate from minInterval OR duration/max. + const initialDripRate = Math.max(limit.minInterval ?? Math.round(limit.duration / limit.max), 1); + + // Calculate dripSize to reach max at exactly duration + const dripSize = Math.max(Math.round(limit.max / (limit.duration / initialDripRate)), 1); + + // Calculate final dripRate from dripSize and duration/max + const dripRate = Math.max(Math.round(limit.duration / (limit.max / dripSize)), 1); + + const bucketLimit: Keyed = { + type: 'bucket', + key: limit.key, + size: limit.max, + dripRate, + dripSize, + }; + return await this.limitBucket(bucketLimit, actor, factor); + } + + private async limitLegacyMinOnly(limit: Keyed, actor: string, factor: number): Promise { + if (limit.minInterval === 0) return disabledLimitInfo; + if (limit.minInterval < 0) throw new Error(`Invalid rate limit ${limit.key}: minInterval is negative (${limit.minInterval})`); + + const dripRate = Math.max(Math.round(limit.minInterval), 1); + const bucketLimit: Keyed = { + type: 'bucket', + key: limit.key, + size: 1, + dripRate, + dripSize: 1, + }; + return await this.limitBucket(bucketLimit, actor, factor); + } + + /** + * Implementation of Leaky Bucket rate limiting - see SkRateLimiterService.md for details. + */ + private async limitBucket(limit: Keyed, actor: string, factor: number): Promise { + if (limit.size < 1) throw new Error(`Invalid rate limit ${limit.key}: size is less than 1 (${limit.size})`); + if (limit.dripRate != null && limit.dripRate < 1) throw new Error(`Invalid rate limit ${limit.key}: dripRate is less than 1 (${limit.dripRate})`); + if (limit.dripSize != null && limit.dripSize < 1) throw new Error(`Invalid rate limit ${limit.key}: dripSize is less than 1 (${limit.dripSize})`); + + // 0 - Calculate + const now = this.timeService.now; + const bucketSize = Math.max(Math.ceil(limit.size / factor), 1); + const dripRate = Math.ceil(limit.dripRate ?? 1000); + const dripSize = Math.ceil(limit.dripSize ?? 1); + const expirationSec = Math.max(Math.ceil((dripRate * Math.ceil(bucketSize / dripSize)) / 1000), 1); + + // 1 - Read + const counterKey = createLimitKey(limit, actor, 'c'); + const timestampKey = createLimitKey(limit, actor, 't'); + const counter = await this.getLimitCounter(counterKey, timestampKey); + + // 2 - Drip + const dripsSinceLastTick = Math.floor((now - counter.timestamp) / dripRate) * dripSize; + const deltaCounter = Math.min(dripsSinceLastTick, counter.counter); + const deltaTimestamp = dripsSinceLastTick * dripRate; + if (deltaCounter > 0) { + // Execute the next drip(s) + const results = await this.executeRedisMulti( + ['get', timestampKey], + ['incrby', timestampKey, deltaTimestamp], + ['expire', timestampKey, expirationSec], + ['get', timestampKey], + ['decrby', counterKey, deltaCounter], + ['expire', counterKey, expirationSec], + ['get', counterKey], + ); + const expectedTimestamp = counter.timestamp; + const canaryTimestamp = results[0] ? parseInt(results[0]) : 0; + counter.timestamp = results[3] ? parseInt(results[3]) : 0; + counter.counter = results[6] ? parseInt(results[6]) : 0; + + // Check for a data collision and rollback + if (canaryTimestamp !== expectedTimestamp) { + const rollbackResults = await this.executeRedisMulti( + ['decrby', timestampKey, deltaTimestamp], + ['get', timestampKey], + ['incrby', counterKey, deltaCounter], + ['get', counterKey], + ); + counter.timestamp = rollbackResults[1] ? parseInt(rollbackResults[1]) : 0; + counter.counter = rollbackResults[3] ? parseInt(rollbackResults[3]) : 0; + } + } + + // 3 - Check + const blocked = counter.counter >= bucketSize; + if (!blocked) { + if (counter.timestamp === 0) { + const results = await this.executeRedisMulti( + ['set', timestampKey, now], + ['expire', timestampKey, expirationSec], + ['incr', counterKey], + ['expire', counterKey, expirationSec], + ['get', counterKey], + ); + counter.timestamp = now; + counter.counter = results[4] ? parseInt(results[4]) : 0; + } else { + const results = await this.executeRedisMulti( + ['incr', counterKey], + ['expire', counterKey, expirationSec], + ['get', counterKey], + ); + counter.counter = results[2] ? parseInt(results[2]) : 0; + } + } + + // Calculate how much time is needed to free up a bucket slot + const overflow = Math.max((counter.counter + 1) - bucketSize, 0); + const dripsNeeded = Math.ceil(overflow / dripSize); + const timeNeeded = Math.max((dripRate * dripsNeeded) - (this.timeService.now - counter.timestamp), 0); + + // Calculate limit status + const remaining = Math.max(bucketSize - counter.counter, 0); + const resetMs = timeNeeded; + const resetSec = Math.ceil(resetMs / 1000); + const fullResetMs = Math.ceil(counter.counter / dripSize) * dripRate; + const fullResetSec = Math.ceil(fullResetMs / 1000); + return { blocked, remaining, resetSec, resetMs, fullResetSec, fullResetMs }; + } + + private async getLimitCounter(counterKey: string, timestampKey: string): Promise { + const [counter, timestamp] = await this.executeRedisMulti( + ['get', counterKey], + ['get', timestampKey], + ); + + return { + counter: counter ? parseInt(counter) : 0, + timestamp: timestamp ? parseInt(timestamp) : 0, + }; + } + + private async executeRedisMulti(...batch: RedisCommand[]): Promise { + const results = await this.redisClient.multi(batch).exec(); + + // Transaction conflict (retryable) + if (!results) { + throw new ConflictError('Redis error: transaction conflict'); + } + + // Transaction failed (fatal) + if (results.length !== batch.length) { + throw new Error('Redis error: failed to execute batch'); + } + + // Map responses + const errors: Error[] = []; + const responses: RedisResult[] = []; + for (const [error, response] of results) { + if (error) errors.push(error); + responses.push(response as RedisResult); + } + + // Command failed (fatal) + if (errors.length > 0) { + const errorMessages = errors + .map((e, i) => `Error in command ${i}: ${e}`) + .join('\', \''); + throw new AggregateError(errors, `Redis error: failed to execute command(s): '${errorMessages}'`); + } + + return responses; + } +} + +// Not correct, but good enough for the basic commands we use. +type RedisResult = string | null; +type RedisCommand = [command: string, ...args: unknown[]]; + +function createLimitKey(limit: Keyed, actor: string, value: string): string { + return `rl_${actor}_${limit.key}_${value}`; +} + +class ConflictError extends Error {} + +interface LimitCounter { + timestamp: number; + counter: number; +} diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts index 9c3952d541..5ce358d68f 100644 --- a/packages/backend/src/server/api/ApiCallService.ts +++ b/packages/backend/src/server/api/ApiCallService.ts @@ -19,7 +19,7 @@ import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import type { Config } from '@/config.js'; import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { ApiError } from './error.js'; import { ApiLoggerService } from './ApiLoggerService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 25ea03a356..72712bce60 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -26,7 +26,7 @@ import { UserAuthService } from '@/core/UserAuthService.js'; import { CaptchaService } from '@/core/CaptchaService.js'; import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { isSystemAccount } from '@/misc/is-system-account.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { Keyed, RateLimit, sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import { SigninService } from './SigninService.js'; import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts index e94d2b6b68..f84f50523b 100644 --- a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts +++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts @@ -21,7 +21,7 @@ import { WebAuthnService } from '@/core/WebAuthnService.js'; import Logger from '@/logger.js'; import { LoggerService } from '@/core/LoggerService.js'; import type { IdentifiableError } from '@/misc/identifiable-error.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import { SigninService } from './SigninService.js'; import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; diff --git a/packages/backend/src/server/api/SkRateLimiterService.ts b/packages/backend/src/server/api/SkRateLimiterService.ts deleted file mode 100644 index 038f12cb25..0000000000 --- a/packages/backend/src/server/api/SkRateLimiterService.ts +++ /dev/null @@ -1,280 +0,0 @@ -/* - * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Inject, Injectable } from '@nestjs/common'; -import Redis from 'ioredis'; -import type { TimeService } from '@/core/TimeService.js'; -import type { EnvService } from '@/core/EnvService.js'; -import { BucketRateLimit, LegacyRateLimit, LimitInfo, RateLimit, hasMinLimit, isLegacyRateLimit, Keyed, hasMaxLimit, disabledLimitInfo, MaxLegacyLimit, MinLegacyLimit } from '@/misc/rate-limit-utils.js'; -import { DI } from '@/di-symbols.js'; -import { MemoryKVCache } from '@/misc/cache.js'; -import type { MiUser } from '@/models/_.js'; -import type { RoleService } from '@/core/RoleService.js'; - -// Sentinel value used for caching the default role template. -// Required because MemoryKVCache doesn't support null keys. -const defaultUserKey = ''; - -@Injectable() -export class SkRateLimiterService { - // 1-minute cache interval - private readonly factorCache = new MemoryKVCache(1000 * 60); - private readonly disabled: boolean; - - constructor( - @Inject('TimeService') - private readonly timeService: TimeService, - - @Inject(DI.redis) - private readonly redisClient: Redis.Redis, - - @Inject('RoleService') - private readonly roleService: RoleService, - - @Inject('EnvService') - envService: EnvService, - ) { - this.disabled = envService.env.NODE_ENV === 'test'; - } - - /** - * Check & increment a rate limit for a client. - * - * If the client (actorOrUser) is passed as a string, then it uses the default rate limit factor from the role template. - * If the client (actorOrUser) is passed as an MiUser, then it queries the user's actual rate limit factor from their assigned roles. - * - * A factor of zero (0) will disable the limit, while any negative number will produce an error. - * A factor between zero (0) and one (1) will increase the limit from its default values (allowing more actions per time interval). - * A factor greater than one (1) will decrease the limit from its default values (allowing fewer actions per time interval). - * - * @param limit The limit definition - * @param actorOrUser authenticated client user or IP hash - */ - public async limit(limit: Keyed, actorOrUser: string | MiUser): Promise { - if (this.disabled) { - return disabledLimitInfo; - } - - const actor = typeof(actorOrUser) === 'object' ? actorOrUser.id : actorOrUser; - const userCacheKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : defaultUserKey; - const userRoleKey = typeof(actorOrUser) === 'object' ? actorOrUser.id : null; - const factor = this.factorCache.get(userCacheKey) ?? await this.factorCache.fetch(userCacheKey, async () => { - const role = await this.roleService.getUserPolicies(userRoleKey); - return role.rateLimitFactor; - }); - - if (factor === 0) { - return disabledLimitInfo; - } - - if (factor < 0) { - throw new Error(`Rate limit factor is zero or negative: ${factor}`); - } - - if (isLegacyRateLimit(limit)) { - return await this.limitLegacy(limit, actor, factor); - } else { - return await this.limitBucket(limit, actor, factor); - } - } - - private async limitLegacy(limit: Keyed, actor: string, factor: number): Promise { - if (hasMaxLimit(limit)) { - return await this.limitLegacyMinMax(limit, actor, factor); - } else if (hasMinLimit(limit)) { - return await this.limitLegacyMinOnly(limit, actor, factor); - } else { - return disabledLimitInfo; - } - } - - private async limitLegacyMinMax(limit: Keyed, actor: string, factor: number): Promise { - if (limit.duration === 0) return disabledLimitInfo; - if (limit.duration < 0) throw new Error(`Invalid rate limit ${limit.key}: duration is negative (${limit.duration})`); - if (limit.max < 1) throw new Error(`Invalid rate limit ${limit.key}: max is less than 1 (${limit.max})`); - - // Derive initial dripRate from minInterval OR duration/max. - const initialDripRate = Math.max(limit.minInterval ?? Math.round(limit.duration / limit.max), 1); - - // Calculate dripSize to reach max at exactly duration - const dripSize = Math.max(Math.round(limit.max / (limit.duration / initialDripRate)), 1); - - // Calculate final dripRate from dripSize and duration/max - const dripRate = Math.max(Math.round(limit.duration / (limit.max / dripSize)), 1); - - const bucketLimit: Keyed = { - type: 'bucket', - key: limit.key, - size: limit.max, - dripRate, - dripSize, - }; - return await this.limitBucket(bucketLimit, actor, factor); - } - - private async limitLegacyMinOnly(limit: Keyed, actor: string, factor: number): Promise { - if (limit.minInterval === 0) return disabledLimitInfo; - if (limit.minInterval < 0) throw new Error(`Invalid rate limit ${limit.key}: minInterval is negative (${limit.minInterval})`); - - const dripRate = Math.max(Math.round(limit.minInterval), 1); - const bucketLimit: Keyed = { - type: 'bucket', - key: limit.key, - size: 1, - dripRate, - dripSize: 1, - }; - return await this.limitBucket(bucketLimit, actor, factor); - } - - /** - * Implementation of Leaky Bucket rate limiting - see SkRateLimiterService.md for details. - */ - private async limitBucket(limit: Keyed, actor: string, factor: number): Promise { - if (limit.size < 1) throw new Error(`Invalid rate limit ${limit.key}: size is less than 1 (${limit.size})`); - if (limit.dripRate != null && limit.dripRate < 1) throw new Error(`Invalid rate limit ${limit.key}: dripRate is less than 1 (${limit.dripRate})`); - if (limit.dripSize != null && limit.dripSize < 1) throw new Error(`Invalid rate limit ${limit.key}: dripSize is less than 1 (${limit.dripSize})`); - - // 0 - Calculate - const now = this.timeService.now; - const bucketSize = Math.max(Math.ceil(limit.size / factor), 1); - const dripRate = Math.ceil(limit.dripRate ?? 1000); - const dripSize = Math.ceil(limit.dripSize ?? 1); - const expirationSec = Math.max(Math.ceil((dripRate * Math.ceil(bucketSize / dripSize)) / 1000), 1); - - // 1 - Read - const counterKey = createLimitKey(limit, actor, 'c'); - const timestampKey = createLimitKey(limit, actor, 't'); - const counter = await this.getLimitCounter(counterKey, timestampKey); - - // 2 - Drip - const dripsSinceLastTick = Math.floor((now - counter.timestamp) / dripRate) * dripSize; - const deltaCounter = Math.min(dripsSinceLastTick, counter.counter); - const deltaTimestamp = dripsSinceLastTick * dripRate; - if (deltaCounter > 0) { - // Execute the next drip(s) - const results = await this.executeRedisMulti( - ['get', timestampKey], - ['incrby', timestampKey, deltaTimestamp], - ['expire', timestampKey, expirationSec], - ['get', timestampKey], - ['decrby', counterKey, deltaCounter], - ['expire', counterKey, expirationSec], - ['get', counterKey], - ); - const expectedTimestamp = counter.timestamp; - const canaryTimestamp = results[0] ? parseInt(results[0]) : 0; - counter.timestamp = results[3] ? parseInt(results[3]) : 0; - counter.counter = results[6] ? parseInt(results[6]) : 0; - - // Check for a data collision and rollback - if (canaryTimestamp !== expectedTimestamp) { - const rollbackResults = await this.executeRedisMulti( - ['decrby', timestampKey, deltaTimestamp], - ['get', timestampKey], - ['incrby', counterKey, deltaCounter], - ['get', counterKey], - ); - counter.timestamp = rollbackResults[1] ? parseInt(rollbackResults[1]) : 0; - counter.counter = rollbackResults[3] ? parseInt(rollbackResults[3]) : 0; - } - } - - // 3 - Check - const blocked = counter.counter >= bucketSize; - if (!blocked) { - if (counter.timestamp === 0) { - const results = await this.executeRedisMulti( - ['set', timestampKey, now], - ['expire', timestampKey, expirationSec], - ['incr', counterKey], - ['expire', counterKey, expirationSec], - ['get', counterKey], - ); - counter.timestamp = now; - counter.counter = results[4] ? parseInt(results[4]) : 0; - } else { - const results = await this.executeRedisMulti( - ['incr', counterKey], - ['expire', counterKey, expirationSec], - ['get', counterKey], - ); - counter.counter = results[2] ? parseInt(results[2]) : 0; - } - } - - // Calculate how much time is needed to free up a bucket slot - const overflow = Math.max((counter.counter + 1) - bucketSize, 0); - const dripsNeeded = Math.ceil(overflow / dripSize); - const timeNeeded = Math.max((dripRate * dripsNeeded) - (this.timeService.now - counter.timestamp), 0); - - // Calculate limit status - const remaining = Math.max(bucketSize - counter.counter, 0); - const resetMs = timeNeeded; - const resetSec = Math.ceil(resetMs / 1000); - const fullResetMs = Math.ceil(counter.counter / dripSize) * dripRate; - const fullResetSec = Math.ceil(fullResetMs / 1000); - return { blocked, remaining, resetSec, resetMs, fullResetSec, fullResetMs }; - } - - private async getLimitCounter(counterKey: string, timestampKey: string): Promise { - const [counter, timestamp] = await this.executeRedisMulti( - ['get', counterKey], - ['get', timestampKey], - ); - - return { - counter: counter ? parseInt(counter) : 0, - timestamp: timestamp ? parseInt(timestamp) : 0, - }; - } - - private async executeRedisMulti(...batch: RedisCommand[]): Promise { - const results = await this.redisClient.multi(batch).exec(); - - // Transaction conflict (retryable) - if (!results) { - throw new ConflictError('Redis error: transaction conflict'); - } - - // Transaction failed (fatal) - if (results.length !== batch.length) { - throw new Error('Redis error: failed to execute batch'); - } - - // Map responses - const errors: Error[] = []; - const responses: RedisResult[] = []; - for (const [error, response] of results) { - if (error) errors.push(error); - responses.push(response as RedisResult); - } - - // Command failed (fatal) - if (errors.length > 0) { - const errorMessages = errors - .map((e, i) => `Error in command ${i}: ${e}`) - .join('\', \''); - throw new AggregateError(errors, `Redis error: failed to execute command(s): '${errorMessages}'`); - } - - return responses; - } -} - -// Not correct, but good enough for the basic commands we use. -type RedisResult = string | null; -type RedisCommand = [command: string, ...args: unknown[]]; - -function createLimitKey(limit: Keyed, actor: string, value: string): string { - return `rl_${actor}_${limit.key}_${value}`; -} - -class ConflictError extends Error {} - -interface LimitCounter { - timestamp: number; - counter: number; -} diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts index f30bbb928b..6e7abcfae6 100644 --- a/packages/backend/src/server/api/StreamingApiServerService.ts +++ b/packages/backend/src/server/api/StreamingApiServerService.ts @@ -20,7 +20,7 @@ import { UserService } from '@/core/UserService.js'; import { ChannelFollowingService } from '@/core/ChannelFollowingService.js'; import { getIpHash } from '@/misc/get-ip-hash.js'; import { LoggerService } from '@/core/LoggerService.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { AuthenticateService, AuthenticationError } from './AuthenticateService.js'; import MainStreamConnection from './stream/Connection.js'; import { ChannelsService } from './stream/ChannelsService.js'; diff --git a/packages/backend/test/unit/SigninWithPasskeyApiService.ts b/packages/backend/test/unit/SigninWithPasskeyApiService.ts index 7df991c15c..efed905e02 100644 --- a/packages/backend/test/unit/SigninWithPasskeyApiService.ts +++ b/packages/backend/test/unit/SigninWithPasskeyApiService.ts @@ -20,7 +20,7 @@ import { SigninWithPasskeyApiService } from '@/server/api/SigninWithPasskeyApiSe import { WebAuthnService } from '@/core/WebAuthnService.js'; import { SigninService } from '@/server/api/SigninService.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { LimitInfo } from '@/misc/rate-limit-utils.js'; const moduleMocker = new ModuleMocker(global); diff --git a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts index 6d4f117c87..b1f100698b 100644 --- a/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts +++ b/packages/backend/test/unit/server/api/SkRateLimiterServiceTests.ts @@ -6,7 +6,7 @@ import type Redis from 'ioredis'; import type { MiUser } from '@/models/User.js'; import type { RolePolicies, RoleService } from '@/core/RoleService.js'; -import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; +import { SkRateLimiterService } from '@/server/SkRateLimiterService.js'; import { BucketRateLimit, Keyed, LegacyRateLimit } from '@/misc/rate-limit-utils.js'; /* eslint-disable @typescript-eslint/no-non-null-assertion */ -- cgit v1.2.3-freya From b171c1c4408c1438976f3473c8104b68305a53f9 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 5 Feb 2025 11:18:53 -0500 Subject: move imports to fix git diff in ActivityPubServerService.ts --- packages/backend/src/server/ActivityPubServerService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 19049e528c..6e8c249471 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -30,12 +30,12 @@ import type { MiNote } from '@/models/Note.js'; import { QueryService } from '@/core/QueryService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import type Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import { IActivity } from '@/core/activitypub/type.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; import * as Acct from '@/misc/acct.js'; -import type Logger from '@/logger.js'; -import { LoggerService } from '@/core/LoggerService.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify'; import type { FindOptionsWhere } from 'typeorm'; -- cgit v1.2.3-freya From 1bedf954f296a7151e0e878cecb0154508c93967 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 27 Jan 2025 01:57:14 -0500 Subject: increase rate limit for `users/show` endpoint --- packages/backend/src/server/api/endpoints/users/show.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 7ebca78a7d..118362149d 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -57,10 +57,10 @@ export const meta = { }, }, - // 5 calls per 2 seconds + // up to 50 calls @ 4 per second limit: { - duration: 1000 * 2, - max: 5, + max: 50, + dripRate: 250, }, } as const; -- cgit v1.2.3-freya From 9a7a9e346156828547406e48653892f9015bdc65 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 29 Jan 2025 22:51:48 -0500 Subject: loosen parameter types for getApId and getNullableApId --- packages/backend/src/core/activitypub/type.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 119a9d8ccb..08bd224700 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -56,10 +56,17 @@ export function getOneApId(value: ApObject): string { return getApId(firstOne); } +/** + * Minimal AP payload - just an object with optional ID. + */ +export interface ObjectWithId { + id?: string; +} + /** * Get ActivityStreams Object id */ -export function getApId(value: string | IObject | [string | IObject]): string { +export function getApId(value: string | ObjectWithId | [string | ObjectWithId]): string { // eslint-disable-next-line no-param-reassign value = fromTuple(value); @@ -71,7 +78,7 @@ export function getApId(value: string | IObject | [string | IObject]): string { /** * Get ActivityStreams Object id, or null if not present */ -export function getNullableApId(value: string | IObject | [string | IObject]): string | null { +export function getNullableApId(value: string | ObjectWithId | [string | ObjectWithId]): string | null { // eslint-disable-next-line no-param-reassign value = fromTuple(value); -- cgit v1.2.3-freya From 88dd36ce837e45285261b9bbb2c76ab49280a230 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 29 Jan 2025 22:52:26 -0500 Subject: narrow return type for signedGet --- packages/backend/src/core/activitypub/ApRequestService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 8036c9638f..e205512993 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -185,7 +185,7 @@ export class ApRequestService { * @param url URL to fetch */ @bindThis - public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise { + public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise { const _followAlternate = followAlternate ?? true; const keypair = await this.userKeypairService.getUserKeypair(user.id); -- cgit v1.2.3-freya From 788dc69d11fac8dd9e8912c4c0a2ad33581e606b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 29 Jan 2025 22:53:53 -0500 Subject: use leaky bucket rate limit for ap/show --- packages/backend/src/server/api/endpoints/ap/show.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 616a77e337..474ff81822 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -4,7 +4,6 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import ms from 'ms'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MiNote } from '@/models/Note.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; @@ -26,9 +25,10 @@ export const meta = { requireCredential: true, kind: 'read:account', + // Up to 30 calls, then 1 per 1/2 second limit: { - duration: ms('1minute'), max: 30, + dripRate: 500, }, errors: { -- cgit v1.2.3-freya From d831c168aad20dbba7292d9964e3573124e869da Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 29 Jan 2025 23:45:34 -0500 Subject: support Mastodon's version of "alternate links" --- packages/backend/src/core/activitypub/ApRequestService.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index e205512993..aca322e745 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -239,7 +239,18 @@ export class ApRequestService { try { document.documentElement.innerHTML = html; - const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]'); + // Search for any matching value in priority order: + // 1. Type=AP > Type=none > Type=anything + // 2. Alternate > Canonical + // 3. Page order (fallback) + const alternate = + document.querySelector('head > link[href][rel="alternate"][type="application/activity+json"]') ?? + document.querySelector('head > link[href][rel="canonical"][type="application/activity+json"]') ?? + document.querySelector('head > link[href][rel="alternate"]:not([type])') ?? + document.querySelector('head > link[href][rel="canonical"]:not([type])') ?? + document.querySelector('head > link[href][rel="alternate"]') ?? + document.querySelector('head > link[href][rel="canonical"]'); + if (alternate) { const href = alternate.getAttribute('href'); if (href) { -- cgit v1.2.3-freya From b92591e2eddb7a1ebda75195a65ff61f655b876a Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 29 Jan 2025 23:46:43 -0500 Subject: allow ap/show to follow cross-domain redirects --- .../backend/src/server/api/endpoints/ap/show.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 474ff81822..4904e3cb87 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { MiNote } from '@/models/Note.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; -import { isActor, isPost, getApId } from '@/core/activitypub/type.js'; +import { isActor, isPost, getApId, getNullableApId, ObjectWithId } from '@/core/activitypub/type.js'; import type { SchemaType } from '@/misc/json-schema.js'; import { ApResolverService } from '@/core/activitypub/ApResolverService.js'; import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; @@ -17,6 +17,8 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; +import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; +import { InstanceActorService } from '@/core/InstanceActorService.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -94,6 +96,8 @@ export default class extends Endpoint { // eslint- private apDbResolverService: ApDbResolverService, private apPersonService: ApPersonService, private apNoteService: ApNoteService, + private readonly apRequestService: ApRequestService, + private readonly instanceActorService: InstanceActorService, ) { super(meta, paramDef, async (ps, me) => { const object = await this.fetchAny(ps.uri, me); @@ -118,6 +122,12 @@ export default class extends Endpoint { // eslint- ])); if (local != null) return local; + // No local object found with that uri. + // Before we fetch, resolve the URI in case it has a cross-origin redirect or anything like that. + // Resolver.resolve() uses strict verification, which is overly paranoid for a user-provided lookup. + uri = await this.resolveCanonicalUri(uri); // eslint-disable-line no-param-reassign + if (!this.utilityService.isFederationAllowedUri(uri)) return null; + const host = this.utilityService.extractDbHost(uri); // local object, not found in db? fail @@ -167,4 +177,13 @@ export default class extends Endpoint { // eslint- return null; } + + /** + * Resolves an arbitrary URI to its canonical, post-redirect form. + */ + private async resolveCanonicalUri(uri: string): Promise { + const user = await this.instanceActorService.getInstanceActor(); + const res = await this.apRequestService.signedGet(uri, user, true) as ObjectWithId; + return getNullableApId(res) ?? uri; + } } -- cgit v1.2.3-freya From 74407bc8ee43a8c7b4bc8b7e16bdfb8acd2c794c Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 1 Feb 2025 17:07:34 -0500 Subject: add MiUserProfile.defaultCW property and API --- locales/index.d.ts | 2 +- .../1738446745738-add_user_profile_default_cw.js | 11 +++++++++++ packages/backend/src/core/entities/UserEntityService.ts | 1 + packages/backend/src/models/UserProfile.ts | 10 ++++++++-- packages/backend/src/models/json-schema/user.ts | 4 ++++ packages/backend/src/server/api/endpoints/i/update.ts | 17 +++++++++++++++++ packages/misskey-js/src/autogen/types.ts | 5 +++++ 7 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 packages/backend/migration/1738446745738-add_user_profile_default_cw.js (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index 70eba52ea0..af5faefe1a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -11631,7 +11631,7 @@ export interface Locale extends ILocale { */ "robotsTxt": string; /** - * Adding entries here will override the default robots.txt packaged with Sharkey. Maximum 2048 characters. + * Adding entries here will override the default robots.txt packaged with Sharkey. */ "robotsTxtDescription": string; } diff --git a/packages/backend/migration/1738446745738-add_user_profile_default_cw.js b/packages/backend/migration/1738446745738-add_user_profile_default_cw.js new file mode 100644 index 0000000000..205ca2087a --- /dev/null +++ b/packages/backend/migration/1738446745738-add_user_profile_default_cw.js @@ -0,0 +1,11 @@ +export class AddUserProfileDefaultCw1738446745738 { + name = 'AddUserProfileDefaultCw1738446745738' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ADD "default_cw" text`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "default_cw"`); + } +} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 6bfe865038..0ca784fa52 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -669,6 +669,7 @@ export class UserEntityService implements OnModuleInit { achievements: profile!.achievements, loggedInDays: profile!.loggedInDates.length, policies: this.roleService.getUserPolicies(user.id), + defaultCW: profile?.defaultCW ?? null, } : {}), ...(opts.includeSecrets ? { diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 751b1aff08..3c2362227e 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -36,10 +36,10 @@ export class MiUserProfile { }) public birthday: string | null; - @Column("varchar", { + @Column('varchar', { length: 128, nullable: true, - comment: "The ListenBrainz username of the User.", + comment: 'The ListenBrainz username of the User.', }) public listenbrainz: string | null; @@ -290,6 +290,12 @@ export class MiUserProfile { unlockedAt: number; }[]; + @Column('text', { + name: 'default_cw', + nullable: true, + }) + public defaultCW: string | null; + //#region Denormalized fields @Index() @Column('varchar', { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index f953008b3f..f6c7bd2151 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -752,6 +752,10 @@ export const packedMeDetailedOnlySchema = { }, }, //#endregion + defaultCW: { + type: 'string', + nullable: true, optional: false, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 09c06a108d..e487562687 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -133,6 +133,12 @@ export const meta = { id: '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191', httpStatusCode: 422, }, + + maxCwLength: { + message: 'You tried setting a default content warning which is too long.', + code: 'MAX_CW_LENGTH', + id: '7004c478-bda3-4b4f-acb2-4316398c9d52', + }, }, res: { @@ -243,6 +249,7 @@ export const paramDef = { uniqueItems: true, items: { type: 'string' }, }, + defaultCW: { type: 'string', nullable: true }, }, } as const; @@ -494,6 +501,16 @@ export default class extends Endpoint { // eslint- updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null; } + let defaultCW = ps.defaultCW; + if (defaultCW !== undefined) { + if (defaultCW === '') defaultCW = null; + if (defaultCW && defaultCW.length > this.config.maxCwLength) { + throw new ApiError(meta.errors.maxCwLength); + } + + profileUpdates.defaultCW = defaultCW; + } + //#region emojis/tags let emojis = [] as string[]; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 888e46e008..78dac5f08b 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -4217,6 +4217,7 @@ export type components = { /** Format: date-time */ lastUsed: string; }[]; + defaultCW: string | null; }; UserDetailedNotMe: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly']; MeDetailed: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'] & components['schemas']['MeDetailedOnly']; @@ -5224,6 +5225,7 @@ export type components = { enableFC: boolean; fcSiteKey: string | null; enableAchievements: boolean | null; + robotsTxt: string | null; enableTestcaptcha: boolean; swPublickey: string | null; /** @default /assets/ai.png */ @@ -5434,6 +5436,7 @@ export type operations = { enableStatsForFederatedInstances: boolean; enableServerMachineStats: boolean; enableAchievements: boolean; + robotsTxt: string | null; enableIdenticonGeneration: boolean; manifestJsonOverride: string; policies: Record; @@ -10163,6 +10166,7 @@ export type operations = { enableStatsForFederatedInstances?: boolean; enableServerMachineStats?: boolean; enableAchievements?: boolean; + robotsTxt?: string | null; enableIdenticonGeneration?: boolean; serverRules?: string[]; bannedEmailDomains?: string[]; @@ -21631,6 +21635,7 @@ export type operations = { }; emailNotificationTypes?: string[]; alsoKnownAs?: string[]; + defaultCW?: string | null; }; }; }; -- cgit v1.2.3-freya From c8f8a61a00d07802dc5056eae48144e49bce742c Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 1 Feb 2025 23:15:02 -0500 Subject: add MiUserProfile.defaultCWPriority property and API --- .../1738468079662-add_user_profile_default_cw_priority.js | 13 +++++++++++++ packages/backend/src/core/entities/UserEntityService.ts | 7 +++++-- packages/backend/src/models/UserProfile.ts | 9 ++++++++- packages/backend/src/models/json-schema/user.ts | 5 +++++ packages/backend/src/server/api/endpoints/i/update.ts | 8 ++++++++ packages/backend/src/types.ts | 2 ++ packages/misskey-js/src/autogen/types.ts | 4 ++++ 7 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 packages/backend/migration/1738468079662-add_user_profile_default_cw_priority.js (limited to 'packages/backend/src') diff --git a/packages/backend/migration/1738468079662-add_user_profile_default_cw_priority.js b/packages/backend/migration/1738468079662-add_user_profile_default_cw_priority.js new file mode 100644 index 0000000000..90de25e06f --- /dev/null +++ b/packages/backend/migration/1738468079662-add_user_profile_default_cw_priority.js @@ -0,0 +1,13 @@ +export class AddUserProfileDefaultCwPriority1738468079662 { + name = 'AddUserProfileDefaultCwPriority1738468079662' + + async up(queryRunner) { + await queryRunner.query(`CREATE TYPE "public"."user_profile_default_cw_priority_enum" AS ENUM ('default', 'parent', 'defaultParent', 'parentDefault')`); + await queryRunner.query(`ALTER TABLE "user_profile" ADD "default_cw_priority" "public"."user_profile_default_cw_priority_enum" NOT NULL DEFAULT 'parent'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "default_cw_priority"`); + await queryRunner.query(`DROP TYPE "public"."user_profile_default_cw_priority_enum"`); + } +} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 0ca784fa52..6ea2d6629a 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -49,11 +49,13 @@ import { IdService } from '@/core/IdService.js'; import type { AnnouncementService } from '@/core/AnnouncementService.js'; import type { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { isSystemAccount } from '@/misc/is-system-account.js'; import type { OnModuleInit } from '@nestjs/common'; import type { NoteEntityService } from './NoteEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; import type { PageEntityService } from './PageEntityService.js'; -import { isSystemAccount } from '@/misc/is-system-account.js'; + +/* eslint-disable @typescript-eslint/no-non-null-assertion */ const Ajv = _Ajv.default; const ajv = new Ajv(); @@ -669,7 +671,8 @@ export class UserEntityService implements OnModuleInit { achievements: profile!.achievements, loggedInDays: profile!.loggedInDates.length, policies: this.roleService.getUserPolicies(user.id), - defaultCW: profile?.defaultCW ?? null, + defaultCW: profile!.defaultCW, + defaultCWPriority: profile!.defaultCWPriority, } : {}), ...(opts.includeSecrets ? { diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 3c2362227e..449c2f370b 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -4,7 +4,7 @@ */ import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; -import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes } from '@/types.js'; +import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes, noteVisibilities, defaultCWPriorities } from '@/types.js'; import { id } from './util/id.js'; import { MiUser } from './User.js'; import { MiPage } from './Page.js'; @@ -296,6 +296,13 @@ export class MiUserProfile { }) public defaultCW: string | null; + @Column('enum', { + name: 'default_cw_priority', + enum: defaultCWPriorities, + default: 'parent', + }) + public defaultCWPriority: typeof defaultCWPriorities[number]; + //#region Denormalized fields @Index() @Column('varchar', { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index f6c7bd2151..93b031e9c5 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -756,6 +756,11 @@ export const packedMeDetailedOnlySchema = { type: 'string', nullable: true, optional: false, }, + defaultCWPriority: { + type: 'string', + enum: ['default', 'parent', 'defaultParent', 'parentDefault'], + nullable: false, optional: false, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index e487562687..e1552fed8a 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -250,6 +250,11 @@ export const paramDef = { items: { type: 'string' }, }, defaultCW: { type: 'string', nullable: true }, + defaultCWPriority: { + type: 'string', + enum: ['default', 'parent', 'defaultParent', 'parentDefault'], + nullable: false, + }, }, } as const; @@ -510,6 +515,9 @@ export default class extends Endpoint { // eslint- profileUpdates.defaultCW = defaultCW; } + if (ps.defaultCWPriority !== undefined) { + profileUpdates.defaultCWPriority = ps.defaultCWPriority; + } //#region emojis/tags diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 37bed27fb1..067481d9da 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -58,6 +58,8 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const; export const followingVisibilities = ['public', 'followers', 'private'] as const; export const followersVisibilities = ['public', 'followers', 'private'] as const; +export const defaultCWPriorities = ['default', 'parent', 'defaultParent', 'parentDefault'] as const; + /** * ユーザーがエクスポートできるものの種類 * diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 78dac5f08b..c7268ade6a 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -4218,6 +4218,8 @@ export type components = { lastUsed: string; }[]; defaultCW: string | null; + /** @enum {string} */ + defaultCWPriority: 'default' | 'parent' | 'defaultParent' | 'parentDefault'; }; UserDetailedNotMe: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly']; MeDetailed: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'] & components['schemas']['MeDetailedOnly']; @@ -21636,6 +21638,8 @@ export type operations = { emailNotificationTypes?: string[]; alsoKnownAs?: string[]; defaultCW?: string | null; + /** @enum {string} */ + defaultCWPriority?: 'default' | 'parent' | 'defaultParent' | 'parentDefault'; }; }; }; -- cgit v1.2.3-freya From 0d1c993dd21f21fd7100cc956ba729afb6c155ed Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 6 Feb 2025 12:57:44 -0500 Subject: restore support for local dev using a non-local `url` --- packages/backend/src/server/web/ClientServerService.ts | 7 ++----- packages/frontend-embed/vite.config.ts | 6 ------ packages/frontend/vite.config.ts | 6 ------ 3 files changed, 2 insertions(+), 17 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index cd1df1605c..3ed811e737 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -323,19 +323,16 @@ export class ClientServerService { done(); }); } else { - const configUrl = new URL(this.config.url); - const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); - const port = (process.env.VITE_PORT ?? '5173'); fastify.register(fastifyProxy, { - upstream: urlOriginWithoutPort + ':' + port, + upstream: `http://localhost:${port}`, prefix: '/vite', rewritePrefix: '/vite', }); const embedPort = (process.env.EMBED_VITE_PORT ?? '5174'); fastify.register(fastifyProxy, { - upstream: urlOriginWithoutPort + ':' + embedPort, + upstream: `http://localhost:${embedPort}`, prefix: '/embed_vite', rewritePrefix: '/embed_vite', }); diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index 13f272612c..1cd47b2754 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -2,8 +2,6 @@ import path from 'path'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; import { localesVersion } from '../../locales/version.js'; -import * as yaml from 'js-yaml'; -import { promises as fsp } from 'fs'; import locales from '../../locales/index.js'; import meta from '../../package.json'; @@ -11,9 +9,6 @@ import packageInfo from './package.json' with { type: 'json' }; import pluginJson5 from './vite.json5.js'; import { pluginReplaceIcons } from '../frontend/vite.replaceIcons.js'; -const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null; -const host = url ? (new URL(url)).hostname : undefined; - const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; /** @@ -69,7 +64,6 @@ export function getConfig(): UserConfig { base: '/embed_vite/', server: { - host, port: 5174, hmr: { // バックエンド経由での起動時、Viteは5174経由でアセットを参照していると思い込んでいるが実際は3000から配信される diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 51940486bd..5ff07d05c9 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -3,8 +3,6 @@ import pluginReplace from '@rollup/plugin-replace'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; import { localesVersion } from '../../locales/version.js'; -import * as yaml from 'js-yaml'; -import { promises as fsp } from 'fs'; import locales from '../../locales/index.js'; import meta from '../../package.json'; @@ -13,9 +11,6 @@ import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-modul import pluginJson5 from './vite.json5.js'; import { pluginReplaceIcons } from './vite.replaceIcons.js'; -const url = process.env.NODE_ENV === 'development' ? yaml.load(await fsp.readFile('../../.config/default.yml', 'utf-8')).url : null; -const host = url ? (new URL(url)).hostname : undefined; - const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue', '.wasm']; /** @@ -82,7 +77,6 @@ export function getConfig(): UserConfig { base: '/vite/', server: { - host, port: 5173, hmr: { // バックエンド経由での起動時、Viteは5173経由でアセットを参照していると思い込んでいるが実際は3000から配信される -- cgit v1.2.3-freya From 24528ed0c398077c74206abfdda56a771b09d54f Mon Sep 17 00:00:00 2001 From: Marie Date: Thu, 6 Feb 2025 22:54:47 +0100 Subject: fix: Allow FreeBSD to start Backend without any issues --- locales/index.d.ts | 2 +- packages/backend/package.json | 2 +- packages/backend/src/core/UserListService.ts | 2 +- packages/backend/src/misc/gen-identicon.ts | 4 +- packages/frontend-shared/package.json | 1 + pnpm-lock.yaml | 441 ++++++++++++++++++--------- 6 files changed, 305 insertions(+), 147 deletions(-) (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index 70eba52ea0..af5faefe1a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -11631,7 +11631,7 @@ export interface Locale extends ILocale { */ "robotsTxt": string; /** - * Adding entries here will override the default robots.txt packaged with Sharkey. Maximum 2048 characters. + * Adding entries here will override the default robots.txt packaged with Sharkey. */ "robotsTxtDescription": string; } diff --git a/packages/backend/package.json b/packages/backend/package.json index bace8c1f96..f01a05076a 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -81,7 +81,6 @@ "@fastify/view": "10.0.1", "@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/summaly": "5.1.0", - "@napi-rs/canvas": "0.1.56", "@nestjs/common": "10.4.7", "@nestjs/core": "10.4.7", "@nestjs/testing": "10.4.7", @@ -106,6 +105,7 @@ "body-parser": "1.20.3", "bullmq": "5.26.1", "cacheable-lookup": "7.0.0", + "canvas": "^3.1.0", "cbor": "9.0.2", "chalk": "5.3.0", "chalk-template": "1.1.0", diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 6333356fe9..11085fff8d 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -58,7 +58,7 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { } async onModuleInit() { - this.roleService = this.moduleRef.get(RoleService.name); + this.roleService = this.moduleRef.get(RoleService); } @bindThis diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts index 342e0f8602..f3c08cc76e 100644 --- a/packages/backend/src/misc/gen-identicon.ts +++ b/packages/backend/src/misc/gen-identicon.ts @@ -8,7 +8,7 @@ * https://en.wikipedia.org/wiki/Identicon */ -import { createCanvas } from '@napi-rs/canvas'; +import { createCanvas } from 'canvas'; import gen from 'random-seed'; const size = 128; // px @@ -100,5 +100,5 @@ export async function genIdenticon(seed: string): Promise { } } - return await canvas.encode('png'); + return await canvas.toBuffer('image/png'); } diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 8bf25da161..8a9a6b6948 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -34,6 +34,7 @@ ], "dependencies": { "misskey-js": "workspace:*", + "nodemon": "3.1.7", "vue": "3.5.12" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5580f1a7e..1268658f16 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,9 +128,6 @@ importers: '@misskey-dev/summaly': specifier: 5.1.0 version: 5.1.0 - '@napi-rs/canvas': - specifier: 0.1.56 - version: 0.1.56 '@nestjs/common': specifier: 10.4.7 version: 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -203,6 +200,9 @@ importers: cacheable-lookup: specifier: 7.0.0 version: 7.0.0 + canvas: + specifier: ^3.1.0 + version: 3.1.0 cbor: specifier: 9.0.2 version: 9.0.2 @@ -289,7 +289,7 @@ importers: version: 4.1.0 jsdom: specifier: 24.1.1 - version: 24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3) + version: 24.1.1(bufferutil@4.0.7)(canvas@3.1.0)(utf-8-validate@6.0.3) json5: specifier: 2.2.3 version: 2.2.3 @@ -1258,6 +1258,9 @@ importers: misskey-js: specifier: workspace:* version: link:../misskey-js + nodemon: + specifier: 3.1.7 + version: 3.1.7 vue: specifier: 3.5.12 version: 3.5.12(typescript@5.6.3) @@ -1370,7 +1373,7 @@ importers: version: 3.3.3 ts-jest: specifier: ^29.1.1 - version: 29.1.2(@babel/core@7.23.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.5))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6) + version: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6) typedoc: specifier: ^0.25.3 version: 0.25.13(typescript@5.1.6) @@ -2642,6 +2645,7 @@ packages: '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -2653,6 +2657,7 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@humanwhocodes/retry@0.3.0': resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} @@ -3010,64 +3015,6 @@ packages: resolution: {integrity: sha512-GXrJgakgJW3DWKueebkvtYgGKkxA7s0u5B0P5syJM5rvQUnrpLPigvci8Hukl7yEM+sU06l+er2Fgvx/gmiRgg==} engines: {node: '>=18'} - '@napi-rs/canvas-android-arm64@0.1.56': - resolution: {integrity: sha512-xBGqW2RZMAupkzar9t3gpbok9r524f3Wlk4PG2qnQdxbsiEND06OB8VxVtTcql6R02uJpXJGnyIhN02Te+GMVQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@napi-rs/canvas-darwin-arm64@0.1.56': - resolution: {integrity: sha512-Pvuz6Ib9YZTB5MlGL9WSu9a2asUC0DZ1zBHozDiBXr/6Zurs9l/ZH5NxFYTM829BpkdkO8kuI8b8Rz7ek30zzQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@napi-rs/canvas-darwin-x64@0.1.56': - resolution: {integrity: sha512-O393jWt7G6rg0X1ralbsbBeskSG0iwlkD7mEHhMLJxqRqe+eQn0/xnwhs9l6dUNFC+5dM8LOvfFca4o9Vs2Vww==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.56': - resolution: {integrity: sha512-30NFb5lrF3YEwAO5XuATxpWDSXaBAgaFVswPJ+hYcAUyE3IkPPIFRY4ijQEh4frcSBvrzFGGYdNSoC18oLLWaQ==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@napi-rs/canvas-linux-arm64-gnu@0.1.56': - resolution: {integrity: sha512-ODbWH9TLvba+39UxFwPn2Hm1ImALmWOZ0pEv5do/pz0439326Oz49hlfGot4KmkSBeKK81knWxRj9EXMSPwXPg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@napi-rs/canvas-linux-arm64-musl@0.1.56': - resolution: {integrity: sha512-zqE4nz8CWiJJ0q5By7q9CDPicNkc0oyErgavK3ZV279zJL7Aapd3cIqayT6ynECArg7GgBl2WYSvr5AaRFmYgg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@napi-rs/canvas-linux-x64-gnu@0.1.56': - resolution: {integrity: sha512-JTnGAtJBQMhfSpN8/rbMnf5oxuO/juUNa0n4LA0LlW0JS9UBpmsS2BwFNCakFqOeAPaqIM6sFFsK3M4hve+Esw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@napi-rs/canvas-linux-x64-musl@0.1.56': - resolution: {integrity: sha512-mpws7DhVDIj8ZKa/qcnUVLAm0fxD9RK5ojfNNSI9TOzn2E0f+GUXx8sGsCxDpMVMtN+mtyrMwRqH3F3rTUMWXw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@napi-rs/canvas-win32-x64-msvc@0.1.56': - resolution: {integrity: sha512-VKAAkgXF+lbFvRFawPOtkfV/P7ogAgWTu5FMCIiBn0Gc3vnkKFG2cLo/IHIJ7FuriToKEidkJGT88iAh7W7GDA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@napi-rs/canvas@0.1.56': - resolution: {integrity: sha512-SujSchzG6lLc/wT+Mwxam/w30Kk2sFTiU6bLFcidecKSmlhenAhGMQhZh2iGFfKoh2+8iit0jrt99n6TqReICQ==} - engines: {node: '>= 10'} - '@nestjs/common@10.4.7': resolution: {integrity: sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w==} peerDependencies: @@ -4448,6 +4395,7 @@ packages: '@types/form-data@2.5.0': resolution: {integrity: sha512-23/wYiuckYYtFpL+4RPWiWmRQH2BjFuqCUi2+N3amB1a1Drv+i/byTrGvlLwRVLFNAZbwpbQ7JvTK+VCAPMbcg==} + deprecated: This is a stub types definition. form-data provides its own type definitions, so you do not need this installed. '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -5385,6 +5333,9 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + blob-util@2.0.2: resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} @@ -5561,6 +5512,10 @@ packages: canvas-confetti@1.9.3: resolution: {integrity: sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==} + canvas@3.1.0: + resolution: {integrity: sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==} + engines: {node: ^18.12.0 || >= 20.9.0} + caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -5666,6 +5621,9 @@ packages: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -6111,6 +6069,10 @@ packages: deep-equal@2.2.0: resolution: {integrity: sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -6500,6 +6462,7 @@ packages: eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@9.14.0: @@ -6603,6 +6566,10 @@ packages: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6864,6 +6831,9 @@ packages: from@0.1.7: resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -6965,6 +6935,9 @@ packages: getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -6991,10 +6964,12 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -7289,6 +7264,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -7976,6 +7952,7 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} @@ -8403,6 +8380,9 @@ packages: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -8499,6 +8479,9 @@ packages: engines: {node: ^18 || >=20} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -9274,6 +9257,11 @@ packages: postgres-range@1.1.3: resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -9480,6 +9468,10 @@ packages: resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} engines: {node: '>= 0.8'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + rdf-canonize@3.4.0: resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==} engines: {node: '>=12'} @@ -9675,6 +9667,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup@4.26.0: @@ -9869,6 +9862,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-oauth2@5.1.0: resolution: {integrity: sha512-gWDa38Ccm4MwlG5U7AlcJxPv3lvr80dU7ARJWrGdgvOKyzSj1gr3GBPN1rABTedAYvC/LsGYoFuFxwDBPtGEbw==} @@ -10232,6 +10231,10 @@ packages: resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} engines: {node: '>=12'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -10298,6 +10301,13 @@ packages: os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true + tar-fs@2.1.2: + resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar-stream@3.1.6: resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==} @@ -11976,26 +11986,56 @@ snapshots: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 @@ -12006,36 +12046,78 @@ snapshots: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5)': dependencies: '@babel/core': 7.23.5 @@ -12496,7 +12578,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -12665,7 +12747,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -13132,45 +13214,6 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/canvas-android-arm64@0.1.56': - optional: true - - '@napi-rs/canvas-darwin-arm64@0.1.56': - optional: true - - '@napi-rs/canvas-darwin-x64@0.1.56': - optional: true - - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.56': - optional: true - - '@napi-rs/canvas-linux-arm64-gnu@0.1.56': - optional: true - - '@napi-rs/canvas-linux-arm64-musl@0.1.56': - optional: true - - '@napi-rs/canvas-linux-x64-gnu@0.1.56': - optional: true - - '@napi-rs/canvas-linux-x64-musl@0.1.56': - optional: true - - '@napi-rs/canvas-win32-x64-msvc@0.1.56': - optional: true - - '@napi-rs/canvas@0.1.56': - optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.56 - '@napi-rs/canvas-darwin-arm64': 0.1.56 - '@napi-rs/canvas-darwin-x64': 0.1.56 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.56 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.56 - '@napi-rs/canvas-linux-arm64-musl': 0.1.56 - '@napi-rs/canvas-linux-x64-gnu': 0.1.56 - '@napi-rs/canvas-linux-x64-musl': 0.1.56 - '@napi-rs/canvas-win32-x64-msvc': 0.1.56 - '@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: iterare: 1.2.1 @@ -15152,7 +15195,7 @@ snapshots: '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.5 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -15208,7 +15251,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.5 eslint: 8.57.0 optionalDependencies: typescript: 5.1.6 @@ -15234,7 +15277,7 @@ snapshots: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.5 eslint: 9.14.0 optionalDependencies: typescript: 5.6.3 @@ -15260,7 +15303,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.1.6) optionalDependencies: @@ -15272,7 +15315,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3) '@typescript-eslint/utils': 7.1.0(eslint@9.14.0)(typescript@5.6.3) - debug: 4.3.4 + debug: 4.3.7(supports-color@8.1.1) eslint: 9.14.0 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: @@ -15284,7 +15327,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.3) '@typescript-eslint/utils': 7.17.0(eslint@9.14.0)(typescript@5.6.3) - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) eslint: 9.14.0 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: @@ -15302,11 +15345,11 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.1.6) optionalDependencies: typescript: 5.1.6 @@ -15317,11 +15360,11 @@ snapshots: dependencies: '@typescript-eslint/types': 7.1.0 '@typescript-eslint/visitor-keys': 7.1.0 - debug: 4.3.4 + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 @@ -15332,11 +15375,11 @@ snapshots: dependencies: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 - semver: 7.6.0 + semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 @@ -15352,7 +15395,7 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) eslint: 8.57.0 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript @@ -15366,7 +15409,7 @@ snapshots: '@typescript-eslint/types': 7.1.0 '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3) eslint: 9.14.0 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript @@ -15413,7 +15456,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 @@ -15432,7 +15475,7 @@ snapshots: dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.5 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 @@ -15671,7 +15714,7 @@ snapshots: agent-base@7.1.0: dependencies: - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -15994,6 +16037,20 @@ snapshots: transitivePeerDependencies: - supports-color + babel-jest@29.7.0(@babel/core@7.24.7): + dependencies: + '@babel/core': 7.24.7 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.0 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.24.7) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + optional: true + babel-plugin-istanbul@6.1.1: dependencies: '@babel/helper-plugin-utils': 7.22.5 @@ -16027,12 +16084,36 @@ snapshots: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.5) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.5) + babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.7): + dependencies: + '@babel/core': 7.24.7 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) + optional: true + babel-preset-jest@29.6.3(@babel/core@7.23.5): dependencies: '@babel/core': 7.23.5 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.5) + babel-preset-jest@29.6.3(@babel/core@7.24.7): + dependencies: + '@babel/core': 7.24.7 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7) + optional: true + babel-walk@3.0.0-canary-5: dependencies: '@babel/types': 7.24.7 @@ -16071,6 +16152,12 @@ snapshots: binary-extensions@2.2.0: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.0 + blob-util@2.0.2: optional: true @@ -16167,7 +16254,6 @@ snapshots: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - optional: true buffer@6.0.3: dependencies: @@ -16300,6 +16386,11 @@ snapshots: canvas-confetti@1.9.3: {} + canvas@3.1.0: + dependencies: + node-addon-api: 7.1.0 + prebuild-install: 7.1.3 + caseless@0.12.0: optional: true @@ -16436,6 +16527,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@1.1.4: {} + chownr@2.0.0: {} chromatic@11.18.1: {} @@ -16869,9 +16962,13 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.3.5(supports-color@5.5.0): + debug@4.3.5: dependencies: ms: 2.1.2 + + debug@4.3.7(supports-color@5.5.0): + dependencies: + ms: 2.1.3 optionalDependencies: supports-color: 5.5.0 @@ -16946,6 +17043,8 @@ snapshots: which-collection: 1.0.1 which-typed-array: 1.1.11 + deep-extend@0.6.0: {} + deep-is@0.1.4: {} deepmerge@4.2.2: {} @@ -17511,7 +17610,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.5 doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -17705,6 +17804,8 @@ snapshots: exit@0.1.2: {} + expand-template@2.0.3: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -17874,7 +17975,7 @@ snapshots: fastify-plugin@2.3.4: dependencies: - semver: 7.6.0 + semver: 7.6.3 fastify-plugin@4.5.1: {} @@ -18077,6 +18178,8 @@ snapshots: from@0.1.7: {} + fs-constants@1.0.0: {} + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -18190,6 +18293,8 @@ snapshots: dependencies: assert-plus: 1.0.0 + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -18207,7 +18312,7 @@ snapshots: dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 - minimatch: 9.0.3 + minimatch: 9.0.4 minipass: 7.0.4 path-scurry: 1.10.1 @@ -18462,7 +18567,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18495,7 +18600,7 @@ snapshots: https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.0 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -18849,7 +18954,7 @@ snapshots: istanbul-lib-source-maps@5.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -19143,7 +19248,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.5.4 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -19277,7 +19382,7 @@ snapshots: - utf-8-validate optional: true - jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3): + jsdom@24.1.1(bufferutil@4.0.7)(canvas@3.1.0)(utf-8-validate@6.0.3): dependencies: cssstyle: 4.0.1 data-urls: 5.0.0 @@ -19300,6 +19405,8 @@ snapshots: whatwg-url: 14.0.0 ws: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) xml-name-validator: 5.0.0 + optionalDependencies: + canvas: 3.1.0 transitivePeerDependencies: - bufferutil - supports-color @@ -19603,7 +19710,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.6.0 + semver: 7.6.3 make-error@1.3.6: {} @@ -20094,6 +20201,8 @@ snapshots: minipass: 3.3.6 yallist: 4.0.0 + mkdirp-classic@0.5.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -20201,6 +20310,8 @@ snapshots: nanoid@5.0.8: {} + napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} ncp@2.0.0: {} @@ -20275,7 +20386,7 @@ snapshots: make-fetch-happen: 13.0.0 nopt: 7.2.0 proc-log: 4.2.0 - semver: 7.6.0 + semver: 7.6.3 tar: 6.2.1 which: 4.0.0 transitivePeerDependencies: @@ -20290,11 +20401,11 @@ snapshots: nodemon@3.1.7: dependencies: chokidar: 3.5.3 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.7(supports-color@5.5.0) ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 - semver: 7.6.0 + semver: 7.6.3 simple-update-notifier: 2.0.0 supports-color: 5.5.0 touch: 3.1.0 @@ -20325,7 +20436,7 @@ snapshots: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.13.1 - semver: 7.6.0 + semver: 7.6.3 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -20943,6 +21054,21 @@ snapshots: postgres-range@1.1.3: {} + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.62.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.2 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier@3.3.3: {} @@ -21167,6 +21293,13 @@ snapshots: iconv-lite: 0.6.3 unpipe: 1.0.0 + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + rdf-canonize@3.4.0: dependencies: setimmediate: 1.0.5 @@ -21674,11 +21807,19 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-oauth2@5.1.0: dependencies: '@hapi/hoek': 11.0.4 '@hapi/wreck': 18.0.1 - debug: 4.3.5(supports-color@5.5.0) + debug: 4.3.5 joi: 17.11.0 transitivePeerDependencies: - supports-color @@ -21689,7 +21830,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.5.4 + semver: 7.6.3 sinon@16.1.3: dependencies: @@ -22054,6 +22195,8 @@ snapshots: dependencies: min-indent: 1.0.1 + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} strip-literal@2.1.0: @@ -22115,6 +22258,21 @@ snapshots: systeminformation@5.23.5: {} + tar-fs@2.1.2: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.0 + tar-stream@3.1.6: dependencies: b4a: 1.6.4 @@ -22273,7 +22431,7 @@ snapshots: ts-dedent@2.2.0: {} - ts-jest@29.1.2(@babel/core@7.23.5)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.5))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6): + ts-jest@29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -22286,9 +22444,9 @@ snapshots: typescript: 5.1.6 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.23.5 + '@babel/core': 7.24.7 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.23.5) + babel-jest: 29.7.0(@babel/core@7.24.7) esbuild: 0.24.0 ts-map@1.0.3: {} @@ -22341,7 +22499,6 @@ snapshots: tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 - optional: true tweetnacl@0.14.5: {} -- cgit v1.2.3-freya From c889948f95ad9706403ae07072e5ce76002c73b5 Mon Sep 17 00:00:00 2001 From: Marie Date: Fri, 7 Feb 2025 06:00:49 +0100 Subject: feat: Add generation of keys to admin page --- locales/index.d.ts | 14 ++++++ packages/backend/src/server/api/EndpointsModule.ts | 4 ++ packages/backend/src/server/api/endpoints.ts | 2 + .../server/api/endpoints/admin/gen-vapid-keys.ts | 33 ++++++++++++++ packages/frontend/src/pages/admin/settings.vue | 19 ++++++++ packages/misskey-js/src/autogen/apiClientJSDoc.ts | 11 +++++ packages/misskey-js/src/autogen/endpoint.ts | 1 + packages/misskey-js/src/autogen/types.ts | 53 ++++++++++++++++++++++ sharkey-locales/en-US.yml | 5 ++ 9 files changed, 142 insertions(+) create mode 100644 packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index 4a46883e9f..b2612d641f 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -11668,6 +11668,20 @@ export interface Locale extends ILocale { */ "parentDefault": string; }; + /** + * Generate Keys + */ + "genKeys": string; + "_genKeysDialog": { + /** + * Are you sure that you want to generate new keys? + */ + "text": string; + /** + * Generate new keys + */ + "title": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index e319d6e0a4..3b72160597 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -101,6 +101,7 @@ import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js'; import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js'; import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js'; +import * as ep___admin_genVapidKeys from './endpoints/admin/gen-vapid-keys.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___announcements_show from './endpoints/announcements/show.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; @@ -508,6 +509,7 @@ const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/ const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default }; const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default }; const $admin_systemWebhook_test: Provider = { provide: 'ep:admin/system-webhook/test', useClass: ep___admin_systemWebhook_test.default }; +const $admin_genVapidKeys: Provider = { provide: 'ep:admin/gen-vapid-keys', useClass: ep___admin_genVapidKeys.default }; const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default }; const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default }; const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default }; @@ -919,6 +921,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_systemWebhook_show, $admin_systemWebhook_update, $admin_systemWebhook_test, + $admin_genVapidKeys, $announcements, $announcements_show, $antennas_create, @@ -1324,6 +1327,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_systemWebhook_show, $admin_systemWebhook_update, $admin_systemWebhook_test, + $admin_genVapidKeys, $announcements, $announcements_show, $antennas_create, diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index b4f36234f0..b6998ba581 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -108,6 +108,7 @@ import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js'; import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js'; import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js'; +import * as ep___admin_genVapidKeys from './endpoints/admin/gen-vapid-keys.js'; import * as ep___announcements from './endpoints/announcements.js'; import * as ep___announcements_show from './endpoints/announcements/show.js'; import * as ep___antennas_create from './endpoints/antennas/create.js'; @@ -513,6 +514,7 @@ const eps = [ ['admin/system-webhook/show', ep___admin_systemWebhook_show], ['admin/system-webhook/update', ep___admin_systemWebhook_update], ['admin/system-webhook/test', ep___admin_systemWebhook_test], + ['admin/gen-vapid-keys', ep___admin_genVapidKeys], ['announcements', ep___announcements], ['announcements/show', ep___announcements_show], ['antennas/create', ep___antennas_create], diff --git a/packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts b/packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts new file mode 100644 index 0000000000..5695866265 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: marie and sharkey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import webpush from 'web-push'; +const { generateVAPIDKeys } = webpush; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'write:admin:meta', +} as const; + +export const paramDef = {} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const keys = await generateVAPIDKeys(); + + return { public: keys.publicKey, private: keys.privateKey }; + }); + } +} diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index cd05b43be8..ed8b75f9e8 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -138,6 +138,8 @@ SPDX-License-Identifier: AGPL-3.0-only + + {{ i18n.ts.genKeys }} @@ -434,6 +436,23 @@ function chooseProxyAccount() { }); } +async function genKeys() { + if (serviceWorkerForm.savedState.swPrivateKey) { + os.confirm({ type: 'warning', title: i18n.ts._genKeysDialog.title, text: i18n.ts._genKeysDialog.text }).then(result => { + if (result.canceled) return; + os.apiWithDialog('admin/gen-vapid-keys', {}).then(res => { + serviceWorkerForm.state.swPublicKey = res.public; + serviceWorkerForm.state.swPrivateKey = res.private; + }); + }); + } else { + os.apiWithDialog('admin/gen-vapid-keys', {}).then(res => { + serviceWorkerForm.state.swPublicKey = res.public; + serviceWorkerForm.state.swPrivateKey = res.private; + }); + } +} + const headerTabs = computed(() => []); definePageMetadata(() => ({ diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index ccb513b7f9..34dbafe686 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -1060,6 +1060,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 66e7126460..2ba62ea3cd 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -700,6 +700,7 @@ export type Endpoints = { 'admin/system-webhook/show': { req: AdminSystemWebhookShowRequest; res: AdminSystemWebhookShowResponse }; 'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse }; 'admin/system-webhook/test': { req: AdminSystemWebhookTestRequest; res: EmptyResponse }; + 'admin/gen-vapid-keys': { req: EmptyRequest; res: EmptyResponse }; 'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse }; 'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse }; 'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse }; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index c7268ade6a..db709b2889 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -879,6 +879,15 @@ export type paths = { */ post: operations['admin___system-webhook___test']; }; + '/admin/gen-vapid-keys': { + /** + * admin/gen-vapid-keys + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + post: operations['admin___gen-vapid-keys']; + }; '/announcements': { /** * announcements @@ -11203,6 +11212,50 @@ export type operations = { }; }; }; + /** + * admin/gen-vapid-keys + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:meta* + */ + 'admin___gen-vapid-keys': { + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * announcements * @description No description provided. diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index cd3a44407a..068d13faab 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -447,3 +447,8 @@ _defaultCWPriority: parent: "Use Parent (use the inherited CW, ignoring the default CW)" defaultParent: "Use Default, then Parent (use the default CW, and append the inherited CW)" parentDefault: "Use Parent, then Default (use the inherited CW, and append the default CW)" + +genKeys: "Generate Keys" +_genKeysDialog: + text: "Are you sure that you want to generate new keys?" + title: "Generate new keys" -- cgit v1.2.3-freya From ef90b2e5a7d0912794e335c518d7ea495dd8affb Mon Sep 17 00:00:00 2001 From: Marie Date: Fri, 7 Feb 2025 15:09:53 +0100 Subject: chore: add quotes to RoleService --- packages/backend/src/core/UserListService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 11085fff8d..4f4d59a02c 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -58,7 +58,7 @@ export class UserListService implements OnApplicationShutdown, OnModuleInit { } async onModuleInit() { - this.roleService = this.moduleRef.get(RoleService); + this.roleService = this.moduleRef.get('RoleService'); } @bindThis -- cgit v1.2.3-freya From 92a67579140231c6414797171b97711cec8a2b3d Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 7 Feb 2025 11:59:41 -0500 Subject: allow environment override for `fulltextSearch.provider` property --- packages/backend/src/config.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index e980b40224..7d2641ec66 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -518,6 +518,7 @@ function applyEnvOverrides(config: Source) { ['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions'], ['host', 'port', 'username', 'pass', 'db', 'prefix'], ]); + _apply_top(['fulltextSearch', ['provider']]); _apply_top(['meilisearch', ['host', 'port', 'apikey', 'ssl', 'index', 'scope']]); _apply_top([['sentryForFrontend', 'sentryForBackend'], 'options', ['dsn', 'profileSampleRate', 'serverName', 'includeLocalVariables', 'proxy', 'keepAlive', 'caCerts']]); _apply_top(['sentryForBackend', 'enableNodeProfiling']); -- cgit v1.2.3-freya From d426e2a7ef5fae1f93769d2e53cfef248146f447 Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 7 Feb 2025 18:20:36 +0000 Subject: fix our ruby/group hack --- packages/backend/src/core/MfmService.ts | 17 +++++++++++++---- packages/backend/test/unit/MfmService.ts | 10 +++++----- 2 files changed, 18 insertions(+), 9 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 2095ebca98..99adad3018 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -179,7 +179,8 @@ export class MfmService { break; } - case 'ruby': { + // this is here only to catch upstream changes! + case 'ruby--': { let ruby: [string, string][] = []; for (const child of node.childNodes) { if (child.nodeName === 'rp') { @@ -310,16 +311,24 @@ export class MfmService { continue; } if (child.nodeName === 'rt') { - text += '$[ruby $[group '; + // the only case in which we don't need a `$[group ]` + // is when both sides of the ruby are simple words + const needsGroup = nonRtNodes.length > 1 || + /\s|\[|\]/.test(getText(nonRtNodes[0])) || + /\s|\[|\]/.test(getText(child)) ; + text += '$[ruby '; + if (needsGroup) text += '$[group '; appendChildren(nonRtNodes); - text += '] '; + if (needsGroup) text += ']'; + text += ' '; analyze(child); - text += '] '; + text += ']'; nonRtNodes = []; continue; } nonRtNodes.push(child); } + appendChildren(nonRtNodes); } break; } diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts index d263aafa0f..e54c006a4f 100644 --- a/packages/backend/test/unit/MfmService.ts +++ b/packages/backend/test/unit/MfmService.ts @@ -158,16 +158,16 @@ describe('MfmService', () => { }); test('ruby with spaces', () => { - assert.deepStrictEqual(mfmService.fromHtml('

a Miss key(ミスキー) b c

'), 'a Miss key(ミスキー) b c'); - assert.deepStrictEqual(mfmService.fromHtml('

a Misskey(ミス キー) b c

'), 'a Misskey(ミス キー) b c'); + assert.deepStrictEqual(mfmService.fromHtml('

a Miss key(ミスキー) b c

'), 'a $[ruby $[group Miss key] ミスキー] b c'); + assert.deepStrictEqual(mfmService.fromHtml('

a Misskey(ミス キー) b c

'), 'a $[ruby $[group Misskey] ミス キー] b c'); assert.deepStrictEqual( mfmService.fromHtml('

a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b

'), - 'a Misskey(ミスキー)Misskey(ミス キー)Misskey(ミスキー) b' + 'a $[ruby Misskey ミスキー]$[ruby $[group Misskey] ミス キー]$[ruby Misskey ミスキー] b' ); }); test('ruby with other inline tags', () => { - assert.deepStrictEqual(mfmService.fromHtml('

a Misskey(ミスキー) b c

'), 'a **Misskey**(ミスキー) b c'); + assert.deepStrictEqual(mfmService.fromHtml('

a Misskey(ミスキー) b c

'), 'a $[ruby **Misskey** ミスキー] b c'); }); test('mention', () => { @@ -181,7 +181,7 @@ describe('MfmService', () => { test('ruby', () => { assert.deepStrictEqual( mfmService.fromHtml(' some text (ignore me) and more'), - '$[ruby $[group some text ] ignore me] $[ruby $[group and ] more]' + '$[ruby $[group some text ] ignore me]$[ruby $[group and ] more]' ); }); }); -- cgit v1.2.3-freya From 09128ef399c8fc4f40c688617869bfbb4c3d526e Mon Sep 17 00:00:00 2001 From: dakkar Date: Fri, 7 Feb 2025 18:33:24 +0000 Subject: pick lints --- packages/backend/src/core/MfmService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index 99adad3018..6c2f673217 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -315,7 +315,7 @@ export class MfmService { // is when both sides of the ruby are simple words const needsGroup = nonRtNodes.length > 1 || /\s|\[|\]/.test(getText(nonRtNodes[0])) || - /\s|\[|\]/.test(getText(child)) ; + /\s|\[|\]/.test(getText(child)); text += '$[ruby '; if (needsGroup) text += '$[group '; appendChildren(nonRtNodes); -- cgit v1.2.3-freya From e96fe824bb4720e8b079792e6bdc212e4a1431cf Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 7 Feb 2025 13:52:58 -0500 Subject: print an error when booting with both MK_ONLY_SERVER and MK_ONLY_QUEUE set --- packages/backend/src/boot/master.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 44f2176e0a..76b115ba68 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -90,7 +90,7 @@ export async function masterMain() { maxBreadcrumbs: 0, // Set release version - release: "Sharkey@" + meta.version, + release: 'Sharkey@' + meta.version, ...config.sentryForBackend.options, }); @@ -100,6 +100,11 @@ export async function masterMain() { `mode: [disableClustering: ${envOption.disableClustering}, onlyServer: ${envOption.onlyServer}, onlyQueue: ${envOption.onlyQueue}]`, ); + if (envOption.onlyServer && envOption.onlyQueue) { + bootLogger.error('Configuration error: onlyServer and onlyQueue cannot both be set. To run both server and queue workers, disable / remove both options.'); + process.exit(1); + } + if (!envOption.disableClustering) { // clusterモジュール有効時 @@ -115,7 +120,7 @@ export async function masterMain() { } if (config.clusterLimit === 0) { - bootLogger.error("Configuration error: we can't create workers, `config.clusterLimit` is 0 (if you don't want to use clustering, set the environment variable `MK_DISABLE_CLUSTERING` to a non-empty value instead)", null, true); + bootLogger.error('Configuration error: we can\'t create workers, `config.clusterLimit` is 0 (if you don\'t want to use clustering, set the environment variable `MK_DISABLE_CLUSTERING` to a non-empty value instead)', null, true); process.exit(1); } -- cgit v1.2.3-freya From 2c2fb8a6920fe95d01bd556f0865cc6f0e178b87 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 02:39:19 -0500 Subject: export logger `Data` types for reference elsewhere --- packages/backend/src/logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts index 3b20ae5df0..eb2b081220 100644 --- a/packages/backend/src/logger.ts +++ b/packages/backend/src/logger.ts @@ -18,8 +18,8 @@ type Context = { type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; -type Data = DataElement | DataElement[]; -type DataElement = Record | Error | string | null; +export type Data = DataElement | DataElement[]; +export type DataElement = Record | Error | string | null; // eslint-disable-next-line import/no-default-export export default class Logger { -- cgit v1.2.3-freya From 16f483d273114d0ef86d7ac2c24d3d9386f9f486 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 02:46:38 -0500 Subject: cleanup Mastodon API (resolves #804 and #865, partially resolves #492) * Fix TS errors and warnings * Fix ESLint errors and warnings * Fix property typos in various places * Fix property data conversion * Add missing entity properties * Normalize logging and reduce spam * Check for missing request parameters * Allow mastodon API to work with local debugging * Safer error handling * Fix quote-post detection --- packages/backend/src/server/ServerModule.ts | 2 + .../api/mastodon/MastodonApiServerService.ts | 740 +++++++++++---------- .../src/server/api/mastodon/MastodonLogger.ts | 39 ++ .../backend/src/server/api/mastodon/converters.ts | 104 +-- .../src/server/api/mastodon/endpoints/account.ts | 282 +++----- .../src/server/api/mastodon/endpoints/auth.ts | 76 ++- .../src/server/api/mastodon/endpoints/filter.ts | 93 +-- .../src/server/api/mastodon/endpoints/meta.ts | 8 +- .../server/api/mastodon/endpoints/notifications.ts | 80 +-- .../src/server/api/mastodon/endpoints/search.ts | 133 ++-- .../src/server/api/mastodon/endpoints/status.ts | 494 ++++++++------ .../src/server/api/mastodon/endpoints/timeline.ts | 290 ++++---- .../src/server/api/mastodon/timelineArgs.ts | 47 ++ 13 files changed, 1258 insertions(+), 1130 deletions(-) create mode 100644 packages/backend/src/server/api/mastodon/MastodonLogger.ts create mode 100644 packages/backend/src/server/api/mastodon/timelineArgs.ts (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index 6b5156106a..9a0610b1b7 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -27,6 +27,7 @@ import { StreamingApiServerService } from './api/StreamingApiServerService.js'; import { OpenApiServerService } from './api/openapi/OpenApiServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; import { MastoConverters } from './api/mastodon/converters.js'; +import { MastodonLogger } from './api/mastodon/MastodonLogger.js'; import { FeedService } from './web/FeedService.js'; import { UrlPreviewService } from './web/UrlPreviewService.js'; import { ClientLoggerService } from './web/ClientLoggerService.js'; @@ -103,6 +104,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j MastodonApiServerService, OAuth2ProviderService, MastoConverters, + MastodonLogger, ], exports: [ ServerService, diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index b40e4cdaa4..18a974c234 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -3,50 +3,50 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable } from '@nestjs/common'; -import megalodon, { Entity, MegalodonInterface } from 'megalodon'; import querystring from 'querystring'; +import { megalodon, Entity, MegalodonInterface } from 'megalodon'; import { IsNull } from 'typeorm'; import multer from 'fastify-multer'; -import type { AccessTokensRepository, NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository, MiMeta } from '@/models/_.js'; +import { Inject, Injectable } from '@nestjs/common'; +import type { AccessTokensRepository, UserProfilesRepository, UsersRepository, MiMeta } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Config } from '@/config.js'; +import { DriveService } from '@/core/DriveService.js'; +import { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js'; +import { ApiAccountMastodonRoute } from '@/server/api/mastodon/endpoints/account.js'; +import { ApiSearchMastodonRoute } from '@/server/api/mastodon/endpoints/search.js'; +import { ApiFilterMastodonRoute } from '@/server/api/mastodon/endpoints/filter.js'; +import { ApiNotifyMastodonRoute } from '@/server/api/mastodon/endpoints/notifications.js'; +import { AuthMastodonRoute } from './endpoints/auth.js'; +import { toBoolean } from './timelineArgs.js'; import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; import { getInstance } from './endpoints/meta.js'; import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { DriveService } from '@/core/DriveService.js'; export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { const accessTokenArr = authorization?.split(' ') ?? [null]; const accessToken = accessTokenArr[accessTokenArr.length - 1]; - const generator = (megalodon as any).default; - const client = generator('misskey', BASE_URL, accessToken) as MegalodonInterface; - return client; + return megalodon('misskey', BASE_URL, accessToken); } @Injectable() export class MastodonApiServerService { constructor( @Inject(DI.meta) - private serverSettings: MiMeta, - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - @Inject(DI.notesRepository) - private notesRepository: NotesRepository, + private readonly serverSettings: MiMeta, + @Inject(DI.usersRepository) + private readonly usersRepository: UsersRepository, @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, - @Inject(DI.noteEditRepository) - private noteEditRepository: NoteEditRepository, + private readonly userProfilesRepository: UserProfilesRepository, @Inject(DI.accessTokensRepository) - private accessTokensRepository: AccessTokensRepository, - @Inject(DI.config) - private config: Config, - private userEntityService: UserEntityService, - private driveService: DriveService, - private mastoConverter: MastoConverters, + private readonly accessTokensRepository: AccessTokensRepository, + @Inject(DI.config) + private readonly config: Config, + private readonly driveService: DriveService, + private readonly mastoConverter: MastoConverters, + private readonly logger: MastodonLogger, ) { } @bindThis @@ -59,12 +59,12 @@ export class MastodonApiServerService { }, }); - fastify.addHook('onRequest', (request, reply, done) => { + fastify.addHook('onRequest', (_, reply, done) => { reply.header('Access-Control-Allow-Origin', '*'); done(); }); - fastify.addContentTypeParser('application/x-www-form-urlencoded', (request, payload, done) => { + fastify.addContentTypeParser('application/x-www-form-urlencoded', (_, payload, done) => { let body = ''; payload.on('data', (data) => { body += data; @@ -73,8 +73,8 @@ export class MastodonApiServerService { try { const parsed = querystring.parse(body); done(null, parsed); - } catch (e: any) { - done(e); + } catch (e) { + done(e as Error); } }); payload.on('error', done); @@ -83,20 +83,21 @@ export class MastodonApiServerService { fastify.register(multer.contentParser); fastify.get('/v1/custom_emojis', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const data = await client.getInstanceCustomEmojis(); reply.send(data.data); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/custom_emojis', data); + reply.code(401).send(data); } }); fastify.get('/v1/instance', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in @@ -113,116 +114,122 @@ export class MastodonApiServerService { }); const contact = admin == null ? null : await this.mastoConverter.convertAccount((await client.getAccount(admin.id)).data); reply.send(await getInstance(data.data, contact as Entity.Account, this.config, this.serverSettings)); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/instance', data); + reply.code(401).send(data); } }); fastify.get('/v1/announcements', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const data = await client.getInstanceAnnouncements(); reply.send(data.data.map((announcement) => convertAnnouncement(announcement))); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/announcements', data); + reply.code(401).send(data); } }); - fastify.post<{ Body: { id: string } }>('/v1/announcements/:id/dismiss', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post<{ Body: { id?: string } }>('/v1/announcements/:id/dismiss', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.dismissInstanceAnnouncement( - _request.body['id'], - ); + if (!_request.body.id) return reply.code(400).send({ error: 'Missing required payload "id"' }); + const data = await client.dismissInstanceAnnouncement(_request.body['id']); reply.send(data.data); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/announcements/${_request.body.id}/dismiss`, data); + reply.code(401).send(data); } - }, - ); + }); fastify.post('/v1/media', { preHandler: upload.single('file') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const multipartData = await _request.file; + const multipartData = await _request.file(); if (!multipartData) { reply.code(401).send({ error: 'No image' }); return; } const data = await client.uploadMedia(multipartData); reply.send(convertAttachment(data.data as Entity.Attachment)); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('POST /v1/media', data); + reply.code(401).send(data); } }); - fastify.post('/v2/media', { preHandler: upload.single('file') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post<{ Body: { description?: string; focus?: string }}>('/v2/media', { preHandler: upload.single('file') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const multipartData = await _request.file; + const multipartData = await _request.file(); if (!multipartData) { reply.code(401).send({ error: 'No image' }); return; } - const data = await client.uploadMedia(multipartData, _request.body!); + const data = await client.uploadMedia(multipartData, _request.body); reply.send(convertAttachment(data.data as Entity.Attachment)); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('POST /v2/media', data); + reply.code(401).send(data); } }); fastify.get('/v1/filters', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { const data = await client.getFilters(); reply.send(data.data.map((filter) => convertFilter(filter))); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/filters', data); + reply.code(401).send(data); } }); fastify.get('/v1/trends', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { const data = await client.getInstanceTrends(); reply.send(data.data); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/trends', data); + reply.code(401).send(data); } }); fastify.get('/v1/trends/tags', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { const data = await client.getInstanceTrends(); reply.send(data.data); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/trends/tags', data); + reply.code(401).send(data); } }); @@ -231,50 +238,72 @@ export class MastodonApiServerService { reply.send([]); }); - fastify.post('/v1/apps', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/apps', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const client = getClient(BASE_URL, ''); // we are using this here, because in private mode some info isnt // displayed without being logged in try { const data = await ApiAuthMastodon(_request, client); reply.send(data); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/apps', data); + reply.code(401).send(data); } }); fastify.get('/v1/preferences', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { const data = await client.getPreferences(); reply.send(data.data); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/preferences', data); + reply.code(401).send(data); } }); //#region Accounts - fastify.get('/v1/accounts/verify_credentials', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/accounts/verify_credentials', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.verifyCredentials()); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/accounts/verify_credentials', data); + reply.code(401).send(data); } }); - fastify.patch('/v1/accounts/update_credentials', { preHandler: upload.any() }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.patch<{ + Body: { + discoverable?: string, + bot?: string, + display_name?: string, + note?: string, + avatar?: string, + header?: string, + locked?: string, + source?: { + privacy?: string, + sensitive?: string, + language?: string, + }, + fields_attributes?: { + name: string, + value: string, + }[], + }, + }>('/v1/accounts/update_credentials', { preHandler: upload.any() }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt // displayed without being logged in @@ -332,512 +361,539 @@ export class MastodonApiServerService { (_request.body as any).fields_attributes = fields.filter((field: any) => field.name.trim().length > 0 && field.value.length > 0); } - const data = await client.updateCredentials(_request.body!); + const options = { + ..._request.body, + discoverable: toBoolean(_request.body.discoverable), + bot: toBoolean(_request.body.bot), + locked: toBoolean(_request.body.locked), + source: _request.body.source ? { + ..._request.body.source, + sensitive: toBoolean(_request.body.source.sensitive), + } : undefined, + }; + const data = await client.updateCredentials(options); reply.send(await this.mastoConverter.convertAccount(data.data)); - } catch (e: any) { - //console.error(e); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('PATCH /v1/accounts/update_credentials', data); + reply.code(401).send(data); } }); - fastify.get('/v1/accounts/lookup', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get<{ Querystring: { acct?: string }}>('/v1/accounts/lookup', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt - // displayed without being logged in + const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isn't displayed without being logged in try { - const data = await client.search((_request.query as any).acct, { type: 'accounts' }); + if (!_request.query.acct) return reply.code(400).send({ error: 'Missing required property "acct"' }); + const data = await client.search(_request.query.acct, { type: 'accounts' }); const profile = await this.userProfilesRepository.findOneBy({ userId: data.data.accounts[0].id }); - data.data.accounts[0].fields = profile?.fields.map(f => ({ ...f, verified_at: null })) || []; + data.data.accounts[0].fields = profile?.fields.map(f => ({ ...f, verified_at: null })) ?? []; reply.send(await this.mastoConverter.convertAccount(data.data.accounts[0])); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/accounts/lookup', data); + reply.code(401).send(data); } }); - fastify.get('/v1/accounts/relationships', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/accounts/relationships', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt - // displayed without being logged in - let users; + const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isn't displayed without being logged in try { - let ids = _request.query ? (_request.query as any)['id[]'] ?? (_request.query as any)['id'] : null; + let ids = _request.query['id[]'] ?? _request.query['id'] ?? []; if (typeof ids === 'string') { ids = [ids]; } - users = ids; - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); - reply.send(await account.getRelationships(users)); - } catch (e: any) { - /* console.error(e); */ - const data = e.response.data; - data.users = users; - console.error(data); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + reply.send(await account.getRelationships(ids)); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/accounts/relationships', data); reply.code(401).send(data); } }); - fastify.get<{ Params: { id: string } }>('/v1/accounts/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get<{ Params: { id?: string } }>('/v1/accounts/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const sharkId = _request.params.id; - const data = await client.getAccount(sharkId); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const data = await client.getAccount(_request.params.id); const account = await this.mastoConverter.convertAccount(data.data); reply.send(account); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/accounts/${_request.params.id}`, data); + reply.code(401).send(data); } }); - fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/statuses', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/accounts/:id/statuses', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.getStatuses()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/accounts/${_request.params.id}/statuses`, data); + reply.code(401).send(data); } }); - fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/featured_tags', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get<{ Params: { id?: string } }>('/v1/accounts/:id/featured_tags', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getFeaturedTags(); reply.send(data.data.map((tag) => convertFeaturedTag(tag))); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/accounts/${_request.params.id}/featured_tags`, data); + reply.code(401).send(data); } }); - fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/followers', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/accounts/:id/followers', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.getFollowers()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/accounts/${_request.params.id}/followers`, data); + reply.code(401).send(data); } }); - fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/following', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/accounts/:id/following', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.getFollowing()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/accounts/${_request.params.id}/following`, data); + reply.code(401).send(data); } }); - fastify.get<{ Params: { id: string } }>('/v1/accounts/:id/lists', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get<{ Params: { id?: string } }>('/v1/accounts/:id/lists', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getAccountLists(_request.params.id); reply.send(data.data.map((list) => convertList(list))); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/accounts/${_request.params.id}/lists`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/follow', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/accounts/:id/follow', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.addFollow()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/accounts/${_request.params.id}/follow`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/unfollow', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/accounts/:id/unfollow', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.rmFollow()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/accounts/${_request.params.id}/unfollow`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/block', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/accounts/:id/block', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.addBlock()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/accounts/${_request.params.id}/block`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/unblock', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/accounts/:id/unblock', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.rmBlock()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/accounts/${_request.params.id}/unblock`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/mute', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/accounts/:id/mute', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.addMute()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/accounts/${_request.params.id}/mute`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/accounts/:id/unmute', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/accounts/:id/unmute', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.rmMute()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/accounts/${_request.params.id}/unmute`, data); + reply.code(401).send(data); } }); fastify.get('/v1/followed_tags', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const data = await client.getFollowedTags(); reply.send(data.data); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/followed_tags', data); + reply.code(401).send(data); } }); - fastify.get('/v1/bookmarks', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/bookmarks', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.getBookmarks()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/bookmarks', data); + reply.code(401).send(data); } }); - fastify.get('/v1/favourites', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/favourites', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.getFavourites()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/favourites', data); + reply.code(401).send(data); } }); - fastify.get('/v1/mutes', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/mutes', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.getMutes()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/mutes', data); + reply.code(401).send(data); } }); - fastify.get('/v1/blocks', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/blocks', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.getBlocks()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/blocks', data); + reply.code(401).send(data); } }); - fastify.get('/v1/follow_requests', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get<{ Querystring: { limit?: string }}>('/v1/follow_requests', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getFollowRequests( ((_request.query as any) || { limit: 20 }).limit ); + const limit = _request.query.limit ? parseInt(_request.query.limit) : 20; + const data = await client.getFollowRequests(limit); reply.send(await Promise.all(data.data.map(async (account) => await this.mastoConverter.convertAccount(account as Entity.Account)))); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/follow_requests', data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/follow_requests/:id/authorize', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/follow_requests/:id/authorize', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.acceptFollow()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/follow_requests/${_request.params.id}/authorize`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/follow_requests/:id/reject', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/follow_requests/:id/reject', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, BASE_URL, this.mastoConverter); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const account = new ApiAccountMastodon(_request, client, this.mastoConverter); reply.send(await account.rejectFollow()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/follow_requests/${_request.params.id}/reject`, data); + reply.code(401).send(data); } }); //#endregion //#region Search - fastify.get('/v1/search', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/search', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.SearchV1()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/search', data); + reply.code(401).send(data); } }); - fastify.get('/v2/search', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v2/search', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.SearchV2()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v2/search', data); + reply.code(401).send(data); } }); - fastify.get('/v1/trends/statuses', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/trends/statuses', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.getStatusTrends()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/trends/statuses', data); + reply.code(401).send(data); } }); - fastify.get('/v2/suggestions', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v2/suggestions', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); reply.send(await search.getSuggestions()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v2/suggestions', data); + reply.code(401).send(data); } }); //#endregion //#region Notifications - fastify.get('/v1/notifications', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/notifications', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const notify = new ApiNotifyMastodon(_request, client); reply.send(await notify.getNotifications()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/notifications', data); + reply.code(401).send(data); } }); - fastify.get<{ Params: { id: string } }>('/v1/notification/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/notification/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const notify = new ApiNotifyMastodon(_request, client); reply.send(await notify.getNotification()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/notification/${_request.params.id}`, data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/notification/:id/dismiss', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/notification/:id/dismiss', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const notify = new ApiNotifyMastodon(_request, client); reply.send(await notify.rmNotification()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/notification/${_request.params.id}/dismiss`, data); + reply.code(401).send(data); } }); - fastify.post('/v1/notifications/clear', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/notifications/clear', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const notify = new ApiNotifyMastodon(_request, client); reply.send(await notify.rmNotifications()); - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('POST /v1/notifications/clear', data); + reply.code(401).send(data); } }); //#endregion //#region Filters - fastify.get<{ Params: { id: string } }>('/v1/filters/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.get('/v1/filters/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const filter = new ApiFilterMastodon(_request, client); - !_request.params.id ? reply.send(await filter.getFilters()) : reply.send(await filter.getFilter()); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + _request.params.id + ? reply.send(await filter.getFilter()) + : reply.send(await filter.getFilters()); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/filters/${_request.params.id}`, data); + reply.code(401).send(data); } }); - fastify.post('/v1/filters', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/filters', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { const filter = new ApiFilterMastodon(_request, client); reply.send(await filter.createFilter()); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('POST /v1/filters', data); + reply.code(401).send(data); } }); - fastify.post<{ Params: { id: string } }>('/v1/filters/:id', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.post('/v1/filters/:id', { preHandler: upload.single('none') }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const filter = new ApiFilterMastodon(_request, client); reply.send(await filter.updateFilter()); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/filters/${_request.params.id}`, data); + reply.code(401).send(data); } }); - fastify.delete<{ Params: { id: string } }>('/v1/filters/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.delete('/v1/filters/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const filter = new ApiFilterMastodon(_request, client); reply.send(await filter.rmFilter()); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`DELETE /v1/filters/${_request.params.id}`, data); + reply.code(401).send(data); } }); //#endregion //#region Timelines - const TLEndpoint = new ApiTimelineMastodon(fastify, this.config, this.mastoConverter); + const TLEndpoint = new ApiTimelineMastodon(fastify, this.mastoConverter, this.logger); // GET Endpoints TLEndpoint.getTL(); @@ -862,7 +918,7 @@ export class MastodonApiServerService { //#endregion //#region Status - const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverter); + const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverter, this.logger); // GET Endpoints NoteEndpoint.getStatus(); @@ -889,16 +945,32 @@ export class MastodonApiServerService { NoteEndpoint.votePoll(); // PUT Endpoint - fastify.put<{ Params: { id: string } }>('/v1/media/:id', { preHandler: upload.none() }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + fastify.put<{ + Params: { + id?: string, + }, + Body: { + file?: unknown, + description?: string, + focus?: string, + is_sensitive?: string, + }, + }>('/v1/media/:id', { preHandler: upload.none() }, async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.updateMedia(_request.params.id, _request.body!); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const options = { + ..._request.body, + is_sensitive: toBoolean(_request.body.is_sensitive), + }; + const data = await client.updateMedia(_request.params.id, options); reply.send(convertAttachment(data.data)); - } catch (e: any) { - /* console.error(e); */ - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`PUT /v1/media/${_request.params.id}`, data); + reply.code(401).send(data); } }); NoteEndpoint.updateStatus(); diff --git a/packages/backend/src/server/api/mastodon/MastodonLogger.ts b/packages/backend/src/server/api/mastodon/MastodonLogger.ts new file mode 100644 index 0000000000..bb844773c4 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/MastodonLogger.ts @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import Logger, { Data } from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; + +@Injectable() +export class MastodonLogger { + public readonly logger: Logger; + + constructor(loggerService: LoggerService) { + this.logger = loggerService.getLogger('masto-api'); + } + + public error(endpoint: string, error: Data): void { + this.logger.error(`Error in mastodon API endpoint ${endpoint}:`, error); + } +} + +export function getErrorData(error: unknown): Data { + if (error == null) return {}; + if (typeof(error) === 'string') return error; + if (typeof(error) === 'object') { + if ('response' in error) { + if (typeof(error.response) === 'object' && error.response) { + if ('data' in error.response) { + if (typeof(error.response.data) === 'object' && error.response.data) { + return error.response.data as Record; + } + } + } + } + return error as Record; + } + return { error }; +} diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 405701e826..3cb6ca61ce 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -9,18 +9,31 @@ import mfm from '@transfem-org/sfm-js'; import { DI } from '@/di-symbols.js'; import { MfmService } from '@/core/MfmService.js'; import type { Config } from '@/config.js'; -import type { IMentionedRemoteUsers } from '@/models/Note.js'; +import { IMentionedRemoteUsers, MiNote } from '@/models/Note.js'; import type { MiUser } from '@/models/User.js'; -import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { NoteEditRepository, UserProfilesRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { IdService } from '@/core/IdService.js'; +import type { Packed } from '@/misc/json-schema.js'; import { GetterService } from '../GetterService.js'; -export enum IdConvertType { - MastodonId, - SharkeyId, +// Missing from Megalodon apparently +// https://docs.joinmastodon.org/entities/StatusEdit/ +export interface StatusEdit { + content: string; + spoiler_text: string; + sensitive: boolean; + created_at: string; + account: MastodonEntity.Account; + poll?: { + options: { + title: string; + }[] + }, + media_attachments: MastodonEntity.Attachment[], + emojis: MastodonEntity.Emoji[], } export const escapeMFM = (text: string): string => text @@ -36,27 +49,24 @@ export const escapeMFM = (text: string): string => text export class MastoConverters { constructor( @Inject(DI.config) - private config: Config, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, + private readonly config: Config, @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, + private readonly userProfilesRepository: UserProfilesRepository, @Inject(DI.noteEditRepository) - private noteEditRepository: NoteEditRepository, - - private mfmService: MfmService, - private getterService: GetterService, - private customEmojiService: CustomEmojiService, - private idService: IdService, - private driveFileEntityService: DriveFileEntityService, - ) { - } + private readonly noteEditRepository: NoteEditRepository, + + private readonly mfmService: MfmService, + private readonly getterService: GetterService, + private readonly customEmojiService: CustomEmojiService, + private readonly idService: IdService, + private readonly driveFileEntityService: DriveFileEntityService, + ) {} - private encode(u: MiUser, m: IMentionedRemoteUsers): Entity.Mention { + private encode(u: MiUser, m: IMentionedRemoteUsers): MastodonEntity.Mention { let acct = u.username; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing let acctUrl = `https://${u.host || this.config.host}/@${u.username}`; let url: string | null = null; if (u.host) { @@ -89,7 +99,7 @@ export class MastoConverters { return 'unknown'; } - public encodeFile(f: any): Entity.Attachment { + public encodeFile(f: Packed<'DriveFile'>): MastodonEntity.Attachment { return { id: f.id, type: this.fileType(f.type), @@ -112,7 +122,7 @@ export class MastoConverters { }); } - private async encodeField(f: Entity.Field): Promise { + private async encodeField(f: Entity.Field): Promise { return { name: f.name, value: await this.mfmService.toMastoApiHtml(mfm.parse(f.value), [], true) ?? escapeMFM(f.value), @@ -120,7 +130,7 @@ export class MastoConverters { }; } - public async convertAccount(account: Entity.Account | MiUser) { + public async convertAccount(account: Entity.Account | MiUser): Promise { const user = await this.getUser(account.id); const profile = await this.userProfilesRepository.findOneBy({ userId: user.id }); const emojis = await this.customEmojiService.populateEmojis(user.emojis, user.host ? user.host : this.config.host); @@ -137,6 +147,7 @@ export class MastoConverters { }); const fqn = `${user.username}@${user.host ?? this.config.hostname}`; let acct = user.username; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing let acctUrl = `https://${user.host || this.config.host}/@${user.username}`; const acctUri = `https://${this.config.host}/users/${user.id}`; if (user.host) { @@ -166,19 +177,21 @@ export class MastoConverters { fields: Promise.all(profile?.fields.map(async p => this.encodeField(p)) ?? []), bot: user.isBot, discoverable: user.isExplorable, + noindex: user.noindex, + group: null, + suspended: user.isSuspended, + limited: user.isSilenced, }); } public async getEdits(id: string) { const note = await this.getterService.getNote(id); - if (!note) { - return {}; - } const noteUser = await this.getUser(note.userId).then(async (p) => await this.convertAccount(p)); const edits = await this.noteEditRepository.find({ where: { noteId: note.id }, order: { id: 'ASC' } }); - const history: Promise[] = []; + const history: Promise[] = []; + // TODO this looks wrong, according to mastodon docs let lastDate = this.idService.parse(note.id).date; for (const edit of edits) { const files = this.driveFileEntityService.packManyByIds(edit.fileIds); @@ -187,9 +200,8 @@ export class MastoConverters { content: this.mfmService.toMastoApiHtml(mfm.parse(edit.newText ?? ''), JSON.parse(note.mentionedRemoteUsers)).then(p => p ?? ''), created_at: lastDate.toISOString(), emojis: [], - sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false), + sensitive: edit.cw != null && edit.cw.length > 0, spoiler_text: edit.cw ?? '', - poll: null, media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : []), }; lastDate = edit.updatedAt; @@ -199,12 +211,12 @@ export class MastoConverters { return await Promise.all(history); } - private async convertReblog(status: Entity.Status | null): Promise { + private async convertReblog(status: Entity.Status | null): Promise { if (!status) return null; return await this.convertStatus(status); } - public async convertStatus(status: Entity.Status) { + public async convertStatus(status: Entity.Status): Promise { const convertedAccount = this.convertAccount(status.account); const note = await this.getterService.getNote(status.id); const noteUser = await this.getUser(status.account.id); @@ -235,18 +247,22 @@ export class MastoConverters { } as Entity.Tag; }); - const isQuote = note.renoteId && note.text ? true : false; + // This must mirror the usual isQuote / isPureRenote logic used elsewhere. + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const isQuote = note.renoteId && (note.text || note.cw || note.fileIds.length > 0 || note.hasPoll || note.replyId); - const renote = note.renoteId ? this.getterService.getNote(note.renoteId) : null; + const renote: Promise | null = note.renoteId ? this.getterService.getNote(note.renoteId) : null; const quoteUri = Promise.resolve(renote).then(renote => { if (!renote || !isQuote) return null; return renote.url ?? renote.uri ?? `${this.config.url}/notes/${renote.id}`; }); - const content = note.text !== null - ? quoteUri.then(quoteUri => this.mfmService.toMastoApiHtml(mfm.parse(note.text!), JSON.parse(note.mentionedRemoteUsers), false, quoteUri)) - .then(p => p ?? escapeMFM(note.text!)) + const text = note.text; + const content = text !== null + ? quoteUri + .then(quoteUri => this.mfmService.toMastoApiHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers), false, quoteUri)) + .then(p => p ?? escapeMFM(text)) : ''; // noinspection ES6MissingAwait @@ -270,7 +286,7 @@ export class MastoConverters { favourited: status.favourited, muted: status.muted, sensitive: status.sensitive, - spoiler_text: note.cw ? note.cw : '', + spoiler_text: note.cw ?? '', visibility: status.visibility, media_attachments: status.media_attachments, mentions: mentions, @@ -279,21 +295,19 @@ export class MastoConverters { poll: status.poll ?? null, application: null, //FIXME language: null, //FIXME - pinned: false, + pinned: false, //FIXME reactions: status.emoji_reactions, emoji_reactions: status.emoji_reactions, - bookmarked: false, + bookmarked: false, //FIXME quote: isQuote ? await this.convertReblog(status.reblog) : null, - // optional chaining cannot be used, as it evaluates to undefined, not null - edited_at: note.updatedAt ? note.updatedAt.toISOString() : null, + edited_at: note.updatedAt?.toISOString() ?? null, }); } } -function simpleConvert(data: any) { +function simpleConvert(data: T): T { // copy the object to bypass weird pass by reference bugs - const result = Object.assign({}, data); - return result; + return Object.assign({}, data); } export function convertAccount(account: Entity.Account) { @@ -324,12 +338,14 @@ export function convertNotification(notification: Entity.Notification) { export function convertPoll(poll: Entity.Poll) { return simpleConvert(poll); } + export function convertReaction(reaction: Entity.Reaction) { if (reaction.accounts) { reaction.accounts = reaction.accounts.map(convertAccount); } return reaction; } + export function convertRelationship(relationship: Entity.Relationship) { return simpleConvert(relationship); } diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 6fcfb0019c..80b9e4001f 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -3,14 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { Injectable } from '@nestjs/common'; +import { parseTimelineArgs, TimelineArgs } from '@/server/api/mastodon/timelineArgs.js'; import { MastoConverters, convertRelationship } from '../converters.js'; -import { argsToBools, limitToInt } from './timeline.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; -import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { Config } from '@/config.js'; -import { Injectable } from '@nestjs/common'; const relationshipModel = { id: '', @@ -29,247 +26,152 @@ const relationshipModel = { note: '', }; +export interface ApiAccountMastodonRoute { + Params: { id?: string }, + Querystring: TimelineArgs & { acct?: string }, + Body: { notifications?: boolean } +} + @Injectable() export class ApiAccountMastodon { - private request: FastifyRequest; - private client: MegalodonInterface; - private BASE_URL: string; - - constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, private mastoconverter: MastoConverters) { - this.request = request; - this.client = client; - this.BASE_URL = BASE_URL; - } + constructor( + private readonly request: FastifyRequest, + private readonly client: MegalodonInterface, + private readonly mastoConverters: MastoConverters, + ) {} public async verifyCredentials() { - try { - const data = await this.client.verifyAccountCredentials(); - const acct = await this.mastoconverter.convertAccount(data.data); - const newAcct = Object.assign({}, acct, { - source: { - note: acct.note, - fields: acct.fields, - privacy: '', - sensitive: false, - language: '', - }, - }); - return newAcct; - } catch (e: any) { - /* console.error(e); - console.error(e.response.data); */ - return e.response; - } + const data = await this.client.verifyAccountCredentials(); + const acct = await this.mastoConverters.convertAccount(data.data); + return Object.assign({}, acct, { + source: { + note: acct.note, + fields: acct.fields, + privacy: '', + sensitive: false, + language: '', + }, + }); } public async lookup() { - try { - const data = await this.client.search((this.request.query as any).acct, { type: 'accounts' }); - return this.mastoconverter.convertAccount(data.data.accounts[0]); - } catch (e: any) { - /* console.error(e) - console.error(e.response.data); */ - return e.response; - } + if (!this.request.query.acct) throw new Error('Missing required property "acct"'); + const data = await this.client.search(this.request.query.acct, { type: 'accounts' }); + return this.mastoConverters.convertAccount(data.data.accounts[0]); } - public async getRelationships(users: [string]) { - try { - relationshipModel.id = users.toString() || '1'; - - if (!(users.length > 0)) { - return [relationshipModel]; - } + public async getRelationships(users: string[]) { + relationshipModel.id = users.toString() || '1'; - const reqIds = []; - for (let i = 0; i < users.length; i++) { - reqIds.push(users[i]); - } + if (!(users.length > 0)) { + return [relationshipModel]; + } - const data = await this.client.getRelationships(reqIds); - return data.data.map((relationship) => convertRelationship(relationship)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; + const reqIds = []; + for (let i = 0; i < users.length; i++) { + reqIds.push(users[i]); } + + const data = await this.client.getRelationships(reqIds); + return data.data.map((relationship) => convertRelationship(relationship)); } public async getStatuses() { - try { - const data = await this.client.getAccountStatuses((this.request.params as any).id, argsToBools(limitToInt(this.request.query as any))); - return await Promise.all(data.data.map(async (status) => await this.mastoconverter.convertStatus(status))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.getAccountStatuses(this.request.params.id, parseTimelineArgs(this.request.query)); + return await Promise.all(data.data.map(async (status) => await this.mastoConverters.convertStatus(status))); } public async getFollowers() { - try { - const data = await this.client.getAccountFollowers( - (this.request.params as any).id, - limitToInt(this.request.query as any), - ); - return await Promise.all(data.data.map(async (account) => await this.mastoconverter.convertAccount(account))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.getAccountFollowers( + this.request.params.id, + parseTimelineArgs(this.request.query), + ); + return await Promise.all(data.data.map(async (account) => await this.mastoConverters.convertAccount(account))); } public async getFollowing() { - try { - const data = await this.client.getAccountFollowing( - (this.request.params as any).id, - limitToInt(this.request.query as any), - ); - return await Promise.all(data.data.map(async (account) => await this.mastoconverter.convertAccount(account))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.getAccountFollowing( + this.request.params.id, + parseTimelineArgs(this.request.query), + ); + return await Promise.all(data.data.map(async (account) => await this.mastoConverters.convertAccount(account))); } public async addFollow() { - try { - const data = await this.client.followAccount( (this.request.params as any).id ); - const acct = convertRelationship(data.data); - acct.following = true; - return acct; - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.followAccount( this.request.params.id ); + const acct = convertRelationship(data.data); + acct.following = true; + return acct; } public async rmFollow() { - try { - const data = await this.client.unfollowAccount( (this.request.params as any).id ); - const acct = convertRelationship(data.data); - acct.following = false; - return acct; - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.unfollowAccount( this.request.params.id ); + const acct = convertRelationship(data.data); + acct.following = false; + return acct; } public async addBlock() { - try { - const data = await this.client.blockAccount( (this.request.params as any).id ); - return convertRelationship(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.blockAccount( this.request.params.id ); + return convertRelationship(data.data); } public async rmBlock() { - try { - const data = await this.client.unblockAccount( (this.request.params as any).id ); - return convertRelationship(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.unblockAccount( this.request.params.id ); + return convertRelationship(data.data); } public async addMute() { - try { - const data = await this.client.muteAccount( - (this.request.params as any).id, - this.request.body as any, - ); - return convertRelationship(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.muteAccount( + this.request.params.id, + this.request.body.notifications ?? true, + ); + return convertRelationship(data.data); } public async rmMute() { - try { - const data = await this.client.unmuteAccount( (this.request.params as any).id ); - return convertRelationship(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.unmuteAccount( this.request.params.id ); + return convertRelationship(data.data); } public async getBookmarks() { - try { - const data = await this.client.getBookmarks( limitToInt(this.request.query as any) ); - return data.data.map((status) => this.mastoconverter.convertStatus(status)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + const data = await this.client.getBookmarks(parseTimelineArgs(this.request.query)); + return data.data.map((status) => this.mastoConverters.convertStatus(status)); } public async getFavourites() { - try { - const data = await this.client.getFavourites( limitToInt(this.request.query as any) ); - return data.data.map((status) => this.mastoconverter.convertStatus(status)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + const data = await this.client.getFavourites(parseTimelineArgs(this.request.query)); + return data.data.map((status) => this.mastoConverters.convertStatus(status)); } public async getMutes() { - try { - const data = await this.client.getMutes( limitToInt(this.request.query as any) ); - return data.data.map((account) => this.mastoconverter.convertAccount(account)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + const data = await this.client.getMutes(parseTimelineArgs(this.request.query)); + return data.data.map((account) => this.mastoConverters.convertAccount(account)); } public async getBlocks() { - try { - const data = await this.client.getBlocks( limitToInt(this.request.query as any) ); - return data.data.map((account) => this.mastoconverter.convertAccount(account)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + const data = await this.client.getBlocks(parseTimelineArgs(this.request.query)); + return data.data.map((account) => this.mastoConverters.convertAccount(account)); } public async acceptFollow() { - try { - const data = await this.client.acceptFollowRequest( (this.request.params as any).id ); - return convertRelationship(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.acceptFollowRequest(this.request.params.id); + return convertRelationship(data.data); } public async rejectFollow() { - try { - const data = await this.client.rejectFollowRequest( (this.request.params as any).id ); - return convertRelationship(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.rejectFollowRequest(this.request.params.id); + return convertRelationship(data.data); } } diff --git a/packages/backend/src/server/api/mastodon/endpoints/auth.ts b/packages/backend/src/server/api/mastodon/endpoints/auth.ts index a447bdb1b7..b58cc902da 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/auth.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/auth.ts @@ -44,36 +44,54 @@ const writeScope = [ 'write:gallery-likes', ]; -export async function ApiAuthMastodon(request: FastifyRequest, client: MegalodonInterface) { - const body: any = request.body || request.query; - try { - let scope = body.scopes; - if (typeof scope === 'string') scope = scope.split(' ') || scope.split('+'); - const pushScope = new Set(); - for (const s of scope) { - if (s.match(/^read/)) for (const r of readScope) pushScope.add(r); - if (s.match(/^write/)) for (const r of writeScope) pushScope.add(r); - } - const scopeArr = Array.from(pushScope); +export interface AuthPayload { + scopes?: string | string[], + redirect_uris?: string, + client_name?: string, + website?: string, +} + +// Not entirely right, but it gets TypeScript to work so *shrug* +export type AuthMastodonRoute = { Body?: AuthPayload, Querystring: AuthPayload }; - const red = body.redirect_uris; - const appData = await client.registerApp(body.client_name, { - scopes: scopeArr, - redirect_uris: red, - website: body.website, - }); - const returns = { - id: Math.floor(Math.random() * 100).toString(), - name: appData.name, - website: body.website, - redirect_uri: red, - client_id: Buffer.from(appData.url || '').toString('base64'), - client_secret: appData.clientSecret, - }; +export async function ApiAuthMastodon(request: FastifyRequest, client: MegalodonInterface) { + const body = request.body ?? request.query; + if (!body.scopes) throw new Error('Missing required payload "scopes"'); + if (!body.redirect_uris) throw new Error('Missing required payload "redirect_uris"'); + if (!body.client_name) throw new Error('Missing required payload "client_name"'); - return returns; - } catch (e: any) { - console.error(e); - return e.response.data; + let scope = body.scopes; + if (typeof scope === 'string') { + scope = scope.split(/[ +]/g); } + + const pushScope = new Set(); + for (const s of scope) { + if (s.match(/^read/)) { + for (const r of readScope) { + pushScope.add(r); + } + } + if (s.match(/^write/)) { + for (const r of writeScope) { + pushScope.add(r); + } + } + } + + const red = body.redirect_uris; + const appData = await client.registerApp(body.client_name, { + scopes: Array.from(pushScope), + redirect_uris: red, + website: body.website, + }); + + return { + id: Math.floor(Math.random() * 100).toString(), + name: appData.name, + website: body.website, + redirect_uri: red, + client_id: Buffer.from(appData.url || '').toString('base64'), // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing + client_secret: appData.clientSecret, + }; } diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index ce6809d230..382f0a8f1f 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts @@ -3,68 +3,73 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { toBoolean } from '@/server/api/mastodon/timelineArgs.js'; import { convertFilter } from '../converters.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; -export class ApiFilterMastodon { - private request: FastifyRequest; - private client: MegalodonInterface; - - constructor(request: FastifyRequest, client: MegalodonInterface) { - this.request = request; - this.client = client; +export interface ApiFilterMastodonRoute { + Params: { + id?: string, + }, + Body: { + phrase?: string, + context?: string[], + irreversible?: string, + whole_word?: string, + expires_in?: string, } +} + +export class ApiFilterMastodon { + constructor( + private readonly request: FastifyRequest, + private readonly client: MegalodonInterface, + ) {} public async getFilters() { - try { - const data = await this.client.getFilters(); - return data.data.map((filter) => convertFilter(filter)); - } catch (e: any) { - console.error(e); - return e.response.data; - } + const data = await this.client.getFilters(); + return data.data.map((filter) => convertFilter(filter)); } public async getFilter() { - try { - const data = await this.client.getFilter( (this.request.params as any).id ); - return convertFilter(data.data); - } catch (e: any) { - console.error(e); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.getFilter(this.request.params.id); + return convertFilter(data.data); } public async createFilter() { - try { - const body: any = this.request.body; - const data = await this.client.createFilter(body.pharse, body.context, body); - return convertFilter(data.data); - } catch (e: any) { - console.error(e); - return e.response.data; - } + if (!this.request.body.phrase) throw new Error('Missing required payload "phrase"'); + if (!this.request.body.context) throw new Error('Missing required payload "context"'); + const options = { + phrase: this.request.body.phrase, + context: this.request.body.context, + irreversible: toBoolean(this.request.body.irreversible), + whole_word: toBoolean(this.request.body.whole_word), + expires_in: this.request.body.expires_in, + }; + const data = await this.client.createFilter(this.request.body.phrase, this.request.body.context, options); + return convertFilter(data.data); } public async updateFilter() { - try { - const body: any = this.request.body; - const data = await this.client.updateFilter((this.request.params as any).id, body.pharse, body.context); - return convertFilter(data.data); - } catch (e: any) { - console.error(e); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + if (!this.request.body.phrase) throw new Error('Missing required payload "phrase"'); + if (!this.request.body.context) throw new Error('Missing required payload "context"'); + const options = { + phrase: this.request.body.phrase, + context: this.request.body.context, + irreversible: toBoolean(this.request.body.irreversible), + whole_word: toBoolean(this.request.body.whole_word), + expires_in: this.request.body.expires_in, + }; + const data = await this.client.updateFilter(this.request.params.id, this.request.body.phrase, this.request.body.context, options); + return convertFilter(data.data); } public async rmFilter() { - try { - const data = await this.client.deleteFilter( (this.request.params as any).id ); - return data.data; - } catch (e: any) { - console.error(e); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.deleteFilter(this.request.params.id); + return data.data; } } diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index c9833b85d7..48a56138cf 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -8,6 +8,7 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; import type { Config } from '@/config.js'; import type { MiMeta } from '@/models/Meta.js'; +/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ export async function getInstance( response: Entity.Instance, contact: Entity.Account, @@ -17,11 +18,8 @@ export async function getInstance( return { uri: config.url, title: meta.name || 'Sharkey', - short_description: - meta.description || 'This is a vanilla Sharkey Instance. It doesn\'t seem to have a description.', - description: - meta.description || - 'This is a vanilla Sharkey Instance. It doesn\'t seem to have a description.', + short_description: meta.description || 'This is a vanilla Sharkey Instance. It doesn\'t seem to have a description.', + description: meta.description || 'This is a vanilla Sharkey Instance. It doesn\'t seem to have a description.', email: response.email || '', version: `3.0.0 (compatible; Sharkey ${config.version})`, urls: response.urls, diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index 0eefb5894c..c228e17ddc 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -3,73 +3,53 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { parseTimelineArgs, TimelineArgs } from '@/server/api/mastodon/timelineArgs.js'; import { convertNotification } from '../converters.js'; -import type { MegalodonInterface, Entity } from 'megalodon'; +import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; -function toLimitToInt(q: any) { - if (q.limit) if (typeof q.limit === 'string') q.limit = parseInt(q.limit, 10); - return q; +export interface ApiNotifyMastodonRoute { + Params: { + id?: string, + }, + Querystring: TimelineArgs, } export class ApiNotifyMastodon { - private request: FastifyRequest; - private client: MegalodonInterface; - - constructor(request: FastifyRequest, client: MegalodonInterface) { - this.request = request; - this.client = client; - } + constructor( + private readonly request: FastifyRequest, + private readonly client: MegalodonInterface, + ) {} public async getNotifications() { - try { - const data = await this.client.getNotifications( toLimitToInt(this.request.query) ); - const notifs = data.data; - const processed = notifs.map((n: Entity.Notification) => { - const convertedn = convertNotification(n); - if (convertedn.type !== 'follow' && convertedn.type !== 'follow_request') { - if (convertedn.type === 'reaction') convertedn.type = 'favourite'; - return convertedn; - } else { - return convertedn; - } - }); - return processed; - } catch (e: any) { - console.error(e); - return e.response.data; - } + const data = await this.client.getNotifications(parseTimelineArgs(this.request.query)); + return data.data.map(n => { + const converted = convertNotification(n); + if (converted.type === 'reaction') { + converted.type = 'favourite'; + } + return converted; + }); } public async getNotification() { - try { - const data = await this.client.getNotification( (this.request.params as any).id ); - const notif = convertNotification(data.data); - if (notif.type !== 'follow' && notif.type !== 'follow_request' && notif.type === 'reaction') notif.type = 'favourite'; - return notif; - } catch (e: any) { - console.error(e); - return e.response.data; + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.getNotification(this.request.params.id); + const converted = convertNotification(data.data); + if (converted.type === 'reaction') { + converted.type = 'favourite'; } + return converted; } public async rmNotification() { - try { - const data = await this.client.dismissNotification( (this.request.params as any).id ); - return data.data; - } catch (e: any) { - console.error(e); - return e.response.data; - } + if (!this.request.params.id) throw new Error('Missing required parameter "id"'); + const data = await this.client.dismissNotification(this.request.params.id); + return data.data; } public async rmNotifications() { - try { - const data = await this.client.dismissNotifications(); - return data.data; - } catch (e: any) { - console.error(e); - return e.response.data; - } + const data = await this.client.dismissNotifications(); + return data.data; } } diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 946e796e2a..9435695532 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -4,87 +4,86 @@ */ import { MastoConverters } from '../converters.js'; -import { limitToInt } from './timeline.js'; +import { parseTimelineArgs, TimelineArgs } from '../timelineArgs.js'; +import Account = Entity.Account; +import Status = Entity.Status; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; -export class ApiSearchMastodon { - private request: FastifyRequest; - private client: MegalodonInterface; - private BASE_URL: string; - - constructor(request: FastifyRequest, client: MegalodonInterface, BASE_URL: string, private mastoConverter: MastoConverters) { - this.request = request; - this.client = client; - this.BASE_URL = BASE_URL; +export interface ApiSearchMastodonRoute { + Querystring: TimelineArgs & { + type?: 'accounts' | 'hashtags' | 'statuses'; + q?: string; } +} + +export class ApiSearchMastodon { + constructor( + private readonly request: FastifyRequest, + private readonly client: MegalodonInterface, + private readonly BASE_URL: string, + private readonly mastoConverter: MastoConverters, + ) {} public async SearchV1() { - try { - const query: any = limitToInt(this.request.query as any); - const type = query.type || ''; - const data = await this.client.search(query.q, { type: type, ...query }); - return data.data; - } catch (e: any) { - console.error(e); - return e.response.data; - } + if (!this.request.query.q) throw new Error('Missing required property "q"'); + const query = parseTimelineArgs(this.request.query); + const data = await this.client.search(this.request.query.q, { type: this.request.query.type, ...query }); + return data.data; } public async SearchV2() { - try { - const query: any = limitToInt(this.request.query as any); - const type = query.type; - const acct = !type || type === 'accounts' ? await this.client.search(query.q, { type: 'accounts', ...query }) : null; - const stat = !type || type === 'statuses' ? await this.client.search(query.q, { type: 'statuses', ...query }) : null; - const tags = !type || type === 'hashtags' ? await this.client.search(query.q, { type: 'hashtags', ...query }) : null; - const data = { - accounts: await Promise.all(acct?.data.accounts.map(async (account: any) => await this.mastoConverter.convertAccount(account)) ?? []), - statuses: await Promise.all(stat?.data.statuses.map(async (status: any) => await this.mastoConverter.convertStatus(status)) ?? []), - hashtags: tags?.data.hashtags ?? [], - }; - return data; - } catch (e: any) { - console.error(e); - return e.response.data; - } + if (!this.request.query.q) throw new Error('Missing required property "q"'); + const query = parseTimelineArgs(this.request.query); + const type = this.request.query.type; + const acct = !type || type === 'accounts' ? await this.client.search(this.request.query.q, { type: 'accounts', ...query }) : null; + const stat = !type || type === 'statuses' ? await this.client.search(this.request.query.q, { type: 'statuses', ...query }) : null; + const tags = !type || type === 'hashtags' ? await this.client.search(this.request.query.q, { type: 'hashtags', ...query }) : null; + return { + accounts: await Promise.all(acct?.data.accounts.map(async (account: Account) => await this.mastoConverter.convertAccount(account)) ?? []), + statuses: await Promise.all(stat?.data.statuses.map(async (status: Status) => await this.mastoConverter.convertStatus(status)) ?? []), + hashtags: tags?.data.hashtags ?? [], + }; } public async getStatusTrends() { - try { - const data = await fetch(`${this.BASE_URL}/api/notes/featured`, - { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}), - }) - .then(res => res.json()) - .then(data => data.map((status: any) => this.mastoConverter.convertStatus(status))); - return data; - } catch (e: any) { - console.error(e); - return []; - } + return await fetch(`${this.BASE_URL}/api/notes/featured`, + { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }) + .then(res => res.json()) + .then(data => data.map((status: Status) => this.mastoConverter.convertStatus(status))); } public async getSuggestions() { - try { - const data = await fetch(`${this.BASE_URL}/api/users`, - { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ i: this.request.headers.authorization?.replace('Bearer ', ''), limit: parseInt((this.request.query as any).limit) || 20, origin: 'local', sort: '+follower', state: 'alive' }), - }).then((res) => res.json()).then(data => data.map(((entry: any) => { return { source: 'global', account: entry }; }))); - return Promise.all(data.map(async (suggestion: any) => { suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); return suggestion; })); - } catch (e: any) { - console.error(e); - return []; - } + const data = await fetch(`${this.BASE_URL}/api/users`, + { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + i: this.request.headers.authorization?.replace('Bearer ', ''), + limit: parseTimelineArgs(this.request.query).limit ?? 20, + origin: 'local', + sort: '+follower', + state: 'alive', + }), + }) + .then((res) => res.json()) + .then((data: Account[]) => data.map((entry => ({ + source: 'global', + account: entry, + })))); + return Promise.all(data.map(async suggestion => { + suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); + return suggestion; + })); } } diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index ddc99639fa..1767439c2f 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -3,181 +3,214 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import querystring from 'querystring'; +import querystring, { ParsedUrlQueryInput } from 'querystring'; import { emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js'; -import { convertAttachment, convertPoll, convertStatusSource, MastoConverters } from '../converters.js'; +import { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js'; +import { parseTimelineArgs, TimelineArgs, toBoolean, toInt } from '@/server/api/mastodon/timelineArgs.js'; +import { convertAttachment, convertPoll, MastoConverters } from '../converters.js'; import { getClient } from '../MastodonApiServerService.js'; -import { limitToInt } from './timeline.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; -import type { Config } from '@/config.js'; -import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -function normalizeQuery(data: any) { - const str = querystring.stringify(data); +function normalizeQuery(data: Record) { + const str = querystring.stringify(data as ParsedUrlQueryInput); return querystring.parse(str); } export class ApiStatusMastodon { - private fastify: FastifyInstance; - private mastoconverter: MastoConverters; + constructor( + private readonly fastify: FastifyInstance, + private readonly mastoConverters: MastoConverters, + private readonly logger: MastodonLogger, + ) {} - constructor(fastify: FastifyInstance, mastoconverter: MastoConverters) { - this.fastify = fastify; - this.mastoconverter = mastoconverter; - } - - public async getStatus() { - this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getStatus() { + this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getStatus(_request.params.id); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(_request.is404 ? 404 : 401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/statuses/${_request.params.id}`, data); + reply.code(_request.is404 ? 404 : 401).send(data); } }); } - public async getStatusSource() { - this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/source', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getStatusSource() { + this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/source', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getStatusSource(_request.params.id); reply.send(data.data); - } catch (e: any) { - console.error(e); - reply.code(_request.is404 ? 404 : 401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/statuses/${_request.params.id}/source`, data); + reply.code(_request.is404 ? 404 : 401).send(data); } }); } - public async getContext() { - this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/context', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getContext() { + this.fastify.get<{ Params: { id?: string }, Querystring: TimelineArgs }>('/v1/statuses/:id/context', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const query: any = _request.query; try { - const data = await client.getStatusContext(_request.params.id, limitToInt(query)); - data.data.ancestors = await Promise.all(data.data.ancestors.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))); - data.data.descendants = await Promise.all(data.data.descendants.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status))); - reply.send(data.data); - } catch (e: any) { - console.error(e); - reply.code(_request.is404 ? 404 : 401).send(e.response.data); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { data } = await client.getStatusContext(_request.params.id, parseTimelineArgs(_request.query)); + const ancestors = await Promise.all(data.ancestors.map(async status => await this.mastoConverters.convertStatus(status))); + const descendants = await Promise.all(data.descendants.map(async status => await this.mastoConverters.convertStatus(status))); + reply.send({ ancestors, descendants }); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/statuses/${_request.params.id}/context`, data); + reply.code(_request.is404 ? 404 : 401).send(data); } }); } - public async getHistory() { - this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/history', async (_request, reply) => { + public getHistory() { + this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/history', async (_request, reply) => { try { - const edits = await this.mastoconverter.getEdits(_request.params.id); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const edits = await this.mastoConverters.getEdits(_request.params.id); reply.send(edits); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/statuses/${_request.params.id}/history`, data); + reply.code(401).send(data); } }); } - public async getReblogged() { - this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/reblogged_by', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getReblogged() { + this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/reblogged_by', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getStatusRebloggedBy(_request.params.id); - reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoconverter.convertAccount(account)))); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoConverters.convertAccount(account)))); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/statuses/${_request.params.id}/reblogged_by`, data); + reply.code(401).send(data); } }); } - public async getFavourites() { - this.fastify.get<{ Params: { id: string } }>('/v1/statuses/:id/favourited_by', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getFavourites() { + this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/favourited_by', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getStatusFavouritedBy(_request.params.id); - reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoconverter.convertAccount(account)))); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await Promise.all(data.data.map(async (account: Entity.Account) => await this.mastoConverters.convertAccount(account)))); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/statuses/${_request.params.id}/favourited_by`, data); + reply.code(401).send(data); } }); } - public async getMedia() { - this.fastify.get<{ Params: { id: string } }>('/v1/media/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getMedia() { + this.fastify.get<{ Params: { id?: string } }>('/v1/media/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getMedia(_request.params.id); reply.send(convertAttachment(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/media/${_request.params.id}`, data); + reply.code(401).send(data); } }); } - public async getPoll() { - this.fastify.get<{ Params: { id: string } }>('/v1/polls/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getPoll() { + this.fastify.get<{ Params: { id?: string } }>('/v1/polls/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getPoll(_request.params.id); reply.send(convertPoll(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/polls/${_request.params.id}`, data); + reply.code(401).send(data); } }); } - public async votePoll() { - this.fastify.post<{ Params: { id: string } }>('/v1/polls/:id/votes', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public votePoll() { + this.fastify.post<{ Params: { id?: string }, Body: { choices?: number[] } }>('/v1/polls/:id/votes', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const body: any = _request.body; try { - const data = await client.votePoll(_request.params.id, body.choices); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + if (!_request.body.choices) return reply.code(400).send({ error: 'Missing required payload "choices"' }); + const data = await client.votePoll(_request.params.id, _request.body.choices); reply.send(convertPoll(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/polls/${_request.params.id}/votes`, data); + reply.code(401).send(data); } }); } - public async postStatus() { - this.fastify.post('/v1/statuses', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public postStatus() { + this.fastify.post<{ + Body: { + media_ids?: string[], + poll?: { + options?: string[], + expires_in?: string, + multiple?: string, + hide_totals?: string, + }, + in_reply_to_id?: string, + sensitive?: string, + spoiler_text?: string, + visibility?: 'public' | 'unlisted' | 'private' | 'direct', + scheduled_at?: string, + language?: string, + quote_id?: string, + status?: string, + + // Broken clients + 'poll[options][]'?: string[], + 'media_ids[]'?: string[], + } + }>('/v1/statuses', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - let body: any = _request.body; + let body = _request.body; try { - if ( - (!body.poll && body['poll[options][]']) || - (!body.media_ids && body['media_ids[]']) + if ((!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]']) ) { body = normalizeQuery(body); } - const text = body.status ? body.status : ' '; + const text = body.status ??= ' '; const removed = text.replace(/@\S+/g, '').replace(/\s|/g, ''); const isDefaultEmoji = emojiRegexAtStartToEnd.test(removed); const isCustomEmoji = /^:[a-zA-Z0-9@_]+:$/.test(removed); @@ -189,226 +222,275 @@ export class ApiStatusMastodon { reply.send(a.data); } if (body.in_reply_to_id && removed === '/unreact') { - try { - const id = body.in_reply_to_id; - const post = await client.getStatus(id); - const react = post.data.emoji_reactions.filter((e: any) => e.me)[0].name; - const data = await client.deleteEmojiReaction(id, react); - reply.send(data.data); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); - } + const id = body.in_reply_to_id; + const post = await client.getStatus(id); + const react = post.data.emoji_reactions.filter(e => e.me)[0].name; + const data = await client.deleteEmojiReaction(id, react); + reply.send(data.data); } if (!body.media_ids) body.media_ids = undefined; if (body.media_ids && !body.media_ids.length) body.media_ids = undefined; - const { sensitive } = body; - body.sensitive = typeof sensitive === 'string' ? sensitive === 'true' : sensitive; - - if (body.poll) { - if ( - body.poll.expires_in != null && - typeof body.poll.expires_in === 'string' - ) body.poll.expires_in = parseInt(body.poll.expires_in); - if ( - body.poll.multiple != null && - typeof body.poll.multiple === 'string' - ) body.poll.multiple = body.poll.multiple === 'true'; - if ( - body.poll.hide_totals != null && - typeof body.poll.hide_totals === 'string' - ) body.poll.hide_totals = body.poll.hide_totals === 'true'; + if (body.poll && !body.poll.options) { + return reply.code(400).send({ error: 'Missing required payload "poll.options"' }); + } + if (body.poll && !body.poll.expires_in) { + return reply.code(400).send({ error: 'Missing required payload "poll.expires_in"' }); } - const data = await client.postStatus(text, body); - reply.send(await this.mastoconverter.convertStatus(data.data as Entity.Status)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + const options = { + ...body, + sensitive: toBoolean(body.sensitive), + poll: body.poll ? { + options: body.poll.options!, // eslint-disable-line @typescript-eslint/no-non-null-assertion + expires_in: toInt(body.poll.expires_in)!, // eslint-disable-line @typescript-eslint/no-non-null-assertion + multiple: toBoolean(body.poll.multiple), + hide_totals: toBoolean(body.poll.hide_totals), + } : undefined, + }; + + const data = await client.postStatus(text, options); + reply.send(await this.mastoConverters.convertStatus(data.data as Entity.Status)); + } catch (e) { + const data = getErrorData(e); + this.logger.error('POST /v1/statuses', data); + reply.code(401).send(data); } }); } - public async updateStatus() { - this.fastify.put<{ Params: { id: string } }>('/v1/statuses/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public updateStatus() { + this.fastify.put<{ + Params: { id: string }, + Body: { + status?: string, + spoiler_text?: string, + sensitive?: string, + media_ids?: string[], + poll?: { + options?: string[], + expires_in?: string, + multiple?: string, + hide_totals?: string, + }, + } + }>('/v1/statuses/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const body: any = _request.body; try { - if (!body.media_ids) body.media_ids = undefined; - if (body.media_ids && !body.media_ids.length) body.media_ids = undefined; - const data = await client.editStatus(_request.params.id, body); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(_request.is404 ? 404 : 401).send(e.response.data); + const body = _request.body; + + if (!body.media_ids || !body.media_ids.length) { + body.media_ids = undefined; + } + + const options = { + ...body, + sensitive: toBoolean(body.sensitive), + poll: body.poll ? { + options: body.poll.options, + expires_in: toInt(body.poll.expires_in), + multiple: toBoolean(body.poll.multiple), + hide_totals: toBoolean(body.poll.hide_totals), + } : undefined, + }; + + const data = await client.editStatus(_request.params.id, options); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}`, data); + reply.code(401).send(data); } }); } - public async addFavourite() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/favourite', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public addFavourite() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/favourite', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = (await client.createEmojiReaction(_request.params.id, '❤')) as any; - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const data = await client.createEmojiReaction(_request.params.id, '❤'); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/favorite`, data); + reply.code(401).send(data); } }); } - public async rmFavourite() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unfavourite', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public rmFavourite() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unfavourite', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.deleteEmojiReaction(_request.params.id, '❤'); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/statuses/${_request.params.id}/unfavorite`, data); + reply.code(401).send(data); } }); } - public async reblogStatus() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/reblog', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public reblogStatus() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/reblog', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.reblogStatus(_request.params.id); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/reblog`, data); + reply.code(401).send(data); } }); } - public async unreblogStatus() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unreblog', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public unreblogStatus() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unreblog', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.unreblogStatus(_request.params.id); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/unreblog`, data); + reply.code(401).send(data); } }); } - public async bookmarkStatus() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/bookmark', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public bookmarkStatus() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/bookmark', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.bookmarkStatus(_request.params.id); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/bookmark`, data); + reply.code(401).send(data); } }); } - public async unbookmarkStatus() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unbookmark', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public unbookmarkStatus() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unbookmark', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.unbookmarkStatus(_request.params.id); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/unbookmark`, data); + reply.code(401).send(data); } }); } - public async pinStatus() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/pin', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public pinStatus() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/pin', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.pinStatus(_request.params.id); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/pin`, data); + reply.code(401).send(data); } }); } - public async unpinStatus() { - this.fastify.post<{ Params: { id: string } }>('/v1/statuses/:id/unpin', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public unpinStatus() { + this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unpin', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.unpinStatus(_request.params.id); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/unpin`, data); + reply.code(401).send(data); } }); } - public async reactStatus() { - this.fastify.post<{ Params: { id: string, name: string } }>('/v1/statuses/:id/react/:name', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public reactStatus() { + this.fastify.post<{ Params: { id?: string, name?: string } }>('/v1/statuses/:id/react/:name', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + if (!_request.params.name) return reply.code(400).send({ error: 'Missing required parameter "name"' }); const data = await client.createEmojiReaction(_request.params.id, _request.params.name); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/react/${_request.params.name}`, data); + reply.code(401).send(data); } }); } - public async unreactStatus() { - this.fastify.post<{ Params: { id: string, name: string } }>('/v1/statuses/:id/unreact/:name', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public unreactStatus() { + this.fastify.post<{ Params: { id?: string, name?: string } }>('/v1/statuses/:id/unreact/:name', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + if (!_request.params.name) return reply.code(400).send({ error: 'Missing required parameter "name"' }); const data = await client.deleteEmojiReaction(_request.params.id, _request.params.name); - reply.send(await this.mastoconverter.convertStatus(data.data)); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + reply.send(await this.mastoConverters.convertStatus(data.data)); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/statuses/${_request.params.id}/unreact/${_request.params.name}`, data); + reply.code(401).send(data); } }); } - public async deleteStatus() { - this.fastify.delete<{ Params: { id: string } }>('/v1/statuses/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public deleteStatus() { + this.fastify.delete<{ Params: { id?: string } }>('/v1/statuses/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.deleteStatus(_request.params.id); reply.send(data.data); - } catch (e: any) { - console.error(e); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`DELETE /v1/statuses/${_request.params.id}`, data); + reply.code(401).send(data); } }); } diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index 3eb4898713..c298078e8a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -3,270 +3,238 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { ParsedUrlQuery } from 'querystring'; +import { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js'; import { convertConversation, convertList, MastoConverters } from '../converters.js'; import { getClient } from '../MastodonApiServerService.js'; +import { parseTimelineArgs, TimelineArgs, toBoolean } from '../timelineArgs.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; -import type { Config } from '@/config.js'; -import { NoteEditRepository, NotesRepository, UsersRepository } from '@/models/_.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; - -export function limitToInt(q: ParsedUrlQuery) { - const object: any = q; - if (q.limit) if (typeof q.limit === 'string') object.limit = parseInt(q.limit, 10); - if (q.offset) if (typeof q.offset === 'string') object.offset = parseInt(q.offset, 10); - return object; -} - -export function argsToBools(q: ParsedUrlQuery) { - // Values taken from https://docs.joinmastodon.org/client/intro/#boolean - const toBoolean = (value: string) => - !['0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].includes(value); - - // Keys taken from: - // - https://docs.joinmastodon.org/methods/accounts/#statuses - // - https://docs.joinmastodon.org/methods/timelines/#public - // - https://docs.joinmastodon.org/methods/timelines/#tag - const object: any = q; - if (q.only_media) if (typeof q.only_media === 'string') object.only_media = toBoolean(q.only_media); - if (q.exclude_replies) if (typeof q.exclude_replies === 'string') object.exclude_replies = toBoolean(q.exclude_replies); - if (q.exclude_reblogs) if (typeof q.exclude_reblogs === 'string') object.exclude_reblogs = toBoolean(q.exclude_reblogs); - if (q.pinned) if (typeof q.pinned === 'string') object.pinned = toBoolean(q.pinned); - if (q.local) if (typeof q.local === 'string') object.local = toBoolean(q.local); - return q; -} export class ApiTimelineMastodon { - private fastify: FastifyInstance; - - constructor(fastify: FastifyInstance, config: Config, private mastoconverter: MastoConverters) { - this.fastify = fastify; - } - - public async getTL() { - this.fastify.get('/v1/timelines/public', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + constructor( + private readonly fastify: FastifyInstance, + private readonly mastoConverters: MastoConverters, + private readonly logger: MastodonLogger, + ) {} + + public getTL() { + this.fastify.get<{ Querystring: TimelineArgs }>('/v1/timelines/public', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const query: any = _request.query; - const data = query.local === 'true' - ? await client.getLocalTimeline(argsToBools(limitToInt(query))) - : await client.getPublicTimeline(argsToBools(limitToInt(query))); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + const data = toBoolean(_request.query.local) + ? await client.getLocalTimeline(parseTimelineArgs(_request.query)) + : await client.getPublicTimeline(parseTimelineArgs(_request.query)); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/timelines/public', data); + reply.code(401).send(data); } }); } - public async getHomeTl() { - this.fastify.get('/v1/timelines/home', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getHomeTl() { + this.fastify.get<{ Querystring: TimelineArgs }>('/v1/timelines/home', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const query: any = _request.query; - const data = await client.getHomeTimeline(limitToInt(query)); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + const data = await client.getHomeTimeline(parseTimelineArgs(_request.query)); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/timelines/home', data); + reply.code(401).send(data); } }); } - public async getTagTl() { - this.fastify.get<{ Params: { hashtag: string } }>('/v1/timelines/tag/:hashtag', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getTagTl() { + this.fastify.get<{ Params: { hashtag?: string }, Querystring: TimelineArgs }>('/v1/timelines/tag/:hashtag', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const query: any = _request.query; - const params: any = _request.params; - const data = await client.getTagTimeline(params.hashtag, limitToInt(query)); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + if (!_request.params.hashtag) return reply.code(400).send({ error: 'Missing required parameter "hashtag"' }); + const data = await client.getTagTimeline(_request.params.hashtag, parseTimelineArgs(_request.query)); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/timelines/tag/${_request.params.hashtag}`, data); + reply.code(401).send(data); } }); } - public async getListTL() { - this.fastify.get<{ Params: { id: string } }>('/v1/timelines/list/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getListTL() { + this.fastify.get<{ Params: { id?: string }, Querystring: TimelineArgs }>('/v1/timelines/list/:id', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const query: any = _request.query; - const params: any = _request.params; - const data = await client.getListTimeline(params.id, limitToInt(query)); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoconverter.convertStatus(status)))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const data = await client.getListTimeline(_request.params.id, parseTimelineArgs(_request.query)); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/timelines/list/${_request.params.id}`, data); + reply.code(401).send(data); } }); } - public async getConversations() { - this.fastify.get('/v1/conversations', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + public getConversations() { + this.fastify.get<{ Querystring: TimelineArgs }>('/v1/conversations', async (_request, reply) => { + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const query: any = _request.query; - const data = await client.getConversationTimeline(limitToInt(query)); + const data = await client.getConversationTimeline(parseTimelineArgs(_request.query)); reply.send(data.data.map((conversation: Entity.Conversation) => convertConversation(conversation))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/conversations', data); + reply.code(401).send(data); } }); } - public async getList() { - this.fastify.get<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => { + public getList() { + this.fastify.get<{ Params: { id?: string } }>('/v1/lists/:id', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const params: any = _request.params; - const data = await client.getList(params.id); + const data = await client.getList(_request.params.id); reply.send(convertList(data.data)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/lists/${_request.params.id}`, data); + reply.code(401).send(data); } }); } - public async getLists() { + public getLists() { this.fastify.get('/v1/lists', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); const data = await client.getLists(); reply.send(data.data.map((list: Entity.List) => convertList(list))); - } catch (e: any) { - console.error(e); - return e.response.data; + } catch (e) { + const data = getErrorData(e); + this.logger.error('GET /v1/lists', data); + reply.code(401).send(data); } }); } - public async getListAccounts() { - this.fastify.get<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => { + public getListAccounts() { + this.fastify.get<{ Params: { id?: string }, Querystring: { limit?: number, max_id?: string, since_id?: string } }>('/v1/lists/:id/accounts', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const params: any = _request.params; - const query: any = _request.query; - const data = await client.getAccountsInList(params.id, query); - reply.send(data.data.map((account: Entity.Account) => this.mastoconverter.convertAccount(account))); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + const data = await client.getAccountsInList(_request.params.id, _request.query); + reply.send(data.data.map((account: Entity.Account) => this.mastoConverters.convertAccount(account))); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`GET /v1/lists/${_request.params.id}/accounts`, data); + reply.code(401).send(data); } }); } - public async addListAccount() { - this.fastify.post<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => { + public addListAccount() { + this.fastify.post<{ Params: { id?: string }, Querystring: { accounts_id?: string[] } }>('/v1/lists/:id/accounts', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + if (!_request.query.accounts_id) return reply.code(400).send({ error: 'Missing required property "accounts_id"' }); + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const params: any = _request.params; - const query: any = _request.query; - const data = await client.addAccountsToList(params.id, query.accounts_id); + const data = await client.addAccountsToList(_request.params.id, _request.query.accounts_id); reply.send(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`POST /v1/lists/${_request.params.id}/accounts`, data); + reply.code(401).send(data); } }); } - public async rmListAccount() { - this.fastify.delete<{ Params: { id: string } }>('/v1/lists/:id/accounts', async (_request, reply) => { + public rmListAccount() { + this.fastify.delete<{ Params: { id?: string }, Querystring: { accounts_id?: string[] } }>('/v1/lists/:id/accounts', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + if (!_request.query.accounts_id) return reply.code(400).send({ error: 'Missing required property "accounts_id"' }); + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const params: any = _request.params; - const query: any = _request.query; - const data = await client.deleteAccountsFromList(params.id, query.accounts_id); + const data = await client.deleteAccountsFromList(_request.params.id, _request.query.accounts_id); reply.send(data.data); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`DELETE /v1/lists/${_request.params.id}/accounts`, data); + reply.code(401).send(data); } }); } - public async createList() { - this.fastify.post('/v1/lists', async (_request, reply) => { + public createList() { + this.fastify.post<{ Body: { title?: string } }>('/v1/lists', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + if (!_request.body.title) return reply.code(400).send({ error: 'Missing required payload "title"' }); + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const body: any = _request.body; - const data = await client.createList(body.title); + const data = await client.createList(_request.body.title); reply.send(convertList(data.data)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error('POST /v1/lists', data); + reply.code(401).send(data); } }); } - public async updateList() { - this.fastify.put<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => { + public updateList() { + this.fastify.put<{ Params: { id?: string }, Body: { title?: string } }>('/v1/lists/:id', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + if (!_request.body.title) return reply.code(400).send({ error: 'Missing required payload "title"' }); + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const body: any = _request.body; - const params: any = _request.params; - const data = await client.updateList(params.id, body.title); + const data = await client.updateList(_request.params.id, _request.body.title); reply.send(convertList(data.data)); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`PUT /v1/lists/${_request.params.id}`, data); + reply.code(401).send(data); } }); } - public async deleteList() { - this.fastify.delete<{ Params: { id: string } }>('/v1/lists/:id', async (_request, reply) => { + public deleteList() { + this.fastify.delete<{ Params: { id?: string } }>('/v1/lists/:id', async (_request, reply) => { try { - const BASE_URL = `${_request.protocol}://${_request.hostname}`; + if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const BASE_URL = `${_request.protocol}://${_request.host}`; const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); - const params: any = _request.params; - const data = await client.deleteList(params.id); + await client.deleteList(_request.params.id); reply.send({}); - } catch (e: any) { - console.error(e); - console.error(e.response.data); - reply.code(401).send(e.response.data); + } catch (e) { + const data = getErrorData(e); + this.logger.error(`DELETE /v1/lists/${_request.params.id}`, data); + reply.code(401).send(data); } }); } diff --git a/packages/backend/src/server/api/mastodon/timelineArgs.ts b/packages/backend/src/server/api/mastodon/timelineArgs.ts new file mode 100644 index 0000000000..3fba8ec57a --- /dev/null +++ b/packages/backend/src/server/api/mastodon/timelineArgs.ts @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// Keys taken from: +// - https://docs.joinmastodon.org/methods/accounts/#statuses +// - https://docs.joinmastodon.org/methods/timelines/#public +// - https://docs.joinmastodon.org/methods/timelines/#tag +export interface TimelineArgs { + max_id?: string; + min_id?: string; + since_id?: string; + limit?: string; + offset?: string; + local?: string; + pinned?: string; + exclude_reblogs?: string; + exclude_replies?: string; + only_media?: string; +} + +// Values taken from https://docs.joinmastodon.org/client/intro/#boolean +export function toBoolean(value: string | undefined): boolean | undefined { + if (value === undefined) return undefined; + return !['0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].includes(value); +} + +export function toInt(value: string | undefined): number | undefined { + if (value === undefined) return undefined; + return parseInt(value); +} + +export function parseTimelineArgs(q: TimelineArgs) { + return { + max_id: q.max_id, + min_id: q.min_id, + since_id: q.since_id, + limit: typeof(q.limit) === 'string' ? parseInt(q.limit, 10) : undefined, + offset: typeof(q.offset) === 'string' ? parseInt(q.offset, 10) : undefined, + local: typeof(q.local) === 'string' ? toBoolean(q.local) : undefined, + pinned: typeof(q.pinned) === 'string' ? toBoolean(q.pinned) : undefined, + exclude_reblogs: typeof(q.exclude_reblogs) === 'string' ? toBoolean(q.exclude_reblogs) : undefined, + exclude_replies: typeof(q.exclude_replies) === 'string' ? toBoolean(q.exclude_replies) : undefined, + only_media: typeof(q.only_media) === 'string' ? toBoolean(q.only_media) : undefined, + }; +} -- cgit v1.2.3-freya From c38f04fe38415cdd6c946f37d31a79d4ba4d67ef Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 03:06:55 -0500 Subject: fix empty masto-api responses for several endpoints (resolves #721 and #707) --- packages/backend/src/server/api/mastodon/endpoints/account.ts | 8 ++++---- packages/backend/src/server/api/mastodon/endpoints/search.ts | 9 +++++---- packages/backend/src/server/api/mastodon/endpoints/timeline.ts | 3 ++- 3 files changed, 11 insertions(+), 9 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 80b9e4001f..3ba3667f90 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -145,22 +145,22 @@ export class ApiAccountMastodon { public async getBookmarks() { const data = await this.client.getBookmarks(parseTimelineArgs(this.request.query)); - return data.data.map((status) => this.mastoConverters.convertStatus(status)); + return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status))); } public async getFavourites() { const data = await this.client.getFavourites(parseTimelineArgs(this.request.query)); - return data.data.map((status) => this.mastoConverters.convertStatus(status)); + return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status))); } public async getMutes() { const data = await this.client.getMutes(parseTimelineArgs(this.request.query)); - return data.data.map((account) => this.mastoConverters.convertAccount(account)); + return Promise.all(data.data.map((account) => this.mastoConverters.convertAccount(account))); } public async getBlocks() { const data = await this.client.getBlocks(parseTimelineArgs(this.request.query)); - return data.data.map((account) => this.mastoConverters.convertAccount(account)); + return Promise.all(data.data.map((account) => this.mastoConverters.convertAccount(account))); } public async acceptFollow() { diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 9435695532..7c9aef303b 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -47,17 +47,18 @@ export class ApiSearchMastodon { } public async getStatusTrends() { - return await fetch(`${this.BASE_URL}/api/notes/featured`, + const data = await fetch(`${this.BASE_URL}/api/notes/featured`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, - body: JSON.stringify({}), + body: '{}', }) - .then(res => res.json()) - .then(data => data.map((status: Status) => this.mastoConverter.convertStatus(status))); + .then(res => res.json() as Promise) + .then(data => data.map(status => this.mastoConverter.convertStatus(status))); + return Promise.all(data); } public async getSuggestions() { diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index c298078e8a..a6a778721a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -142,7 +142,8 @@ export class ApiTimelineMastodon { const accessTokens = _request.headers.authorization; const client = getClient(BASE_URL, accessTokens); const data = await client.getAccountsInList(_request.params.id, _request.query); - reply.send(data.data.map((account: Entity.Account) => this.mastoConverters.convertAccount(account))); + const accounts = await Promise.all(data.data.map((account: Entity.Account) => this.mastoConverters.convertAccount(account))); + reply.send(accounts); } catch (e) { const data = getErrorData(e); this.logger.error(`GET /v1/lists/${_request.params.id}/accounts`, data); -- cgit v1.2.3-freya From bbfba495dffec7acd3c6258b4164ae170cdeeb10 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 03:14:29 -0500 Subject: remove un-needed relationshipModel static data --- .../src/server/api/mastodon/endpoints/account.ts | 32 ++-------------------- .../src/server/api/mastodon/endpoints/search.ts | 4 +-- 2 files changed, 4 insertions(+), 32 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 3ba3667f90..d24a62fb06 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -9,23 +9,6 @@ import { MastoConverters, convertRelationship } from '../converters.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; -const relationshipModel = { - id: '', - following: false, - followed_by: false, - delivery_following: false, - blocking: false, - blocked_by: false, - muting: false, - muting_notifications: false, - requested: false, - domain_blocking: false, - showing_reblogs: false, - endorsed: false, - notifying: false, - note: '', -}; - export interface ApiAccountMastodonRoute { Params: { id?: string }, Querystring: TimelineArgs & { acct?: string }, @@ -60,20 +43,9 @@ export class ApiAccountMastodon { return this.mastoConverters.convertAccount(data.data.accounts[0]); } - public async getRelationships(users: string[]) { - relationshipModel.id = users.toString() || '1'; - - if (!(users.length > 0)) { - return [relationshipModel]; - } - - const reqIds = []; - for (let i = 0; i < users.length; i++) { - reqIds.push(users[i]); - } - + public async getRelationships(reqIds: string[]) { const data = await this.client.getRelationships(reqIds); - return data.data.map((relationship) => convertRelationship(relationship)); + return data.data.map(relationship => convertRelationship(relationship)); } public async getStatuses() { diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index 7c9aef303b..ac5b836c1e 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -77,8 +77,8 @@ export class ApiSearchMastodon { state: 'alive', }), }) - .then((res) => res.json()) - .then((data: Account[]) => data.map((entry => ({ + .then(res => res.json() as Promise) + .then(data => data.map((entry => ({ source: 'global', account: entry, })))); -- cgit v1.2.3-freya From 5a1d1394d42d116ae5bcdbda3670a9047158d42b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 11:12:00 -0500 Subject: add `memo` and `isInstanceMuted` to UserRelation API entity --- .../backend/src/core/entities/UserEntityService.ts | 50 ++++++++++++++++++++++ .../src/server/api/endpoints/users/relation.ts | 16 +++++++ 2 files changed, 66 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 6ea2d6629a..ef0b5213c8 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -83,6 +83,8 @@ export type UserRelation = { isBlocked: boolean isMuted: boolean isRenoteMuted: boolean + isInstanceMuted?: boolean + memo?: string | null } @Injectable() @@ -182,6 +184,9 @@ export class UserEntityService implements OnModuleInit { isBlocked, isMuted, isRenoteMuted, + host, + memo, + mutedInstances, ] = await Promise.all([ this.followingsRepository.findOneBy({ followerId: me, @@ -229,8 +234,25 @@ export class UserEntityService implements OnModuleInit { muteeId: target, }, }), + this.usersRepository.createQueryBuilder('u') + .select('u.host') + .where({ id: target }) + .getRawOne<{ u_host: string }>() + .then(it => it?.u_host ?? null), + this.userMemosRepository.createQueryBuilder('m') + .select('m.memo') + .where({ userId: me, targetUserId: target }) + .getRawOne<{ m_memo: string | null }>() + .then(it => it?.m_memo ?? null), + this.userProfilesRepository.createQueryBuilder('p') + .select('p.mutedInstances') + .where({ userId: me }) + .getRawOne<{ p_mutedInstances: string[] }>() + .then(it => it?.p_mutedInstances ?? []), ]); + const isInstanceMuted = !!host && mutedInstances.includes(host); + return { id: target, following, @@ -242,6 +264,8 @@ export class UserEntityService implements OnModuleInit { isBlocked, isMuted, isRenoteMuted, + isInstanceMuted, + memo, }; } @@ -256,6 +280,9 @@ export class UserEntityService implements OnModuleInit { blockees, muters, renoteMuters, + hosts, + memos, + mutedInstances, ] = await Promise.all([ this.followingsRepository.findBy({ followerId: me }) .then(f => new Map(f.map(it => [it.followeeId, it]))), @@ -294,6 +321,27 @@ export class UserEntityService implements OnModuleInit { .where('m.muterId = :me', { me }) .getRawMany<{ m_muteeId: string }>() .then(it => it.map(it => it.m_muteeId)), + this.usersRepository.createQueryBuilder('u') + .select(['u.id', 'u.host']) + .where({ id: In(targets) } ) + .getRawMany<{ m_id: string, m_host: string }>() + .then(it => it.reduce((map, it) => { + map[it.m_id] = it.m_host; + return map; + }, {} as Record)), + this.userMemosRepository.createQueryBuilder('m') + .select(['m.targetUserId', 'm.memo']) + .where({ userId: me, targetUserId: In(targets) }) + .getRawMany<{ m_targetUserId: string, m_memo: string | null }>() + .then(it => it.reduce((map, it) => { + map[it.m_targetUserId] = it.m_memo; + return map; + }, {} as Record)), + this.userProfilesRepository.createQueryBuilder('p') + .select('p.mutedInstances') + .where({ userId: me }) + .getRawOne<{ p_mutedInstances: string[] }>() + .then(it => it?.p_mutedInstances ?? []), ]); return new Map( @@ -313,6 +361,8 @@ export class UserEntityService implements OnModuleInit { isBlocked: blockees.includes(target), isMuted: muters.includes(target), isRenoteMuted: renoteMuters.includes(target), + isInstanceMuted: mutedInstances.includes(hosts[target]), + memo: memos[target] ?? null, }, ]; }), diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index e659c46713..c7016d8d32 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -58,6 +58,14 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + isInstanceMuted: { + type: 'boolean', + optional: true, nullable: false, + }, + memo: { + type: 'string', + optional: true, nullable: true, + }, }, }, { @@ -103,6 +111,14 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + isInstanceMuted: { + type: 'boolean', + optional: true, nullable: false, + }, + memo: { + type: 'string', + optional: true, nullable: true, + }, }, }, }, -- cgit v1.2.3-freya From bd95e8a555e2fcb8b72ae4fbe496495af28fc8b3 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 11:14:21 -0500 Subject: fix relationship data for Mastodon API (resolves #714) --- .../backend/src/server/api/mastodon/converters.ts | 23 ++++++++++++++++++++-- .../src/server/api/mastodon/endpoints/account.ts | 10 +++++----- packages/megalodon/src/entities/relationship.ts | 1 + .../src/mastodon/entities/relationship.ts | 1 + packages/megalodon/src/misskey.ts | 4 ++-- packages/megalodon/src/misskey/api_client.ts | 11 ++++++----- .../megalodon/src/misskey/entities/relation.ts | 3 +++ 7 files changed, 39 insertions(+), 14 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 3cb6ca61ce..4aea91b4f2 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -339,6 +339,7 @@ export function convertPoll(poll: Entity.Poll) { return simpleConvert(poll); } +// noinspection JSUnusedGlobalSymbols export function convertReaction(reaction: Entity.Reaction) { if (reaction.accounts) { reaction.accounts = reaction.accounts.map(convertAccount); @@ -346,8 +347,25 @@ export function convertReaction(reaction: Entity.Reaction) { return reaction; } -export function convertRelationship(relationship: Entity.Relationship) { - return simpleConvert(relationship); +// Megalodon sometimes returns broken / stubbed relationship data +export function convertRelationship(relationship: Partial & { id: string }): MastodonEntity.Relationship { + return { + id: relationship.id, + following: relationship.following ?? false, + showing_reblogs: relationship.showing_reblogs ?? true, + notifying: relationship.notifying ?? true, + languages: [], + followed_by: relationship.followed_by ?? false, + blocking: relationship.blocking ?? false, + blocked_by: relationship.blocked_by ?? false, + muting: relationship.muting ?? false, + muting_notifications: relationship.muting_notifications ?? false, + requested: relationship.requested ?? false, + requested_by: relationship.requested_by ?? false, + domain_blocking: relationship.domain_blocking ?? false, + endorsed: relationship.endorsed ?? false, + note: relationship.note ?? '', + }; } export function convertStatus(status: Entity.Status) { @@ -361,6 +379,7 @@ export function convertStatus(status: Entity.Status) { return status; } +// noinspection JSUnusedGlobalSymbols export function convertStatusSource(status: Entity.StatusSource) { return simpleConvert(status); } diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index d24a62fb06..1481a48924 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -74,7 +74,7 @@ export class ApiAccountMastodon { public async addFollow() { if (!this.request.params.id) throw new Error('Missing required parameter "id"'); - const data = await this.client.followAccount( this.request.params.id ); + const data = await this.client.followAccount(this.request.params.id); const acct = convertRelationship(data.data); acct.following = true; return acct; @@ -82,7 +82,7 @@ export class ApiAccountMastodon { public async rmFollow() { if (!this.request.params.id) throw new Error('Missing required parameter "id"'); - const data = await this.client.unfollowAccount( this.request.params.id ); + const data = await this.client.unfollowAccount(this.request.params.id); const acct = convertRelationship(data.data); acct.following = false; return acct; @@ -90,13 +90,13 @@ export class ApiAccountMastodon { public async addBlock() { if (!this.request.params.id) throw new Error('Missing required parameter "id"'); - const data = await this.client.blockAccount( this.request.params.id ); + const data = await this.client.blockAccount(this.request.params.id); return convertRelationship(data.data); } public async rmBlock() { if (!this.request.params.id) throw new Error('Missing required parameter "id"'); - const data = await this.client.unblockAccount( this.request.params.id ); + const data = await this.client.unblockAccount(this.request.params.id); return convertRelationship(data.data); } @@ -111,7 +111,7 @@ export class ApiAccountMastodon { public async rmMute() { if (!this.request.params.id) throw new Error('Missing required parameter "id"'); - const data = await this.client.unmuteAccount( this.request.params.id ); + const data = await this.client.unmuteAccount(this.request.params.id); return convertRelationship(data.data); } diff --git a/packages/megalodon/src/entities/relationship.ts b/packages/megalodon/src/entities/relationship.ts index 283a1158c6..eb8daf9c7b 100644 --- a/packages/megalodon/src/entities/relationship.ts +++ b/packages/megalodon/src/entities/relationship.ts @@ -8,6 +8,7 @@ namespace Entity { muting: boolean muting_notifications: boolean requested: boolean + requested_by?: boolean domain_blocking: boolean showing_reblogs: boolean endorsed: boolean diff --git a/packages/megalodon/src/mastodon/entities/relationship.ts b/packages/megalodon/src/mastodon/entities/relationship.ts index 8e02df5769..f868a64063 100644 --- a/packages/megalodon/src/mastodon/entities/relationship.ts +++ b/packages/megalodon/src/mastodon/entities/relationship.ts @@ -8,6 +8,7 @@ namespace MastodonEntity { muting: boolean muting_notifications: boolean requested: boolean + requested_by: boolean domain_blocking: boolean showing_reblogs: boolean endorsed: boolean diff --git a/packages/megalodon/src/misskey.ts b/packages/megalodon/src/misskey.ts index 7d68d4eddf..7e493d8fb7 100644 --- a/packages/megalodon/src/misskey.ts +++ b/packages/megalodon/src/misskey.ts @@ -604,7 +604,7 @@ export default class Misskey implements MegalodonInterface { /** * POST /api/users/relation * - * @param id Array of account ID, for example `['1sdfag', 'ds12aa']`. + * @param ids Array of account ID, for example `['1sdfag', 'ds12aa']`. */ public async getRelationships(ids: Array): Promise>> { return Promise.all(ids.map(id => this.getRelationship(id))).then(results => ({ @@ -2232,7 +2232,7 @@ export default class Misskey implements MegalodonInterface { hashtags: [], }, })); - + if (result.status !== 200) { result.status = 200; result.statusText = "OK"; diff --git a/packages/megalodon/src/misskey/api_client.ts b/packages/megalodon/src/misskey/api_client.ts index 8996b802c8..a4352613eb 100644 --- a/packages/megalodon/src/misskey/api_client.ts +++ b/packages/megalodon/src/misskey/api_client.ts @@ -227,13 +227,14 @@ namespace MisskeyAPI { blocking: r.isBlocking, blocked_by: r.isBlocked, muting: r.isMuted, - muting_notifications: false, + muting_notifications: r.isMuted, requested: r.hasPendingFollowRequestFromYou, - domain_blocking: false, - showing_reblogs: true, + requested_by: r.hasPendingFollowRequestToYou, + domain_blocking: r.isInstanceMuted ?? false, + showing_reblogs: !r.isRenoteMuted, endorsed: false, - notifying: false, - note: null + notifying: !r.isMuted, + note: r.memo ?? '', } } diff --git a/packages/megalodon/src/misskey/entities/relation.ts b/packages/megalodon/src/misskey/entities/relation.ts index 07653b4865..a43dfbab4e 100644 --- a/packages/megalodon/src/misskey/entities/relation.ts +++ b/packages/megalodon/src/misskey/entities/relation.ts @@ -8,5 +8,8 @@ namespace MisskeyEntity { isBlocking: boolean isBlocked: boolean isMuted: boolean + isRenoteMuted: boolean + isInstanceMuted?: boolean + memo?: string | null } } -- cgit v1.2.3-freya From 3550ce27d50f160917b3e68829fd649e3e3f0571 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 11:38:20 -0500 Subject: hide restricted edit history from mastodon api (resolves #811) --- packages/backend/src/server/ServerModule.ts | 2 ++ .../api/mastodon/MastodonApiServerService.ts | 12 +++++-- .../src/server/api/mastodon/MastodonDataService.ts | 40 ++++++++++++++++++++++ .../backend/src/server/api/mastodon/converters.ts | 14 +++++--- .../src/server/api/mastodon/endpoints/status.ts | 7 ++-- 5 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 packages/backend/src/server/api/mastodon/MastodonDataService.ts (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index 9a0610b1b7..2c067afe88 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -28,6 +28,7 @@ import { OpenApiServerService } from './api/openapi/OpenApiServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; import { MastoConverters } from './api/mastodon/converters.js'; import { MastodonLogger } from './api/mastodon/MastodonLogger.js'; +import { MastodonDataService } from './api/mastodon/MastodonDataService.js'; import { FeedService } from './web/FeedService.js'; import { UrlPreviewService } from './web/UrlPreviewService.js'; import { ClientLoggerService } from './web/ClientLoggerService.js'; @@ -105,6 +106,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j OAuth2ProviderService, MastoConverters, MastodonLogger, + MastodonDataService, ], exports: [ ServerService, diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 18a974c234..40180394d3 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -18,6 +18,7 @@ import { ApiAccountMastodonRoute } from '@/server/api/mastodon/endpoints/account import { ApiSearchMastodonRoute } from '@/server/api/mastodon/endpoints/search.js'; import { ApiFilterMastodonRoute } from '@/server/api/mastodon/endpoints/filter.js'; import { ApiNotifyMastodonRoute } from '@/server/api/mastodon/endpoints/notifications.js'; +import { AuthenticateService } from '@/server/api/AuthenticateService.js'; import { AuthMastodonRoute } from './endpoints/auth.js'; import { toBoolean } from './timelineArgs.js'; import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; @@ -25,9 +26,13 @@ import { getInstance } from './endpoints/meta.js'; import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js'; import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; -export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { +export function getAccessToken(authorization: string | undefined): string | null { const accessTokenArr = authorization?.split(' ') ?? [null]; - const accessToken = accessTokenArr[accessTokenArr.length - 1]; + return accessTokenArr[accessTokenArr.length - 1]; +} + +export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface { + const accessToken = getAccessToken(authorization); return megalodon('misskey', BASE_URL, accessToken); } @@ -47,6 +52,7 @@ export class MastodonApiServerService { private readonly driveService: DriveService, private readonly mastoConverter: MastoConverters, private readonly logger: MastodonLogger, + private readonly authenticateService: AuthenticateService, ) { } @bindThis @@ -918,7 +924,7 @@ export class MastodonApiServerService { //#endregion //#region Status - const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverter, this.logger); + const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverter, this.logger, this.authenticateService); // GET Endpoints NoteEndpoint.getStatus(); diff --git a/packages/backend/src/server/api/mastodon/MastodonDataService.ts b/packages/backend/src/server/api/mastodon/MastodonDataService.ts new file mode 100644 index 0000000000..782f592ee5 --- /dev/null +++ b/packages/backend/src/server/api/mastodon/MastodonDataService.ts @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import { QueryService } from '@/core/QueryService.js'; +import type { MiNote, NotesRepository } from '@/models/_.js'; +import type { MiLocalUser } from '@/models/User.js'; + +/** + * Utility service for accessing data with Mastodon semantics + */ +@Injectable() +export class MastodonDataService { + constructor( + @Inject(DI.notesRepository) + private readonly notesRepository: NotesRepository, + + @Inject(QueryService) + private readonly queryService: QueryService, + ) {} + + public async getNote(noteId: string, me?: MiLocalUser | null): Promise { + // Root query: note + required dependencies + const query = this.notesRepository + .createQueryBuilder('note') + .where('note.id = :noteId', { noteId }) + .innerJoinAndSelect('note.user', 'user'); + + // Restrict visibility + this.queryService.generateVisibilityQuery(query, me); + if (me) { + this.queryService.generateBlockedUserQuery(query, me); + } + + return await query.getOne(); + } +} diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 4aea91b4f2..d7309b5a87 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -10,14 +10,15 @@ import { DI } from '@/di-symbols.js'; import { MfmService } from '@/core/MfmService.js'; import type { Config } from '@/config.js'; import { IMentionedRemoteUsers, MiNote } from '@/models/Note.js'; -import type { MiUser } from '@/models/User.js'; +import type { MiLocalUser, MiUser } from '@/models/User.js'; import type { NoteEditRepository, UserProfilesRepository } from '@/models/_.js'; import { awaitAll } from '@/misc/prelude/await-all.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { IdService } from '@/core/IdService.js'; import type { Packed } from '@/misc/json-schema.js'; -import { GetterService } from '../GetterService.js'; +import { MastodonDataService } from '@/server/api/mastodon/MastodonDataService.js'; +import { GetterService } from '@/server/api/GetterService.js'; // Missing from Megalodon apparently // https://docs.joinmastodon.org/entities/StatusEdit/ @@ -62,6 +63,7 @@ export class MastoConverters { private readonly customEmojiService: CustomEmojiService, private readonly idService: IdService, private readonly driveFileEntityService: DriveFileEntityService, + private readonly mastodonDataService: MastodonDataService, ) {} private encode(u: MiUser, m: IMentionedRemoteUsers): MastodonEntity.Mention { @@ -184,9 +186,11 @@ export class MastoConverters { }); } - public async getEdits(id: string) { - const note = await this.getterService.getNote(id); - + public async getEdits(id: string, me?: MiLocalUser | null) { + const note = await this.mastodonDataService.getNote(id, me); + if (!note) { + return []; + } const noteUser = await this.getUser(note.userId).then(async (p) => await this.convertAccount(p)); const edits = await this.noteEditRepository.find({ where: { noteId: note.id }, order: { id: 'ASC' } }); const history: Promise[] = []; diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 1767439c2f..99259d2542 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -7,8 +7,9 @@ import querystring, { ParsedUrlQueryInput } from 'querystring'; import { emojiRegexAtStartToEnd } from '@/misc/emoji-regex.js'; import { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js'; import { parseTimelineArgs, TimelineArgs, toBoolean, toInt } from '@/server/api/mastodon/timelineArgs.js'; +import { AuthenticateService } from '@/server/api/AuthenticateService.js'; import { convertAttachment, convertPoll, MastoConverters } from '../converters.js'; -import { getClient } from '../MastodonApiServerService.js'; +import { getAccessToken, getClient } from '../MastodonApiServerService.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; @@ -22,6 +23,7 @@ export class ApiStatusMastodon { private readonly fastify: FastifyInstance, private readonly mastoConverters: MastoConverters, private readonly logger: MastodonLogger, + private readonly authenticateService: AuthenticateService, ) {} public getStatus() { @@ -81,7 +83,8 @@ export class ApiStatusMastodon { this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id/history', async (_request, reply) => { try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const edits = await this.mastoConverters.getEdits(_request.params.id); + const [user] = await this.authenticateService.authenticate(getAccessToken(_request.headers.authorization)); + const edits = await this.mastoConverters.getEdits(_request.params.id, user); reply.send(edits); } catch (e) { const data = getErrorData(e); -- cgit v1.2.3-freya From 1e43162ba777d471c5406f614b72425e0800bf2f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 12:54:18 -0500 Subject: improve mastodon note conversion and use access checks in more places (resolves #509) --- .../api/mastodon/MastodonApiServerService.ts | 184 +++++++++------------ .../src/server/api/mastodon/MastodonDataService.ts | 44 +++++ .../backend/src/server/api/mastodon/converters.ts | 68 ++++---- .../src/server/api/mastodon/endpoints/account.ts | 8 +- .../server/api/mastodon/endpoints/notifications.ts | 13 +- .../src/server/api/mastodon/endpoints/search.ts | 16 +- .../src/server/api/mastodon/endpoints/status.ts | 89 ++++------ .../src/server/api/mastodon/endpoints/timeline.ts | 36 ++-- 8 files changed, 222 insertions(+), 236 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts index 40180394d3..69799bdade 100644 --- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts @@ -19,12 +19,13 @@ import { ApiSearchMastodonRoute } from '@/server/api/mastodon/endpoints/search.j import { ApiFilterMastodonRoute } from '@/server/api/mastodon/endpoints/filter.js'; import { ApiNotifyMastodonRoute } from '@/server/api/mastodon/endpoints/notifications.js'; import { AuthenticateService } from '@/server/api/AuthenticateService.js'; +import { MiLocalUser } from '@/models/User.js'; import { AuthMastodonRoute } from './endpoints/auth.js'; import { toBoolean } from './timelineArgs.js'; import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js'; import { getInstance } from './endpoints/meta.js'; import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js'; -import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; +import type { FastifyInstance, FastifyPluginOptions, FastifyRequest } from 'fastify'; export function getAccessToken(authorization: string | undefined): string | null { const accessTokenArr = authorization?.split(' ') ?? [null]; @@ -50,11 +51,29 @@ export class MastodonApiServerService { @Inject(DI.config) private readonly config: Config, private readonly driveService: DriveService, - private readonly mastoConverter: MastoConverters, + private readonly mastoConverters: MastoConverters, private readonly logger: MastodonLogger, private readonly authenticateService: AuthenticateService, ) { } + @bindThis + public async getAuthClient(request: FastifyRequest): Promise<{ client: MegalodonInterface, me: MiLocalUser | null }> { + const accessToken = getAccessToken(request.headers.authorization); + const [me] = await this.authenticateService.authenticate(accessToken); + + const baseUrl = `${request.protocol}://${request.host}`; + const client = megalodon('misskey', baseUrl, accessToken); + + return { client, me }; + } + + @bindThis + public async getAuthOnly(request: FastifyRequest): Promise { + const accessToken = getAccessToken(request.headers.authorization); + const [me] = await this.authenticateService.authenticate(accessToken); + return me; + } + @bindThis public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) { const upload = multer({ @@ -118,7 +137,7 @@ export class MastodonApiServerService { }, order: { id: 'ASC' }, }); - const contact = admin == null ? null : await this.mastoConverter.convertAccount((await client.getAccount(admin.id)).data); + const contact = admin == null ? null : await this.mastoConverters.convertAccount((await client.getAccount(admin.id)).data); reply.send(await getInstance(data.data, contact as Entity.Account, this.config, this.serverSettings)); } catch (e) { const data = getErrorData(e); @@ -275,12 +294,9 @@ export class MastodonApiServerService { //#region Accounts fastify.get('/v1/accounts/verify_credentials', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt - // displayed without being logged in try { - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.verifyCredentials()); } catch (e) { const data = getErrorData(e); @@ -378,7 +394,7 @@ export class MastodonApiServerService { } : undefined, }; const data = await client.updateCredentials(options); - reply.send(await this.mastoConverter.convertAccount(data.data)); + reply.send(await this.mastoConverters.convertAccount(data.data)); } catch (e) { const data = getErrorData(e); this.logger.error('PATCH /v1/accounts/update_credentials', data); @@ -395,7 +411,7 @@ export class MastodonApiServerService { const data = await client.search(_request.query.acct, { type: 'accounts' }); const profile = await this.userProfilesRepository.findOneBy({ userId: data.data.accounts[0].id }); data.data.accounts[0].fields = profile?.fields.map(f => ({ ...f, verified_at: null })) ?? []; - reply.send(await this.mastoConverter.convertAccount(data.data.accounts[0])); + reply.send(await this.mastoConverters.convertAccount(data.data.accounts[0])); } catch (e) { const data = getErrorData(e); this.logger.error('GET /v1/accounts/lookup', data); @@ -404,15 +420,13 @@ export class MastodonApiServerService { }); fastify.get('/v1/accounts/relationships', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isn't displayed without being logged in try { + const { client, me } = await this.getAuthClient(_request); let ids = _request.query['id[]'] ?? _request.query['id'] ?? []; if (typeof ids === 'string') { ids = [ids]; } - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getRelationships(ids)); } catch (e) { const data = getErrorData(e); @@ -428,7 +442,7 @@ export class MastodonApiServerService { try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getAccount(_request.params.id); - const account = await this.mastoConverter.convertAccount(data.data); + const account = await this.mastoConverters.convertAccount(data.data); reply.send(account); } catch (e) { const data = getErrorData(e); @@ -438,12 +452,10 @@ export class MastodonApiServerService { }); fastify.get('/v1/accounts/:id/statuses', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getStatuses()); } catch (e) { const data = getErrorData(e); @@ -468,12 +480,10 @@ export class MastodonApiServerService { }); fastify.get('/v1/accounts/:id/followers', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getFollowers()); } catch (e) { const data = getErrorData(e); @@ -483,12 +493,10 @@ export class MastodonApiServerService { }); fastify.get('/v1/accounts/:id/following', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getFollowing()); } catch (e) { const data = getErrorData(e); @@ -513,12 +521,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/accounts/:id/follow', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.addFollow()); } catch (e) { const data = getErrorData(e); @@ -528,12 +534,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/accounts/:id/unfollow', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.rmFollow()); } catch (e) { const data = getErrorData(e); @@ -543,12 +547,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/accounts/:id/block', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.addBlock()); } catch (e) { const data = getErrorData(e); @@ -558,12 +560,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/accounts/:id/unblock', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.rmBlock()); } catch (e) { const data = getErrorData(e); @@ -573,12 +573,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/accounts/:id/mute', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.addMute()); } catch (e) { const data = getErrorData(e); @@ -588,12 +586,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/accounts/:id/unmute', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.rmMute()); } catch (e) { const data = getErrorData(e); @@ -617,11 +613,9 @@ export class MastodonApiServerService { }); fastify.get('/v1/bookmarks', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getBookmarks()); } catch (e) { const data = getErrorData(e); @@ -631,11 +625,9 @@ export class MastodonApiServerService { }); fastify.get('/v1/favourites', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getFavourites()); } catch (e) { const data = getErrorData(e); @@ -645,11 +637,9 @@ export class MastodonApiServerService { }); fastify.get('/v1/mutes', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getMutes()); } catch (e) { const data = getErrorData(e); @@ -659,11 +649,9 @@ export class MastodonApiServerService { }); fastify.get('/v1/blocks', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.getBlocks()); } catch (e) { const data = getErrorData(e); @@ -679,7 +667,7 @@ export class MastodonApiServerService { try { const limit = _request.query.limit ? parseInt(_request.query.limit) : 20; const data = await client.getFollowRequests(limit); - reply.send(await Promise.all(data.data.map(async (account) => await this.mastoConverter.convertAccount(account as Entity.Account)))); + reply.send(await Promise.all(data.data.map(async (account) => await this.mastoConverters.convertAccount(account as Entity.Account)))); } catch (e) { const data = getErrorData(e); this.logger.error('GET /v1/follow_requests', data); @@ -688,12 +676,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/follow_requests/:id/authorize', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.acceptFollow()); } catch (e) { const data = getErrorData(e); @@ -703,12 +689,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/follow_requests/:id/reject', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const account = new ApiAccountMastodon(_request, client, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const account = new ApiAccountMastodon(_request, client, me, this.mastoConverters); reply.send(await account.rejectFollow()); } catch (e) { const data = getErrorData(e); @@ -721,10 +705,9 @@ export class MastodonApiServerService { //#region Search fastify.get('/v1/search', async (_request, reply) => { const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const search = new ApiSearchMastodon(_request, client, me, BASE_URL, this.mastoConverters); reply.send(await search.SearchV1()); } catch (e) { const data = getErrorData(e); @@ -735,10 +718,9 @@ export class MastodonApiServerService { fastify.get('/v2/search', async (_request, reply) => { const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const search = new ApiSearchMastodon(_request, client, me, BASE_URL, this.mastoConverters); reply.send(await search.SearchV2()); } catch (e) { const data = getErrorData(e); @@ -749,10 +731,9 @@ export class MastodonApiServerService { fastify.get('/v1/trends/statuses', async (_request, reply) => { const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const search = new ApiSearchMastodon(_request, client, me, BASE_URL, this.mastoConverters); reply.send(await search.getStatusTrends()); } catch (e) { const data = getErrorData(e); @@ -763,10 +744,9 @@ export class MastodonApiServerService { fastify.get('/v2/suggestions', async (_request, reply) => { const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const search = new ApiSearchMastodon(_request, client, BASE_URL, this.mastoConverter); + const { client, me } = await this.getAuthClient(_request); + const search = new ApiSearchMastodon(_request, client, me, BASE_URL, this.mastoConverters); reply.send(await search.getSuggestions()); } catch (e) { const data = getErrorData(e); @@ -778,11 +758,9 @@ export class MastodonApiServerService { //#region Notifications fastify.get('/v1/notifications', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const notify = new ApiNotifyMastodon(_request, client); + const { client, me } = await this.getAuthClient(_request); + const notify = new ApiNotifyMastodon(_request, client, me, this.mastoConverters); reply.send(await notify.getNotifications()); } catch (e) { const data = getErrorData(e); @@ -792,12 +770,10 @@ export class MastodonApiServerService { }); fastify.get('/v1/notification/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const notify = new ApiNotifyMastodon(_request, client); + const { client, me } = await this.getAuthClient(_request); + const notify = new ApiNotifyMastodon(_request, client, me, this.mastoConverters); reply.send(await notify.getNotification()); } catch (e) { const data = getErrorData(e); @@ -807,12 +783,10 @@ export class MastodonApiServerService { }); fastify.post('/v1/notification/:id/dismiss', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); - const notify = new ApiNotifyMastodon(_request, client); + const { client, me } = await this.getAuthClient(_request); + const notify = new ApiNotifyMastodon(_request, client, me, this.mastoConverters); reply.send(await notify.rmNotification()); } catch (e) { const data = getErrorData(e); @@ -822,11 +796,9 @@ export class MastodonApiServerService { }); fastify.post('/v1/notifications/clear', { preHandler: upload.single('none') }, async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { - const notify = new ApiNotifyMastodon(_request, client); + const { client, me } = await this.getAuthClient(_request); + const notify = new ApiNotifyMastodon(_request, client, me, this.mastoConverters); reply.send(await notify.rmNotifications()); } catch (e) { const data = getErrorData(e); @@ -899,7 +871,7 @@ export class MastodonApiServerService { //#endregion //#region Timelines - const TLEndpoint = new ApiTimelineMastodon(fastify, this.mastoConverter, this.logger); + const TLEndpoint = new ApiTimelineMastodon(fastify, this.mastoConverters, this.logger, this); // GET Endpoints TLEndpoint.getTL(); @@ -924,7 +896,7 @@ export class MastodonApiServerService { //#endregion //#region Status - const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverter, this.logger, this.authenticateService); + const NoteEndpoint = new ApiStatusMastodon(fastify, this.mastoConverters, this.logger, this.authenticateService, this); // GET Endpoints NoteEndpoint.getStatus(); diff --git a/packages/backend/src/server/api/mastodon/MastodonDataService.ts b/packages/backend/src/server/api/mastodon/MastodonDataService.ts index 782f592ee5..671ecdcbed 100644 --- a/packages/backend/src/server/api/mastodon/MastodonDataService.ts +++ b/packages/backend/src/server/api/mastodon/MastodonDataService.ts @@ -4,10 +4,12 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import { QueryService } from '@/core/QueryService.js'; import type { MiNote, NotesRepository } from '@/models/_.js'; import type { MiLocalUser } from '@/models/User.js'; +import { ApiError } from '../error.js'; /** * Utility service for accessing data with Mastodon semantics @@ -22,6 +24,28 @@ export class MastodonDataService { private readonly queryService: QueryService, ) {} + /** + * Fetches a note in the context of the current user, and throws an exception if not found. + */ + public async requireNote(noteId: string, me?: MiLocalUser | null): Promise { + const note = await this.getNote(noteId, me); + + if (!note) { + throw new ApiError({ + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', + kind: 'client', + httpStatusCode: 404, + }); + } + + return note; + } + + /** + * Fetches a note in the context of the current user. + */ public async getNote(noteId: string, me?: MiLocalUser | null): Promise { // Root query: note + required dependencies const query = this.notesRepository @@ -37,4 +61,24 @@ export class MastodonDataService { return await query.getOne(); } + + /** + * Checks where the current user has made a reblog / boost / pure renote of a given target note. + */ + public async hasReblog(noteId: string, me: MiLocalUser | null | undefined): Promise { + if (!me) return false; + + return await this.notesRepository.existsBy({ + // Reblog of the target note by me + userId: me.id, + renoteId: noteId, + + // That is pure (not a quote) + text: IsNull(), + cw: IsNull(), + replyId: IsNull(), + hasPoll: false, + fileIds: '{}', + }); + } } diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index d7309b5a87..773dfb6923 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -215,15 +215,16 @@ export class MastoConverters { return await Promise.all(history); } - private async convertReblog(status: Entity.Status | null): Promise { + private async convertReblog(status: Entity.Status | null, me?: MiLocalUser | null): Promise { if (!status) return null; - return await this.convertStatus(status); + return await this.convertStatus(status, me); } - public async convertStatus(status: Entity.Status): Promise { + public async convertStatus(status: Entity.Status, me?: MiLocalUser | null): Promise { const convertedAccount = this.convertAccount(status.account); - const note = await this.getterService.getNote(status.id); + const note = await this.mastodonDataService.requireNote(status.id, me); const noteUser = await this.getUser(status.account.id); + const mentionedRemoteUsers = JSON.parse(note.mentionedRemoteUsers); const emojis = await this.customEmojiService.populateEmojis(note.emojis, noteUser.host ? noteUser.host : this.config.host); const emoji: Entity.Emoji[] = []; @@ -240,7 +241,7 @@ export class MastoConverters { const mentions = Promise.all(note.mentions.map(p => this.getUser(p) - .then(u => this.encode(u, JSON.parse(note.mentionedRemoteUsers))) + .then(u => this.encode(u, mentionedRemoteUsers)) .catch(() => null))) .then(p => p.filter(m => m)) as Promise; @@ -255,7 +256,7 @@ export class MastoConverters { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const isQuote = note.renoteId && (note.text || note.cw || note.fileIds.length > 0 || note.hasPoll || note.replyId); - const renote: Promise | null = note.renoteId ? this.getterService.getNote(note.renoteId) : null; + const renote: Promise | null = note.renoteId ? this.mastodonDataService.requireNote(note.renoteId, me) : null; const quoteUri = Promise.resolve(renote).then(renote => { if (!renote || !isQuote) return null; @@ -265,10 +266,12 @@ export class MastoConverters { const text = note.text; const content = text !== null ? quoteUri - .then(quoteUri => this.mfmService.toMastoApiHtml(mfm.parse(text), JSON.parse(note.mentionedRemoteUsers), false, quoteUri)) + .then(quoteUri => this.mfmService.toMastoApiHtml(mfm.parse(text), mentionedRemoteUsers, false, quoteUri)) .then(p => p ?? escapeMFM(text)) : ''; + const reblogged = await this.mastodonDataService.hasReblog(note.id, me); + // noinspection ES6MissingAwait return await awaitAll({ id: note.id, @@ -277,7 +280,7 @@ export class MastoConverters { account: convertedAccount, in_reply_to_id: note.replyId, in_reply_to_account_id: note.replyUserId, - reblog: !isQuote ? await this.convertReblog(status.reblog) : null, + reblog: !isQuote ? await this.convertReblog(status.reblog, me) : null, content: content, content_type: 'text/x.misskeymarkdown', text: note.text, @@ -286,7 +289,7 @@ export class MastoConverters { replies_count: note.repliesCount, reblogs_count: note.renoteCount, favourites_count: status.favourites_count, - reblogged: false, + reblogged, favourited: status.favourited, muted: status.muted, sensitive: status.sensitive, @@ -303,10 +306,29 @@ export class MastoConverters { reactions: status.emoji_reactions, emoji_reactions: status.emoji_reactions, bookmarked: false, //FIXME - quote: isQuote ? await this.convertReblog(status.reblog) : null, + quote: isQuote ? await this.convertReblog(status.reblog, me) : null, edited_at: note.updatedAt?.toISOString() ?? null, }); } + + public async convertConversation(conversation: Entity.Conversation, me?: MiLocalUser | null): Promise { + return { + id: conversation.id, + accounts: await Promise.all(conversation.accounts.map(a => this.convertAccount(a))), + last_status: conversation.last_status ? await this.convertStatus(conversation.last_status, me) : null, + unread: conversation.unread, + }; + } + + public async convertNotification(notification: Entity.Notification, me?: MiLocalUser | null): Promise { + return { + account: await this.convertAccount(notification.account), + created_at: notification.created_at, + id: notification.id, + status: notification.status ? await this.convertStatus(notification.status, me) : undefined, + type: notification.type, + }; + } } function simpleConvert(data: T): T { @@ -333,12 +355,6 @@ export function convertFeaturedTag(tag: Entity.FeaturedTag) { return simpleConvert(tag); } -export function convertNotification(notification: Entity.Notification) { - notification.account = convertAccount(notification.account); - if (notification.status) notification.status = convertStatus(notification.status); - return notification; -} - export function convertPoll(poll: Entity.Poll) { return simpleConvert(poll); } @@ -372,27 +388,7 @@ export function convertRelationship(relationship: Partial & }; } -export function convertStatus(status: Entity.Status) { - status.account = convertAccount(status.account); - status.media_attachments = status.media_attachments.map((attachment) => - convertAttachment(attachment), - ); - if (status.poll) status.poll = convertPoll(status.poll); - if (status.reblog) status.reblog = convertStatus(status.reblog); - - return status; -} - // noinspection JSUnusedGlobalSymbols export function convertStatusSource(status: Entity.StatusSource) { return simpleConvert(status); } - -export function convertConversation(conversation: Entity.Conversation) { - conversation.accounts = conversation.accounts.map(convertAccount); - if (conversation.last_status) { - conversation.last_status = convertStatus(conversation.last_status); - } - - return conversation; -} diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 1481a48924..79cdddcb9e 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -5,6 +5,7 @@ import { Injectable } from '@nestjs/common'; import { parseTimelineArgs, TimelineArgs } from '@/server/api/mastodon/timelineArgs.js'; +import { MiLocalUser } from '@/models/User.js'; import { MastoConverters, convertRelationship } from '../converters.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; @@ -20,6 +21,7 @@ export class ApiAccountMastodon { constructor( private readonly request: FastifyRequest, private readonly client: MegalodonInterface, + private readonly me: MiLocalUser | null, private readonly mastoConverters: MastoConverters, ) {} @@ -51,7 +53,7 @@ export class ApiAccountMastodon { public async getStatuses() { if (!this.request.params.id) throw new Error('Missing required parameter "id"'); const data = await this.client.getAccountStatuses(this.request.params.id, parseTimelineArgs(this.request.query)); - return await Promise.all(data.data.map(async (status) => await this.mastoConverters.convertStatus(status))); + return await Promise.all(data.data.map(async (status) => await this.mastoConverters.convertStatus(status, this.me))); } public async getFollowers() { @@ -117,12 +119,12 @@ export class ApiAccountMastodon { public async getBookmarks() { const data = await this.client.getBookmarks(parseTimelineArgs(this.request.query)); - return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status))); + return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status, this.me))); } public async getFavourites() { const data = await this.client.getFavourites(parseTimelineArgs(this.request.query)); - return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status))); + return Promise.all(data.data.map((status) => this.mastoConverters.convertStatus(status, this.me))); } public async getMutes() { diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index c228e17ddc..14eee8565a 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -4,7 +4,8 @@ */ import { parseTimelineArgs, TimelineArgs } from '@/server/api/mastodon/timelineArgs.js'; -import { convertNotification } from '../converters.js'; +import { MiLocalUser } from '@/models/User.js'; +import { MastoConverters } from '@/server/api/mastodon/converters.js'; import type { MegalodonInterface } from 'megalodon'; import type { FastifyRequest } from 'fastify'; @@ -19,23 +20,25 @@ export class ApiNotifyMastodon { constructor( private readonly request: FastifyRequest, private readonly client: MegalodonInterface, + private readonly me: MiLocalUser | null, + private readonly mastoConverters: MastoConverters, ) {} public async getNotifications() { const data = await this.client.getNotifications(parseTimelineArgs(this.request.query)); - return data.data.map(n => { - const converted = convertNotification(n); + return Promise.all(data.data.map(async n => { + const converted = await this.mastoConverters.convertNotification(n, this.me); if (converted.type === 'reaction') { converted.type = 'favourite'; } return converted; - }); + })); } public async getNotification() { if (!this.request.params.id) throw new Error('Missing required parameter "id"'); const data = await this.client.getNotification(this.request.params.id); - const converted = convertNotification(data.data); + const converted = await this.mastoConverters.convertNotification(data.data, this.me); if (converted.type === 'reaction') { converted.type = 'favourite'; } diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index ac5b836c1e..4850b4652f 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { MiLocalUser } from '@/models/User.js'; import { MastoConverters } from '../converters.js'; import { parseTimelineArgs, TimelineArgs } from '../timelineArgs.js'; import Account = Entity.Account; @@ -21,8 +22,9 @@ export class ApiSearchMastodon { constructor( private readonly request: FastifyRequest, private readonly client: MegalodonInterface, + private readonly me: MiLocalUser | null, private readonly BASE_URL: string, - private readonly mastoConverter: MastoConverters, + private readonly mastoConverters: MastoConverters, ) {} public async SearchV1() { @@ -40,8 +42,8 @@ export class ApiSearchMastodon { const stat = !type || type === 'statuses' ? await this.client.search(this.request.query.q, { type: 'statuses', ...query }) : null; const tags = !type || type === 'hashtags' ? await this.client.search(this.request.query.q, { type: 'hashtags', ...query }) : null; return { - accounts: await Promise.all(acct?.data.accounts.map(async (account: Account) => await this.mastoConverter.convertAccount(account)) ?? []), - statuses: await Promise.all(stat?.data.statuses.map(async (status: Status) => await this.mastoConverter.convertStatus(status)) ?? []), + accounts: await Promise.all(acct?.data.accounts.map(async (account: Account) => await this.mastoConverters.convertAccount(account)) ?? []), + statuses: await Promise.all(stat?.data.statuses.map(async (status: Status) => await this.mastoConverters.convertStatus(status, this.me)) ?? []), hashtags: tags?.data.hashtags ?? [], }; } @@ -54,10 +56,12 @@ export class ApiSearchMastodon { 'Accept': 'application/json', 'Content-Type': 'application/json', }, - body: '{}', + body: JSON.stringify({ + i: this.request.headers.authorization?.replace('Bearer ', ''), + }), }) .then(res => res.json() as Promise) - .then(data => data.map(status => this.mastoConverter.convertStatus(status))); + .then(data => data.map(status => this.mastoConverters.convertStatus(status, this.me))); return Promise.all(data); } @@ -83,7 +87,7 @@ export class ApiSearchMastodon { account: entry, })))); return Promise.all(data.map(async suggestion => { - suggestion.account = await this.mastoConverter.convertAccount(suggestion.account); + suggestion.account = await this.mastoConverters.convertAccount(suggestion.account); return suggestion; })); } diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 99259d2542..4c49a6a293 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -9,7 +9,7 @@ import { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogg import { parseTimelineArgs, TimelineArgs, toBoolean, toInt } from '@/server/api/mastodon/timelineArgs.js'; import { AuthenticateService } from '@/server/api/AuthenticateService.js'; import { convertAttachment, convertPoll, MastoConverters } from '../converters.js'; -import { getAccessToken, getClient } from '../MastodonApiServerService.js'; +import { getAccessToken, getClient, MastodonApiServerService } from '../MastodonApiServerService.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; @@ -24,17 +24,16 @@ export class ApiStatusMastodon { private readonly mastoConverters: MastoConverters, private readonly logger: MastodonLogger, private readonly authenticateService: AuthenticateService, + private readonly mastodon: MastodonApiServerService, ) {} public getStatus() { this.fastify.get<{ Params: { id?: string } }>('/v1/statuses/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { + const { client, me } = await this.mastodon.getAuthClient(_request); if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.getStatus(_request.params.id); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`GET /v1/statuses/${_request.params.id}`, data); @@ -62,14 +61,12 @@ export class ApiStatusMastodon { public getContext() { this.fastify.get<{ Params: { id?: string }, Querystring: TimelineArgs }>('/v1/statuses/:id/context', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const { data } = await client.getStatusContext(_request.params.id, parseTimelineArgs(_request.query)); - const ancestors = await Promise.all(data.ancestors.map(async status => await this.mastoConverters.convertStatus(status))); - const descendants = await Promise.all(data.descendants.map(async status => await this.mastoConverters.convertStatus(status))); + const ancestors = await Promise.all(data.ancestors.map(async status => await this.mastoConverters.convertStatus(status, me))); + const descendants = await Promise.all(data.descendants.map(async status => await this.mastoConverters.convertStatus(status, me))); reply.send({ ancestors, descendants }); } catch (e) { const data = getErrorData(e); @@ -204,11 +201,9 @@ export class ApiStatusMastodon { 'media_ids[]'?: string[], } }>('/v1/statuses', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); let body = _request.body; try { + const { client, me } = await this.mastodon.getAuthClient(_request); if ((!body.poll && body['poll[options][]']) || (!body.media_ids && body['media_ids[]']) ) { body = normalizeQuery(body); @@ -253,7 +248,7 @@ export class ApiStatusMastodon { }; const data = await client.postStatus(text, options); - reply.send(await this.mastoConverters.convertStatus(data.data as Entity.Status)); + reply.send(await this.mastoConverters.convertStatus(data.data as Entity.Status, me)); } catch (e) { const data = getErrorData(e); this.logger.error('POST /v1/statuses', data); @@ -278,10 +273,8 @@ export class ApiStatusMastodon { }, } }>('/v1/statuses/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { + const { client, me } = await this.mastodon.getAuthClient(_request); const body = _request.body; if (!body.media_ids || !body.media_ids.length) { @@ -300,7 +293,7 @@ export class ApiStatusMastodon { }; const data = await client.editStatus(_request.params.id, options); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}`, data); @@ -311,13 +304,11 @@ export class ApiStatusMastodon { public addFavourite() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/favourite', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.createEmojiReaction(_request.params.id, '❤'); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/favorite`, data); @@ -328,13 +319,11 @@ export class ApiStatusMastodon { public rmFavourite() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unfavourite', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { + const { client, me } = await this.mastodon.getAuthClient(_request); if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); const data = await client.deleteEmojiReaction(_request.params.id, '❤'); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`GET /v1/statuses/${_request.params.id}/unfavorite`, data); @@ -345,13 +334,11 @@ export class ApiStatusMastodon { public reblogStatus() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/reblog', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.reblogStatus(_request.params.id); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/reblog`, data); @@ -362,13 +349,11 @@ export class ApiStatusMastodon { public unreblogStatus() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unreblog', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.unreblogStatus(_request.params.id); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/unreblog`, data); @@ -379,13 +364,11 @@ export class ApiStatusMastodon { public bookmarkStatus() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/bookmark', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.bookmarkStatus(_request.params.id); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/bookmark`, data); @@ -396,13 +379,11 @@ export class ApiStatusMastodon { public unbookmarkStatus() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unbookmark', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.unbookmarkStatus(_request.params.id); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/unbookmark`, data); @@ -413,13 +394,11 @@ export class ApiStatusMastodon { public pinStatus() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/pin', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.pinStatus(_request.params.id); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/pin`, data); @@ -430,13 +409,11 @@ export class ApiStatusMastodon { public unpinStatus() { this.fastify.post<{ Params: { id?: string } }>('/v1/statuses/:id/unpin', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.unpinStatus(_request.params.id); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/unpin`, data); @@ -447,14 +424,12 @@ export class ApiStatusMastodon { public reactStatus() { this.fastify.post<{ Params: { id?: string, name?: string } }>('/v1/statuses/:id/react/:name', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); if (!_request.params.name) return reply.code(400).send({ error: 'Missing required parameter "name"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.createEmojiReaction(_request.params.id, _request.params.name); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/react/${_request.params.name}`, data); @@ -465,14 +440,12 @@ export class ApiStatusMastodon { public unreactStatus() { this.fastify.post<{ Params: { id?: string, name?: string } }>('/v1/statuses/:id/unreact/:name', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); if (!_request.params.name) return reply.code(400).send({ error: 'Missing required parameter "name"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.deleteEmojiReaction(_request.params.id, _request.params.name); - reply.send(await this.mastoConverters.convertStatus(data.data)); + reply.send(await this.mastoConverters.convertStatus(data.data, me)); } catch (e) { const data = getErrorData(e); this.logger.error(`POST /v1/statuses/${_request.params.id}/unreact/${_request.params.name}`, data); diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index a6a778721a..1a732d62de 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -4,8 +4,8 @@ */ import { getErrorData, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js'; -import { convertConversation, convertList, MastoConverters } from '../converters.js'; -import { getClient } from '../MastodonApiServerService.js'; +import { convertList, MastoConverters } from '../converters.js'; +import { getClient, MastodonApiServerService } from '../MastodonApiServerService.js'; import { parseTimelineArgs, TimelineArgs, toBoolean } from '../timelineArgs.js'; import type { Entity } from 'megalodon'; import type { FastifyInstance } from 'fastify'; @@ -15,18 +15,17 @@ export class ApiTimelineMastodon { private readonly fastify: FastifyInstance, private readonly mastoConverters: MastoConverters, private readonly logger: MastodonLogger, + private readonly mastodon: MastodonApiServerService, ) {} public getTL() { this.fastify.get<{ Querystring: TimelineArgs }>('/v1/timelines/public', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { + const { client, me } = await this.mastodon.getAuthClient(_request); const data = toBoolean(_request.query.local) ? await client.getLocalTimeline(parseTimelineArgs(_request.query)) : await client.getPublicTimeline(parseTimelineArgs(_request.query)); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me)))); } catch (e) { const data = getErrorData(e); this.logger.error('GET /v1/timelines/public', data); @@ -37,12 +36,10 @@ export class ApiTimelineMastodon { public getHomeTl() { this.fastify.get<{ Querystring: TimelineArgs }>('/v1/timelines/home', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.getHomeTimeline(parseTimelineArgs(_request.query)); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me)))); } catch (e) { const data = getErrorData(e); this.logger.error('GET /v1/timelines/home', data); @@ -53,13 +50,11 @@ export class ApiTimelineMastodon { public getTagTl() { this.fastify.get<{ Params: { hashtag?: string }, Querystring: TimelineArgs }>('/v1/timelines/tag/:hashtag', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.hashtag) return reply.code(400).send({ error: 'Missing required parameter "hashtag"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.getTagTimeline(_request.params.hashtag, parseTimelineArgs(_request.query)); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me)))); } catch (e) { const data = getErrorData(e); this.logger.error(`GET /v1/timelines/tag/${_request.params.hashtag}`, data); @@ -70,13 +65,11 @@ export class ApiTimelineMastodon { public getListTL() { this.fastify.get<{ Params: { id?: string }, Querystring: TimelineArgs }>('/v1/timelines/list/:id', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { if (!_request.params.id) return reply.code(400).send({ error: 'Missing required parameter "id"' }); + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.getListTimeline(_request.params.id, parseTimelineArgs(_request.query)); - reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status)))); + reply.send(await Promise.all(data.data.map(async (status: Entity.Status) => await this.mastoConverters.convertStatus(status, me)))); } catch (e) { const data = getErrorData(e); this.logger.error(`GET /v1/timelines/list/${_request.params.id}`, data); @@ -87,12 +80,11 @@ export class ApiTimelineMastodon { public getConversations() { this.fastify.get<{ Querystring: TimelineArgs }>('/v1/conversations', async (_request, reply) => { - const BASE_URL = `${_request.protocol}://${_request.host}`; - const accessTokens = _request.headers.authorization; - const client = getClient(BASE_URL, accessTokens); try { + const { client, me } = await this.mastodon.getAuthClient(_request); const data = await client.getConversationTimeline(parseTimelineArgs(_request.query)); - reply.send(data.data.map((conversation: Entity.Conversation) => convertConversation(conversation))); + const conversations = await Promise.all(data.data.map(async (conversation: Entity.Conversation) => await this.mastoConverters.convertConversation(conversation, me))); + reply.send(conversations); } catch (e) { const data = getErrorData(e); this.logger.error('GET /v1/conversations', data); -- cgit v1.2.3-freya From daf715578ef5a9afee7310e971cbccfdebb63b8e Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 13:11:17 -0500 Subject: fix mastodon media attachment conversion (resolves #495) --- .../backend/src/server/api/mastodon/converters.ts | 45 ++++++++++++++++++---- 1 file changed, 38 insertions(+), 7 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 773dfb6923..c3694f3580 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -102,6 +102,10 @@ export class MastoConverters { } public encodeFile(f: Packed<'DriveFile'>): MastodonEntity.Attachment { + const { width, height } = f.properties; + const size = (width && height) ? `${width}x${height}` : undefined; + const aspect = (width && height) ? (width / height) : undefined; + return { id: f.id, type: this.fileType(f.type), @@ -110,11 +114,19 @@ export class MastoConverters { preview_url: f.thumbnailUrl, text_url: f.url, meta: { - width: f.properties.width, - height: f.properties.height, + original: { + width, + height, + size, + aspect, + }, + width, + height, + size, + aspect, }, - description: f.comment ? f.comment : null, - blurhash: f.blurhash ? f.blurhash : null, + description: f.comment ?? null, + blurhash: f.blurhash ?? null, }; } @@ -295,7 +307,7 @@ export class MastoConverters { sensitive: status.sensitive, spoiler_text: note.cw ?? '', visibility: status.visibility, - media_attachments: status.media_attachments, + media_attachments: status.media_attachments.map(a => convertAttachment(a)), mentions: mentions, tags: tags, card: null, //FIXME @@ -342,8 +354,27 @@ export function convertAccount(account: Entity.Account) { export function convertAnnouncement(announcement: Entity.Announcement) { return simpleConvert(announcement); } -export function convertAttachment(attachment: Entity.Attachment) { - return simpleConvert(attachment); +export function convertAttachment(attachment: Entity.Attachment): MastodonEntity.Attachment { + const { width, height } = attachment.meta?.original ?? attachment.meta ?? {}; + const size = (width && height) ? `${width}x${height}` : undefined; + const aspect = (width && height) ? (width / height) : undefined; + return { + ...attachment, + meta: attachment.meta ? { + ...attachment.meta, + original: attachment.meta.original ?? { + width: attachment.meta.width, + height: attachment.meta.height, + size, + aspect, + frame_rate: String(attachment.meta.fps), + duration: attachment.meta.duration, + bitrate: attachment.meta.audio_bitrate ? parseInt(attachment.meta.audio_bitrate) : undefined, + }, + size, + aspect, + } : null, + }; } export function convertFilter(filter: Entity.Filter) { return simpleConvert(filter); -- cgit v1.2.3-freya From 1126c096691a2e8dcac3f500e812dc5d6d71cb65 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 31 Jan 2025 13:40:59 -0500 Subject: make convertAttachment match file->attachment schema --- packages/backend/src/server/api/mastodon/converters.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index c3694f3580..b6ff5bc59a 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -362,15 +362,18 @@ export function convertAttachment(attachment: Entity.Attachment): MastodonEntity ...attachment, meta: attachment.meta ? { ...attachment.meta, - original: attachment.meta.original ?? { - width: attachment.meta.width, - height: attachment.meta.height, + original: { + ...attachment.meta.original, + width, + height, size, aspect, frame_rate: String(attachment.meta.fps), duration: attachment.meta.duration, bitrate: attachment.meta.audio_bitrate ? parseInt(attachment.meta.audio_bitrate) : undefined, }, + width, + height, size, aspect, } : null, -- cgit v1.2.3-freya From 4567f243a9c8fa0f6e36b14c34086cea1286bcd8 Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 9 Feb 2025 11:39:28 +0000 Subject: allow overriding logging via env --- packages/backend/src/config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 7d2641ec66..843c552254 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -518,7 +518,7 @@ function applyEnvOverrides(config: Source) { ['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions'], ['host', 'port', 'username', 'pass', 'db', 'prefix'], ]); - _apply_top(['fulltextSearch', ['provider']]); + _apply_top(['fulltextSearch', 'provider']); _apply_top(['meilisearch', ['host', 'port', 'apikey', 'ssl', 'index', 'scope']]); _apply_top([['sentryForFrontend', 'sentryForBackend'], 'options', ['dsn', 'profileSampleRate', 'serverName', 'includeLocalVariables', 'proxy', 'keepAlive', 'caCerts']]); _apply_top(['sentryForBackend', 'enableNodeProfiling']); @@ -527,4 +527,5 @@ function applyEnvOverrides(config: Source) { _apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile', 'filePermissionBits']]); _apply_top(['import', ['downloadTimeout', 'maxFileSize']]); _apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature', 'setupPassword']]); + _apply_top(['logging', 'sql', ['disableQueryTruncation', 'enableQueryParamLogging']]); } -- cgit v1.2.3-freya From 7b507485b54eaae283ca388dd9948c35592a4ced Mon Sep 17 00:00:00 2001 From: dakkar Date: Mon, 10 Feb 2025 10:16:27 +0000 Subject: search-by-tags returns "home" notes - fixes #933 featured / trending tags count both "home" and "public" notes, so this should do the same --- packages/backend/src/server/api/endpoints/notes/search-by-tag.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index 227ac0ebbf..6bba7bf37e 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -87,7 +87,7 @@ export default class extends Endpoint { // eslint- ) { super(meta, paramDef, async (ps, me) => { const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere('note.visibility = \'public\'') + .andWhere("note.visibility IN ('public', 'home')") // keep in sync with NoteCreateService call to `hashtagService.updateHashtags()` .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') -- cgit v1.2.3-freya From 427d09e643a554a6711b9349f8a6bbc3499dcfb7 Mon Sep 17 00:00:00 2001 From: dakkar Date: Mon, 10 Feb 2025 10:40:06 +0000 Subject: make the listen address configurable - fixes #927 sadly `fastify.listen` doesn't support passing more than 1 address --- .config/example.yml | 2 ++ packages/backend/src/boot/master.ts | 4 +++- packages/backend/src/config.ts | 5 ++++- packages/backend/src/server/ServerService.ts | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/.config/example.yml b/.config/example.yml index f781b72b91..8d438e93aa 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -100,6 +100,8 @@ url: https://example.tld/ # The port that your Misskey server should listen on. port: 3000 +# the address to bind to, defaults to "every address" +# address: '0.0.0.0' # You can also use UNIX domain socket. # socket: /path/to/misskey.sock diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 355e095c12..80ea1b7431 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -18,6 +18,7 @@ import type { Config } from '@/config.js'; import { showMachineInfo } from '@/misc/show-machine-info.js'; import { envOption } from '@/env.js'; import { jobQueue, server } from './common.js'; +import * as net from 'node:net'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -126,7 +127,8 @@ export async function masterMain() { if (envOption.onlyQueue) { bootLogger.succ('Queue started', null, true); } else { - bootLogger.succ(config.socket ? `Now listening on socket ${config.socket} on ${config.url}` : `Now listening on port ${config.port} on ${config.url}`, null, true); + const addressString = net.isIPv6(config.address) ? `[${config.address}]` : config.address; + bootLogger.succ(config.socket ? `Now listening on socket ${config.socket} on ${config.url}` : `Now listening on ${addressString}:${config.port} on ${config.url}`, null, true); } } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 4af1140f36..1a3a916624 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -27,6 +27,7 @@ type RedisOptionsSource = Partial & { type Source = { url?: string; port?: number; + address?: string; socket?: string; chmodSocket?: string; disableHsts?: boolean; @@ -123,6 +124,7 @@ type Source = { export type Config = { url: string; port: number; + address: string; socket: string | undefined; chmodSocket: string | undefined; disableHsts: boolean | undefined; @@ -288,6 +290,7 @@ export function loadConfig(): Config { setupPassword: config.setupPassword, url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '3000', 10), + address: config.address ?? '0.0.0.0', socket: config.socket, chmodSocket: config.chmodSocket, disableHsts: config.disableHsts, @@ -488,7 +491,7 @@ function applyEnvOverrides(config: Source) { // these are all the settings that can be overridden - _apply_top([['url', 'port', 'socket', 'chmodSocket', 'disableHsts', 'id', 'dbReplications']]); + _apply_top([['url', 'port', 'address', 'socket', 'chmodSocket', 'disableHsts', 'id', 'dbReplications']]); _apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]); _apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]); _apply_top([ diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 43a2a3a2b0..690fdcfe29 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -262,7 +262,7 @@ export class ServerService implements OnApplicationShutdown { } }); } else { - fastify.listen({ port: this.config.port, host: '0.0.0.0' }); + fastify.listen({ port: this.config.port, host: this.config.address }); } await fastify.ready(); -- cgit v1.2.3-freya From fb058410f6e907428a2fd7b25237d11a6ef39420 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 11:01:26 -0500 Subject: fix pgroona note.text query --- packages/backend/src/core/SearchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 35c67400ba..3ad2042d53 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -225,7 +225,7 @@ export class SearchService { .leftJoinAndSelect('renote.user', 'renoteUser'); if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { - query.andWhere('note.text &@ :q', { q }); + query.andWhere('note.text &@~ :q', { q }); } else { query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` }); } -- cgit v1.2.3-freya From ed981a6970df4cecedb3fa7553f5fa8d43665a51 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 13 Feb 2025 09:28:46 -0500 Subject: add new note search file types (module, flash) and optimize file type query --- locales/index.d.ts | 36 +++++++ .../1739451520729-index_note_attachedFileTypes.js | 12 +++ packages/backend/src/core/SearchService.ts | 107 +++++++++++---------- packages/backend/src/models/Note.ts | 1 + .../src/server/api/endpoints/notes/search.ts | 8 +- packages/frontend/src/pages/search.note.vue | 16 +-- packages/misskey-js/src/autogen/types.ts | 3 +- sharkey-locales/en-US.yml | 11 +++ 8 files changed, 135 insertions(+), 59 deletions(-) create mode 100644 packages/backend/migration/1739451520729-index_note_attachedFileTypes.js (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index a1bfc4a595..7e90a779dc 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -11706,6 +11706,42 @@ export interface Locale extends ILocale { */ "title": string; }; + "_noteSearch": { + /** + * Sort by newest to oldest + */ + "newestToOldest": string; + /** + * File Type + */ + "fileType": string; + "_fileType": { + /** + * None + */ + "none": string; + /** + * Images + */ + "image": string; + /** + * Videos + */ + "video": string; + /** + * Audio + */ + "audio": string; + /** + * Module + */ + "module": string; + /** + * Flash + */ + "flash": string; + }; + }; } declare const locales: { [lang: string]: Locale; diff --git a/packages/backend/migration/1739451520729-index_note_attachedFileTypes.js b/packages/backend/migration/1739451520729-index_note_attachedFileTypes.js new file mode 100644 index 0000000000..351908a68c --- /dev/null +++ b/packages/backend/migration/1739451520729-index_note_attachedFileTypes.js @@ -0,0 +1,12 @@ +// https://stackoverflow.com/a/4059785 +export class IndexNoteAttachedFileTypes1739451520729 { + name = 'IndexNoteAttachedFileTypes1739451520729' + + async up(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_NOTE_ATTACHED_FILE_TYPES" ON "note" USING GIN ("attachedFileTypes" array_ops)`); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "IDX_NOTE_ATTACHED_FILE_TYPES"`); + } +} diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 6dc3e85fc8..a8c6ac61f3 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -61,6 +61,60 @@ function compileQuery(q: Q): string { } } +const fileTypes = { + image: [ + 'image/webp', + 'image/png', + 'image/jpeg', + 'image/avif', + 'image/apng', + 'image/gif', + ], + video: [ + 'video/mp4', + 'video/webm', + 'video/mpeg', + 'video/x-m4v', + ], + audio: [ + 'audio/mpeg', + 'audio/flac', + 'audio/wav', + 'audio/aac', + 'audio/webm', + 'audio/opus', + 'audio/ogg', + 'audio/x-m4a', + 'audio/mod', + 'audio/s3m', + 'audio/xm', + 'audio/it', + 'audio/x-mod', + 'audio/x-s3m', + 'audio/x-xm', + 'audio/x-it', + ], + // Keep in sync with frontend-shared/js/const.ts + module: [ + 'audio/mod', + 'audio/x-mod', + 'audio/s3m', + 'audio/x-s3m', + 'audio/xm', + 'audio/x-xm', + 'audio/it', + 'audio/x-it', + ], + flash: [ + 'application/x-shockwave-flash', + 'application/vnd.adobe.flash.movie', + ], +}; + +// Make sure to regenerate misskey-js and check search.note.vue after changing these +export const fileTypeCategories = ['image', 'video', 'audio', 'module', 'flash'] as const; +export type FileTypeCategory = typeof fileTypeCategories[number]; + @Injectable() export class SearchService { private readonly meilisearchIndexScope: 'local' | 'global' | string[] = 'local'; @@ -163,7 +217,7 @@ export class SearchService { userId?: MiNote['userId'] | null; channelId?: MiNote['channelId'] | null; host?: string | null; - filetype?: string | null; + filetype?: FileTypeCategory | null; order?: string | null; disableMeili?: boolean | null; }, pagination: { @@ -188,42 +242,8 @@ export class SearchService { } } if (opts.filetype) { - if (opts.filetype === 'image') { - filter.qs.push({ op: 'or', qs: [ - { op: '=', k: 'attachedFileTypes', v: 'image/webp' }, - { op: '=', k: 'attachedFileTypes', v: 'image/png' }, - { op: '=', k: 'attachedFileTypes', v: 'image/jpeg' }, - { op: '=', k: 'attachedFileTypes', v: 'image/avif' }, - { op: '=', k: 'attachedFileTypes', v: 'image/apng' }, - { op: '=', k: 'attachedFileTypes', v: 'image/gif' }, - ] }); - } else if (opts.filetype === 'video') { - filter.qs.push({ op: 'or', qs: [ - { op: '=', k: 'attachedFileTypes', v: 'video/mp4' }, - { op: '=', k: 'attachedFileTypes', v: 'video/webm' }, - { op: '=', k: 'attachedFileTypes', v: 'video/mpeg' }, - { op: '=', k: 'attachedFileTypes', v: 'video/x-m4v' }, - ] }); - } else if (opts.filetype === 'audio') { - filter.qs.push({ op: 'or', qs: [ - { op: '=', k: 'attachedFileTypes', v: 'audio/mpeg' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/flac' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/wav' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/aac' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/webm' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/opus' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/ogg' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/x-m4a' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/mod' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/s3m' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/xm' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/it' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/x-mod' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/x-s3m' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/x-xm' }, - { op: '=', k: 'attachedFileTypes', v: 'audio/x-it' }, - ] }); - } + const filters = fileTypes[opts.filetype].map(mime => ({ op: '=' as const, k: 'attachedFileTypes', v: mime })); + filter.qs.push({ op: 'or', qs: filters }); } const res = await this.meilisearchNoteIndex!.search(q, { sort: [`createdAt:${opts.order ? opts.order : 'desc'}`], @@ -274,18 +294,7 @@ export class SearchService { } if (opts.filetype) { - /* this is very ugly, but the "correct" solution would - be `and exists (select 1 from - unnest(note."attachedFileTypes") x(t) where t like - :type)` and I can't find a way to get TypeORM to - generate that; this hack works because `~*` is - "regexp match, ignoring case" and the stringified - version of an array of varchars (which is what - `attachedFileTypes` is) looks like `{foo,bar}`, so - we're looking for opts.filetype as the first half of - a MIME type, either at start of the array (after the - `{`) or later (after a `,`) */ - query.andWhere(`note."attachedFileTypes"::varchar ~* :type`, { type: `[{,]${opts.filetype}/` }); + query.andWhere('note."attachedFileTypes" && :types', { types: fileTypes[opts.filetype] }); } this.queryService.generateVisibilityQuery(query, me); diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 408e023ff7..8b5265e8fe 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -143,6 +143,7 @@ export class MiNote { }) public fileIds: MiDriveFile['id'][]; + @Index('IDX_NOTE_ATTACHED_FILE_TYPES', { synchronize: false }) @Column('varchar', { length: 256, array: true, default: '{}', }) diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index eca55cd085..f46f4d2adb 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -5,7 +5,7 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { SearchService } from '@/core/SearchService.js'; +import { fileTypeCategories, SearchService } from '@/core/SearchService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { RoleService } from '@/core/RoleService.js'; import { ApiError } from '../../error.js'; @@ -52,7 +52,11 @@ export const paramDef = { type: 'string', description: 'The local host is represented with `.`.', }, - filetype: { type: 'string', nullable: true }, + filetype: { + type: 'string', + nullable: true, + enum: fileTypeCategories, + }, userId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, order: { type: 'string' }, diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue index d64537d289..e080aea064 100644 --- a/packages/frontend/src/pages/search.note.vue +++ b/packages/frontend/src/pages/search.note.vue @@ -22,13 +22,15 @@ SPDX-License-Identifier: AGPL-3.0-only - Sort by newest to oldest + {{ i18n.ts._noteSearch.newestToOldest }} - - - - - + + + + + + + @@ -97,7 +99,7 @@ const notePagination = ref(); const user = ref(null); const hostInput = ref(toRef(props, 'host').value); const order = ref(false); -const filetype = ref(null); +const filetype = ref<'image' | 'video' | 'audio' | 'module' | 'flash' | null>(null); const noteSearchableScope = instance.noteSearchableScope ?? 'local'; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 5d252fc030..816641bf76 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -25029,7 +25029,8 @@ export type operations = { offset?: number; /** @description The local host is represented with `.`. */ host?: string; - filetype?: string | null; + /** @enum {string|null} */ + filetype?: 'image' | 'video' | 'audio' | 'module' | 'flash'; /** * Format: misskey:id * @default null diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index 57889ec995..8118227ce1 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -458,3 +458,14 @@ genKeys: "Generate Keys" _genKeysDialog: text: "Are you sure that you want to generate new keys? This will stop push notifications for all users who have already enabled them." title: "Generate new keys" + +_noteSearch: + newestToOldest: "Sort by newest to oldest" + fileType: "File Type" + _fileType: + none: "None" + image: "Images" + video: "Videos" + audio: "Audio" + module: "Module" + flash: "Flash" -- cgit v1.2.3-freya From a95bfb7241ca2ea139413fae92e65e5ae5aaf225 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 13 Feb 2025 06:57:43 -0500 Subject: search notes with denormalized userHost field --- packages/backend/src/core/SearchService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index a8c6ac61f3..9fb057e7b2 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -287,9 +287,9 @@ export class SearchService { if (opts.host) { if (opts.host === '.') { - query.andWhere('user.host IS NULL'); + query.andWhere('note.userHost IS NULL'); } else { - query.andWhere('user.host = :host', { host: opts.host }); + query.andWhere('note.userHost = :host', { host: opts.host }); } } -- cgit v1.2.3-freya From c8bb7b9816eeba22edb762f9ff2a23ed6af1e328 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 13 Feb 2025 17:40:32 -0500 Subject: fix error when searching without a specified host --- packages/backend/src/core/SearchService.ts | 4 ++-- packages/misskey-js/src/autogen/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index a8c6ac61f3..e60c75c6cc 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -112,7 +112,7 @@ const fileTypes = { }; // Make sure to regenerate misskey-js and check search.note.vue after changing these -export const fileTypeCategories = ['image', 'video', 'audio', 'module', 'flash'] as const; +export const fileTypeCategories = ['image', 'video', 'audio', 'module', 'flash', null] as const; export type FileTypeCategory = typeof fileTypeCategories[number]; @Injectable() @@ -217,7 +217,7 @@ export class SearchService { userId?: MiNote['userId'] | null; channelId?: MiNote['channelId'] | null; host?: string | null; - filetype?: FileTypeCategory | null; + filetype?: FileTypeCategory; order?: string | null; disableMeili?: boolean | null; }, pagination: { diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 816641bf76..b5d64d6286 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -25030,7 +25030,7 @@ export type operations = { /** @description The local host is represented with `.`. */ host?: string; /** @enum {string|null} */ - filetype?: 'image' | 'video' | 'audio' | 'module' | 'flash'; + filetype?: 'image' | 'video' | 'audio' | 'module' | 'flash' | null; /** * Format: misskey:id * @default null -- cgit v1.2.3-freya From 493000290b830eda62a10f372a9ccc24e1e24d72 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 14 Feb 2025 20:16:39 -0500 Subject: fix performance of SQL LIKE note search --- packages/backend/src/core/SearchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 5794d27d4a..d16f9eb6bd 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -281,7 +281,7 @@ export class SearchService { if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { query.andWhere('note.text &@~ :q', { q }); } else { - query.andWhere('LOWER(note.text) LIKE :q', { q: `%${ sqlLikeEscape(q.toLowerCase()) }%` }); + query.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }); } if (opts.host) { -- cgit v1.2.3-freya From 949664147e50c489af5af9ce34b570df678b688d Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 16 Feb 2025 09:31:47 +0000 Subject: fill `myReaction` in more cases - may fix !944 * first of all, fetch from the buffer all the notes we may need (including replied-to ones) * then, distinguish between "we know there's no myReaction" and "we don't know if there's any myReaction" * finally, look at both a note and the note it's replying to --- .../backend/src/core/entities/NoteEntityService.ts | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index be45e75d74..b3cd191ad2 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -42,8 +42,14 @@ function getAppearNoteIds(notes: MiNote[]): Set { for (const note of notes) { if (isPureRenote(note)) { appearNoteIds.add(note.renoteId); + if (note.renote?.replyId) { + appearNoteIds.add(note.renote.replyId); + } } else { appearNoteIds.add(note.id); + if (note.replyId) { + appearNoteIds.add(note.replyId); + } } } return appearNoteIds; @@ -272,7 +278,9 @@ export class NoteEntityService implements OnModuleInit { const reaction = _hint_.myReactions.get(note.id); if (reaction) { return this.reactionService.convertLegacyReaction(reaction); - } else { + } else if (reaction === null) { + // the hints explicitly say this note has no reactions from + // this user return undefined; } } @@ -525,9 +533,6 @@ export class NoteEntityService implements OnModuleInit { if (meId) { const idsNeedFetchMyReaction = new Set(); - // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない - const oldId = this.idService.gen(Date.now() - 2000); - const targetNotes: MiNote[] = []; for (const note of notes) { if (isPureRenote(note)) { @@ -537,15 +542,13 @@ export class NoteEntityService implements OnModuleInit { // idem if the renote is also a reply. targetNotes.push(note.renote.reply); } - } else if (note.reply) { - // idem for OP of a regular reply. - targetNotes.push(note.reply); } else { - if (note.id < oldId) { - targetNotes.push(note); - } else { - myReactionsMap.set(note.id, null); + if (note.reply) { + // idem for OP of a regular reply. + targetNotes.push(note.reply); } + + targetNotes.push(note); } } -- cgit v1.2.3-freya From 447534d0949dec2e65aea1251e95e03780ef0f97 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 16 Feb 2025 02:32:50 -0500 Subject: support Peertube preview thumbnails --- .../src/core/activitypub/models/ApImageService.ts | 2 +- .../src/core/activitypub/models/ApNoteService.ts | 36 +++++++++++++++++++++- packages/backend/src/core/activitypub/type.ts | 2 ++ 3 files changed, 38 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 423044b985..383c77a177 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -18,7 +18,7 @@ import type { Config } from '@/config.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; -import { isDocument, type IObject } from '../type.js'; +import { isDocument, type IObject, isApObject } from '../type.js'; @Injectable() export class ApImageService { diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 9fc6945edb..2995b1e764 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -25,7 +25,7 @@ import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; import { checkHttps } from '@/misc/check-https.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; +import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType, isApObject, isDocument, IApDocument } from '../type.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApMfmService } from '../ApMfmService.js'; import { ApDbResolverService } from '../ApDbResolverService.js'; @@ -270,6 +270,14 @@ export class ApNoteService { if (file) files.push(file); } + // Some software (Peertube) attaches a thumbnail under "icon" instead of "attachment" + const icon = getBestIcon(note); + if (icon) { + icon.sensitive ??= note.sensitive; + const file = await this.apImageService.resolveImage(actor, icon); + if (file) files.push(file); + } + // リプライ const reply: MiNote | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, { resolver }) @@ -504,6 +512,14 @@ export class ApNoteService { if (file) files.push(file); } + // Some software (Peertube) attaches a thumbnail under "icon" instead of "attachment" + const icon = getBestIcon(note); + if (icon) { + icon.sensitive ??= note.sensitive; + const file = await this.apImageService.resolveImage(actor, icon); + if (file) files.push(file); + } + // リプライ const reply: MiNote | null = note.inReplyTo ? await this.resolveNote(note.inReplyTo, { resolver }) @@ -719,3 +735,21 @@ export class ApNoteService { })); } } + +function getBestIcon(note: IObject): IObject | null { + const icons: IObject[] = toArray(note.icon); + if (icons.length < 2) { + return icons[0] ?? null; + } + + return icons.reduce((best, i) => { + if (!isApObject(i)) return best; + if (!isDocument(i)) return best; + if (!best) return i; + if (!best.width || !best.height) return i; + if (!i.width || !i.height) return best; + if (i.width > best.width) return i; + if (i.height > best.height) return i; + return best; + }, null as IApDocument | null) ?? null; +} diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 4e0f131647..731a46c0d4 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -297,6 +297,8 @@ export const validDocumentTypes = ['Audio', 'Document', 'Image', 'Page', 'Video' export interface IApDocument extends IObject { type: 'Audio' | 'Document' | 'Image' | 'Page' | 'Video'; + width?: number; + height?: number; } export const isDocument = (object: IObject): object is IApDocument => { -- cgit v1.2.3-freya From 6d459cdf8054dcf2751d5347eb264a412bfada32 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 16 Feb 2025 17:46:49 +0000 Subject: Update ApImageService.ts --- packages/backend/src/core/activitypub/models/ApImageService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index 383c77a177..423044b985 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -18,7 +18,7 @@ import type { Config } from '@/config.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { ApResolverService } from '../ApResolverService.js'; import { ApLoggerService } from '../ApLoggerService.js'; -import { isDocument, type IObject, isApObject } from '../type.js'; +import { isDocument, type IObject } from '../type.js'; @Injectable() export class ApImageService { -- cgit v1.2.3-freya From 3ab310e4727f04a2c2f17ef3881075dbe1daf5cb Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 16 Feb 2025 01:29:02 -0500 Subject: relax validation of Announce(Note) timestamps --- packages/backend/src/core/activitypub/ApInboxService.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 278c97f907..1eef85aeef 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -363,10 +363,12 @@ export class ApInboxService { this.logger.info(`Creating the (Re)Note: ${uri}`); const activityAudience = await this.apAudienceService.parseAudience(actor, activity.to, activity.cc, resolver); - const createdAt = activity.published ? new Date(activity.published) : null; + let createdAt = activity.published ? new Date(activity.published) : null; - if (createdAt && createdAt < this.idService.parse(renote.id).date) { - return 'skip: malformed createdAt'; + const renoteDate = this.idService.parse(renote.id).date; + if (createdAt && createdAt < renoteDate) { + this.logger.warn(`Correcting invalid publish time for Announce "${uri}"`); + createdAt = renoteDate; } await this.noteCreateService.create(actor, { -- cgit v1.2.3-freya From feeaf26641e9c5c8343d4ae3a157792495ed9d6c Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 28 Jan 2025 00:29:27 -0500 Subject: fix lint errors in NoteCreateService & NoteEditService --- packages/backend/src/core/NoteCreateService.ts | 4 ++-- packages/backend/src/core/NoteEditService.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 3bfced1d80..f24c665659 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -486,10 +486,10 @@ export class NoteCreateService implements OnApplicationShutdown { // should really not happen, but better safe than sorry if (data.reply?.id === insert.id) { - throw new Error("A note can't reply to itself"); + throw new Error('A note can\'t reply to itself'); } if (data.renote?.id === insert.id) { - throw new Error("A note can't renote itself"); + throw new Error('A note can\'t renote itself'); } if (data.uri != null) insert.uri = data.uri; diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 453ad5d9d0..18912181d7 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -309,7 +309,7 @@ export class NoteEditService implements OnApplicationShutdown { if (this.isRenote(data)) { if (data.renote.id === oldnote.id) { - throw new Error("A note can't renote itself"); + throw new Error('A note can\'t renote itself'); } switch (data.renote.visibility) { -- cgit v1.2.3-freya From 2bf8648ebc547f6ca335392b7fe20899f1b53862 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 28 Jan 2025 01:41:10 -0500 Subject: refresh cache when marking a user as NSFW --- packages/backend/src/server/api/endpoints/admin/nsfw-user.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts b/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts index d3fa4251dd..f64ba7f48a 100644 --- a/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/nsfw-user.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import type { UsersRepository, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; +import { CacheService } from '@/core/CacheService.js'; export const meta = { tags: ['admin'], @@ -28,10 +29,12 @@ export const paramDef = { export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( @Inject(DI.usersRepository) - private usersRepository: UsersRepository, + private readonly usersRepository: UsersRepository, @Inject(DI.userProfilesRepository) - private userProfilesRepository: UserProfilesRepository, + private readonly userProfilesRepository: UserProfilesRepository, + + private readonly cacheService: CacheService, ) { super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneBy({ id: ps.userId }); @@ -43,6 +46,8 @@ export default class extends Endpoint { // eslint- await this.userProfilesRepository.update(user.id, { alwaysMarkNsfw: true, }); + + await this.cacheService.userProfileCache.refresh(ps.userId); }); } } -- cgit v1.2.3-freya From ea89348b62706c4f6fbeaf603fc73d1b9874e7d0 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 28 Jan 2025 01:47:03 -0500 Subject: add user-level "force content warning" moderation feature --- locales/index.d.ts | 8 + .../1738043621143-add_user_mandatoryCW.js | 11 + packages/backend/src/core/NoteCreateService.ts | 11 + packages/backend/src/core/NoteEditService.ts | 10 + packages/backend/src/models/User.ts | 9 + .../src/server/api/endpoints/admin/cw-user.ts | 53 ++ .../src/server/api/endpoints/admin/show-user.ts | 5 + packages/frontend/src/pages/admin-user.vue | 16 + packages/misskey-js/src/autogen/apiClientJSDoc.ts | 814 ++++++++++----------- packages/misskey-js/src/consts.ts | 1 + sharkey-locales/en-US.yml | 3 + 11 files changed, 534 insertions(+), 407 deletions(-) create mode 100644 packages/backend/migration/1738043621143-add_user_mandatoryCW.js create mode 100644 packages/backend/src/server/api/endpoints/admin/cw-user.ts (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index 9624b48b42..65e8096403 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -12089,6 +12089,14 @@ export interface Locale extends ILocale { * ID */ "id": string; + /** + * Force content warning + */ + "mandatoryCW": string; + /** + * Applies a content warning to all posts created by this user. If the post already has a CW, then this is appended to the end. + */ + "mandatoryCWDescription": string; } declare const locales: { [lang: string]: Locale; diff --git a/packages/backend/migration/1738043621143-add_user_mandatoryCW.js b/packages/backend/migration/1738043621143-add_user_mandatoryCW.js new file mode 100644 index 0000000000..dd05076dd2 --- /dev/null +++ b/packages/backend/migration/1738043621143-add_user_mandatoryCW.js @@ -0,0 +1,11 @@ +export class AddUserMandatoryCW1738043621143 { + name = 'AddUserCW1738043621143' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "mandatoryCW" text`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "mandatoryCW"`); + } +} diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index f24c665659..ecf711e011 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -234,6 +234,7 @@ export class NoteCreateService implements OnApplicationShutdown { host: MiUser['host']; isBot: MiUser['isBot']; noindex: MiUser['noindex']; + mandatoryCW: MiUser['mandatoryCW']; }, data: Option, silent = false): Promise { // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) @@ -368,6 +369,15 @@ export class NoteCreateService implements OnApplicationShutdown { data.cw = null; } + // Apply mandatory CW, if applicable + if (user.mandatoryCW) { + if (data.cw) { + data.cw += `, ${user.mandatoryCW}`; + } else { + data.cw = user.mandatoryCW; + } + } + let tags = data.apHashtags; let emojis = data.apEmojis; let mentionedUsers = data.apMentions; @@ -441,6 +451,7 @@ export class NoteCreateService implements OnApplicationShutdown { host: MiUser['host']; isBot: MiUser['isBot']; noindex: MiUser['noindex']; + mandatoryCW: MiUser['mandatoryCW']; }, data: Option): Promise { return this.create(user, data, true); } diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 18912181d7..1f947aaffb 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -230,6 +230,7 @@ export class NoteEditService implements OnApplicationShutdown { host: MiUser['host']; isBot: MiUser['isBot']; noindex: MiUser['noindex']; + mandatoryCW: MiUser['mandatoryCW']; }, editid: MiNote['id'], data: Option, silent = false): Promise { if (!editid) { throw new Error('fail'); @@ -396,6 +397,15 @@ export class NoteEditService implements OnApplicationShutdown { data.cw = null; } + // Apply mandatory CW, if applicable + if (user.mandatoryCW) { + if (data.cw) { + data.cw += `, ${user.mandatoryCW}`; + } else { + data.cw = user.mandatoryCW; + } + } + let tags = data.apHashtags; let emojis = data.apEmojis; let mentionedUsers = data.apMentions; diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 3a825d36a7..8a3ad1003d 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -339,6 +339,15 @@ export class MiUser { }) public enableRss: boolean; + /** + * Specifies a Content Warning that should be forcibly applied to all notes by this user. + * If null (default), then no Content Warning is applied. + */ + @Column('text', { + nullable: true, + }) + public mandatoryCW: string | null; + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/server/api/endpoints/admin/cw-user.ts b/packages/backend/src/server/api/endpoints/admin/cw-user.ts new file mode 100644 index 0000000000..d48ca565a4 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/cw-user.ts @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { CacheService } from '@/core/CacheService.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'write:admin:cw-user', +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + cw: { type: 'string', nullable: true }, + }, + required: ['userId', 'cw'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.usersRepository) + private readonly usersRepository: UsersRepository, + + private readonly globalEventService: GlobalEventService, + ) { + super(meta, paramDef, async ps => { + const result = await this.usersRepository.update(ps.userId, { + // Collapse empty strings to null + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + mandatoryCW: ps.cw || null, + }); + + if (result.affected && result.affected < 1) { + throw new Error('No such user'); + } + + // Synchronize caches and other processes + this.globalEventService.publishInternalEvent('localUserUpdated', { id: ps.userId }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 669bffe2dc..0f0b0f8e7a 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -144,6 +144,10 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + mandatoryCW: { + type: 'string', + optional: false, nullable: true, + }, signins: { type: 'array', optional: false, nullable: false, @@ -260,6 +264,7 @@ export default class extends Endpoint { // eslint- isHibernated: user.isHibernated, lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null, moderationNote: profile.moderationNote ?? '', + mandatoryCW: user.mandatoryCW, signins, policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me), diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 11a34d34ef..e21db84334 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -83,6 +83,11 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.suspend }} {{ i18n.ts.markAsNSFW }} + + + + +
{{ i18n.ts.resetPassword }}
@@ -222,6 +227,7 @@ import { i18n } from '@/i18n.js'; import { iAmAdmin, $i, iAmModerator } from '@/account.js'; import MkRolePreview from '@/components/MkRolePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; +import MkInput from '@/components/MkInput.vue'; const props = withDefaults(defineProps<{ userId: string; @@ -243,6 +249,7 @@ const approved = ref(false); const suspended = ref(false); const markedAsNSFW = ref(false); const moderationNote = ref(''); +const mandatoryCW = ref(null); const isSystem = computed(() => info.value?.isSystem ?? false); const filesPagination = { endpoint: 'admin/drive/files' as const, @@ -281,6 +288,15 @@ function createFetcher() { markedAsNSFW.value = info.value.alwaysMarkNsfw; suspended.value = info.value.isSuspended; moderationNote.value = info.value.moderationNote; + mandatoryCW.value = info.value.mandatoryCW; + + // These watch statements work because they're lazy-initialized. + // The watched values are already set, so they don't trigger any "change" just from refreshing the user. + + watch(mandatoryCW, async () => { + await os.apiWithDialog('admin/cw-user', { userId: props.userId, cw: mandatoryCW.value }); + refreshUser(); + }); watch(moderationNote, async () => { await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value }); diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index f4120b3afc..5e47ad15ad 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -5,7 +5,7 @@ declare module '../api.js' { export interface APIClient { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ @@ -17,7 +17,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ @@ -29,7 +29,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-report:notification-recipient* */ @@ -41,7 +41,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-report:notification-recipient* */ @@ -53,7 +53,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ @@ -65,7 +65,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* */ request( @@ -76,7 +76,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -87,7 +87,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:account* */ request( @@ -98,7 +98,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:account* */ request( @@ -109,7 +109,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ request( @@ -120,7 +120,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ request( @@ -131,7 +131,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:ad* */ request( @@ -142,7 +142,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ request( @@ -153,7 +153,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ request( @@ -164,7 +164,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ request( @@ -175,7 +175,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:announcements* */ request( @@ -186,7 +186,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ request( @@ -197,7 +197,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:approve-user* */ request( @@ -208,7 +208,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ request( @@ -219,7 +219,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ request( @@ -230,7 +230,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:avatar-decorations* */ request( @@ -241,7 +241,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ request( @@ -252,7 +252,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:meta* */ request( @@ -263,7 +263,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ request( @@ -274,7 +274,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:decline-user* */ request( @@ -285,7 +285,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* */ request( @@ -296,7 +296,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* */ request( @@ -307,7 +307,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ request( @@ -318,7 +318,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ request( @@ -329,7 +329,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ request( @@ -340,7 +340,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ request( @@ -351,7 +351,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -362,7 +362,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -373,7 +373,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -384,7 +384,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -395,7 +395,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -406,7 +406,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -418,7 +418,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( @@ -429,7 +429,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( @@ -440,7 +440,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -451,7 +451,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -462,7 +462,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -473,7 +473,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -484,7 +484,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -495,7 +495,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -506,7 +506,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -517,7 +517,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -528,7 +528,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -539,7 +539,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ request( @@ -550,7 +550,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ request( @@ -561,7 +561,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:index-stats* */ request( @@ -572,7 +572,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:table-stats* */ request( @@ -583,7 +583,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:user-ips* */ request( @@ -594,7 +594,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:invite-codes* */ request( @@ -605,7 +605,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:invite-codes* */ request( @@ -616,7 +616,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:meta* */ request( @@ -627,7 +627,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user* */ request( @@ -638,7 +638,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:promo* */ request( @@ -649,7 +649,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ request( @@ -660,7 +660,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ request( @@ -671,7 +671,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ request( @@ -682,7 +682,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ request( @@ -693,7 +693,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( @@ -704,7 +704,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ request( @@ -715,7 +715,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:relays* */ request( @@ -726,7 +726,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ request( @@ -737,7 +737,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:reset-password* */ request( @@ -748,7 +748,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ request( @@ -759,7 +759,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -770,7 +770,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -781,7 +781,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -792,7 +792,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ request( @@ -803,7 +803,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ request( @@ -814,7 +814,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -825,7 +825,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -836,7 +836,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -847,7 +847,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:admin:roles* */ request( @@ -858,7 +858,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* */ request( @@ -869,7 +869,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* */ request( @@ -880,7 +880,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* */ request( @@ -891,7 +891,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ request( @@ -902,7 +902,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ request( @@ -913,7 +913,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:silence-user* */ request( @@ -924,7 +924,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* */ request( @@ -935,7 +935,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -947,7 +947,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -959,7 +959,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -971,7 +971,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -983,7 +983,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* */ @@ -995,7 +995,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -1007,7 +1007,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user* */ request( @@ -1018,7 +1018,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* */ request( @@ -1029,7 +1029,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* */ request( @@ -1040,7 +1040,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user* */ request( @@ -1051,7 +1051,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* */ request( @@ -1062,7 +1062,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ request( @@ -1073,7 +1073,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ request( @@ -1084,7 +1084,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* */ request( @@ -1095,7 +1095,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1106,7 +1106,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1117,7 +1117,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1128,7 +1128,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1139,7 +1139,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1150,7 +1150,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1161,7 +1161,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1172,7 +1172,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1183,7 +1183,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:federation* */ request( @@ -1194,7 +1194,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1205,7 +1205,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1216,7 +1216,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1227,7 +1227,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -1239,7 +1239,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1250,7 +1250,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1261,7 +1261,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1272,7 +1272,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ request( @@ -1283,7 +1283,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ request( @@ -1294,7 +1294,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:blocks* */ request( @@ -1305,7 +1305,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1316,7 +1316,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1327,7 +1327,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1338,7 +1338,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1349,7 +1349,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1360,7 +1360,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1371,7 +1371,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:channels* */ request( @@ -1382,7 +1382,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:channels* */ request( @@ -1393,7 +1393,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:channels* */ request( @@ -1404,7 +1404,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1415,7 +1415,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1426,7 +1426,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1437,7 +1437,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1448,7 +1448,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1459,7 +1459,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1470,7 +1470,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1481,7 +1481,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1492,7 +1492,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1503,7 +1503,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1514,7 +1514,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1525,7 +1525,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1536,7 +1536,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1547,7 +1547,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1558,7 +1558,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1569,7 +1569,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1580,7 +1580,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1591,7 +1591,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1602,7 +1602,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1613,7 +1613,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1624,7 +1624,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1635,7 +1635,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ request( @@ -1646,7 +1646,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1657,7 +1657,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* */ request( @@ -1668,7 +1668,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -1679,7 +1679,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1690,7 +1690,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -1701,7 +1701,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ request( @@ -1712,7 +1712,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1723,7 +1723,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1734,7 +1734,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1745,7 +1745,7 @@ declare module '../api.js' { /** * Find the notes to which the given file is attached. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1756,7 +1756,7 @@ declare module '../api.js' { /** * Check if a given file exists. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1767,7 +1767,7 @@ declare module '../api.js' { /** * Upload a new drive file. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1778,7 +1778,7 @@ declare module '../api.js' { /** * Delete an existing drive file. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1789,7 +1789,7 @@ declare module '../api.js' { /** * Search for a drive file by the given parameters. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1800,7 +1800,7 @@ declare module '../api.js' { /** * Search for a drive file by a hash of the contents. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1811,7 +1811,7 @@ declare module '../api.js' { /** * Show the properties of a drive file. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1822,7 +1822,7 @@ declare module '../api.js' { /** * Update the properties of a drive file. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1833,7 +1833,7 @@ declare module '../api.js' { /** * Request the server to download a new drive file from the specified URL. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1844,7 +1844,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1855,7 +1855,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1866,7 +1866,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1877,7 +1877,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1888,7 +1888,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1899,7 +1899,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1910,7 +1910,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1921,7 +1921,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1932,7 +1932,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1943,7 +1943,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1954,7 +1954,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1965,7 +1965,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1976,7 +1976,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -1988,7 +1988,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1999,7 +1999,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2010,7 +2010,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2021,7 +2021,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2032,7 +2032,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2043,7 +2043,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2054,7 +2054,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2065,7 +2065,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2077,7 +2077,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2088,7 +2088,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash* */ request( @@ -2099,7 +2099,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash* */ request( @@ -2110,7 +2110,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2121,7 +2121,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ request( @@ -2132,7 +2132,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:flash* */ request( @@ -2143,7 +2143,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:flash-likes* */ request( @@ -2154,7 +2154,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2165,7 +2165,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ request( @@ -2176,7 +2176,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash* */ request( @@ -2187,7 +2187,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2198,7 +2198,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2209,7 +2209,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2220,7 +2220,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2231,7 +2231,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2242,7 +2242,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:following* */ request( @@ -2253,7 +2253,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2264,7 +2264,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:following* */ request( @@ -2275,7 +2275,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2286,7 +2286,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2297,7 +2297,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2308,7 +2308,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2319,7 +2319,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2330,7 +2330,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ request( @@ -2341,7 +2341,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ request( @@ -2352,7 +2352,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ request( @@ -2363,7 +2363,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2374,7 +2374,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ request( @@ -2385,7 +2385,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ request( @@ -2396,7 +2396,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2407,7 +2407,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2418,7 +2418,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2429,7 +2429,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2440,7 +2440,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2451,7 +2451,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2462,7 +2462,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2473,7 +2473,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2484,7 +2484,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2496,7 +2496,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2508,7 +2508,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2520,7 +2520,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2532,7 +2532,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2544,7 +2544,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2556,7 +2556,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2568,7 +2568,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2580,7 +2580,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2592,7 +2592,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2604,7 +2604,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2616,7 +2616,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2627,7 +2627,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2639,7 +2639,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2651,7 +2651,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2663,7 +2663,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2675,7 +2675,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2687,7 +2687,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2699,7 +2699,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2711,7 +2711,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2723,7 +2723,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2735,7 +2735,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2747,7 +2747,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:favorites* */ request( @@ -2758,7 +2758,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:gallery-likes* */ request( @@ -2769,7 +2769,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:gallery* */ request( @@ -2780,7 +2780,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2792,7 +2792,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2804,7 +2804,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2816,7 +2816,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2828,7 +2828,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2840,7 +2840,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2852,7 +2852,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2864,7 +2864,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ request( @@ -2875,7 +2875,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ request( @@ -2886,7 +2886,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:page-likes* */ request( @@ -2897,7 +2897,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:pages* */ request( @@ -2908,7 +2908,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2919,7 +2919,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2930,7 +2930,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2941,7 +2941,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2953,7 +2953,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2964,7 +2964,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2975,7 +2975,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2986,7 +2986,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2997,7 +2997,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3008,7 +3008,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3019,7 +3019,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3030,7 +3030,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3042,7 +3042,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3053,7 +3053,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3065,7 +3065,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3077,7 +3077,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3088,7 +3088,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3099,7 +3099,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3111,7 +3111,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3122,7 +3122,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3133,7 +3133,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3144,7 +3144,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3155,7 +3155,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:account* */ @@ -3167,7 +3167,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3178,7 +3178,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ request( @@ -3189,7 +3189,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ request( @@ -3200,7 +3200,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ request( @@ -3211,7 +3211,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ request( @@ -3222,7 +3222,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3233,7 +3233,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3245,7 +3245,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3256,7 +3256,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3267,7 +3267,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ request( @@ -3278,7 +3278,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3289,7 +3289,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3300,7 +3300,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3311,7 +3311,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3322,7 +3322,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3333,7 +3333,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3344,7 +3344,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3355,7 +3355,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3366,7 +3366,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3377,7 +3377,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ request( @@ -3388,7 +3388,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ request( @@ -3399,7 +3399,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3410,7 +3410,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3421,7 +3421,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3432,7 +3432,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3443,7 +3443,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ request( @@ -3454,7 +3454,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3465,7 +3465,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3476,7 +3476,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3487,7 +3487,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:federation* */ request( @@ -3498,7 +3498,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:votes* */ request( @@ -3509,7 +3509,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3520,7 +3520,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ request( @@ -3531,7 +3531,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ request( @@ -3542,7 +3542,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3553,7 +3553,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3564,7 +3564,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes-schedule* */ request( @@ -3575,7 +3575,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes-schedule* */ request( @@ -3586,7 +3586,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:notes-schedule* */ request( @@ -3597,7 +3597,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3608,7 +3608,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3619,7 +3619,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3630,7 +3630,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3641,7 +3641,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3652,7 +3652,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3663,7 +3663,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3674,7 +3674,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3685,7 +3685,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3696,7 +3696,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3707,7 +3707,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3718,7 +3718,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3729,7 +3729,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3740,7 +3740,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3751,7 +3751,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3762,7 +3762,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3774,7 +3774,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:pages* */ request( @@ -3785,7 +3785,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:pages* */ request( @@ -3796,7 +3796,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3807,7 +3807,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ request( @@ -3818,7 +3818,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3829,7 +3829,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ request( @@ -3840,7 +3840,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:pages* */ request( @@ -3851,7 +3851,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3862,7 +3862,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3873,7 +3873,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3884,7 +3884,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3895,7 +3895,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3906,7 +3906,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ request( @@ -3917,7 +3917,7 @@ declare module '../api.js' { /** * Request a users password to be reset. - * + * * **Credential required**: *No* */ request( @@ -3928,7 +3928,7 @@ declare module '../api.js' { /** * Only available when running with NODE_ENV=testing. Reset the database and flush Redis. - * + * * **Credential required**: *No* */ request( @@ -3939,7 +3939,7 @@ declare module '../api.js' { /** * Complete the password reset that was previously requested. - * + * * **Credential required**: *No* */ request( @@ -3950,7 +3950,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3961,7 +3961,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3972,7 +3972,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3983,7 +3983,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3994,7 +3994,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4005,7 +4005,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4016,7 +4016,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4027,7 +4027,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4038,7 +4038,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4049,7 +4049,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4060,7 +4060,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4071,7 +4071,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4082,7 +4082,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4093,7 +4093,7 @@ declare module '../api.js' { /** * Get Sharkey Sponsors or Instance Sponsors - * + * * **Credential required**: *No* */ request( @@ -4104,7 +4104,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4115,7 +4115,7 @@ declare module '../api.js' { /** * Register to receive push notifications. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -4127,7 +4127,7 @@ declare module '../api.js' { /** * Check push notification registration exists. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -4139,7 +4139,7 @@ declare module '../api.js' { /** * Unregister from receiving push notifications. - * + * * **Credential required**: *No* */ request( @@ -4150,7 +4150,7 @@ declare module '../api.js' { /** * Update push notification registration. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -4162,7 +4162,7 @@ declare module '../api.js' { /** * Endpoint for testing input validation. - * + * * **Credential required**: *No* */ request( @@ -4173,7 +4173,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4184,7 +4184,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4195,7 +4195,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4206,7 +4206,7 @@ declare module '../api.js' { /** * Show all clips this user owns. - * + * * **Credential required**: *No* */ request( @@ -4217,7 +4217,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4228,7 +4228,7 @@ declare module '../api.js' { /** * Show all flashs this user created. - * + * * **Credential required**: *No* */ request( @@ -4239,7 +4239,7 @@ declare module '../api.js' { /** * Show everyone that follows this user. - * + * * **Credential required**: *No* */ request( @@ -4250,7 +4250,7 @@ declare module '../api.js' { /** * Show everyone that this user is following. - * + * * **Credential required**: *No* */ request( @@ -4261,7 +4261,7 @@ declare module '../api.js' { /** * Show all gallery posts by the given user. - * + * * **Credential required**: *No* */ request( @@ -4272,7 +4272,7 @@ declare module '../api.js' { /** * Get a list of other users that the specified user frequently replies to. - * + * * **Credential required**: *No* */ request( @@ -4283,7 +4283,7 @@ declare module '../api.js' { /** * Create a new list of users. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4294,7 +4294,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4305,7 +4305,7 @@ declare module '../api.js' { /** * Delete an existing list of users. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4316,7 +4316,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4327,7 +4327,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -4338,7 +4338,7 @@ declare module '../api.js' { /** * Show all lists that the authenticated user has created. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -4349,7 +4349,7 @@ declare module '../api.js' { /** * Remove a user from a list. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4360,7 +4360,7 @@ declare module '../api.js' { /** * Add a user to an existing list. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4371,7 +4371,7 @@ declare module '../api.js' { /** * Show the properties of a list. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -4382,7 +4382,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4393,7 +4393,7 @@ declare module '../api.js' { /** * Update the properties of a list. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4404,7 +4404,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4415,7 +4415,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4426,7 +4426,7 @@ declare module '../api.js' { /** * Show all pages this user created. - * + * * **Credential required**: *No* */ request( @@ -4437,7 +4437,7 @@ declare module '../api.js' { /** * Show all reactions this user made. - * + * * **Credential required**: *No* */ request( @@ -4448,7 +4448,7 @@ declare module '../api.js' { /** * Show users that the authenticated user might be interested to follow. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4459,7 +4459,7 @@ declare module '../api.js' { /** * Show the different kinds of relations between the authenticated user and the specified user(s). - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4470,7 +4470,7 @@ declare module '../api.js' { /** * File a report. - * + * * **Credential required**: *Yes* / **Permission**: *write:report-abuse* */ request( @@ -4481,7 +4481,7 @@ declare module '../api.js' { /** * Search for users. - * + * * **Credential required**: *No* */ request( @@ -4492,7 +4492,7 @@ declare module '../api.js' { /** * Search for a user by username and/or host. - * + * * **Credential required**: *No* */ request( @@ -4503,7 +4503,7 @@ declare module '../api.js' { /** * Show the properties of a user. - * + * * **Credential required**: *No* */ request( @@ -4514,7 +4514,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4525,7 +4525,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index 0faf3dddc4..1d4950ceea 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -83,6 +83,7 @@ export const permissions = [ 'write:admin:decline-user', 'write:admin:nsfw-user', 'write:admin:unnsfw-user', + 'write:admin:cw-user', 'write:admin:silence-user', 'write:admin:unsilence-user', 'write:admin:unset-user-avatar', diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index 86eafc8a33..b95a912a5f 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -471,3 +471,6 @@ _noteSearch: flash: "Flash" id: "ID" + +mandatoryCW: "Force content warning" +mandatoryCWDescription: "Applies a content warning to all posts created by this user. If the post already has a CW, then this is appended to the end." -- cgit v1.2.3-freya From 935a62f5e5fe443f92f20b7be093492de1a6d43a Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 28 Jan 2025 01:58:42 -0500 Subject: fix typescript errors in WebhookTestService.ts --- packages/backend/src/core/WebhookTestService.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index dfe7a259c4..4a45c11de1 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -99,6 +99,7 @@ function generateDummyUser(override?: Partial): MiUser { signupReason: null, noindex: false, enableRss: true, + mandatoryCW: null, ...override, }; } -- cgit v1.2.3-freya From 568d82a9746d3d67a756b13fc007beb057dcc011 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 28 Jan 2025 11:39:41 -0500 Subject: record ModLog entry when setting a user's content warning --- locales/index.d.ts | 4 ++++ .../src/server/api/endpoints/admin/cw-user.ts | 26 +++++++++++++++++----- packages/backend/src/types.ts | 8 +++++++ .../frontend/src/pages/admin/modlog.ModLog.vue | 8 +++++++ packages/misskey-js/etc/misskey-js.api.md | 5 ++++- packages/misskey-js/src/consts.ts | 8 +++++++ packages/misskey-js/src/entities.ts | 4 ++-- sharkey-locales/en-US.yml | 1 + 8 files changed, 55 insertions(+), 9 deletions(-) (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index 39feda5075..5b7df00f93 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -10218,6 +10218,10 @@ export interface Locale extends ILocale { * Declined */ "decline": string; + /** + * Set content warning for user + */ + "setMandatoryCW": string; /** * Set remote instance as NSFW */ diff --git a/packages/backend/src/server/api/endpoints/admin/cw-user.ts b/packages/backend/src/server/api/endpoints/admin/cw-user.ts index d48ca565a4..bdcfa6a0d9 100644 --- a/packages/backend/src/server/api/endpoints/admin/cw-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/cw-user.ts @@ -9,6 +9,7 @@ import type { UsersRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { CacheService } from '@/core/CacheService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; export const meta = { tags: ['admin'], @@ -34,18 +35,31 @@ export default class extends Endpoint { // eslint- private readonly usersRepository: UsersRepository, private readonly globalEventService: GlobalEventService, + private readonly cacheService: CacheService, + private readonly moderationLogService: ModerationLogService, ) { - super(meta, paramDef, async ps => { - const result = await this.usersRepository.update(ps.userId, { + super(meta, paramDef, async (ps, me) => { + const user = await this.cacheService.findUserById(ps.userId); + + // Skip if there's nothing to do + if (user.mandatoryCW === ps.cw) return; + + // Log event first. + // This ensures that we don't "lose" the log if an error occurs + await this.moderationLogService.log(me, 'setMandatoryCW', { + newCW: ps.cw, + oldCW: user.mandatoryCW, + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + await this.usersRepository.update(ps.userId, { // Collapse empty strings to null // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing mandatoryCW: ps.cw || null, }); - if (result.affected && result.affected < 1) { - throw new Error('No such user'); - } - // Synchronize caches and other processes this.globalEventService.publishInternalEvent('localUserUpdated', { id: ps.userId }); }); diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 067481d9da..b359fa5a39 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -100,6 +100,7 @@ export const moderationLogTypes = [ 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'resetPassword', + 'setMandatoryCW', 'setRemoteInstanceNSFW', 'unsetRemoteInstanceNSFW', 'suspendRemoteInstance', @@ -261,6 +262,13 @@ export type ModerationLogPayloads = { userUsername: string; userHost: string | null; }; + setMandatoryCW: { + newCW: string | null; + oldCW: string | null; + userId: string; + userUsername: string; + userHost: string | null; + }; setRemoteInstanceNSFW: { id: string; host: string; diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index 2e5c820054..741de875bc 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.logYellow]: [ 'markSensitiveDriveFile', 'resetPassword', + 'setMandatoryCW', 'suspendRemoteInstance', 'setRemoteInstanceNSFW', 'unsetRemoteInstanceNSFW', @@ -55,6 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} + : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} {{ log.info.roleName }} : @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} {{ log.info.roleName }} : {{ log.info.role.name }} @@ -123,6 +125,12 @@ SPDX-License-Identifier: AGPL-3.0-only + diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index ee8bfe322c..25921d14c8 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2609,6 +2609,9 @@ type ModerationLog = { } | { type: 'deleteUserAnnouncement'; info: ModerationLogPayloads['deleteUserAnnouncement']; +} | { + type: 'setMandatoryCW'; + info: ModerationLogPayloads['setMandatoryCW']; } | { type: 'setRemoteInstanceNSFW'; info: ModerationLogPayloads['setRemoteInstanceNSFW']; @@ -2708,7 +2711,7 @@ type ModerationLog = { }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "decline", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "setRemoteInstanceNSFW", "unsetRemoteInstanceNSFW", "suspendRemoteInstance", "unsuspendRemoteInstance", "rejectRemoteInstanceReports", "acceptRemoteInstanceReports", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "forwardAbuseReport", "updateAbuseReportNote", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "decline", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "setMandatoryCW", "setRemoteInstanceNSFW", "unsetRemoteInstanceNSFW", "suspendRemoteInstance", "unsuspendRemoteInstance", "rejectRemoteInstanceReports", "acceptRemoteInstanceReports", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "forwardAbuseReport", "updateAbuseReportNote", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost"]; // @public (undocumented) type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index fcb19be303..96da0a8fad 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -147,6 +147,7 @@ export const moderationLogTypes = [ 'deleteGlobalAnnouncement', 'deleteUserAnnouncement', 'resetPassword', + 'setMandatoryCW', 'setRemoteInstanceNSFW', 'unsetRemoteInstanceNSFW', 'suspendRemoteInstance', @@ -335,6 +336,13 @@ export type ModerationLogPayloads = { userUsername: string; userHost: string | null; }; + setMandatoryCW: { + newCW: string | null; + oldCW: string | null; + userId: string; + userUsername: string; + userHost: string | null; + }; setRemoteInstanceNSFW: { id: string; host: string; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index b92c2537a1..3e88eae275 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -118,8 +118,8 @@ export type ModerationLog = { type: 'deleteUserAnnouncement'; info: ModerationLogPayloads['deleteUserAnnouncement']; } | { - type: 'resetPassword'; - info: ModerationLogPayloads['resetPassword']; + type: 'setMandatoryCW'; + info: ModerationLogPayloads['setMandatoryCW']; } | { type: 'setRemoteInstanceNSFW'; info: ModerationLogPayloads['setRemoteInstanceNSFW']; diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index a12d84e2f5..84dd527694 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -304,6 +304,7 @@ _abuseReport: _moderationLogTypes: approve: "Approved" decline: "Declined" + setMandatoryCW: "Set content warning for user" setRemoteInstanceNSFW: "Set remote instance as NSFW" unsetRemoteInstanceNSFW: "Set remote instance as NSFW" rejectRemoteInstanceReports: "Rejected reports from remote instance" -- cgit v1.2.3-freya From b256ac32d0c3ec3a3bd61589f1d70ee0dd6afa1f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 28 Jan 2025 13:08:56 -0500 Subject: don't duplicate mandatory CWs --- packages/backend/src/core/NoteCreateService.ts | 6 +++--- packages/backend/src/core/NoteEditService.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index ecf711e011..10706f366d 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -371,10 +371,10 @@ export class NoteCreateService implements OnApplicationShutdown { // Apply mandatory CW, if applicable if (user.mandatoryCW) { - if (data.cw) { - data.cw += `, ${user.mandatoryCW}`; - } else { + if (!data.cw) { data.cw = user.mandatoryCW; + } else if (!data.cw.includes(user.mandatoryCW)) { + data.cw += `, ${user.mandatoryCW}`; } } diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 1f947aaffb..91d81c2965 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -399,10 +399,10 @@ export class NoteEditService implements OnApplicationShutdown { // Apply mandatory CW, if applicable if (user.mandatoryCW) { - if (data.cw) { - data.cw += `, ${user.mandatoryCW}`; - } else { + if (!data.cw) { data.cw = user.mandatoryCW; + } else if (!data.cw.includes(user.mandatoryCW)) { + data.cw += `, ${user.mandatoryCW}`; } } -- cgit v1.2.3-freya From 7814c6e54e18b02d30d0eebb7f0780e23a9e911e Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 13:59:49 -0500 Subject: remove mandatory CW logic from NoteCreateService and NoteEditService --- packages/backend/src/core/NoteCreateService.ts | 17 +++-------------- packages/backend/src/core/NoteEditService.ts | 10 ---------- 2 files changed, 3 insertions(+), 24 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 10706f366d..6564a64d30 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -234,7 +234,6 @@ export class NoteCreateService implements OnApplicationShutdown { host: MiUser['host']; isBot: MiUser['isBot']; noindex: MiUser['noindex']; - mandatoryCW: MiUser['mandatoryCW']; }, data: Option, silent = false): Promise { // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) @@ -369,15 +368,6 @@ export class NoteCreateService implements OnApplicationShutdown { data.cw = null; } - // Apply mandatory CW, if applicable - if (user.mandatoryCW) { - if (!data.cw) { - data.cw = user.mandatoryCW; - } else if (!data.cw.includes(user.mandatoryCW)) { - data.cw += `, ${user.mandatoryCW}`; - } - } - let tags = data.apHashtags; let emojis = data.apEmojis; let mentionedUsers = data.apMentions; @@ -451,7 +441,6 @@ export class NoteCreateService implements OnApplicationShutdown { host: MiUser['host']; isBot: MiUser['isBot']; noindex: MiUser['noindex']; - mandatoryCW: MiUser['mandatoryCW']; }, data: Option): Promise { return this.create(user, data, true); } @@ -764,7 +753,7 @@ export class NoteCreateService implements OnApplicationShutdown { //#region AP deliver if (!data.localOnly && this.userEntityService.isLocalUser(user)) { (async () => { - const noteActivity = await this.renderNoteOrRenoteActivity(data, note); + const noteActivity = await this.renderNoteOrRenoteActivity(data, note, user); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); // メンションされたリモートユーザーに配送 @@ -910,12 +899,12 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private async renderNoteOrRenoteActivity(data: Option, note: MiNote) { + private async renderNoteOrRenoteActivity(data: Option, note: MiNote, user: MiUser) { if (data.localOnly) return null; const content = this.isRenote(data) && !this.isQuote(data) ? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note) - : this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); + : this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, user, false), note); return this.apRendererService.addContext(content); } diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 91d81c2965..18912181d7 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -230,7 +230,6 @@ export class NoteEditService implements OnApplicationShutdown { host: MiUser['host']; isBot: MiUser['isBot']; noindex: MiUser['noindex']; - mandatoryCW: MiUser['mandatoryCW']; }, editid: MiNote['id'], data: Option, silent = false): Promise { if (!editid) { throw new Error('fail'); @@ -397,15 +396,6 @@ export class NoteEditService implements OnApplicationShutdown { data.cw = null; } - // Apply mandatory CW, if applicable - if (user.mandatoryCW) { - if (!data.cw) { - data.cw = user.mandatoryCW; - } else if (!data.cw.includes(user.mandatoryCW)) { - data.cw += `, ${user.mandatoryCW}`; - } - } - let tags = data.apHashtags; let emojis = data.apEmojis; let mentionedUsers = data.apMentions; -- cgit v1.2.3-freya From 563e32316f808995fe2500286ca8df8954ff8c14 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 14:12:13 -0500 Subject: factor out common append-content-warning routine for use in both frontend and backend --- .../backend/src/misc/append-content-warning.ts | 62 +++++++++++++++ .../test/unit/misc/append-content-warning.ts | 92 ++++++++++++++++++++++ .../frontend-shared/js/append-content-warning.ts | 62 +++++++++++++++ packages/frontend/src/components/MkPostForm.vue | 15 +--- 4 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 packages/backend/src/misc/append-content-warning.ts create mode 100644 packages/backend/test/unit/misc/append-content-warning.ts create mode 100644 packages/frontend-shared/js/append-content-warning.ts (limited to 'packages/backend/src') diff --git a/packages/backend/src/misc/append-content-warning.ts b/packages/backend/src/misc/append-content-warning.ts new file mode 100644 index 0000000000..152cd6760e --- /dev/null +++ b/packages/backend/src/misc/append-content-warning.ts @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* + * Important Note: this file must be kept in sync with packages/frontend-shared/js/append-content-warning.ts + */ + +/** + * Appends an additional content warning onto an existing one. + * The additional value will not be added if it already exists within the original input. + * @param original Existing content warning + * @param additional Content warning to append + * @param reverse If true, then the additional CW will be prepended instead of appended. + */ +export function appendContentWarning(original: string | null | undefined, additional: string, reverse = false): string { + // Easy case - if original is empty, then additional replaces it. + if (!original) { + return additional; + } + + // Easy case - if the additional CW is empty, then don't append it. + if (!additional) { + return original; + } + + // If the additional CW already exists in the input, then we *don't* append another copy! + if (includesWholeWord(original, additional)) { + return original; + } + + return reverse + ? `${additional}, ${original}` + : `${original}, ${additional}`; +} + +/** + * Emulates a regular expression like /\b(pattern)\b/, but with a raw non-regex pattern. + * We're checking to see whether the default CW appears inside the existing CW, but *only* if there's word boundaries on either side. + * @param input Input string to search + * @param target Target word / phrase to search for + */ +function includesWholeWord(input: string, target: string): boolean { + const parts = input.split(target); + + // The additional string could appear multiple times within the original input. + // We need to check each occurrence, since any of them could potentially match. + for (let i = 0; i + 1 < parts.length; i++) { + const before = parts[i]; + const after = parts[i + 1]; + + // If either the preceding or following tokens are a "word", then this "match" is actually just part of a longer word. + // Likewise, if *neither* token is a word, then this is a real match and the CW already exists in the input. + if (!/\w$/.test(before) && !/^\w/.test(after)) { + return true; + } + } + + // If we don't match, then there is no existing CW. + return false; +} diff --git a/packages/backend/test/unit/misc/append-content-warning.ts b/packages/backend/test/unit/misc/append-content-warning.ts new file mode 100644 index 0000000000..d25d7c4925 --- /dev/null +++ b/packages/backend/test/unit/misc/append-content-warning.ts @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { appendContentWarning } from '@/misc/append-content-warning.js'; + +describe(appendContentWarning, () => { + it('should return additional when original is null', () => { + const result = appendContentWarning(null, 'additional'); + + expect(result).toBe('additional'); + }); + + it('should return additional when original is undefined', () => { + const result = appendContentWarning(undefined, 'additional'); + + expect(result).toBe('additional'); + }); + + it('should return additional when original is empty', () => { + const result = appendContentWarning('', 'additional'); + + expect(result).toBe('additional'); + }); + + it('should return original when additional is empty', () => { + const result = appendContentWarning('original', ''); + + expect(result).toBe('original'); + }); + + it('should append additional when it does not exist in original', () => { + const result = appendContentWarning('original', 'additional'); + + expect(result).toBe('original, additional'); + }); + + it('should append additional when it exists in original but has preceeding word', () => { + const result = appendContentWarning('notadditional', 'additional'); + + expect(result).toBe('notadditional, additional'); + }); + + it('should append additional when it exists in original but has following word', () => { + const result = appendContentWarning('additionalnot', 'additional'); + + expect(result).toBe('additionalnot, additional'); + }); + + it('should append additional when it exists in original multiple times but has preceeding or following word', () => { + const result = appendContentWarning('notadditional additionalnot', 'additional'); + + expect(result).toBe('notadditional additionalnot, additional'); + }); + + it('should not append additional when it exists in original', () => { + const result = appendContentWarning('an additional word', 'additional'); + + expect(result).toBe('an additional word'); + }); + + it('should not append additional when original starts with it', () => { + const result = appendContentWarning('additional word', 'additional'); + + expect(result).toBe('additional word'); + }); + + it('should not append additional when original ends with it', () => { + const result = appendContentWarning('an additional', 'additional'); + + expect(result).toBe('an additional'); + }); + + it('should not append additional when it appears multiple times', () => { + const result = appendContentWarning('an additional additional word', 'additional'); + + expect(result).toBe('an additional additional word'); + }); + + it('should not append additional when it appears multiple times but some have preceeding or following', () => { + const result = appendContentWarning('a notadditional additional additionalnot word', 'additional'); + + expect(result).toBe('a notadditional additional additionalnot word'); + }); + + it('should prepend additional when reverse is true', () => { + const result = appendContentWarning('original', 'additional', true); + + expect(result).toBe('additional, original'); + }); +}); diff --git a/packages/frontend-shared/js/append-content-warning.ts b/packages/frontend-shared/js/append-content-warning.ts new file mode 100644 index 0000000000..7f24a66f23 --- /dev/null +++ b/packages/frontend-shared/js/append-content-warning.ts @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* + * Important Note: this file must be kept in sync with packages/backend/src/misc/append-content-warning.ts + */ + +/** + * Appends an additional content warning onto an existing one. + * The additional value will not be added if it already exists within the original input. + * @param original Existing content warning + * @param additional Content warning to append + * @param reverse If true, then the additional CW will be prepended instead of appended. + */ +export function appendContentWarning(original: string | null | undefined, additional: string, reverse = false): string { + // Easy case - if original is empty, then additional replaces it. + if (!original) { + return additional; + } + + // Easy case - if the additional CW is empty, then don't append it. + if (!additional) { + return original; + } + + // If the additional CW already exists in the input, then we *don't* append another copy! + if (includesWholeWord(original, additional)) { + return original; + } + + return reverse + ? `${additional}, ${original}` + : `${original}, ${additional}`; +} + +/** + * Emulates a regular expression like /\b(pattern)\b/, but with a raw non-regex pattern. + * We're checking to see whether the default CW appears inside the existing CW, but *only* if there's word boundaries on either side. + * @param input Input string to search + * @param target Target word / phrase to search for + */ +function includesWholeWord(input: string, target: string): boolean { + const parts = input.split(target); + + // The additional string could appear multiple times within the original input. + // We need to check each occurrence, since any of them could potentially match. + for (let i = 0; i + 1 < parts.length; i++) { + const before = parts[i]; + const after = parts[i + 1]; + + // If either the preceding or following tokens are a "word", then this "match" is actually just part of a longer word. + // Likewise, if *neither* token is a word, then this is a real match and the CW already exists in the input. + if (!/\w$/.test(before) && !/^\w/.test(after)) { + return true; + } + } + + // If we don't match, then there is no existing CW. + return false; +} diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 059de8011c..c5f5f4514d 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -112,6 +112,7 @@ import * as Misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; import { toASCII } from 'punycode.js'; import { host, url } from '@@/js/config.js'; +import { appendContentWarning } from '@@/js/append-content-warning.js'; import type { MenuItem } from '@/types/menu.js'; import type { PostFormProps } from '@/types/post-form.js'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; @@ -373,18 +374,8 @@ if ($i.defaultCW) { if (!cw.value || $i.defaultCWPriority === 'default') { cw.value = $i.defaultCW; } else if ($i.defaultCWPriority !== 'parent') { - // This is a fancy way of simulating /\bsearch\b/ without a regular expression. - // We're checking to see whether the default CW appears inside the existing CW, but *only* if there's word boundaries. - const parts = cw.value.split($i.defaultCW); - const hasExistingDefaultCW = parts.length === 2 && !/\w$/.test(parts[0]) && !/^\w/.test(parts[1]); - if (!hasExistingDefaultCW) { - // We need to merge the CWs - if ($i.defaultCWPriority === 'defaultParent') { - cw.value = `${$i.defaultCW}, ${cw.value}`; - } else if ($i.defaultCWPriority === 'parentDefault') { - cw.value = `${cw.value}, ${$i.defaultCW}`; - } - } + const putDefaultFirst = $i.defaultCWPriority === 'defaultParent'; + cw.value = appendContentWarning(cw.value, $i.defaultCW, putDefaultFirst); } // else { do nothing, because existing CW takes priority. } } -- cgit v1.2.3-freya From 6c2034a3736aa1541be9fa257b1bf0ee9162284b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 14:13:00 -0500 Subject: append default CW when rendering AP `Note` objects --- packages/backend/src/core/PollService.ts | 2 +- .../src/core/activitypub/ApRendererService.ts | 16 ++-- .../src/core/activitypub/ApResolverService.ts | 5 +- .../backend/src/server/ActivityPubServerService.ts | 17 ++-- packages/backend/test/unit/activitypub.ts | 92 +++++++++++++++++++++- 5 files changed, 117 insertions(+), 15 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts index 6c96ab16cf..d6364613bd 100644 --- a/packages/backend/src/core/PollService.ts +++ b/packages/backend/src/core/PollService.ts @@ -100,7 +100,7 @@ export class PollService { if (user == null) throw new Error('note not found'); if (this.userEntityService.isLocalUser(user)) { - const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user)); + const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, user, false), user)); this.apDeliverManagerService.deliverToFollowers(user, content); this.relayService.deliverToRelays(user, content); } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 721cb77b2f..22cb6b8282 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -28,6 +28,7 @@ import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFil import { bindThis } from '@/decorators.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { IdService } from '@/core/IdService.js'; +import { appendContentWarning } from '@/misc/append-content-warning.js'; import { JsonLdService } from './JsonLdService.js'; import { ApMfmService } from './ApMfmService.js'; import { CONTEXT } from './misc/contexts.js'; @@ -339,7 +340,7 @@ export class ApRendererService { } @bindThis - public async renderNote(note: MiNote, dive = true): Promise { + public async renderNote(note: MiNote, author: MiUser, dive = true): Promise { const getPromisedFiles = async (ids: string[]): Promise => { if (ids.length === 0) return []; const items = await this.driveFilesRepository.findBy({ id: In(ids) }); @@ -353,14 +354,14 @@ export class ApRendererService { inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); if (inReplyToNote != null) { - const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } }); + const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); - if (inReplyToUserExist) { + if (inReplyToUser) { if (inReplyToNote.uri) { inReplyTo = inReplyToNote.uri; } else { if (dive) { - inReplyTo = await this.renderNote(inReplyToNote, false); + inReplyTo = await this.renderNote(inReplyToNote, inReplyToUser, false); } else { inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; } @@ -423,7 +424,12 @@ export class ApRendererService { apAppend += `\n\nRE: ${quote}`; } - const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; + let summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; + + // Apply mandatory CW, if applicable + if (author.mandatoryCW) { + summary = appendContentWarning(summary, author.mandatoryCW); + } const { content } = this.apMfmService.getNoteHtml(note, apAppend); diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index a0c3a4846c..dfeee5ac7f 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -156,11 +156,12 @@ export class Resolver { case 'notes': return this.notesRepository.findOneByOrFail({ id: parsed.id }) .then(async note => { + const author = await this.usersRepository.findOneByOrFail({ id: note.userId }); if (parsed.rest === 'activity') { // this refers to the create activity and not the note itself - return this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note), note)); + return this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, author), note)); } else { - return this.apRendererService.renderNote(note); + return this.apRendererService.renderNote(note, author); } }); case 'users': diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index 72faa3318c..10dba1660f 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -103,15 +103,16 @@ export class ActivityPubServerService { /** * Pack Create or Announce Activity * @param note Note + * @param author Author of the note */ @bindThis - private async packActivity(note: MiNote): Promise { + private async packActivity(note: MiNote, author: MiUser): Promise { if (isRenote(note) && !isQuote(note)) { const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId }); return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note); } - return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); + return this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, author, false), note); } @bindThis @@ -506,7 +507,7 @@ export class ActivityPubServerService { this.notesRepository.findOneByOrFail({ id: pining.noteId })))) .filter(note => !note.localOnly && ['public', 'home'].includes(note.visibility)); - const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note))); + const renderedNotes = await Promise.all(pinnedNotes.map(note => this.apRendererService.renderNote(note, user))); const rendered = this.apRendererService.renderOrderedCollection( `${this.config.url}/users/${userId}/collections/featured`, @@ -579,7 +580,7 @@ export class ActivityPubServerService { if (sinceId) notes.reverse(); - const activities = await Promise.all(notes.map(note => this.packActivity(note))); + const activities = await Promise.all(notes.map(note => this.packActivity(note, user))); const rendered = this.apRendererService.renderOrderedCollectionPage( `${partOf}?${url.query({ page: 'true', @@ -723,7 +724,9 @@ export class ActivityPubServerService { if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180'); this.setResponseType(request, reply); - return this.apRendererService.addContext(await this.apRendererService.renderNote(note, false)); + + const author = await this.usersRepository.findOneByOrFail({ id: note.userId }); + return this.apRendererService.addContext(await this.apRendererService.renderNote(note, author, false)); }); // note activity @@ -746,7 +749,9 @@ export class ActivityPubServerService { if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180'); this.setResponseType(request, reply); - return (this.apRendererService.addContext(await this.packActivity(note))); + + const author = await this.usersRepository.findOneByOrFail({ id: note.userId }); + return (this.apRendererService.addContext(await this.packActivity(note, author))); }); // outbox diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 73d6186edf..105a3292bf 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { IdService } from '@/core/IdService.js'; + process.env.NODE_ENV = 'test'; import * as assert from 'assert'; @@ -20,7 +22,7 @@ import { CoreModule } from '@/core/CoreModule.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; import { LoggerService } from '@/core/LoggerService.js'; import type { IActor, IApDocument, ICollection, IObject, IPost } from '@/core/activitypub/type.js'; -import { MiMeta, MiNote, UserProfilesRepository } from '@/models/_.js'; +import { MiMeta, MiNote, MiUser, UserProfilesRepository } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { DownloadService } from '@/core/DownloadService.js'; @@ -93,6 +95,7 @@ describe('ActivityPub', () => { let rendererService: ApRendererService; let jsonLdService: JsonLdService; let resolver: MockResolver; + let idService: IdService; const metaInitial = { cacheRemoteFiles: true, @@ -140,6 +143,7 @@ describe('ActivityPub', () => { imageService = app.get(ApImageService); jsonLdService = app.get(JsonLdService); resolver = new MockResolver(await app.resolve(LoggerService)); + idService = app.get(IdService); // Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error const federatedInstanceService = app.get(FederatedInstanceService); @@ -477,4 +481,90 @@ describe('ActivityPub', () => { }); }); }); + + describe(ApRendererService, () => { + describe('renderNote', () => { + let note: MiNote; + let author: MiUser; + + beforeEach(() => { + author = new MiUser({ + id: idService.gen(), + }); + note = new MiNote({ + id: idService.gen(), + userId: author.id, + visibility: 'public', + localOnly: false, + text: 'Note text', + cw: null, + renoteCount: 0, + repliesCount: 0, + clippedCount: 0, + reactions: {}, + fileIds: [], + attachedFileTypes: [], + visibleUserIds: [], + mentions: [], + // This is fucked tbh - it's JSON stored in a TEXT column that gets parsed/serialized all over the place + mentionedRemoteUsers: '[]', + reactionAndUserPairCache: [], + emojis: [], + tags: [], + hasPoll: false, + }); + }); + + describe('summary', () => { + // I actually don't know why it does this, but the logic was already there so I've preserved it. + it('should be special character when CW is empty string', async () => { + note.cw = ''; + + const result = await rendererService.renderNote(note, author, false); + + expect(result.summary).toBe(String.fromCharCode(0x200B)); + }); + + it('should be undefined when CW is null', async () => { + const result = await rendererService.renderNote(note, author, false); + + expect(result.summary).toBeUndefined(); + }); + + it('should be CW when present without mandatoryCW', async () => { + note.cw = 'original'; + + const result = await rendererService.renderNote(note, author, false); + + expect(result.summary).toBe('original'); + }); + + it('should be mandatoryCW when present without CW', async () => { + author.mandatoryCW = 'mandatory'; + + const result = await rendererService.renderNote(note, author, false); + + expect(result.summary).toBe('mandatory'); + }); + + it('should be merged when CW and mandatoryCW are both present', async () => { + note.cw = 'original'; + author.mandatoryCW = 'mandatory'; + + const result = await rendererService.renderNote(note, author, false); + + expect(result.summary).toBe('original, mandatory'); + }); + + it('should be CW when CW includes mandatoryCW', async () => { + note.cw = 'original and mandatory'; + author.mandatoryCW = 'mandatory'; + + const result = await rendererService.renderNote(note, author, false); + + expect(result.summary).toBe('original and mandatory'); + }); + }); + }); + }); }); -- cgit v1.2.3-freya From c5933f369e89c2380881e656f18608e22b4c0585 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 14:42:24 -0500 Subject: move `mandatoryCW` from admin-user to PackedUserLite (public field) --- packages/backend/src/core/entities/UserEntityService.ts | 1 + packages/backend/src/models/json-schema/user.ts | 4 ++++ packages/backend/src/server/api/endpoints/admin/show-user.ts | 5 ----- packages/frontend/src/pages/admin-user.vue | 2 +- packages/misskey-js/src/autogen/types.ts | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index ef0b5213c8..4fbbbdd379 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -592,6 +592,7 @@ export class UserEntityService implements OnModuleInit { isCat: user.isCat, noindex: user.noindex, enableRss: user.enableRss, + mandatoryCW: user.mandatoryCW, isSilenced: user.isSilenced || this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), speakAsCat: user.speakAsCat ?? false, approved: user.approved, diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 93b031e9c5..1c2ba538c1 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -134,6 +134,10 @@ export const packedUserLiteSchema = { type: 'boolean', nullable: false, optional: false, }, + mandatoryCW: { + type: 'string', + nullable: true, optional: false, + }, isBot: { type: 'boolean', nullable: false, optional: true, diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 0f0b0f8e7a..669bffe2dc 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -144,10 +144,6 @@ export const meta = { type: 'string', optional: false, nullable: false, }, - mandatoryCW: { - type: 'string', - optional: false, nullable: true, - }, signins: { type: 'array', optional: false, nullable: false, @@ -264,7 +260,6 @@ export default class extends Endpoint { // eslint- isHibernated: user.isHibernated, lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null, moderationNote: profile.moderationNote ?? '', - mandatoryCW: user.mandatoryCW, signins, policies: await this.roleService.getUserPolicies(user.id), roles: await this.roleEntityService.packMany(roles, me), diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 744c4d9682..229f581672 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -288,7 +288,7 @@ function createFetcher() { markedAsNSFW.value = info.value.alwaysMarkNsfw; suspended.value = info.value.isSuspended; moderationNote.value = info.value.moderationNote; - mandatoryCW.value = info.value.mandatoryCW; + mandatoryCW.value = user.value.mandatoryCW; }); } diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 9bac7a812c..7b3f4c0d83 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -3968,6 +3968,7 @@ export type components = { isSystem?: boolean; noindex: boolean; enableRss: boolean; + mandatoryCW: string | null; isBot?: boolean; isCat?: boolean; speakAsCat?: boolean; -- cgit v1.2.3-freya From 3d23cdc0e45ceee3fbcb7acb7f2e7922664a33bb Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 14:47:38 -0500 Subject: append mandatory CW in note previews --- packages/backend/src/misc/get-note-summary.ts | 11 +++++++++-- packages/frontend/src/scripts/get-note-summary.ts | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts index 60dddee9a2..be2d3ea98d 100644 --- a/packages/backend/src/misc/get-note-summary.ts +++ b/packages/backend/src/misc/get-note-summary.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { appendContentWarning } from './append-content-warning.js'; import type { Packed } from './json-schema.js'; /** @@ -20,9 +21,15 @@ export const getNoteSummary = (note: Packed<'Note'>): string => { let summary = ''; + // Append mandatory CW, if applicable + let cw = note.cw; + if (note.user.mandatoryCW) { + cw = appendContentWarning(cw, note.user.mandatoryCW); + } + // 本文 - if (note.cw != null) { - summary += `CW: ${note.cw}`; + if (cw != null) { + summary += `CW: ${cw}`; } else if (note.text) { summary += note.text; } diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/scripts/get-note-summary.ts index 58d486bf9b..4e093bcf4c 100644 --- a/packages/frontend/src/scripts/get-note-summary.ts +++ b/packages/frontend/src/scripts/get-note-summary.ts @@ -4,6 +4,7 @@ */ import * as Misskey from 'misskey-js'; +import { appendContentWarning } from '@@/js/append-content-warning.js'; import { i18n } from '@/i18n.js'; /** @@ -25,9 +26,15 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => { let summary = ''; + // Append mandatory CW, if applicable + let cw = note.cw; + if (note.user.mandatoryCW) { + cw = appendContentWarning(cw, note.user.mandatoryCW); + } + // 本文 - if (note.cw != null) { - summary += `CW: ${note.cw}`; + if (cw != null) { + summary += `CW: ${cw}`; } else if (note.text) { summary += note.text; } -- cgit v1.2.3-freya From 583f55bc5a4775d5cefeb01d5bddd45cb1736439 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 15:09:57 -0500 Subject: fix type error in WebhookTestService.ts --- packages/backend/src/core/WebhookTestService.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 4a45c11de1..93693216cb 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -217,6 +217,7 @@ function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<' isSystem: false, isSilenced: user.isSilenced, enableRss: true, + mandatoryCW: null, ...override, }; } -- cgit v1.2.3-freya From c54b6bf55d94ac74ee59631003d8b78a8993bbbb Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 15:11:19 -0500 Subject: append mandatory CW in `Update(Note)` activities --- packages/backend/src/core/NoteEditService.ts | 24 +---- .../src/core/activitypub/ApRendererService.ts | 15 ++- packages/backend/test/unit/activitypub.ts | 111 +++++++++++++++------ 3 files changed, 97 insertions(+), 53 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 18912181d7..854112ec1d 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -224,13 +224,7 @@ export class NoteEditService implements OnApplicationShutdown { } @bindThis - public async edit(user: { - id: MiUser['id']; - username: MiUser['username']; - host: MiUser['host']; - isBot: MiUser['isBot']; - noindex: MiUser['noindex']; - }, editid: MiNote['id'], data: Option, silent = false): Promise { + public async edit(user: MiUser, editid: MiNote['id'], data: Option, silent = false): Promise { if (!editid) { throw new Error('fail'); } @@ -584,13 +578,7 @@ export class NoteEditService implements OnApplicationShutdown { } @bindThis - private async postNoteEdited(note: MiNote, oldNote: MiNote, user: { - id: MiUser['id']; - username: MiUser['username']; - host: MiUser['host']; - isBot: MiUser['isBot']; - noindex: MiUser['noindex']; - }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { + private async postNoteEdited(note: MiNote, oldNote: MiNote, user: MiUser, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { // Register host if (this.meta.enableStatsForFederatedInstances) { if (this.userEntityService.isRemoteUser(user)) { @@ -703,7 +691,7 @@ export class NoteEditService implements OnApplicationShutdown { //#region AP deliver if (!data.localOnly && this.userEntityService.isLocalUser(user)) { (async () => { - const noteActivity = await this.renderNoteOrRenoteActivity(data, note); + const noteActivity = await this.renderNoteOrRenoteActivity(data, note, user); const dm = this.apDeliverManagerService.createDeliverManager(user, noteActivity); // メンションされたリモートユーザーに配送 @@ -834,14 +822,12 @@ export class NoteEditService implements OnApplicationShutdown { } @bindThis - private async renderNoteOrRenoteActivity(data: Option, note: MiNote) { + private async renderNoteOrRenoteActivity(data: Option, note: MiNote, user: MiUser) { if (data.localOnly) return null; - const user = await this.usersRepository.findOneBy({ id: note.userId }); - if (user == null) throw new Error('user not found'); const content = this.isRenote(data) && !this.isQuote(data) ? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note) - : this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user); + : this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, user, false), user); return this.apRendererService.addContext(content); } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 22cb6b8282..cb9b74f6d7 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -642,7 +642,7 @@ export class ApRendererService { } @bindThis - public async renderUpNote(note: MiNote, dive = true): Promise { + public async renderUpNote(note: MiNote, author: MiUser, dive = true): Promise { const getPromisedFiles = async (ids: string[]): Promise => { if (ids.length === 0) return []; const items = await this.driveFilesRepository.findBy({ id: In(ids) }); @@ -656,14 +656,14 @@ export class ApRendererService { inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); if (inReplyToNote != null) { - const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } }); + const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); - if (inReplyToUserExist) { + if (inReplyToUser) { if (inReplyToNote.uri) { inReplyTo = inReplyToNote.uri; } else { if (dive) { - inReplyTo = await this.renderUpNote(inReplyToNote, false); + inReplyTo = await this.renderUpNote(inReplyToNote, inReplyToUser, false); } else { inReplyTo = `${this.config.url}/notes/${inReplyToNote.id}`; } @@ -726,7 +726,12 @@ export class ApRendererService { apAppend += `\n\nRE: ${quote}`; } - const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; + let summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw; + + // Apply mandatory CW, if applicable + if (author.mandatoryCW) { + summary = appendContentWarning(summary, author.mandatoryCW); + } const { content } = this.apMfmService.getNoteHtml(note, apAppend); diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 105a3292bf..b4cffbc706 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -483,38 +483,38 @@ describe('ActivityPub', () => { }); describe(ApRendererService, () => { - describe('renderNote', () => { - let note: MiNote; - let author: MiUser; + let note: MiNote; + let author: MiUser; - beforeEach(() => { - author = new MiUser({ - id: idService.gen(), - }); - note = new MiNote({ - id: idService.gen(), - userId: author.id, - visibility: 'public', - localOnly: false, - text: 'Note text', - cw: null, - renoteCount: 0, - repliesCount: 0, - clippedCount: 0, - reactions: {}, - fileIds: [], - attachedFileTypes: [], - visibleUserIds: [], - mentions: [], - // This is fucked tbh - it's JSON stored in a TEXT column that gets parsed/serialized all over the place - mentionedRemoteUsers: '[]', - reactionAndUserPairCache: [], - emojis: [], - tags: [], - hasPoll: false, - }); + beforeEach(() => { + author = new MiUser({ + id: idService.gen(), + }); + note = new MiNote({ + id: idService.gen(), + userId: author.id, + visibility: 'public', + localOnly: false, + text: 'Note text', + cw: null, + renoteCount: 0, + repliesCount: 0, + clippedCount: 0, + reactions: {}, + fileIds: [], + attachedFileTypes: [], + visibleUserIds: [], + mentions: [], + // This is fucked tbh - it's JSON stored in a TEXT column that gets parsed/serialized all over the place + mentionedRemoteUsers: '[]', + reactionAndUserPairCache: [], + emojis: [], + tags: [], + hasPoll: false, }); + }); + describe('renderNote', () => { describe('summary', () => { // I actually don't know why it does this, but the logic was already there so I've preserved it. it('should be special character when CW is empty string', async () => { @@ -566,5 +566,58 @@ describe('ActivityPub', () => { }); }); }); + + describe('renderUpnote', () => { + describe('summary', () => { + // I actually don't know why it does this, but the logic was already there so I've preserved it. + it('should be special character when CW is empty string', async () => { + note.cw = ''; + + const result = await rendererService.renderUpNote(note, author, false); + + expect(result.summary).toBe(String.fromCharCode(0x200B)); + }); + + it('should be undefined when CW is null', async () => { + const result = await rendererService.renderUpNote(note, author, false); + + expect(result.summary).toBeUndefined(); + }); + + it('should be CW when present without mandatoryCW', async () => { + note.cw = 'original'; + + const result = await rendererService.renderUpNote(note, author, false); + + expect(result.summary).toBe('original'); + }); + + it('should be mandatoryCW when present without CW', async () => { + author.mandatoryCW = 'mandatory'; + + const result = await rendererService.renderUpNote(note, author, false); + + expect(result.summary).toBe('mandatory'); + }); + + it('should be merged when CW and mandatoryCW are both present', async () => { + note.cw = 'original'; + author.mandatoryCW = 'mandatory'; + + const result = await rendererService.renderUpNote(note, author, false); + + expect(result.summary).toBe('original, mandatory'); + }); + + it('should be CW when CW includes mandatoryCW', async () => { + note.cw = 'original and mandatory'; + author.mandatoryCW = 'mandatory'; + + const result = await rendererService.renderUpNote(note, author, false); + + expect(result.summary).toBe('original and mandatory'); + }); + }); + }); }); }); -- cgit v1.2.3-freya From 5c86929b584d799c33b8a75f1129c347167c444f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 12 Feb 2025 15:12:15 -0500 Subject: fix type errors in NoteCreateService.ts --- packages/backend/src/core/NoteCreateService.ts | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 6564a64d30..71c0c0a55d 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -228,13 +228,7 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - public async create(user: { - id: MiUser['id']; - username: MiUser['username']; - host: MiUser['host']; - isBot: MiUser['isBot']; - noindex: MiUser['noindex']; - }, data: Option, silent = false): Promise { + public async create(user: MiUser, data: Option, silent = false): Promise { // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { @@ -435,13 +429,7 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - public async import(user: { - id: MiUser['id']; - username: MiUser['username']; - host: MiUser['host']; - isBot: MiUser['isBot']; - noindex: MiUser['noindex']; - }, data: Option): Promise { + public async import(user: MiUser, data: Option): Promise { return this.create(user, data, true); } @@ -552,13 +540,7 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private async postNoteCreated(note: MiNote, user: { - id: MiUser['id']; - username: MiUser['username']; - host: MiUser['host']; - isBot: MiUser['isBot']; - noindex: MiUser['noindex']; - }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { + private async postNoteCreated(note: MiNote, user: MiUser, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { this.notesChart.update(note, true); if (note.visibility !== 'specified' && (this.meta.enableChartsForRemoteUser || (user.host == null))) { this.perUserNotesChart.update(user, note, true); -- cgit v1.2.3-freya From 86b26fb58ea8264523f468131cd87d0a43aea9fe Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 15 Feb 2025 11:39:57 -0500 Subject: adjust types to avoid merge conflicts in NoteCreateService.ts and NoteEditService.ts --- packages/backend/src/core/NoteCreateService.ts | 24 +++++++++++++++++++++--- packages/backend/src/core/NoteEditService.ts | 16 ++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 71c0c0a55d..8291db9b42 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -228,7 +228,13 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - public async create(user: MiUser, data: Option, silent = false): Promise { + public async create(user: MiUser & { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + isBot: MiUser['isBot']; + noindex: MiUser['noindex']; + }, data: Option, silent = false): Promise { // チャンネル外にリプライしたら対象のスコープに合わせる // (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで) if (data.reply && data.channel && data.reply.channelId !== data.channel.id) { @@ -429,7 +435,13 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - public async import(user: MiUser, data: Option): Promise { + public async import(user: MiUser & { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + isBot: MiUser['isBot']; + noindex: MiUser['noindex']; + }, data: Option): Promise { return this.create(user, data, true); } @@ -540,7 +552,13 @@ export class NoteCreateService implements OnApplicationShutdown { } @bindThis - private async postNoteCreated(note: MiNote, user: MiUser, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { + private async postNoteCreated(note: MiNote, user: MiUser & { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + isBot: MiUser['isBot']; + noindex: MiUser['noindex']; + }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { this.notesChart.update(note, true); if (note.visibility !== 'specified' && (this.meta.enableChartsForRemoteUser || (user.host == null))) { this.perUserNotesChart.update(user, note, true); diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 854112ec1d..24a99156d2 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -224,7 +224,13 @@ export class NoteEditService implements OnApplicationShutdown { } @bindThis - public async edit(user: MiUser, editid: MiNote['id'], data: Option, silent = false): Promise { + public async edit(user: MiUser & { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + isBot: MiUser['isBot']; + noindex: MiUser['noindex']; + }, editid: MiNote['id'], data: Option, silent = false): Promise { if (!editid) { throw new Error('fail'); } @@ -578,7 +584,13 @@ export class NoteEditService implements OnApplicationShutdown { } @bindThis - private async postNoteEdited(note: MiNote, oldNote: MiNote, user: MiUser, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { + private async postNoteEdited(note: MiNote, oldNote: MiNote, user: MiUser & { + id: MiUser['id']; + username: MiUser['username']; + host: MiUser['host']; + isBot: MiUser['isBot']; + noindex: MiUser['noindex']; + }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { // Register host if (this.meta.enableStatsForFederatedInstances) { if (this.userEntityService.isRemoteUser(user)) { -- cgit v1.2.3-freya From ec0b2933e621abf05c6b482eb8a25c13f875c3d7 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 16 Feb 2025 19:22:55 -0500 Subject: add `admin/cw-user` to new endpoints list --- packages/backend/src/server/api/endpoint-list.ts | 1 + packages/misskey-js/etc/misskey-js.api.md | 6 +- packages/misskey-js/src/autogen/apiClientJSDoc.ts | 825 +++++++++++----------- packages/misskey-js/src/autogen/endpoint.ts | 2 + packages/misskey-js/src/autogen/entities.ts | 1 + packages/misskey-js/src/autogen/types.ts | 62 ++ 6 files changed, 489 insertions(+), 408 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index 6dce7f1a3d..551d7b17c2 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -34,6 +34,7 @@ export * as 'admin/avatar-decorations/list' from './endpoints/admin/avatar-decor export * as 'admin/avatar-decorations/update' from './endpoints/admin/avatar-decorations/update.js'; export * as 'admin/captcha/current' from './endpoints/admin/captcha/current.js'; export * as 'admin/captcha/save' from './endpoints/admin/captcha/save.js'; +export * as 'admin/cw-user' from './endpoints/admin/cw-user.js'; export * as 'admin/decline-user' from './endpoints/admin/decline-user.js'; export * as 'admin/delete-account' from './endpoints/admin/delete-account.js'; export * as 'admin/delete-all-files-of-a-user' from './endpoints/admin/delete-all-files-of-a-user.js'; diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 25921d14c8..cc3dcaa765 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -146,6 +146,9 @@ type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['resp // @public (undocumented) type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json']; +// @public (undocumented) +type AdminCwUserRequest = operations['admin___cw-user']['requestBody']['content']['application/json']; + // @public (undocumented) type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json']; @@ -1307,6 +1310,7 @@ declare namespace entities { AdminAvatarDecorationsUpdateRequest, AdminCaptchaCurrentResponse, AdminCaptchaSaveRequest, + AdminCwUserRequest, AdminDeclineUserRequest, AdminDeleteAccountRequest, AdminDeleteAllFilesOfAUserRequest, @@ -3021,7 +3025,7 @@ type PartialRolePolicyOverride = Partial<{ }>; // @public (undocumented) -export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notes-schedule", "write:notes-schedule", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"]; +export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notes-schedule", "write:notes-schedule", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:cw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"]; // @public (undocumented) type PingResponse = operations['ping']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 5e47ad15ad..99e202a1b6 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -5,7 +5,7 @@ declare module '../api.js' { export interface APIClient { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ @@ -17,7 +17,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ @@ -29,7 +29,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-report:notification-recipient* */ @@ -41,7 +41,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-report:notification-recipient* */ @@ -53,7 +53,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:abuse-report:notification-recipient* */ @@ -65,7 +65,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-user-reports* */ request( @@ -76,7 +76,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -87,7 +87,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:account* */ request( @@ -98,7 +98,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:account* */ request( @@ -109,7 +109,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ request( @@ -120,7 +120,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ request( @@ -131,7 +131,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:ad* */ request( @@ -142,7 +142,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ request( @@ -153,7 +153,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ request( @@ -164,7 +164,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ request( @@ -175,7 +175,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:announcements* */ request( @@ -186,7 +186,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* */ request( @@ -197,7 +197,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:approve-user* */ request( @@ -208,7 +208,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ request( @@ -219,7 +219,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ request( @@ -230,7 +230,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:avatar-decorations* */ request( @@ -241,7 +241,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:avatar-decorations* */ request( @@ -252,7 +252,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:meta* */ request( @@ -263,7 +263,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ request( @@ -274,7 +274,18 @@ declare module '../api.js' { /** * No description provided. - * + * + * **Credential required**: *Yes* / **Permission**: *write:admin:cw-user* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * No description provided. + * * **Credential required**: *Yes* / **Permission**: *write:admin:decline-user* */ request( @@ -285,7 +296,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-account* */ request( @@ -296,7 +307,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:delete-all-files-of-a-user* */ request( @@ -307,7 +318,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ request( @@ -318,7 +329,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:drive* */ request( @@ -329,7 +340,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ request( @@ -340,7 +351,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:drive* */ request( @@ -351,7 +362,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -362,7 +373,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -373,7 +384,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -384,7 +395,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -395,7 +406,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -406,7 +417,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -418,7 +429,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( @@ -429,7 +440,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( @@ -440,7 +451,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -451,7 +462,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -462,7 +473,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -473,7 +484,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -484,7 +495,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:emoji* */ request( @@ -495,7 +506,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -506,7 +517,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -517,7 +528,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -528,7 +539,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:federation* */ request( @@ -539,7 +550,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ request( @@ -550,7 +561,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ request( @@ -561,7 +572,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:index-stats* */ request( @@ -572,7 +583,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:table-stats* */ request( @@ -583,7 +594,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:user-ips* */ request( @@ -594,7 +605,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:invite-codes* */ request( @@ -605,7 +616,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:invite-codes* */ request( @@ -616,7 +627,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:meta* */ request( @@ -627,7 +638,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user* */ request( @@ -638,7 +649,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:promo* */ request( @@ -649,7 +660,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ request( @@ -660,7 +671,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ request( @@ -671,7 +682,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:queue* */ request( @@ -682,7 +693,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:queue* */ request( @@ -693,7 +704,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( @@ -704,7 +715,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ request( @@ -715,7 +726,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:relays* */ request( @@ -726,7 +737,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:relays* */ request( @@ -737,7 +748,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:reset-password* */ request( @@ -748,7 +759,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ request( @@ -759,7 +770,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -770,7 +781,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -781,7 +792,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -792,7 +803,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ request( @@ -803,7 +814,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:roles* */ request( @@ -814,7 +825,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -825,7 +836,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -836,7 +847,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:roles* */ request( @@ -847,7 +858,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:admin:roles* */ request( @@ -858,7 +869,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* */ request( @@ -869,7 +880,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:server-info* */ request( @@ -880,7 +891,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:show-moderation-log* */ request( @@ -891,7 +902,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ request( @@ -902,7 +913,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:show-user* */ request( @@ -913,7 +924,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:silence-user* */ request( @@ -924,7 +935,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:suspend-user* */ request( @@ -935,7 +946,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -947,7 +958,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -959,7 +970,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -971,7 +982,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -983,7 +994,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook* */ @@ -995,7 +1006,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *write:admin:system-webhook* */ @@ -1007,7 +1018,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user* */ request( @@ -1018,7 +1029,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-avatar* */ request( @@ -1029,7 +1040,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unset-user-banner* */ request( @@ -1040,7 +1051,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user* */ request( @@ -1051,7 +1062,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:unsuspend-user* */ request( @@ -1062,7 +1073,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* */ request( @@ -1073,7 +1084,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:meta* */ request( @@ -1084,7 +1095,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:admin:user-note* */ request( @@ -1095,7 +1106,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1106,7 +1117,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1117,7 +1128,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1128,7 +1139,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1139,7 +1150,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1150,7 +1161,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1161,7 +1172,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1172,7 +1183,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1183,7 +1194,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:federation* */ request( @@ -1194,7 +1205,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1205,7 +1216,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1216,7 +1227,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1227,7 +1238,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -1239,7 +1250,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1250,7 +1261,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1261,7 +1272,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1272,7 +1283,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ request( @@ -1283,7 +1294,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:blocks* */ request( @@ -1294,7 +1305,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:blocks* */ request( @@ -1305,7 +1316,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1316,7 +1327,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1327,7 +1338,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1338,7 +1349,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1349,7 +1360,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1360,7 +1371,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1371,7 +1382,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:channels* */ request( @@ -1382,7 +1393,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:channels* */ request( @@ -1393,7 +1404,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:channels* */ request( @@ -1404,7 +1415,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1415,7 +1426,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1426,7 +1437,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1437,7 +1448,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1448,7 +1459,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1459,7 +1470,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:channels* */ request( @@ -1470,7 +1481,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1481,7 +1492,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1492,7 +1503,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1503,7 +1514,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1514,7 +1525,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1525,7 +1536,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1536,7 +1547,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1547,7 +1558,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1558,7 +1569,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1569,7 +1580,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1580,7 +1591,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1591,7 +1602,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1602,7 +1613,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1613,7 +1624,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1624,7 +1635,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1635,7 +1646,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ request( @@ -1646,7 +1657,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1657,7 +1668,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:clip-favorite* */ request( @@ -1668,7 +1679,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -1679,7 +1690,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1690,7 +1701,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -1701,7 +1712,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:clip-favorite* */ request( @@ -1712,7 +1723,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -1723,7 +1734,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1734,7 +1745,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1745,7 +1756,7 @@ declare module '../api.js' { /** * Find the notes to which the given file is attached. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1756,7 +1767,7 @@ declare module '../api.js' { /** * Check if a given file exists. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1767,7 +1778,7 @@ declare module '../api.js' { /** * Upload a new drive file. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1778,7 +1789,7 @@ declare module '../api.js' { /** * Delete an existing drive file. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1789,7 +1800,7 @@ declare module '../api.js' { /** * Search for a drive file by the given parameters. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1800,7 +1811,7 @@ declare module '../api.js' { /** * Search for a drive file by a hash of the contents. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1811,7 +1822,7 @@ declare module '../api.js' { /** * Show the properties of a drive file. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1822,7 +1833,7 @@ declare module '../api.js' { /** * Update the properties of a drive file. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1833,7 +1844,7 @@ declare module '../api.js' { /** * Request the server to download a new drive file from the specified URL. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1844,7 +1855,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1855,7 +1866,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1866,7 +1877,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1877,7 +1888,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1888,7 +1899,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1899,7 +1910,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:drive* */ request( @@ -1910,7 +1921,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:drive* */ request( @@ -1921,7 +1932,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1932,7 +1943,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1943,7 +1954,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1954,7 +1965,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1965,7 +1976,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -1976,7 +1987,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -1988,7 +1999,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -1999,7 +2010,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2010,7 +2021,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2021,7 +2032,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2032,7 +2043,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2043,7 +2054,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2054,7 +2065,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2065,7 +2076,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2077,7 +2088,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2088,7 +2099,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash* */ request( @@ -2099,7 +2110,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash* */ request( @@ -2110,7 +2121,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2121,7 +2132,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ request( @@ -2132,7 +2143,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:flash* */ request( @@ -2143,7 +2154,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:flash-likes* */ request( @@ -2154,7 +2165,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2165,7 +2176,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash-likes* */ request( @@ -2176,7 +2187,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:flash* */ request( @@ -2187,7 +2198,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2198,7 +2209,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2209,7 +2220,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2220,7 +2231,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2231,7 +2242,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2242,7 +2253,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:following* */ request( @@ -2253,7 +2264,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2264,7 +2275,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:following* */ request( @@ -2275,7 +2286,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2286,7 +2297,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:following* */ request( @@ -2297,7 +2308,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2308,7 +2319,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2319,7 +2330,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2330,7 +2341,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ request( @@ -2341,7 +2352,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ request( @@ -2352,7 +2363,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ request( @@ -2363,7 +2374,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2374,7 +2385,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery-likes* */ request( @@ -2385,7 +2396,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:gallery* */ request( @@ -2396,7 +2407,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2407,7 +2418,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2418,7 +2429,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2429,7 +2440,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2440,7 +2451,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2451,7 +2462,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2462,7 +2473,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -2473,7 +2484,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2484,7 +2495,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2496,7 +2507,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2508,7 +2519,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2520,7 +2531,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2532,7 +2543,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2544,7 +2555,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2556,7 +2567,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2568,7 +2579,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2580,7 +2591,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2592,7 +2603,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2604,7 +2615,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2616,7 +2627,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2627,7 +2638,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2639,7 +2650,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2651,7 +2662,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2663,7 +2674,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2675,7 +2686,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2687,7 +2698,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2699,7 +2710,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2711,7 +2722,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2723,7 +2734,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2735,7 +2746,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2747,7 +2758,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:favorites* */ request( @@ -2758,7 +2769,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:gallery-likes* */ request( @@ -2769,7 +2780,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:gallery* */ request( @@ -2780,7 +2791,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2792,7 +2803,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2804,7 +2815,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2816,7 +2827,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2828,7 +2839,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2840,7 +2851,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2852,7 +2863,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2864,7 +2875,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ request( @@ -2875,7 +2886,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:notifications* */ request( @@ -2886,7 +2897,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:page-likes* */ request( @@ -2897,7 +2908,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:pages* */ request( @@ -2908,7 +2919,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2919,7 +2930,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2930,7 +2941,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -2941,7 +2952,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -2953,7 +2964,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2964,7 +2975,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2975,7 +2986,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2986,7 +2997,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -2997,7 +3008,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3008,7 +3019,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3019,7 +3030,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3030,7 +3041,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3042,7 +3053,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3053,7 +3064,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3065,7 +3076,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3077,7 +3088,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3088,7 +3099,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3099,7 +3110,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3111,7 +3122,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3122,7 +3133,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3133,7 +3144,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3144,7 +3155,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3155,7 +3166,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* / **Permission**: *read:account* */ @@ -3167,7 +3178,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3178,7 +3189,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ request( @@ -3189,7 +3200,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:invite-codes* */ request( @@ -3200,7 +3211,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ request( @@ -3211,7 +3222,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:invite-codes* */ request( @@ -3222,7 +3233,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3233,7 +3244,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3245,7 +3256,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3256,7 +3267,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3267,7 +3278,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ request( @@ -3278,7 +3289,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3289,7 +3300,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3300,7 +3311,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3311,7 +3322,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3322,7 +3333,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3333,7 +3344,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3344,7 +3355,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3355,7 +3366,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3366,7 +3377,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3377,7 +3388,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ request( @@ -3388,7 +3399,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:favorites* */ request( @@ -3399,7 +3410,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3410,7 +3421,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3421,7 +3432,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3432,7 +3443,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3443,7 +3454,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ request( @@ -3454,7 +3465,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3465,7 +3476,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3476,7 +3487,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3487,7 +3498,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:federation* */ request( @@ -3498,7 +3509,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:votes* */ request( @@ -3509,7 +3520,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3520,7 +3531,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ request( @@ -3531,7 +3542,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:reactions* */ request( @@ -3542,7 +3553,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3553,7 +3564,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3564,7 +3575,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes-schedule* */ request( @@ -3575,7 +3586,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes-schedule* */ request( @@ -3586,7 +3597,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:notes-schedule* */ request( @@ -3597,7 +3608,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3608,7 +3619,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3619,7 +3630,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3630,7 +3641,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3641,7 +3652,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3652,7 +3663,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3663,7 +3674,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3674,7 +3685,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3685,7 +3696,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notes* */ request( @@ -3696,7 +3707,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3707,7 +3718,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3718,7 +3729,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3729,7 +3740,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3740,7 +3751,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3751,7 +3762,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:notifications* */ request( @@ -3762,7 +3773,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -3774,7 +3785,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:pages* */ request( @@ -3785,7 +3796,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:pages* */ request( @@ -3796,7 +3807,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3807,7 +3818,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ request( @@ -3818,7 +3829,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3829,7 +3840,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:page-likes* */ request( @@ -3840,7 +3851,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:pages* */ request( @@ -3851,7 +3862,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3862,7 +3873,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3873,7 +3884,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3884,7 +3895,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3895,7 +3906,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:mutes* */ request( @@ -3906,7 +3917,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:mutes* */ request( @@ -3917,7 +3928,7 @@ declare module '../api.js' { /** * Request a users password to be reset. - * + * * **Credential required**: *No* */ request( @@ -3928,7 +3939,7 @@ declare module '../api.js' { /** * Only available when running with NODE_ENV=testing. Reset the database and flush Redis. - * + * * **Credential required**: *No* */ request( @@ -3939,7 +3950,7 @@ declare module '../api.js' { /** * Complete the password reset that was previously requested. - * + * * **Credential required**: *No* */ request( @@ -3950,7 +3961,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3961,7 +3972,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -3972,7 +3983,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -3983,7 +3994,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -3994,7 +4005,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4005,7 +4016,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4016,7 +4027,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4027,7 +4038,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4038,7 +4049,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4049,7 +4060,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4060,7 +4071,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4071,7 +4082,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4082,7 +4093,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4093,7 +4104,7 @@ declare module '../api.js' { /** * Get Sharkey Sponsors or Instance Sponsors - * + * * **Credential required**: *No* */ request( @@ -4104,7 +4115,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4115,7 +4126,7 @@ declare module '../api.js' { /** * Register to receive push notifications. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -4127,7 +4138,7 @@ declare module '../api.js' { /** * Check push notification registration exists. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -4139,7 +4150,7 @@ declare module '../api.js' { /** * Unregister from receiving push notifications. - * + * * **Credential required**: *No* */ request( @@ -4150,7 +4161,7 @@ declare module '../api.js' { /** * Update push notification registration. - * + * * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. * **Credential required**: *Yes* */ @@ -4162,7 +4173,7 @@ declare module '../api.js' { /** * Endpoint for testing input validation. - * + * * **Credential required**: *No* */ request( @@ -4173,7 +4184,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4184,7 +4195,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4195,7 +4206,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4206,7 +4217,7 @@ declare module '../api.js' { /** * Show all clips this user owns. - * + * * **Credential required**: *No* */ request( @@ -4217,7 +4228,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4228,7 +4239,7 @@ declare module '../api.js' { /** * Show all flashs this user created. - * + * * **Credential required**: *No* */ request( @@ -4239,7 +4250,7 @@ declare module '../api.js' { /** * Show everyone that follows this user. - * + * * **Credential required**: *No* */ request( @@ -4250,7 +4261,7 @@ declare module '../api.js' { /** * Show everyone that this user is following. - * + * * **Credential required**: *No* */ request( @@ -4261,7 +4272,7 @@ declare module '../api.js' { /** * Show all gallery posts by the given user. - * + * * **Credential required**: *No* */ request( @@ -4272,7 +4283,7 @@ declare module '../api.js' { /** * Get a list of other users that the specified user frequently replies to. - * + * * **Credential required**: *No* */ request( @@ -4283,7 +4294,7 @@ declare module '../api.js' { /** * Create a new list of users. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4294,7 +4305,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4305,7 +4316,7 @@ declare module '../api.js' { /** * Delete an existing list of users. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4316,7 +4327,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4327,7 +4338,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -4338,7 +4349,7 @@ declare module '../api.js' { /** * Show all lists that the authenticated user has created. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -4349,7 +4360,7 @@ declare module '../api.js' { /** * Remove a user from a list. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4360,7 +4371,7 @@ declare module '../api.js' { /** * Add a user to an existing list. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4371,7 +4382,7 @@ declare module '../api.js' { /** * Show the properties of a list. - * + * * **Credential required**: *No* / **Permission**: *read:account* */ request( @@ -4382,7 +4393,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4393,7 +4404,7 @@ declare module '../api.js' { /** * Update the properties of a list. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4404,7 +4415,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4415,7 +4426,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *No* */ request( @@ -4426,7 +4437,7 @@ declare module '../api.js' { /** * Show all pages this user created. - * + * * **Credential required**: *No* */ request( @@ -4437,7 +4448,7 @@ declare module '../api.js' { /** * Show all reactions this user made. - * + * * **Credential required**: *No* */ request( @@ -4448,7 +4459,7 @@ declare module '../api.js' { /** * Show users that the authenticated user might be interested to follow. - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4459,7 +4470,7 @@ declare module '../api.js' { /** * Show the different kinds of relations between the authenticated user and the specified user(s). - * + * * **Credential required**: *Yes* / **Permission**: *read:account* */ request( @@ -4470,7 +4481,7 @@ declare module '../api.js' { /** * File a report. - * + * * **Credential required**: *Yes* / **Permission**: *write:report-abuse* */ request( @@ -4481,7 +4492,7 @@ declare module '../api.js' { /** * Search for users. - * + * * **Credential required**: *No* */ request( @@ -4492,7 +4503,7 @@ declare module '../api.js' { /** * Search for a user by username and/or host. - * + * * **Credential required**: *No* */ request( @@ -4503,7 +4514,7 @@ declare module '../api.js' { /** * Show the properties of a user. - * + * * **Credential required**: *No* */ request( @@ -4514,7 +4525,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *write:account* */ request( @@ -4525,7 +4536,7 @@ declare module '../api.js' { /** * No description provided. - * + * * **Credential required**: *Yes* / **Permission**: *read:admin:emoji* */ request( diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index c8000fc1d0..b31dd2d8f1 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -38,6 +38,7 @@ import type { AdminAvatarDecorationsUpdateRequest, AdminCaptchaCurrentResponse, AdminCaptchaSaveRequest, + AdminCwUserRequest, AdminDeclineUserRequest, AdminDeleteAccountRequest, AdminDeleteAllFilesOfAUserRequest, @@ -633,6 +634,7 @@ export type Endpoints = { 'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse }; 'admin/captcha/current': { req: EmptyRequest; res: AdminCaptchaCurrentResponse }; 'admin/captcha/save': { req: AdminCaptchaSaveRequest; res: EmptyResponse }; + 'admin/cw-user': { req: AdminCwUserRequest; res: EmptyResponse }; 'admin/decline-user': { req: AdminDeclineUserRequest; res: EmptyResponse }; 'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse }; 'admin/delete-all-files-of-a-user': { req: AdminDeleteAllFilesOfAUserRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 3db711f9a4..c65669d056 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -41,6 +41,7 @@ export type AdminAvatarDecorationsListResponse = operations['admin___avatar-deco export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json']; export type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json']; export type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json']; +export type AdminCwUserRequest = operations['admin___cw-user']['requestBody']['content']['application/json']; export type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json']; export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json']; export type AdminDeleteAllFilesOfAUserRequest = operations['admin___delete-all-files-of-a-user']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 7b3f4c0d83..46356613ac 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -233,6 +233,15 @@ export type paths = { */ post: operations['admin___captcha___save']; }; + '/admin/cw-user': { + /** + * admin/cw-user + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:cw-user* + */ + post: operations['admin___cw-user']; + }; '/admin/decline-user': { /** * admin/decline-user @@ -6888,6 +6897,59 @@ export type operations = { }; }; }; + /** + * admin/cw-user + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:cw-user* + */ + 'admin___cw-user': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + userId: string; + cw: string | null; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * admin/decline-user * @description No description provided. -- cgit v1.2.3-freya From b65b4ecadcd364adeede80f71a2f106671fb434f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 14 Nov 2024 12:11:37 -0500 Subject: add inbound activity logger for debugging --- .config/ci.yml | 23 +++-- .config/cypress-devcontainer.yml | 15 +++ .config/docker_example.yml | 15 +++ .config/example.yml | 15 +++ .../migration/1731565470048-add-activity-log.js | 28 ++++++ packages/backend/src/boot/common.ts | 2 + packages/backend/src/config.ts | 18 ++++ .../src/daemons/ActivityLogCleanupService.ts | 64 +++++++++++++ packages/backend/src/daemons/DaemonModule.ts | 3 + packages/backend/src/di-symbols.ts | 3 + packages/backend/src/models/RepositoryModule.ts | 20 +++- packages/backend/src/models/SkActivityContext.ts | 24 +++++ packages/backend/src/models/SkActivityLog.ts | 82 +++++++++++++++++ packages/backend/src/models/_.ts | 6 ++ packages/backend/src/postgres.ts | 4 + .../src/queue/processors/InboxProcessorService.ts | 102 +++++++++++++++++++++ 16 files changed, 414 insertions(+), 10 deletions(-) create mode 100644 packages/backend/migration/1731565470048-add-activity-log.js create mode 100644 packages/backend/src/daemons/ActivityLogCleanupService.ts create mode 100644 packages/backend/src/models/SkActivityContext.ts create mode 100644 packages/backend/src/models/SkActivityLog.ts (limited to 'packages/backend/src') diff --git a/.config/ci.yml b/.config/ci.yml index 311a98d8fb..790c4704fa 100644 --- a/.config/ci.yml +++ b/.config/ci.yml @@ -263,12 +263,17 @@ checkActivityPubGetSignature: false # # default: false # disableQueryTruncation: false -# Log settings -# logging: -# sql: -# # Outputs query parameters during SQL execution to the log. -# # default: false -# enableQueryParamLogging: false -# # Disable query truncation. If set to true, the full text of the query will be output to the log. -# # default: false -# disableQueryTruncation: false +# Settings for the activity logger, which records inbound activities to the database. +# Disabled by default due to the large volume of data it saves. +#activityLogging: + # Log activities to the database (default: false) + #enabled: false + + # Save the activity before processing, then update later with the results. + # This has the advantage of capturing activities that cause a hard-crash, but doubles the number of queries used. + # Default: false + #preSave: false + + # How long to save each log entry before deleting it. + # Default: 2592000000 (1 week) + #maxAge: 2592000000 diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index 391bc9998c..9a6f9769e6 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -272,3 +272,18 @@ allowedPrivateNetworks: [ # # Disable query truncation. If set to true, the full text of the query will be output to the log. # # default: false # disableQueryTruncation: false + +# Settings for the activity logger, which records inbound activities to the database. +# Disabled by default due to the large volume of data it saves. +#activityLogging: + # Log activities to the database (default: false) + #enabled: false + + # Save the activity before processing, then update later with the results. + # This has the advantage of capturing activities that cause a hard-crash, but doubles the number of queries used. + # Default: false + #preSave: false + + # How long to save each log entry before deleting it. + # Default: 2592000000 (1 week) + #maxAge: 2592000000 diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 1e03e902bf..2d088547ba 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -345,3 +345,18 @@ checkActivityPubGetSignature: false # # Disable query truncation. If set to true, the full text of the query will be output to the log. # # default: false # disableQueryTruncation: false + +# Settings for the activity logger, which records inbound activities to the database. +# Disabled by default due to the large volume of data it saves. +#activityLogging: + # Log activities to the database (default: false) + #enabled: false + + # Save the activity before processing, then update later with the results. + # This has the advantage of capturing activities that cause a hard-crash, but doubles the number of queries used. + # Default: false + #preSave: false + + # How long to save each log entry before deleting it. + # Default: 2592000000 (1 week) + #maxAge: 2592000000 diff --git a/.config/example.yml b/.config/example.yml index 7d4cd0c659..7bca8642be 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -383,3 +383,18 @@ checkActivityPubGetSignature: false # # Disable query truncation. If set to true, the full text of the query will be output to the log. # # default: false # disableQueryTruncation: false + +# Settings for the activity logger, which records inbound activities to the database. +# Disabled by default due to the large volume of data it saves. +#activityLogging: + # Log activities to the database (default: false) + #enabled: false + + # Save the activity before processing, then update later with the results. + # This has the advantage of capturing activities that cause a hard-crash, but doubles the number of queries used. + # Default: false + #preSave: false + + # How long to save each log entry before deleting it. + # Default: 2592000000 (1 week) + #maxAge: 2592000000 diff --git a/packages/backend/migration/1731565470048-add-activity-log.js b/packages/backend/migration/1731565470048-add-activity-log.js new file mode 100644 index 0000000000..19c6b336af --- /dev/null +++ b/packages/backend/migration/1731565470048-add-activity-log.js @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class AddActivityLog1731565470048 { + name = 'AddActivityLog1731565470048' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "activity_context" ("md5" text NOT NULL, "json" jsonb NOT NULL, CONSTRAINT "PK_activity_context" PRIMARY KEY ("md5"))`); + await queryRunner.query(`CREATE INDEX "IDK_activity_context_md5" ON "activity_context" ("md5") `); + await queryRunner.query(`CREATE TABLE "activity_log" ("id" character varying(32) NOT NULL, "at" TIMESTAMP WITH TIME ZONE NOT NULL, "key_id" text NOT NULL, "host" text NOT NULL, "verified" boolean NOT NULL, "accepted" boolean NOT NULL, "result" text NOT NULL, "activity" jsonb NOT NULL, "context_hash" text, "auth_user_id" character varying(32), CONSTRAINT "PK_activity_log" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_activity_log_at" ON "activity_log" ("at") `); + await queryRunner.query(`CREATE INDEX "IDX_activity_log_host" ON "activity_log" ("host") `); + await queryRunner.query(`ALTER TABLE "activity_log" ADD CONSTRAINT "FK_activity_log_context_hash" FOREIGN KEY ("context_hash") REFERENCES "activity_context"("md5") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "activity_log" ADD CONSTRAINT "FK_activity_log_auth_user_id" FOREIGN KEY ("auth_user_id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "activity_log" DROP CONSTRAINT "FK_activity_log_auth_user_id"`); + await queryRunner.query(`ALTER TABLE "activity_log" DROP CONSTRAINT "FK_activity_log_context_hash"`); + await queryRunner.query(`DROP INDEX "public"."IDX_activity_log_host"`); + await queryRunner.query(`DROP INDEX "public"."IDX_activity_log_at"`); + await queryRunner.query(`DROP TABLE "activity_log"`); + await queryRunner.query(`DROP INDEX "public"."IDK_activity_context_md5"`); + await queryRunner.query(`DROP TABLE "activity_context"`); + } +} diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts index ad59a55688..3584e71153 100644 --- a/packages/backend/src/boot/common.ts +++ b/packages/backend/src/boot/common.ts @@ -13,6 +13,7 @@ import { ServerStatsService } from '@/daemons/ServerStatsService.js'; import { ServerService } from '@/server/ServerService.js'; import { MainModule } from '@/MainModule.js'; import { envOption } from '@/env.js'; +import { ActivityLogCleanupService } from '@/daemons/ActivityLogCleanupService.js'; export async function server() { const app = await NestFactory.createApplicationContext(MainModule, { @@ -28,6 +29,7 @@ export async function server() { if (!envOption.noDaemons) { app.get(QueueStatsService).start(); app.get(ServerStatsService).start(); + app.get(ActivityLogCleanupService).start(); } return app; diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index d35befdc2b..24f3c472a4 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -129,6 +129,12 @@ type Source = { enableQueryParamLogging? : boolean, } } + + activityLogging?: { + enabled?: boolean; + preSave?: boolean; + maxAge?: number; + }; }; export type Config = { @@ -238,6 +244,12 @@ export type Config = { pidFile: string; filePermissionBits?: string; + + activityLogging: { + enabled: boolean; + preSave: boolean; + maxAge: number; + }; }; export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch'; @@ -380,6 +392,11 @@ export function loadConfig(): Config { pidFile: config.pidFile, filePermissionBits: config.filePermissionBits, logging: config.logging, + activityLogging: { + enabled: config.activityLogging?.enabled ?? false, + preSave: config.activityLogging?.preSave ?? false, + maxAge: config.activityLogging?.maxAge ?? (1000 * 60 * 60 * 24 * 30), + }, }; } @@ -531,4 +548,5 @@ function applyEnvOverrides(config: Source) { _apply_top(['import', ['downloadTimeout', 'maxFileSize']]); _apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature', 'setupPassword']]); _apply_top(['logging', 'sql', ['disableQueryTruncation', 'enableQueryParamLogging']]); + _apply_top(['activityLogging', ['enabled', 'preSave', 'maxAge']]); } diff --git a/packages/backend/src/daemons/ActivityLogCleanupService.ts b/packages/backend/src/daemons/ActivityLogCleanupService.ts new file mode 100644 index 0000000000..e2ffef3c5f --- /dev/null +++ b/packages/backend/src/daemons/ActivityLogCleanupService.ts @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable, type OnApplicationShutdown } from '@nestjs/common'; +import { LessThan } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; +import type { ActivityLogsRepository } from '@/models/_.js'; + +// 10 minutes +export const scanInterval = 1000 * 60 * 10; + +@Injectable() +export class ActivityLogCleanupService implements OnApplicationShutdown { + private scanTimer: NodeJS.Timeout | null = null; + + constructor( + @Inject(DI.config) + private readonly config: Config, + + @Inject(DI.activityLogsRepository) + private readonly activityLogsRepository: ActivityLogsRepository, + ) {} + + @bindThis + public async start(): Promise { + // Just in case start() gets called multiple times. + this.dispose(); + + // Prune at startup, in case the server was rebooted during the interval. + // noinspection ES6MissingAwait + this.tick(); + + // Prune on a regular interval for the lifetime of the server. + this.scanTimer = setInterval(this.tick, scanInterval); + } + + @bindThis + private async tick(): Promise { + // This is the date in UTC of the oldest log to KEEP + const oldestAllowed = new Date(Date.now() - this.config.activityLogging.maxAge); + + // Delete all logs older than the threshold. + await this.activityLogsRepository.delete({ + at: LessThan(oldestAllowed), + }); + } + + @bindThis + public onApplicationShutdown(): void { + this.dispose(); + } + + @bindThis + public dispose(): void { + if (this.scanTimer) { + clearInterval(this.scanTimer); + this.scanTimer = null; + } + } +} diff --git a/packages/backend/src/daemons/DaemonModule.ts b/packages/backend/src/daemons/DaemonModule.ts index a67907e6dd..12f890b3eb 100644 --- a/packages/backend/src/daemons/DaemonModule.ts +++ b/packages/backend/src/daemons/DaemonModule.ts @@ -8,6 +8,7 @@ import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; import { QueueStatsService } from './QueueStatsService.js'; import { ServerStatsService } from './ServerStatsService.js'; +import { ActivityLogCleanupService } from './ActivityLogCleanupService.js'; @Module({ imports: [ @@ -17,10 +18,12 @@ import { ServerStatsService } from './ServerStatsService.js'; providers: [ QueueStatsService, ServerStatsService, + ActivityLogCleanupService, ], exports: [ QueueStatsService, ServerStatsService, + ActivityLogCleanupService, ], }) export class DaemonModule {} diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 296cc4815b..e591024fbd 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -22,6 +22,9 @@ export const DI = { appsRepository: Symbol('appsRepository'), avatarDecorationsRepository: Symbol('avatarDecorationsRepository'), latestNotesRepository: Symbol('latestNotesRepository'), + activityContextRepository: Symbol('activityContextRepository'), + contextUsagesRepository: Symbol('contextUsagesRepository'), + activityLogsRepository: Symbol('activityLogsRepository'), noteFavoritesRepository: Symbol('noteFavoritesRepository'), noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'), noteReactionsRepository: Symbol('noteReactionsRepository'), diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 3a1158a42a..37c4e4fd92 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -80,7 +80,9 @@ import { MiUserPublickey, MiUserSecurityKey, MiWebhook, - NoteEdit + NoteEdit, + SkActivityContext, + SkActivityLog, } from './_.js'; import type { DataSource } from 'typeorm'; @@ -126,6 +128,18 @@ const $latestNotesRepository: Provider = { inject: [DI.db], }; +const $activityContextRepository: Provider = { + provide: DI.activityContextRepository, + useFactory: (db: DataSource) => db.getRepository(SkActivityContext).extend(miRepository as MiRepository), + inject: [DI.db], +}; + +const $activityLogsRepository: Provider = { + provide: DI.activityLogsRepository, + useFactory: (db: DataSource) => db.getRepository(SkActivityLog).extend(miRepository as MiRepository), + inject: [DI.db], +}; + const $noteFavoritesRepository: Provider = { provide: DI.noteFavoritesRepository, useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite).extend(miRepository as MiRepository), @@ -526,6 +540,8 @@ const $noteScheduleRepository: Provider = { $appsRepository, $avatarDecorationsRepository, $latestNotesRepository, + $activityContextRepository, + $activityLogsRepository, $noteFavoritesRepository, $noteThreadMutingsRepository, $noteReactionsRepository, @@ -600,6 +616,8 @@ const $noteScheduleRepository: Provider = { $appsRepository, $avatarDecorationsRepository, $latestNotesRepository, + $activityContextRepository, + $activityLogsRepository, $noteFavoritesRepository, $noteThreadMutingsRepository, $noteReactionsRepository, diff --git a/packages/backend/src/models/SkActivityContext.ts b/packages/backend/src/models/SkActivityContext.ts new file mode 100644 index 0000000000..9fdd0a9525 --- /dev/null +++ b/packages/backend/src/models/SkActivityContext.ts @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Column, PrimaryColumn, Entity, Index } from 'typeorm'; + +@Entity('activity_context') +export class SkActivityContext { + @PrimaryColumn('text') + @Index() + public md5: string; + + @Column('jsonb') + // https://github.com/typeorm/typeorm/issues/8559 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public json: any; + + constructor(data?: Partial) { + if (data) { + Object.assign(this, data); + } + } +} diff --git a/packages/backend/src/models/SkActivityLog.ts b/packages/backend/src/models/SkActivityLog.ts new file mode 100644 index 0000000000..229c333588 --- /dev/null +++ b/packages/backend/src/models/SkActivityLog.ts @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; +import { SkActivityContext } from '@/models/SkActivityContext.js'; +import { MiUser } from '@/models/_.js'; +import { id } from './util/id.js'; + +@Entity('activity_log') +export class SkActivityLog { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamptz') + public at: Date; + + @Column({ + type: 'text', + name: 'key_id', + }) + public keyId: string; + + @Index() + @Column('text') + public host: string; + + @Column('boolean') + public verified: boolean; + + @Column('boolean') + public accepted: boolean; + + @Column('text') + public result: string; + + @Column('jsonb') + // https://github.com/typeorm/typeorm/issues/8559 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public activity: any; + + @Column({ + type: 'text', + name: 'context_hash', + nullable: true, + }) + public contextHash: string | null; + + @ManyToOne(() => SkActivityContext, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ + name: 'context_hash', + }) + public context: SkActivityContext | null; + + @Column({ + type: 'varchar' as const, + length: 32, + name: 'auth_user_id', + nullable: true, + }) + public authUserId: string | null; + + @ManyToOne(() => MiUser, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ + name: 'auth_user_id', + }) + public authUser: MiUser | null; + + constructor(data?: Partial) { + if (data) { + Object.assign(this, data); + } + } +} diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index 9a4ebfc90f..aeb6133d70 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -82,6 +82,8 @@ import { NoteEdit } from '@/models/NoteEdit.js'; import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import { MiReversiGame } from '@/models/ReversiGame.js'; import { MiNoteSchedule } from '@/models/NoteSchedule.js'; +import { SkActivityLog } from '@/models/SkActivityLog.js'; +import { SkActivityContext } from './SkActivityContext.js'; import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; export interface MiRepository { @@ -129,6 +131,8 @@ export const miRepository = { export { SkLatestNote, + SkActivityContext, + SkActivityLog, MiAbuseUserReport, MiAbuseReportNotificationRecipient, MiAccessToken, @@ -229,6 +233,8 @@ export type HashtagsRepository = Repository & MiRepository export type InstancesRepository = Repository & MiRepository; export type MetasRepository = Repository & MiRepository; export type LatestNotesRepository = Repository & MiRepository; +export type ActivityContextRepository = Repository & MiRepository; +export type ActivityLogsRepository = Repository & MiRepository; export type ModerationLogsRepository = Repository & MiRepository; export type MutingsRepository = Repository & MiRepository; export type RenoteMutingsRepository = Repository & MiRepository; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 98405052c6..658830ffcb 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -85,6 +85,8 @@ import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { SkLatestNote } from '@/models/LatestNote.js'; +import { SkActivityContext } from '@/models/SkActivityContext.js'; +import { SkActivityLog } from '@/models/SkActivityLog.js'; pg.types.setTypeParser(20, Number); @@ -171,6 +173,8 @@ class MyCustomLogger implements Logger { export const entities = [ SkLatestNote, + SkActivityContext, + SkActivityLog, MiAnnouncement, MiAnnouncementRead, MiMeta, diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 87d4bf52fa..d40104ee9b 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -4,6 +4,7 @@ */ import { URL } from 'node:url'; +import { createHash } from 'crypto'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import httpSignature from '@peertube/http-signature'; import * as Bull from 'bullmq'; @@ -29,6 +30,11 @@ import { CollapsedQueue } from '@/misc/collapsed-queue.js'; import { MiNote } from '@/models/Note.js'; import { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; +import { IdService } from '@/core/IdService.js'; +import { JsonValue } from '@/misc/json-value.js'; +import { SkActivityLog, SkActivityContext } from '@/models/_.js'; +import type { ActivityLogsRepository, ActivityContextRepository } from '@/models/_.js'; +import type { Config } from '@/config.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { InboxJobData } from '../types.js'; @@ -46,6 +52,9 @@ export class InboxProcessorService implements OnApplicationShutdown { @Inject(DI.meta) private meta: MiMeta, + @Inject(DI.config) + private config: Config, + private utilityService: UtilityService, private apInboxService: ApInboxService, private federatedInstanceService: FederatedInstanceService, @@ -57,6 +66,13 @@ export class InboxProcessorService implements OnApplicationShutdown { private apRequestChart: ApRequestChart, private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, + private idService: IdService, + + @Inject(DI.activityContextRepository) + private activityContextRepository: ActivityContextRepository, + + @Inject(DI.activityLogsRepository) + private activityLogsRepository: ActivityLogsRepository, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance); @@ -64,6 +80,42 @@ export class InboxProcessorService implements OnApplicationShutdown { @bindThis public async process(job: Bull.Job): Promise { + if (this.config.activityLogging.enabled) { + return await this._processLogged(job); + } else { + return await this._process(job); + } + } + + private async _processLogged(job: Bull.Job): Promise { + const payload = job.data.activity; + const keyId = job.data.signature.keyId; + const log = this.createLog(payload, keyId); + + // Pre-save the activity in case it leads to a hard-crash. + if (this.config.activityLogging.preSave) { + await this.recordLog(log); + } + + try { + const result = await this._process(job, log); + + log.accepted = result.startsWith('ok'); + log.result = result; + + return result; + } catch (err) { + log.accepted = false; + log.result = String(err); + + throw err; + } finally { + // Save or finalize asynchronously + this.recordLog(log).catch(err => this.logger.error('Failed to record AP activity:', err)); + } + } + + private async _process(job: Bull.Job, log?: SkActivityLog): Promise { const signature = job.data.signature; // HTTP-signature let activity = job.data.activity; @@ -197,6 +249,13 @@ export class InboxProcessorService implements OnApplicationShutdown { delete activity.id; } + // Attach log to verified user + if (log) { + log.verified = true; + log.authUser = authUser.user; + log.authUserId = authUser.user.id; + } + this.apRequestChart.inbox(); this.federationChart.inbox(authUser.user.host); @@ -292,4 +351,47 @@ export class InboxProcessorService implements OnApplicationShutdown { async onApplicationShutdown(signal?: string) { await this.dispose(); } + + private createLog(payload: IActivity, keyId: string): SkActivityLog { + const activity = Object.assign({}, payload, { '@context': undefined }) as unknown as JsonValue; + const host = this.utilityService.extractDbHost(keyId); + + const log = new SkActivityLog({ + id: this.idService.gen(), + at: new Date(), + verified: false, + accepted: false, + result: 'not processed', + activity, + keyId, + host, + }); + + const context = payload['@context']; + if (context) { + const md5 = createHash('md5').update(JSON.stringify(context)).digest('base64'); + log.contextHash = md5; + log.context = new SkActivityContext({ + md5, + json: context, + }); + } + + return log; + } + + private async recordLog(log: SkActivityLog): Promise { + if (log.context) { + // https://stackoverflow.com/a/47064558 + await this.activityContextRepository + .createQueryBuilder('context_body') + .insert() + .into(SkActivityContext) + .values(log.context) + .orIgnore('md5') + .execute(); + } + + await this.activityLogsRepository.upsert(log, ['id']); + } } -- cgit v1.2.3-freya From 561f46b8d4a159fe6f2317a112ca7f0464ef74cc Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 14 Nov 2024 15:03:58 -0500 Subject: add logging for ActivityLogCleanupService --- packages/backend/src/daemons/ActivityLogCleanupService.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/daemons/ActivityLogCleanupService.ts b/packages/backend/src/daemons/ActivityLogCleanupService.ts index e2ffef3c5f..bf5ddec05d 100644 --- a/packages/backend/src/daemons/ActivityLogCleanupService.ts +++ b/packages/backend/src/daemons/ActivityLogCleanupService.ts @@ -9,12 +9,15 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { bindThis } from '@/decorators.js'; import type { ActivityLogsRepository } from '@/models/_.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import Logger from '@/logger.js'; // 10 minutes export const scanInterval = 1000 * 60 * 10; @Injectable() export class ActivityLogCleanupService implements OnApplicationShutdown { + private readonly logger: Logger; private scanTimer: NodeJS.Timeout | null = null; constructor( @@ -23,7 +26,11 @@ export class ActivityLogCleanupService implements OnApplicationShutdown { @Inject(DI.activityLogsRepository) private readonly activityLogsRepository: ActivityLogsRepository, - ) {} + + loggerService: LoggerService, + ) { + this.logger = loggerService.getLogger('activity-log-cleanup'); + } @bindThis public async start(): Promise { @@ -44,9 +51,11 @@ export class ActivityLogCleanupService implements OnApplicationShutdown { const oldestAllowed = new Date(Date.now() - this.config.activityLogging.maxAge); // Delete all logs older than the threshold. - await this.activityLogsRepository.delete({ + const { affected } = await this.activityLogsRepository.delete({ at: LessThan(oldestAllowed), }); + + this.logger.info(`Activity Log cleanup complete; removed ${affected ?? 0} expired logs.`); } @bindThis -- cgit v1.2.3-freya From 07cd01ec34fc61f43bc87db943e53f531386b76f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 18 Nov 2024 01:16:12 -0500 Subject: add missing constraint names to `SkActivityLog` and `SkActivityContext` --- .../1731910422761-rename-activity-log-indexes.js | 16 ++++++++++++++++ packages/backend/src/models/SkActivityContext.ts | 7 ++++--- packages/backend/src/models/SkActivityLog.ts | 14 +++++++++----- 3 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 packages/backend/migration/1731910422761-rename-activity-log-indexes.js (limited to 'packages/backend/src') diff --git a/packages/backend/migration/1731910422761-rename-activity-log-indexes.js b/packages/backend/migration/1731910422761-rename-activity-log-indexes.js new file mode 100644 index 0000000000..82d5a796e9 --- /dev/null +++ b/packages/backend/migration/1731910422761-rename-activity-log-indexes.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RenameActivityLogIndexes1731910422761 { + name = 'RenameActivityLogIndexes1731910422761' + + async up(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDK_activity_context_md5"`); + } + + async down(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDK_activity_context_md5" ON "activity_context" ("md5") `); + } +} diff --git a/packages/backend/src/models/SkActivityContext.ts b/packages/backend/src/models/SkActivityContext.ts index 9fdd0a9525..349c3e7113 100644 --- a/packages/backend/src/models/SkActivityContext.ts +++ b/packages/backend/src/models/SkActivityContext.ts @@ -3,12 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Column, PrimaryColumn, Entity, Index } from 'typeorm'; +import { Column, PrimaryColumn, Entity } from 'typeorm'; @Entity('activity_context') export class SkActivityContext { - @PrimaryColumn('text') - @Index() + @PrimaryColumn('text', { + primaryKeyConstraintName: 'PK_activity_context', + }) public md5: string; @Column('jsonb') diff --git a/packages/backend/src/models/SkActivityLog.ts b/packages/backend/src/models/SkActivityLog.ts index 229c333588..f23c0708b9 100644 --- a/packages/backend/src/models/SkActivityLog.ts +++ b/packages/backend/src/models/SkActivityLog.ts @@ -10,10 +10,13 @@ import { id } from './util/id.js'; @Entity('activity_log') export class SkActivityLog { - @PrimaryColumn(id()) + @PrimaryColumn({ + ...id(), + primaryKeyConstraintName: 'PK_activity_log', + }) public id: string; - @Index() + @Index('IDX_activity_log_at') @Column('timestamptz') public at: Date; @@ -23,7 +26,7 @@ export class SkActivityLog { }) public keyId: string; - @Index() + @Index('IDX_activity_log_host') @Column('text') public host: string; @@ -54,12 +57,12 @@ export class SkActivityLog { }) @JoinColumn({ name: 'context_hash', + foreignKeyConstraintName: 'FK_activity_log_context_hash', }) public context: SkActivityContext | null; @Column({ - type: 'varchar' as const, - length: 32, + ...id(), name: 'auth_user_id', nullable: true, }) @@ -71,6 +74,7 @@ export class SkActivityLog { }) @JoinColumn({ name: 'auth_user_id', + foreignKeyConstraintName: 'FK_activity_log_auth_user_id', }) public authUser: MiUser | null; -- cgit v1.2.3-freya From e35e92beb9ccdabf5692107966c2cf9c2e91c4dd Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 18 Nov 2024 01:18:45 -0500 Subject: log inbound activity duration --- .../migration/1731909785724-activity-log-timing.js | 19 +++++++++++++++++++ packages/backend/src/models/SkActivityLog.ts | 10 ++++++++-- .../src/queue/processors/InboxProcessorService.ts | 10 ++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 packages/backend/migration/1731909785724-activity-log-timing.js (limited to 'packages/backend/src') diff --git a/packages/backend/migration/1731909785724-activity-log-timing.js b/packages/backend/migration/1731909785724-activity-log-timing.js new file mode 100644 index 0000000000..8b72fb8972 --- /dev/null +++ b/packages/backend/migration/1731909785724-activity-log-timing.js @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ActivityLogTiming1731909785724 { + name = 'ActivityLogTiming1731909785724' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "activity_log" ADD "duration" double precision NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "activity_log" ALTER COLUMN "result" DROP NOT NULL`); + } + + async down(queryRunner) { + await queryRunner.query(`UPDATE "activity_log" SET "result" = 'not processed' WHERE "result" IS NULL`); + await queryRunner.query(`ALTER TABLE "activity_log" ALTER COLUMN "result" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "activity_log" DROP COLUMN "duration"`); + } +} diff --git a/packages/backend/src/models/SkActivityLog.ts b/packages/backend/src/models/SkActivityLog.ts index f23c0708b9..f2d11487dd 100644 --- a/packages/backend/src/models/SkActivityLog.ts +++ b/packages/backend/src/models/SkActivityLog.ts @@ -20,6 +20,12 @@ export class SkActivityLog { @Column('timestamptz') public at: Date; + /** + * Processing duration in milliseconds + */ + @Column('double precision', { default: 0 }) + public duration = 0; + @Column({ type: 'text', name: 'key_id', @@ -36,8 +42,8 @@ export class SkActivityLog { @Column('boolean') public accepted: boolean; - @Column('text') - public result: string; + @Column('text', { nullable: true }) + public result: string | null = null; @Column('jsonb') // https://github.com/typeorm/typeorm/issues/8559 diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index d40104ee9b..242c67359b 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -98,9 +98,16 @@ export class InboxProcessorService implements OnApplicationShutdown { } try { + const startTime = process.hrtime.bigint(); const result = await this._process(job, log); + const endTime = process.hrtime.bigint(); + + // Truncate nanoseconds to microseconds, then scale to milliseconds. + // 123,456,789 ns -> 123,456 us -> 123.456 ms + const duration = Number((endTime - startTime) / 1000n) / 1000; log.accepted = result.startsWith('ok'); + log.duration = duration; log.result = result; return result; @@ -249,7 +256,7 @@ export class InboxProcessorService implements OnApplicationShutdown { delete activity.id; } - // Attach log to verified user + // Record verified user in log if (log) { log.verified = true; log.authUser = authUser.user; @@ -361,7 +368,6 @@ export class InboxProcessorService implements OnApplicationShutdown { at: new Date(), verified: false, accepted: false, - result: 'not processed', activity, keyId, host, -- cgit v1.2.3-freya From 871c63b48bd958d301f709260dbb01273462da57 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 18 Nov 2024 01:24:23 -0500 Subject: print warning when activity processing exceeds 10 seonds --- packages/backend/src/queue/processors/InboxProcessorService.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'packages/backend/src') diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 242c67359b..1dcce0ffc2 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -105,6 +105,9 @@ export class InboxProcessorService implements OnApplicationShutdown { // Truncate nanoseconds to microseconds, then scale to milliseconds. // 123,456,789 ns -> 123,456 us -> 123.456 ms const duration = Number((endTime - startTime) / 1000n) / 1000; + if (duration > 10000) { + this.logger.warn(`Activity ${JSON.stringify(payload.id)} by "${keyId}" took ${(duration / 1000).toFixed(1)} seconds to complete`); + } log.accepted = result.startsWith('ok'); log.duration = duration; -- cgit v1.2.3-freya From 15148b787527cf62cbd569985b16f788e8a52546 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 18 Nov 2024 08:03:15 -0500 Subject: fix activity duration calculation * Ensure that timing is recorded even if an exception is thrown. * Round to the correct decimal place. --- .../src/queue/processors/InboxProcessorService.ts | 27 ++++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 1dcce0ffc2..5ed124a049 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -88,6 +88,7 @@ export class InboxProcessorService implements OnApplicationShutdown { } private async _processLogged(job: Bull.Job): Promise { + const startTime = process.hrtime.bigint(); const payload = job.data.activity; const keyId = job.data.signature.keyId; const log = this.createLog(payload, keyId); @@ -98,19 +99,9 @@ export class InboxProcessorService implements OnApplicationShutdown { } try { - const startTime = process.hrtime.bigint(); const result = await this._process(job, log); - const endTime = process.hrtime.bigint(); - - // Truncate nanoseconds to microseconds, then scale to milliseconds. - // 123,456,789 ns -> 123,456 us -> 123.456 ms - const duration = Number((endTime - startTime) / 1000n) / 1000; - if (duration > 10000) { - this.logger.warn(`Activity ${JSON.stringify(payload.id)} by "${keyId}" took ${(duration / 1000).toFixed(1)} seconds to complete`); - } log.accepted = result.startsWith('ok'); - log.duration = duration; log.result = result; return result; @@ -120,6 +111,22 @@ export class InboxProcessorService implements OnApplicationShutdown { throw err; } finally { + // Calculate the activity processing time with correct rounding and decimals. + // 1. Truncate nanoseconds to microseconds + // 2. Scale to 1/10 millisecond ticks. + // 3. Round to nearest tick. + // 4. Sale to milliseconds + // Example: 123,456,789 ns -> 123,456 us -> 12,345.6 ticks -> 12,346 ticks -> 123.46 ms + const endTime = process.hrtime.bigint(); + const duration = Math.round(Number((endTime - startTime) / 1000n) / 10) / 100; + log.duration = duration; + + // Activities should time out after roughly 5 seconds. + // A runtime longer than 10 seconds could indicate a problem or attack. + if (duration > 10000) { + this.logger.warn(`Activity ${JSON.stringify(payload.id)} by "${keyId}" took ${(duration / 1000).toFixed(1)} seconds to complete`); + } + // Save or finalize asynchronously this.recordLog(log).catch(err => this.logger.error('Failed to record AP activity:', err)); } -- cgit v1.2.3-freya From 0979392925aa05e7b86307e17d6dc7e2940038fc Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 18 Nov 2024 08:06:30 -0500 Subject: make `activity_log.duration` nullable --- .../1731935047347-nullable-activity-log-duration.js | 20 ++++++++++++++++++++ packages/backend/src/models/SkActivityLog.ts | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 packages/backend/migration/1731935047347-nullable-activity-log-duration.js (limited to 'packages/backend/src') diff --git a/packages/backend/migration/1731935047347-nullable-activity-log-duration.js b/packages/backend/migration/1731935047347-nullable-activity-log-duration.js new file mode 100644 index 0000000000..2acbd2bca5 --- /dev/null +++ b/packages/backend/migration/1731935047347-nullable-activity-log-duration.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NullableActivityLogDuration1731935047347 { + name = 'NullableActivityLogDuration1731935047347' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "activity_log" ALTER COLUMN "duration" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "activity_log" ALTER COLUMN "duration" DROP DEFAULT`); + await queryRunner.query(`UPDATE "activity_log" SET "duration" = NULL WHERE "duration" = 0`); + } + + async down(queryRunner) { + await queryRunner.query(`UPDATE "activity_log" SET "duration" = 0 WHERE "duration" IS NULL`); + await queryRunner.query(`ALTER TABLE "activity_log" ALTER COLUMN "duration" SET DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "activity_log" ALTER COLUMN "duration" SET NOT NULL`); + } +} diff --git a/packages/backend/src/models/SkActivityLog.ts b/packages/backend/src/models/SkActivityLog.ts index f2d11487dd..6e462eccef 100644 --- a/packages/backend/src/models/SkActivityLog.ts +++ b/packages/backend/src/models/SkActivityLog.ts @@ -23,8 +23,8 @@ export class SkActivityLog { /** * Processing duration in milliseconds */ - @Column('double precision', { default: 0 }) - public duration = 0; + @Column('double precision', { nullable: true }) + public duration: number | null = null; @Column({ type: 'text', -- cgit v1.2.3-freya From cc2edae7abff566ba968a6027018826099400320 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 9 Dec 2024 10:00:25 -0500 Subject: rename activity_log and activity_context to ap_inbox_log and ap_context --- ...56280460-rename_activity_log_to_ap_inbox_log.js | 32 ++++++ packages/backend/src/boot/common.ts | 4 +- .../src/daemons/ActivityLogCleanupService.ts | 73 -------------- .../backend/src/daemons/ApLogCleanupService.ts | 73 ++++++++++++++ packages/backend/src/daemons/DaemonModule.ts | 6 +- packages/backend/src/di-symbols.ts | 5 +- packages/backend/src/models/RepositoryModule.ts | 24 ++--- packages/backend/src/models/SkActivityContext.ts | 25 ----- packages/backend/src/models/SkActivityLog.ts | 92 ----------------- packages/backend/src/models/SkApContext.ts | 25 +++++ packages/backend/src/models/SkApInboxLog.ts | 109 +++++++++++++++++++++ packages/backend/src/models/_.ts | 12 +-- packages/backend/src/postgres.ts | 8 +- .../src/queue/processors/InboxProcessorService.ts | 30 +++--- 14 files changed, 283 insertions(+), 235 deletions(-) create mode 100644 packages/backend/migration/1733756280460-rename_activity_log_to_ap_inbox_log.js delete mode 100644 packages/backend/src/daemons/ActivityLogCleanupService.ts create mode 100644 packages/backend/src/daemons/ApLogCleanupService.ts delete mode 100644 packages/backend/src/models/SkActivityContext.ts delete mode 100644 packages/backend/src/models/SkActivityLog.ts create mode 100644 packages/backend/src/models/SkApContext.ts create mode 100644 packages/backend/src/models/SkApInboxLog.ts (limited to 'packages/backend/src') diff --git a/packages/backend/migration/1733756280460-rename_activity_log_to_ap_inbox_log.js b/packages/backend/migration/1733756280460-rename_activity_log_to_ap_inbox_log.js new file mode 100644 index 0000000000..ad25135188 --- /dev/null +++ b/packages/backend/migration/1733756280460-rename_activity_log_to_ap_inbox_log.js @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RenameActivityLogToApInboxLog1733756280460 { + name = 'RenameActivityLogToApInboxLog1733756280460' + + async up(queryRunner) { + await queryRunner.query(`ALTER INDEX "IDX_activity_log_at" RENAME TO "IDX_ap_inbox_log_at"`); + await queryRunner.query(`ALTER INDEX "IDX_activity_log_host" RENAME TO "IDX_ap_inbox_log_host"`); + await queryRunner.query(`ALTER TABLE "activity_log" RENAME CONSTRAINT "PK_activity_log" TO "PK_ap_inbox_log"`); + await queryRunner.query(`ALTER TABLE "activity_log" RENAME CONSTRAINT "FK_activity_log_context_hash" TO "FK_ap_inbox_log_context_hash"`); + await queryRunner.query(`ALTER TABLE "activity_log" RENAME CONSTRAINT "FK_activity_log_auth_user_id" TO "FK_ap_inbox_log_auth_user_id"`); + await queryRunner.query(`ALTER TABLE "activity_log" RENAME TO "ap_inbox_log"`); + + await queryRunner.query(`ALTER TABLE "activity_context" RENAME CONSTRAINT "PK_activity_context" TO "PK_ap_context"`); + await queryRunner.query(`ALTER TABLE "activity_context" RENAME TO "ap_context"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "ap_context" RENAME TO "activity_context"`); + await queryRunner.query(`ALTER TABLE "activity_context" RENAME CONSTRAINT "PK_ap_context" TO "PK_activity_context"`); + + await queryRunner.query(`ALTER TABLE "ap_inbox_log" RENAME TO "activity_log"`); + await queryRunner.query(`ALTER TABLE "activity_log" RENAME CONSTRAINT "FK_ap_inbox_log_auth_user_id" TO "FK_activity_log_auth_user_id"`); + await queryRunner.query(`ALTER TABLE "activity_log" RENAME CONSTRAINT "FK_ap_inbox_log_context_hash" TO "FK_activity_log_context_hash"`); + await queryRunner.query(`ALTER TABLE "activity_log" RENAME CONSTRAINT "PK_ap_inbox_log" TO "PK_activity_log"`); + await queryRunner.query(`ALTER INDEX "IDX_ap_inbox_log_host" RENAME TO "IDX_activity_log_host"`); + await queryRunner.query(`ALTER INDEX "IDX_ap_inbox_log_at" RENAME TO "IDX_activity_log_at"`); + } +} diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts index 3584e71153..2f97980e9a 100644 --- a/packages/backend/src/boot/common.ts +++ b/packages/backend/src/boot/common.ts @@ -13,7 +13,7 @@ import { ServerStatsService } from '@/daemons/ServerStatsService.js'; import { ServerService } from '@/server/ServerService.js'; import { MainModule } from '@/MainModule.js'; import { envOption } from '@/env.js'; -import { ActivityLogCleanupService } from '@/daemons/ActivityLogCleanupService.js'; +import { ApLogCleanupService } from '@/daemons/ApLogCleanupService.js'; export async function server() { const app = await NestFactory.createApplicationContext(MainModule, { @@ -29,7 +29,7 @@ export async function server() { if (!envOption.noDaemons) { app.get(QueueStatsService).start(); app.get(ServerStatsService).start(); - app.get(ActivityLogCleanupService).start(); + app.get(ApLogCleanupService).start(); } return app; diff --git a/packages/backend/src/daemons/ActivityLogCleanupService.ts b/packages/backend/src/daemons/ActivityLogCleanupService.ts deleted file mode 100644 index bf5ddec05d..0000000000 --- a/packages/backend/src/daemons/ActivityLogCleanupService.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Inject, Injectable, type OnApplicationShutdown } from '@nestjs/common'; -import { LessThan } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import { bindThis } from '@/decorators.js'; -import type { ActivityLogsRepository } from '@/models/_.js'; -import { LoggerService } from '@/core/LoggerService.js'; -import Logger from '@/logger.js'; - -// 10 minutes -export const scanInterval = 1000 * 60 * 10; - -@Injectable() -export class ActivityLogCleanupService implements OnApplicationShutdown { - private readonly logger: Logger; - private scanTimer: NodeJS.Timeout | null = null; - - constructor( - @Inject(DI.config) - private readonly config: Config, - - @Inject(DI.activityLogsRepository) - private readonly activityLogsRepository: ActivityLogsRepository, - - loggerService: LoggerService, - ) { - this.logger = loggerService.getLogger('activity-log-cleanup'); - } - - @bindThis - public async start(): Promise { - // Just in case start() gets called multiple times. - this.dispose(); - - // Prune at startup, in case the server was rebooted during the interval. - // noinspection ES6MissingAwait - this.tick(); - - // Prune on a regular interval for the lifetime of the server. - this.scanTimer = setInterval(this.tick, scanInterval); - } - - @bindThis - private async tick(): Promise { - // This is the date in UTC of the oldest log to KEEP - const oldestAllowed = new Date(Date.now() - this.config.activityLogging.maxAge); - - // Delete all logs older than the threshold. - const { affected } = await this.activityLogsRepository.delete({ - at: LessThan(oldestAllowed), - }); - - this.logger.info(`Activity Log cleanup complete; removed ${affected ?? 0} expired logs.`); - } - - @bindThis - public onApplicationShutdown(): void { - this.dispose(); - } - - @bindThis - public dispose(): void { - if (this.scanTimer) { - clearInterval(this.scanTimer); - this.scanTimer = null; - } - } -} diff --git a/packages/backend/src/daemons/ApLogCleanupService.ts b/packages/backend/src/daemons/ApLogCleanupService.ts new file mode 100644 index 0000000000..261c6e3517 --- /dev/null +++ b/packages/backend/src/daemons/ApLogCleanupService.ts @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable, type OnApplicationShutdown } from '@nestjs/common'; +import { LessThan } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { bindThis } from '@/decorators.js'; +import type { ApInboxLogsRepository } from '@/models/_.js'; +import { LoggerService } from '@/core/LoggerService.js'; +import Logger from '@/logger.js'; + +// 10 minutes +export const scanInterval = 1000 * 60 * 10; + +@Injectable() +export class ApLogCleanupService implements OnApplicationShutdown { + private readonly logger: Logger; + private scanTimer: NodeJS.Timeout | null = null; + + constructor( + @Inject(DI.config) + private readonly config: Config, + + @Inject(DI.apInboxLogsRepository) + private readonly apInboxLogsRepository: ApInboxLogsRepository, + + loggerService: LoggerService, + ) { + this.logger = loggerService.getLogger('activity-log-cleanup'); + } + + @bindThis + public async start(): Promise { + // Just in case start() gets called multiple times. + this.dispose(); + + // Prune at startup, in case the server was rebooted during the interval. + // noinspection ES6MissingAwait + this.tick(); + + // Prune on a regular interval for the lifetime of the server. + this.scanTimer = setInterval(this.tick, scanInterval); + } + + @bindThis + private async tick(): Promise { + // This is the date in UTC of the oldest log to KEEP + const oldestAllowed = new Date(Date.now() - this.config.activityLogging.maxAge); + + // Delete all logs older than the threshold. + const { affected } = await this.apInboxLogsRepository.delete({ + at: LessThan(oldestAllowed), + }); + + this.logger.info(`Activity Log cleanup complete; removed ${affected ?? 0} expired logs.`); + } + + @bindThis + public onApplicationShutdown(): void { + this.dispose(); + } + + @bindThis + public dispose(): void { + if (this.scanTimer) { + clearInterval(this.scanTimer); + this.scanTimer = null; + } + } +} diff --git a/packages/backend/src/daemons/DaemonModule.ts b/packages/backend/src/daemons/DaemonModule.ts index 12f890b3eb..ea71875f19 100644 --- a/packages/backend/src/daemons/DaemonModule.ts +++ b/packages/backend/src/daemons/DaemonModule.ts @@ -8,7 +8,7 @@ import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; import { QueueStatsService } from './QueueStatsService.js'; import { ServerStatsService } from './ServerStatsService.js'; -import { ActivityLogCleanupService } from './ActivityLogCleanupService.js'; +import { ApLogCleanupService } from './ApLogCleanupService.js'; @Module({ imports: [ @@ -18,12 +18,12 @@ import { ActivityLogCleanupService } from './ActivityLogCleanupService.js'; providers: [ QueueStatsService, ServerStatsService, - ActivityLogCleanupService, + ApLogCleanupService, ], exports: [ QueueStatsService, ServerStatsService, - ActivityLogCleanupService, + ApLogCleanupService, ], }) export class DaemonModule {} diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index e591024fbd..6b53d38fb7 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -22,9 +22,8 @@ export const DI = { appsRepository: Symbol('appsRepository'), avatarDecorationsRepository: Symbol('avatarDecorationsRepository'), latestNotesRepository: Symbol('latestNotesRepository'), - activityContextRepository: Symbol('activityContextRepository'), - contextUsagesRepository: Symbol('contextUsagesRepository'), - activityLogsRepository: Symbol('activityLogsRepository'), + apContextsRepository: Symbol('apContextsRepository'), + apInboxLogsRepository: Symbol('apInboxLogsRepository'), noteFavoritesRepository: Symbol('noteFavoritesRepository'), noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'), noteReactionsRepository: Symbol('noteReactionsRepository'), diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 37c4e4fd92..dd4ba1c0e4 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -81,8 +81,8 @@ import { MiUserSecurityKey, MiWebhook, NoteEdit, - SkActivityContext, - SkActivityLog, + SkApContext, + SkApInboxLog, } from './_.js'; import type { DataSource } from 'typeorm'; @@ -128,15 +128,15 @@ const $latestNotesRepository: Provider = { inject: [DI.db], }; -const $activityContextRepository: Provider = { - provide: DI.activityContextRepository, - useFactory: (db: DataSource) => db.getRepository(SkActivityContext).extend(miRepository as MiRepository), +const $apContextRepository: Provider = { + provide: DI.apContextsRepository, + useFactory: (db: DataSource) => db.getRepository(SkApContext).extend(miRepository as MiRepository), inject: [DI.db], }; -const $activityLogsRepository: Provider = { - provide: DI.activityLogsRepository, - useFactory: (db: DataSource) => db.getRepository(SkActivityLog).extend(miRepository as MiRepository), +const $apInboxLogsRepository: Provider = { + provide: DI.apInboxLogsRepository, + useFactory: (db: DataSource) => db.getRepository(SkApInboxLog).extend(miRepository as MiRepository), inject: [DI.db], }; @@ -540,8 +540,8 @@ const $noteScheduleRepository: Provider = { $appsRepository, $avatarDecorationsRepository, $latestNotesRepository, - $activityContextRepository, - $activityLogsRepository, + $apContextRepository, + $apInboxLogsRepository, $noteFavoritesRepository, $noteThreadMutingsRepository, $noteReactionsRepository, @@ -616,8 +616,8 @@ const $noteScheduleRepository: Provider = { $appsRepository, $avatarDecorationsRepository, $latestNotesRepository, - $activityContextRepository, - $activityLogsRepository, + $apContextRepository, + $apInboxLogsRepository, $noteFavoritesRepository, $noteThreadMutingsRepository, $noteReactionsRepository, diff --git a/packages/backend/src/models/SkActivityContext.ts b/packages/backend/src/models/SkActivityContext.ts deleted file mode 100644 index 349c3e7113..0000000000 --- a/packages/backend/src/models/SkActivityContext.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Column, PrimaryColumn, Entity } from 'typeorm'; - -@Entity('activity_context') -export class SkActivityContext { - @PrimaryColumn('text', { - primaryKeyConstraintName: 'PK_activity_context', - }) - public md5: string; - - @Column('jsonb') - // https://github.com/typeorm/typeorm/issues/8559 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public json: any; - - constructor(data?: Partial) { - if (data) { - Object.assign(this, data); - } - } -} diff --git a/packages/backend/src/models/SkActivityLog.ts b/packages/backend/src/models/SkActivityLog.ts deleted file mode 100644 index 6e462eccef..0000000000 --- a/packages/backend/src/models/SkActivityLog.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; -import { SkActivityContext } from '@/models/SkActivityContext.js'; -import { MiUser } from '@/models/_.js'; -import { id } from './util/id.js'; - -@Entity('activity_log') -export class SkActivityLog { - @PrimaryColumn({ - ...id(), - primaryKeyConstraintName: 'PK_activity_log', - }) - public id: string; - - @Index('IDX_activity_log_at') - @Column('timestamptz') - public at: Date; - - /** - * Processing duration in milliseconds - */ - @Column('double precision', { nullable: true }) - public duration: number | null = null; - - @Column({ - type: 'text', - name: 'key_id', - }) - public keyId: string; - - @Index('IDX_activity_log_host') - @Column('text') - public host: string; - - @Column('boolean') - public verified: boolean; - - @Column('boolean') - public accepted: boolean; - - @Column('text', { nullable: true }) - public result: string | null = null; - - @Column('jsonb') - // https://github.com/typeorm/typeorm/issues/8559 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public activity: any; - - @Column({ - type: 'text', - name: 'context_hash', - nullable: true, - }) - public contextHash: string | null; - - @ManyToOne(() => SkActivityContext, { - onDelete: 'CASCADE', - nullable: true, - }) - @JoinColumn({ - name: 'context_hash', - foreignKeyConstraintName: 'FK_activity_log_context_hash', - }) - public context: SkActivityContext | null; - - @Column({ - ...id(), - name: 'auth_user_id', - nullable: true, - }) - public authUserId: string | null; - - @ManyToOne(() => MiUser, { - onDelete: 'CASCADE', - nullable: true, - }) - @JoinColumn({ - name: 'auth_user_id', - foreignKeyConstraintName: 'FK_activity_log_auth_user_id', - }) - public authUser: MiUser | null; - - constructor(data?: Partial) { - if (data) { - Object.assign(this, data); - } - } -} diff --git a/packages/backend/src/models/SkApContext.ts b/packages/backend/src/models/SkApContext.ts new file mode 100644 index 0000000000..ff4c6d6fbf --- /dev/null +++ b/packages/backend/src/models/SkApContext.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Column, PrimaryColumn, Entity } from 'typeorm'; + +@Entity('ap_context') +export class SkApContext { + @PrimaryColumn('text', { + primaryKeyConstraintName: 'PK_ap_context', + }) + public md5: string; + + @Column('jsonb') + // https://github.com/typeorm/typeorm/issues/8559 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public json: any; + + constructor(data?: Partial) { + if (data) { + Object.assign(this, data); + } + } +} diff --git a/packages/backend/src/models/SkApInboxLog.ts b/packages/backend/src/models/SkApInboxLog.ts new file mode 100644 index 0000000000..867094405c --- /dev/null +++ b/packages/backend/src/models/SkApInboxLog.ts @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; +import { SkApContext } from '@/models/SkApContext.js'; +import { MiUser } from '@/models/_.js'; +import { id } from './util/id.js'; + +/** + * Records activities received in the inbox + */ +@Entity('ap_inbox_log') +export class SkApInboxLog { + @PrimaryColumn({ + ...id(), + primaryKeyConstraintName: 'PK_ap_inbox_log', + }) + public id: string; + + @Index('IDX_ap_inbox_log_at') + @Column('timestamptz') + public at: Date; + + /** + * Processing duration in milliseconds + */ + @Column('double precision', { nullable: true }) + public duration: number | null = null; + + /** + * Key ID that was used to sign this request. + * Untrusted unless verified is true. + */ + @Column({ + type: 'text', + name: 'key_id', + }) + public keyId: string; + + /** + * Instance that the activity was sent from. + * Untrusted unless verified is true. + */ + @Index('IDX_ap_inbox_log_host') + @Column('text') + public host: string; + + @Column('boolean') + public verified: boolean; + + @Column('boolean') + public accepted: boolean; + + @Column('text', { nullable: true }) + public result: string | null = null; + + @Column('jsonb') + // https://github.com/typeorm/typeorm/issues/8559 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public activity: any; + + @Column({ + type: 'text', + name: 'context_hash', + nullable: true, + }) + public contextHash: string | null; + + @ManyToOne(() => SkApContext, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ + name: 'context_hash', + foreignKeyConstraintName: 'FK_ap_inbox_log_context_hash', + }) + public context: SkApContext | null; + + /** + * ID of the user who signed this request. + */ + @Column({ + ...id(), + name: 'auth_user_id', + nullable: true, + }) + public authUserId: string | null; + + /** + * User who signed this request. + */ + @ManyToOne(() => MiUser, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ + name: 'auth_user_id', + foreignKeyConstraintName: 'FK_ap_inbox_log_auth_user_id', + }) + public authUser: MiUser | null; + + constructor(data?: Partial) { + if (data) { + Object.assign(this, data); + } + } +} diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index aeb6133d70..dabcf89d2c 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -82,8 +82,8 @@ import { NoteEdit } from '@/models/NoteEdit.js'; import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import { MiReversiGame } from '@/models/ReversiGame.js'; import { MiNoteSchedule } from '@/models/NoteSchedule.js'; -import { SkActivityLog } from '@/models/SkActivityLog.js'; -import { SkActivityContext } from './SkActivityContext.js'; +import { SkApInboxLog } from '@/models/SkApInboxLog.js'; +import { SkApContext } from '@/models/SkApContext.js'; import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; export interface MiRepository { @@ -131,8 +131,8 @@ export const miRepository = { export { SkLatestNote, - SkActivityContext, - SkActivityLog, + SkApContext, + SkApInboxLog, MiAbuseUserReport, MiAbuseReportNotificationRecipient, MiAccessToken, @@ -233,8 +233,8 @@ export type HashtagsRepository = Repository & MiRepository export type InstancesRepository = Repository & MiRepository; export type MetasRepository = Repository & MiRepository; export type LatestNotesRepository = Repository & MiRepository; -export type ActivityContextRepository = Repository & MiRepository; -export type ActivityLogsRepository = Repository & MiRepository; +export type ApContextsRepository = Repository & MiRepository; +export type ApInboxLogsRepository = Repository & MiRepository; export type ModerationLogsRepository = Repository & MiRepository; export type MutingsRepository = Repository & MiRepository; export type RenoteMutingsRepository = Repository & MiRepository; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 658830ffcb..9437ac936a 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -85,8 +85,8 @@ import { Config } from '@/config.js'; import MisskeyLogger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { SkLatestNote } from '@/models/LatestNote.js'; -import { SkActivityContext } from '@/models/SkActivityContext.js'; -import { SkActivityLog } from '@/models/SkActivityLog.js'; +import { SkApContext } from '@/models/SkApContext.js'; +import { SkApInboxLog } from '@/models/SkApInboxLog.js'; pg.types.setTypeParser(20, Number); @@ -173,8 +173,8 @@ class MyCustomLogger implements Logger { export const entities = [ SkLatestNote, - SkActivityContext, - SkActivityLog, + SkApContext, + SkApInboxLog, MiAnnouncement, MiAnnouncementRead, MiMeta, diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 5ed124a049..4182f3e090 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -32,8 +32,8 @@ import { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { JsonValue } from '@/misc/json-value.js'; -import { SkActivityLog, SkActivityContext } from '@/models/_.js'; -import type { ActivityLogsRepository, ActivityContextRepository } from '@/models/_.js'; +import { SkApInboxLog, SkApContext } from '@/models/_.js'; +import type { ApInboxLogsRepository, ApContextsRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { InboxJobData } from '../types.js'; @@ -68,11 +68,11 @@ export class InboxProcessorService implements OnApplicationShutdown { private queueLoggerService: QueueLoggerService, private idService: IdService, - @Inject(DI.activityContextRepository) - private activityContextRepository: ActivityContextRepository, + @Inject(DI.apContextsRepository) + private apContextsRepository: ApContextsRepository, - @Inject(DI.activityLogsRepository) - private activityLogsRepository: ActivityLogsRepository, + @Inject(DI.apInboxLogsRepository) + private apInboxLogsRepository: ApInboxLogsRepository, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance); @@ -132,7 +132,7 @@ export class InboxProcessorService implements OnApplicationShutdown { } } - private async _process(job: Bull.Job, log?: SkActivityLog): Promise { + private async _process(job: Bull.Job, log?: SkApInboxLog): Promise { const signature = job.data.signature; // HTTP-signature let activity = job.data.activity; @@ -369,11 +369,11 @@ export class InboxProcessorService implements OnApplicationShutdown { await this.dispose(); } - private createLog(payload: IActivity, keyId: string): SkActivityLog { + private createLog(payload: IActivity, keyId: string): SkApInboxLog { const activity = Object.assign({}, payload, { '@context': undefined }) as unknown as JsonValue; const host = this.utilityService.extractDbHost(keyId); - const log = new SkActivityLog({ + const log = new SkApInboxLog({ id: this.idService.gen(), at: new Date(), verified: false, @@ -387,7 +387,7 @@ export class InboxProcessorService implements OnApplicationShutdown { if (context) { const md5 = createHash('md5').update(JSON.stringify(context)).digest('base64'); log.contextHash = md5; - log.context = new SkActivityContext({ + log.context = new SkApContext({ md5, json: context, }); @@ -396,18 +396,18 @@ export class InboxProcessorService implements OnApplicationShutdown { return log; } - private async recordLog(log: SkActivityLog): Promise { + private async recordLog(log: SkApInboxLog): Promise { if (log.context) { // https://stackoverflow.com/a/47064558 - await this.activityContextRepository - .createQueryBuilder('context_body') + await this.apContextsRepository + .createQueryBuilder('activity_context') .insert() - .into(SkActivityContext) + .into(SkApContext) .values(log.context) .orIgnore('md5') .execute(); } - await this.activityLogsRepository.upsert(log, ['id']); + await this.apInboxLogsRepository.upsert(log, ['id']); } } -- cgit v1.2.3-freya From 81944b3bdf49cf95294adcefc265a568b921dee0 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 30 Jan 2025 22:36:19 -0500 Subject: implement AP fetch logs --- .../migration/1738293576355-create_ap_fetch_log.js | 19 +++ packages/backend/src/core/ApLogService.ts | 189 +++++++++++++++++++++ packages/backend/src/core/CoreModule.ts | 6 + .../src/core/activitypub/ApResolverService.ts | 61 ++++++- .../backend/src/daemons/ApLogCleanupService.ts | 29 +--- packages/backend/src/di-symbols.ts | 1 + packages/backend/src/models/RepositoryModule.ts | 9 + packages/backend/src/models/SkApFetchLog.ts | 89 ++++++++++ packages/backend/src/models/_.ts | 3 + packages/backend/src/postgres.ts | 2 + .../src/queue/processors/InboxProcessorService.ts | 82 ++------- 11 files changed, 395 insertions(+), 95 deletions(-) create mode 100644 packages/backend/migration/1738293576355-create_ap_fetch_log.js create mode 100644 packages/backend/src/core/ApLogService.ts create mode 100644 packages/backend/src/models/SkApFetchLog.ts (limited to 'packages/backend/src') diff --git a/packages/backend/migration/1738293576355-create_ap_fetch_log.js b/packages/backend/migration/1738293576355-create_ap_fetch_log.js new file mode 100644 index 0000000000..4371f50b4a --- /dev/null +++ b/packages/backend/migration/1738293576355-create_ap_fetch_log.js @@ -0,0 +1,19 @@ +export class CreateApFetchLog1738293576355 { + name = 'CreateApFetchLog1738293576355' + + async up(queryRunner) { + await queryRunner.query(`CREATE TABLE "ap_fetch_log" ("id" character varying(32) NOT NULL, "at" TIMESTAMP WITH TIME ZONE NOT NULL, "duration" double precision, "host" text NOT NULL, "request_uri" text NOT NULL, "object_uri" text, "accepted" boolean, "result" text, "object" jsonb, "context_hash" text, CONSTRAINT "PK_ap_fetch_log" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_ap_fetch_log_at" ON "ap_fetch_log" ("at") `); + await queryRunner.query(`CREATE INDEX "IDX_ap_fetch_log_host" ON "ap_fetch_log" ("host") `); + await queryRunner.query(`CREATE INDEX "IDX_ap_fetch_log_object_uri" ON "ap_fetch_log" ("object_uri") `); + await queryRunner.query(`ALTER TABLE "ap_fetch_log" ADD CONSTRAINT "FK_ap_fetch_log_context_hash" FOREIGN KEY ("context_hash") REFERENCES "ap_context"("md5") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "ap_fetch_log" DROP CONSTRAINT "FK_ap_fetch_log_context_hash"`); + await queryRunner.query(`DROP INDEX "public"."IDX_ap_fetch_log_object_uri"`); + await queryRunner.query(`DROP INDEX "public"."IDX_ap_fetch_log_host"`); + await queryRunner.query(`DROP INDEX "public"."IDX_ap_fetch_log_at"`); + await queryRunner.query(`DROP TABLE "ap_fetch_log"`); + } +} diff --git a/packages/backend/src/core/ApLogService.ts b/packages/backend/src/core/ApLogService.ts new file mode 100644 index 0000000000..362eba24be --- /dev/null +++ b/packages/backend/src/core/ApLogService.ts @@ -0,0 +1,189 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { createHash } from 'crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import { LessThan } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { SkApFetchLog, SkApInboxLog, SkApContext } from '@/models/_.js'; +import type { ApContextsRepository, ApFetchLogsRepository, ApInboxLogsRepository } from '@/models/_.js'; +import type { Config } from '@/config.js'; +import { JsonValue } from '@/misc/json-value.js'; +import { UtilityService } from '@/core/UtilityService.js'; +import { IdService } from '@/core/IdService.js'; +import { IActivity, IObject } from './activitypub/type.js'; + +@Injectable() +export class ApLogService { + constructor( + @Inject(DI.config) + private readonly config: Config, + + @Inject(DI.apContextsRepository) + private apContextsRepository: ApContextsRepository, + + @Inject(DI.apInboxLogsRepository) + private readonly apInboxLogsRepository: ApInboxLogsRepository, + + @Inject(DI.apFetchLogsRepository) + private readonly apFetchLogsRepository: ApFetchLogsRepository, + + private readonly utilityService: UtilityService, + private readonly idService: IdService, + ) {} + + /** + * Creates an inbox log from an activity, and saves it if pre-save is enabled. + */ + public async createInboxLog(data: Partial & { + activity: IActivity, + keyId: string, + }): Promise { + const { object: activity, context, contextHash } = extractObjectContext(data.activity); + const host = this.utilityService.extractDbHost(data.keyId); + + const log = new SkApInboxLog({ + id: this.idService.gen(), + at: new Date(), + verified: false, + accepted: false, + host, + ...data, + activity, + context, + contextHash, + }); + + if (this.config.activityLogging.preSave) { + await this.saveInboxLog(log); + } + + return log; + } + + /** + * Saves or finalizes an inbox log. + */ + public async saveInboxLog(log: SkApInboxLog): Promise { + if (log.context) { + await this.saveContext(log.context); + } + + // Will be UPDATE with preSave, and INSERT without. + await this.apInboxLogsRepository.upsert(log, ['id']); + return log; + } + + /** + * Creates a fetch log from an activity, and saves it if pre-save is enabled. + */ + public async createFetchLog(data: Partial & { + requestUri: string + host: string, + }): Promise { + const log = new SkApFetchLog({ + id: this.idService.gen(), + at: new Date(), + accepted: false, + ...data, + }); + + if (this.config.activityLogging.preSave) { + await this.saveFetchLog(log); + } + + return log; + } + + /** + * Saves or finalizes a fetch log. + */ + public async saveFetchLog(log: SkApFetchLog): Promise { + if (log.context) { + await this.saveContext(log.context); + } + + // Will be UPDATE with preSave, and INSERT without. + await this.apFetchLogsRepository.upsert(log, ['id']); + return log; + } + + private async saveContext(context: SkApContext): Promise { + // https://stackoverflow.com/a/47064558 + await this.apContextsRepository + .createQueryBuilder('activity_context') + .insert() + .into(SkApContext) + .values(context) + .orIgnore('md5') + .execute(); + } + + /** + * Deletes all expired AP logs and garbage-collects the AP context cache. + * Returns the total number of deleted rows. + */ + public async deleteExpiredLogs(): Promise { + // This is the date in UTC of the oldest log to KEEP + const oldestAllowed = new Date(Date.now() - this.config.activityLogging.maxAge); + + // Delete all logs older than the threshold. + const inboxDeleted = await this.deleteExpiredInboxLogs(oldestAllowed); + const fetchDeleted = await this.deleteExpiredFetchLogs(oldestAllowed); + + return inboxDeleted + fetchDeleted; + } + + private async deleteExpiredInboxLogs(oldestAllowed: Date): Promise { + const { affected } = await this.apInboxLogsRepository.delete({ + at: LessThan(oldestAllowed), + }); + + return affected ?? 0; + } + + private async deleteExpiredFetchLogs(oldestAllowed: Date): Promise { + const { affected } = await this.apFetchLogsRepository.delete({ + at: LessThan(oldestAllowed), + }); + + return affected ?? 0; + } +} + +export function extractObjectContext(input: T) { + const object = Object.assign({}, input, { '@context': undefined }) as Omit; + const { context, contextHash } = parseContext(input['@context']); + + return { object, context, contextHash }; +} + +export function parseContext(input: JsonValue | undefined): { contextHash: string | null, context: SkApContext | null } { + // Empty contexts are excluded for easier querying + if (input == null) { + return { + contextHash: null, + context: null, + }; + } + + const contextHash = createHash('md5').update(JSON.stringify(input)).digest('base64'); + const context = new SkApContext({ + md5: contextHash, + json: input, + }); + return { contextHash, context }; +} + +export function calculateDurationSince(startTime: bigint): number { + // Calculate the processing time with correct rounding and decimals. + // 1. Truncate nanoseconds to microseconds + // 2. Scale to 1/10 millisecond ticks. + // 3. Round to nearest tick. + // 4. Sale to milliseconds + // Example: 123,456,789 ns -> 123,456 us -> 12,345.6 ticks -> 12,346 ticks -> 123.46 ms + const endTime = process.hrtime.bigint(); + return Math.round(Number((endTime - startTime) / 1000n) / 10) / 100; +} diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index 8c9f419c44..47be6967d7 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -157,6 +157,7 @@ import { QueueService } from './QueueService.js'; import { LoggerService } from './LoggerService.js'; import { SponsorsService } from './SponsorsService.js'; import type { Provider } from '@nestjs/common'; +import { ApLogService } from '@/core/ApLogService.js'; //#region 文字列ベースでのinjection用(循環参照対応のため) const $LoggerService: Provider = { provide: 'LoggerService', useExisting: LoggerService }; @@ -166,6 +167,7 @@ const $AccountMoveService: Provider = { provide: 'AccountMoveService', useExisti const $AccountUpdateService: Provider = { provide: 'AccountUpdateService', useExisting: AccountUpdateService }; const $AnnouncementService: Provider = { provide: 'AnnouncementService', useExisting: AnnouncementService }; const $AntennaService: Provider = { provide: 'AntennaService', useExisting: AntennaService }; +const $ApLogService: Provider = { provide: 'ApLogService', useExisting: ApLogService }; const $AppLockService: Provider = { provide: 'AppLockService', useExisting: AppLockService }; const $AchievementService: Provider = { provide: 'AchievementService', useExisting: AchievementService }; const $AvatarDecorationService: Provider = { provide: 'AvatarDecorationService', useExisting: AvatarDecorationService }; @@ -322,6 +324,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp AccountUpdateService, AnnouncementService, AntennaService, + ApLogService, AppLockService, AchievementService, AvatarDecorationService, @@ -474,6 +477,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp $AccountUpdateService, $AnnouncementService, $AntennaService, + $ApLogService, $AppLockService, $AchievementService, $AvatarDecorationService, @@ -627,6 +631,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp AccountUpdateService, AnnouncementService, AntennaService, + ApLogService, AppLockService, AchievementService, AvatarDecorationService, @@ -778,6 +783,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp $AccountUpdateService, $AnnouncementService, $AntennaService, + $ApLogService, $AppLockService, $AchievementService, $AvatarDecorationService, diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index a0c3a4846c..410803609c 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, Not } from 'typeorm'; import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; -import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js'; +import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta, SkApFetchLog } from '@/models/_.js'; import type { Config } from '@/config.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { DI } from '@/di-symbols.js'; @@ -17,7 +17,8 @@ import { LoggerService } from '@/core/LoggerService.js'; import type Logger from '@/logger.js'; import { fromTuple } from '@/misc/from-tuple.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; -import { isCollectionOrOrderedCollection } from './type.js'; +import { ApLogService, calculateDurationSince, extractObjectContext } from '@/core/ApLogService.js'; +import { getNullableApId, isCollectionOrOrderedCollection } from './type.js'; import { ApDbResolverService } from './ApDbResolverService.js'; import { ApRendererService } from './ApRendererService.js'; import { ApRequestService } from './ApRequestService.js'; @@ -43,6 +44,7 @@ export class Resolver { private apRendererService: ApRendererService, private apDbResolverService: ApDbResolverService, private loggerService: LoggerService, + private readonly apLogService: ApLogService, private recursionLimit = 256, ) { this.history = new Set(); @@ -81,6 +83,44 @@ export class Resolver { return value; } + const host = this.utilityService.extractDbHost(value); + if (this.config.activityLogging.enabled && !this.utilityService.isSelfHost(host)) { + return await this._resolveLogged(value, host); + } else { + return await this._resolve(value, host); + } + } + + private async _resolveLogged(requestUri: string, host: string): Promise { + const startTime = process.hrtime.bigint(); + + const log = await this.apLogService.createFetchLog({ + host: host, + requestUri, + }); + + try { + const result = await this._resolve(requestUri, host, log); + + log.accepted = true; + log.result = 'ok'; + + return result; + } catch (err) { + log.accepted = false; + log.result = String(err); + + throw err; + } finally { + log.duration = calculateDurationSince(startTime); + + // Save or finalize asynchronously + this.apLogService.saveFetchLog(log) + .catch(err => this.logger.error('Failed to record AP object fetch:', err)); + } + } + + private async _resolve(value: string, host: string, log?: SkApFetchLog): Promise { if (value.includes('#')) { // URLs with fragment parts cannot be resolved correctly because // the fragment part does not get transmitted over HTTP(S). @@ -98,7 +138,6 @@ export class Resolver { this.history.add(value); - const host = this.utilityService.extractDbHost(value); if (this.utilityService.isSelfHost(host)) { return await this.resolveLocal(value); } @@ -115,6 +154,20 @@ export class Resolver { ? await this.apRequestService.signedGet(value, this.user) as IObject : await this.httpRequestService.getActivityJson(value)) as IObject; + if (log) { + const { object: objectOnly, context, contextHash } = extractObjectContext(object); + const objectUri = getNullableApId(object); + + if (objectUri) { + log.objectUri = objectUri; + log.host = this.utilityService.extractDbHost(objectUri); + } + + log.object = objectOnly; + log.context = context; + log.contextHash = contextHash; + } + if ( Array.isArray(object['@context']) ? !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : @@ -232,6 +285,7 @@ export class ApResolverService { private apRendererService: ApRendererService, private apDbResolverService: ApDbResolverService, private loggerService: LoggerService, + private readonly apLogService: ApLogService, ) { } @@ -252,6 +306,7 @@ export class ApResolverService { this.apRendererService, this.apDbResolverService, this.loggerService, + this.apLogService, ); } } diff --git a/packages/backend/src/daemons/ApLogCleanupService.ts b/packages/backend/src/daemons/ApLogCleanupService.ts index 261c6e3517..2b6693e19e 100644 --- a/packages/backend/src/daemons/ApLogCleanupService.ts +++ b/packages/backend/src/daemons/ApLogCleanupService.ts @@ -3,14 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Inject, Injectable, type OnApplicationShutdown } from '@nestjs/common'; -import { LessThan } from 'typeorm'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; +import { Injectable, type OnApplicationShutdown } from '@nestjs/common'; import { bindThis } from '@/decorators.js'; -import type { ApInboxLogsRepository } from '@/models/_.js'; import { LoggerService } from '@/core/LoggerService.js'; import Logger from '@/logger.js'; +import { ApLogService } from '@/core/ApLogService.js'; // 10 minutes export const scanInterval = 1000 * 60 * 10; @@ -21,12 +18,7 @@ export class ApLogCleanupService implements OnApplicationShutdown { private scanTimer: NodeJS.Timeout | null = null; constructor( - @Inject(DI.config) - private readonly config: Config, - - @Inject(DI.apInboxLogsRepository) - private readonly apInboxLogsRepository: ApInboxLogsRepository, - + private readonly apLogService: ApLogService, loggerService: LoggerService, ) { this.logger = loggerService.getLogger('activity-log-cleanup'); @@ -47,15 +39,12 @@ export class ApLogCleanupService implements OnApplicationShutdown { @bindThis private async tick(): Promise { - // This is the date in UTC of the oldest log to KEEP - const oldestAllowed = new Date(Date.now() - this.config.activityLogging.maxAge); - - // Delete all logs older than the threshold. - const { affected } = await this.apInboxLogsRepository.delete({ - at: LessThan(oldestAllowed), - }); - - this.logger.info(`Activity Log cleanup complete; removed ${affected ?? 0} expired logs.`); + try { + const affected = this.apLogService.deleteExpiredLogs(); + this.logger.info(`Activity Log cleanup complete; removed ${affected} expired logs.`); + } catch (err) { + this.logger.error('Activity Log cleanup failed:', err as Error); + } } @bindThis diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 6b53d38fb7..9f4ef5e2e9 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -23,6 +23,7 @@ export const DI = { avatarDecorationsRepository: Symbol('avatarDecorationsRepository'), latestNotesRepository: Symbol('latestNotesRepository'), apContextsRepository: Symbol('apContextsRepository'), + apFetchLogsRepository: Symbol('apFetchLogsRepository'), apInboxLogsRepository: Symbol('apInboxLogsRepository'), noteFavoritesRepository: Symbol('noteFavoritesRepository'), noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'), diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index dd4ba1c0e4..78510ba588 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -82,6 +82,7 @@ import { MiWebhook, NoteEdit, SkApContext, + SkApFetchLog, SkApInboxLog, } from './_.js'; import type { DataSource } from 'typeorm'; @@ -134,6 +135,12 @@ const $apContextRepository: Provider = { inject: [DI.db], }; +const $apFetchLogsRepository: Provider = { + provide: DI.apFetchLogsRepository, + useFactory: (db: DataSource) => db.getRepository(SkApFetchLog).extend(miRepository as MiRepository), + inject: [DI.db], +}; + const $apInboxLogsRepository: Provider = { provide: DI.apInboxLogsRepository, useFactory: (db: DataSource) => db.getRepository(SkApInboxLog).extend(miRepository as MiRepository), @@ -541,6 +548,7 @@ const $noteScheduleRepository: Provider = { $avatarDecorationsRepository, $latestNotesRepository, $apContextRepository, + $apFetchLogsRepository, $apInboxLogsRepository, $noteFavoritesRepository, $noteThreadMutingsRepository, @@ -617,6 +625,7 @@ const $noteScheduleRepository: Provider = { $avatarDecorationsRepository, $latestNotesRepository, $apContextRepository, + $apFetchLogsRepository, $apInboxLogsRepository, $noteFavoritesRepository, $noteThreadMutingsRepository, diff --git a/packages/backend/src/models/SkApFetchLog.ts b/packages/backend/src/models/SkApFetchLog.ts new file mode 100644 index 0000000000..1e7d861b6c --- /dev/null +++ b/packages/backend/src/models/SkApFetchLog.ts @@ -0,0 +1,89 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Column, Index, JoinColumn, ManyToOne, PrimaryColumn, Entity } from 'typeorm'; +import { SkApContext } from '@/models/SkApContext.js'; +import { id } from './util/id.js'; + +/** + * Records objects fetched via AP + */ +@Entity('ap_fetch_log') +export class SkApFetchLog { + @PrimaryColumn({ + ...id(), + primaryKeyConstraintName: 'PK_ap_fetch_log', + }) + public id: string; + + @Index('IDX_ap_fetch_log_at') + @Column('timestamptz') + public at: Date; + + /** + * Processing duration in milliseconds + */ + @Column('double precision', { nullable: true }) + public duration: number | null = null; + + /** + * DB hostname extracted from responseUri, or requestUri if fetch is incomplete + */ + @Index('IDX_ap_fetch_log_host') + @Column('text') + public host: string; + + /** + * Original requested URI + */ + @Column('text', { + name: 'request_uri', + }) + public requestUri: string; + + /** + * Canonical URI / object ID, taken from the final payload + */ + @Column('text', { + name: 'object_uri', + nullable: true, + }) + @Index('IDX_ap_fetch_log_object_uri') + public objectUri: string | null = null; + + @Column('boolean', { nullable: true }) + public accepted: boolean | null = null; + + @Column('text', { nullable: true }) + public result: string | null = null; + + @Column('jsonb', { nullable: true }) + // https://github.com/typeorm/typeorm/issues/8559 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public object: any | null = null; + + @Column({ + type: 'text', + name: 'context_hash', + nullable: true, + }) + public contextHash: string | null; + + @ManyToOne(() => SkApContext, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ + name: 'context_hash', + foreignKeyConstraintName: 'FK_ap_fetch_log_context_hash', + }) + public context: SkApContext | null; + + constructor(data?: Partial) { + if (data) { + Object.assign(this, data); + } + } +} diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index dabcf89d2c..4bd6e78ef4 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -83,6 +83,7 @@ import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js'; import { MiReversiGame } from '@/models/ReversiGame.js'; import { MiNoteSchedule } from '@/models/NoteSchedule.js'; import { SkApInboxLog } from '@/models/SkApInboxLog.js'; +import { SkApFetchLog } from '@/models/SkApFetchLog.js'; import { SkApContext } from '@/models/SkApContext.js'; import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; @@ -132,6 +133,7 @@ export const miRepository = { export { SkLatestNote, SkApContext, + SkApFetchLog, SkApInboxLog, MiAbuseUserReport, MiAbuseReportNotificationRecipient, @@ -234,6 +236,7 @@ export type InstancesRepository = Repository & MiRepository & MiRepository; export type LatestNotesRepository = Repository & MiRepository; export type ApContextsRepository = Repository & MiRepository; +export type ApFetchLogsRepository = Repository & MiRepository; export type ApInboxLogsRepository = Repository & MiRepository; export type ModerationLogsRepository = Repository & MiRepository; export type MutingsRepository = Repository & MiRepository; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 9437ac936a..1a5fdc8412 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -86,6 +86,7 @@ import MisskeyLogger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { SkLatestNote } from '@/models/LatestNote.js'; import { SkApContext } from '@/models/SkApContext.js'; +import { SkApFetchLog } from '@/models/SkApFetchLog.js'; import { SkApInboxLog } from '@/models/SkApInboxLog.js'; pg.types.setTypeParser(20, Number); @@ -174,6 +175,7 @@ class MyCustomLogger implements Logger { export const entities = [ SkLatestNote, SkApContext, + SkApFetchLog, SkApInboxLog, MiAnnouncement, MiAnnouncementRead, diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 4182f3e090..557a759136 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -4,7 +4,6 @@ */ import { URL } from 'node:url'; -import { createHash } from 'crypto'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import httpSignature from '@peertube/http-signature'; import * as Bull from 'bullmq'; @@ -30,11 +29,9 @@ import { CollapsedQueue } from '@/misc/collapsed-queue.js'; import { MiNote } from '@/models/Note.js'; import { MiMeta } from '@/models/Meta.js'; import { DI } from '@/di-symbols.js'; -import { IdService } from '@/core/IdService.js'; -import { JsonValue } from '@/misc/json-value.js'; -import { SkApInboxLog, SkApContext } from '@/models/_.js'; -import type { ApInboxLogsRepository, ApContextsRepository } from '@/models/_.js'; +import { SkApInboxLog } from '@/models/_.js'; import type { Config } from '@/config.js'; +import { ApLogService, calculateDurationSince } from '@/core/ApLogService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type { InboxJobData } from '../types.js'; @@ -66,13 +63,7 @@ export class InboxProcessorService implements OnApplicationShutdown { private apRequestChart: ApRequestChart, private federationChart: FederationChart, private queueLoggerService: QueueLoggerService, - private idService: IdService, - - @Inject(DI.apContextsRepository) - private apContextsRepository: ApContextsRepository, - - @Inject(DI.apInboxLogsRepository) - private apInboxLogsRepository: ApInboxLogsRepository, + private readonly apLogService: ApLogService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance); @@ -89,14 +80,9 @@ export class InboxProcessorService implements OnApplicationShutdown { private async _processLogged(job: Bull.Job): Promise { const startTime = process.hrtime.bigint(); - const payload = job.data.activity; + const activity = job.data.activity; const keyId = job.data.signature.keyId; - const log = this.createLog(payload, keyId); - - // Pre-save the activity in case it leads to a hard-crash. - if (this.config.activityLogging.preSave) { - await this.recordLog(log); - } + const log = await this.apLogService.createInboxLog({ activity, keyId }); try { const result = await this._process(job, log); @@ -111,24 +97,18 @@ export class InboxProcessorService implements OnApplicationShutdown { throw err; } finally { - // Calculate the activity processing time with correct rounding and decimals. - // 1. Truncate nanoseconds to microseconds - // 2. Scale to 1/10 millisecond ticks. - // 3. Round to nearest tick. - // 4. Sale to milliseconds - // Example: 123,456,789 ns -> 123,456 us -> 12,345.6 ticks -> 12,346 ticks -> 123.46 ms - const endTime = process.hrtime.bigint(); - const duration = Math.round(Number((endTime - startTime) / 1000n) / 10) / 100; - log.duration = duration; + const duration = log.duration = calculateDurationSince(startTime); + // TODO remove this // Activities should time out after roughly 5 seconds. // A runtime longer than 10 seconds could indicate a problem or attack. if (duration > 10000) { - this.logger.warn(`Activity ${JSON.stringify(payload.id)} by "${keyId}" took ${(duration / 1000).toFixed(1)} seconds to complete`); + this.logger.warn(`Activity ${JSON.stringify(activity.id)} by "${keyId}" took ${(duration / 1000).toFixed(1)} seconds to complete`); } // Save or finalize asynchronously - this.recordLog(log).catch(err => this.logger.error('Failed to record AP activity:', err)); + this.apLogService.saveInboxLog(log) + .catch(err => this.logger.error('Failed to record AP activity:', err)); } } @@ -368,46 +348,4 @@ export class InboxProcessorService implements OnApplicationShutdown { async onApplicationShutdown(signal?: string) { await this.dispose(); } - - private createLog(payload: IActivity, keyId: string): SkApInboxLog { - const activity = Object.assign({}, payload, { '@context': undefined }) as unknown as JsonValue; - const host = this.utilityService.extractDbHost(keyId); - - const log = new SkApInboxLog({ - id: this.idService.gen(), - at: new Date(), - verified: false, - accepted: false, - activity, - keyId, - host, - }); - - const context = payload['@context']; - if (context) { - const md5 = createHash('md5').update(JSON.stringify(context)).digest('base64'); - log.contextHash = md5; - log.context = new SkApContext({ - md5, - json: context, - }); - } - - return log; - } - - private async recordLog(log: SkApInboxLog): Promise { - if (log.context) { - // https://stackoverflow.com/a/47064558 - await this.apContextsRepository - .createQueryBuilder('activity_context') - .insert() - .into(SkApContext) - .values(log.context) - .orIgnore('md5') - .execute(); - } - - await this.apInboxLogsRepository.upsert(log, ['id']); - } } -- cgit v1.2.3-freya From 4858276465a764f4e0973a463428a653a2a466ab Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 30 Jan 2025 22:38:29 -0500 Subject: don't log slow activities, as this is known to happen under regular circumstances --- packages/backend/src/queue/processors/InboxProcessorService.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 557a759136..35a0bf095d 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -97,14 +97,7 @@ export class InboxProcessorService implements OnApplicationShutdown { throw err; } finally { - const duration = log.duration = calculateDurationSince(startTime); - - // TODO remove this - // Activities should time out after roughly 5 seconds. - // A runtime longer than 10 seconds could indicate a problem or attack. - if (duration > 10000) { - this.logger.warn(`Activity ${JSON.stringify(activity.id)} by "${keyId}" took ${(duration / 1000).toFixed(1)} seconds to complete`); - } + log.duration = calculateDurationSince(startTime); // Save or finalize asynchronously this.apLogService.saveInboxLog(log) -- cgit v1.2.3-freya From 71be39ecc878201e306365876ee059399418169d Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 2 Feb 2025 00:27:03 -0500 Subject: add missing `await` in ApLogCleanupService --- packages/backend/src/daemons/ApLogCleanupService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/daemons/ApLogCleanupService.ts b/packages/backend/src/daemons/ApLogCleanupService.ts index 2b6693e19e..61f76b4e2c 100644 --- a/packages/backend/src/daemons/ApLogCleanupService.ts +++ b/packages/backend/src/daemons/ApLogCleanupService.ts @@ -40,7 +40,7 @@ export class ApLogCleanupService implements OnApplicationShutdown { @bindThis private async tick(): Promise { try { - const affected = this.apLogService.deleteExpiredLogs(); + const affected = await this.apLogService.deleteExpiredLogs(); this.logger.info(`Activity Log cleanup complete; removed ${affected} expired logs.`); } catch (err) { this.logger.error('Activity Log cleanup failed:', err as Error); -- cgit v1.2.3-freya From 9de5ecae518973e29f63bde51062aa0ed7eadb11 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 2 Feb 2025 23:19:41 -0500 Subject: delete fetch logs when a note or user is deleted --- packages/backend/src/core/ApLogService.ts | 20 +++++++++++++++++++- packages/backend/src/core/NoteDeleteService.ts | 16 +++++++++++++++- .../processors/DeleteAccountProcessorService.ts | 16 ++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/ApLogService.ts b/packages/backend/src/core/ApLogService.ts index 362eba24be..096ec21de7 100644 --- a/packages/backend/src/core/ApLogService.ts +++ b/packages/backend/src/core/ApLogService.ts @@ -5,7 +5,7 @@ import { createHash } from 'crypto'; import { Inject, Injectable } from '@nestjs/common'; -import { LessThan } from 'typeorm'; +import { In, LessThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; import { SkApFetchLog, SkApInboxLog, SkApContext } from '@/models/_.js'; import type { ApContextsRepository, ApFetchLogsRepository, ApInboxLogsRepository } from '@/models/_.js'; @@ -121,6 +121,24 @@ export class ApLogService { .execute(); } + /** + * Deletes all logged copies of an object or objects + * @param objectUris URIs / AP IDs of the objects to delete + */ + public async deleteObjectLogs(objectUris: string | string[]): Promise { + if (Array.isArray(objectUris)) { + const logsDeleted = await this.apFetchLogsRepository.delete({ + objectUri: In(objectUris), + }); + return logsDeleted.affected ?? 0; + } else { + const logsDeleted = await this.apFetchLogsRepository.delete({ + objectUri: objectUris, + }); + return logsDeleted.affected ?? 0; + } + } + /** * Deletes all expired AP logs and garbage-collects the AP context cache. * Returns the total number of deleted rows. diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index b51a3143c9..cb258a4f5a 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -24,9 +24,14 @@ import { SearchService } from '@/core/SearchService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; import { LatestNoteService } from '@/core/LatestNoteService.js'; +import { ApLogService } from '@/core/ApLogService.js'; +import Logger from '@/logger.js'; +import { LoggerService } from './LoggerService.js'; @Injectable() export class NoteDeleteService { + private readonly logger: Logger; + constructor( @Inject(DI.config) private config: Config, @@ -55,7 +60,11 @@ export class NoteDeleteService { private perUserNotesChart: PerUserNotesChart, private instanceChart: InstanceChart, private latestNoteService: LatestNoteService, - ) {} + private readonly apLogService: ApLogService, + loggerService: LoggerService, + ) { + this.logger = loggerService.getLogger('note-delete-service'); + } /** * 投稿を削除します。 @@ -156,6 +165,11 @@ export class NoteDeleteService { note: note, }); } + + if (note.uri) { + this.apLogService.deleteObjectLogs(note.uri) + .catch(err => this.logger.error(err, `Failed to delete AP logs for note '${note.uri}'`)); + } } @bindThis diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index e350b97f53..66bed72f18 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -15,6 +15,7 @@ import type { MiNoteReaction } from '@/models/NoteReaction.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; +import { ApLogService } from '@/core/ApLogService.js'; import { ReactionService } from '@/core/ReactionService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -45,6 +46,7 @@ export class DeleteAccountProcessorService { private queueLoggerService: QueueLoggerService, private searchService: SearchService, private reactionService: ReactionService, + private readonly apLogService: ApLogService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('delete-account'); } @@ -84,6 +86,13 @@ export class DeleteAccountProcessorService { for (const note of notes) { await this.searchService.unindexNote(note); } + + // Delete note AP logs + const noteUris = notes.map(n => n.uri).filter(u => !!u) as string[]; + if (noteUris.length > 0) { + await this.apLogService.deleteObjectLogs(noteUris) + .catch(err => this.logger.error(err, `Failed to delete AP logs for notes of user '${user.uri ?? user.id}'`)); + } } this.logger.succ('All of notes deleted'); @@ -149,6 +158,13 @@ export class DeleteAccountProcessorService { this.logger.succ('All of files deleted'); } + { // Delete actor logs + if (user.uri) { + await this.apLogService.deleteObjectLogs(user.uri) + .catch(err => this.logger.error(err, `Failed to delete AP logs for user '${user.uri}'`)); + } + } + { // Send email notification const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); if (profile.email && profile.emailVerified) { -- cgit v1.2.3-freya From 17ec2df3e1ae3e5b0d8be0778b59ae399b940eab Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 16 Feb 2025 01:12:28 -0500 Subject: increase rate limit on server-info to avoid errors and blank graphs --- packages/backend/src/server/api/endpoints/server-info.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts index e765163f8e..528de76707 100644 --- a/packages/backend/src/server/api/endpoints/server-info.ts +++ b/packages/backend/src/server/api/endpoints/server-info.ts @@ -64,10 +64,11 @@ export const meta = { }, }, - // 2 calls per second + // 24 calls, then 7 per second-ish (1 for each type of server info graph) limit: { - duration: 1000, - max: 2, + max: 24, + dripSize: 7, + dripRate: 900, }, } as const; -- cgit v1.2.3-freya From f183df30448163cdc84b9aeaecb01629b44983dd Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 15 Feb 2025 12:26:26 -0500 Subject: fix error message when a peertube object is reject for bad ID / URL --- packages/backend/src/core/activitypub/misc/check-against-url.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/activitypub/misc/check-against-url.ts b/packages/backend/src/core/activitypub/misc/check-against-url.ts index edfab5a216..0c676c6a29 100644 --- a/packages/backend/src/core/activitypub/misc/check-against-url.ts +++ b/packages/backend/src/core/activitypub/misc/check-against-url.ts @@ -26,6 +26,6 @@ export function assertActivityMatchesUrls(activity: IObject, urls: string[]) { .map(u => new URL(u as string).href); if (!actualUrls.some(u => expectedUrls.has(u))) { - throw new UnrecoverableError(`bad Activity: neither id(${activity.id}) nor url(${JSON.stringify(activity.url)}) match location(${urls})`); + throw new UnrecoverableError(`bad Activity: neither id nor url (${actualUrls.join(', ')}) match location (${urls.join(', ')})`); } } -- cgit v1.2.3-freya From 788751d24d91afd1f97b92b633c2177ee38398f4 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Tue, 18 Feb 2025 10:36:29 -0500 Subject: implement redisForRateLimit --- .config/ci.yml | 10 ++++++++++ .config/cypress-devcontainer.yml | 10 ++++++++++ .config/docker_example.yml | 10 ++++++++++ .config/example.yml | 10 ++++++++++ packages/backend/scripts/check_connect.js | 1 + packages/backend/src/GlobalModule.ts | 14 ++++++++++++-- packages/backend/src/config.ts | 5 ++++- packages/backend/src/di-symbols.ts | 1 + packages/backend/src/server/SkRateLimiterService.md | 1 + packages/backend/src/server/SkRateLimiterService.ts | 2 +- 10 files changed, 60 insertions(+), 4 deletions(-) (limited to 'packages/backend/src') diff --git a/.config/ci.yml b/.config/ci.yml index 311a98d8fb..d8c365a47e 100644 --- a/.config/ci.yml +++ b/.config/ci.yml @@ -103,6 +103,16 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForRateLimit: +# host: localhost +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 +# # You can specify more ioredis options... +# #username: example-username + # ┌───────────────────────────────┐ #───┘ Fulltext search configuration └───────────────────────────── diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index 391bc9998c..02442360fe 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -124,6 +124,16 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForRateLimit: +# host: localhost +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 +# # You can specify more ioredis options... +# #username: example-username + # ┌───────────────────────────────┐ #───┘ Fulltext search configuration └───────────────────────────── diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 1e03e902bf..fee5e35b72 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -171,6 +171,16 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForRateLimit: +# host: localhost +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 +# # You can specify more ioredis options... +# #username: example-username + # ┌───────────────────────────────┐ #───┘ Fulltext search configuration └───────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index 7d4cd0c659..2a1afd8481 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -198,6 +198,16 @@ redis: # # You can specify more ioredis options... # #username: example-username +#redisForRateLimit: +# host: localhost +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 +# # You can specify more ioredis options... +# #username: example-username + # ┌───────────────────────────────┐ #───┘ Fulltext search configuration └───────────────────────────── diff --git a/packages/backend/scripts/check_connect.js b/packages/backend/scripts/check_connect.js index 17b198ef62..b15ce51ec8 100644 --- a/packages/backend/scripts/check_connect.js +++ b/packages/backend/scripts/check_connect.js @@ -51,6 +51,7 @@ const promises = Array config.redisForJobQueue, config.redisForTimelines, config.redisForReactions, + config.redisForRateLimit, ])) .map(connectToRedis) .concat([ diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index ace7f7841c..7ca566477d 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -92,6 +92,14 @@ const $redisForReactions: Provider = { inject: [DI.config], }; +const $redisForRateLimit: Provider = { + provide: DI.redisForRateLimit, + useFactory: (config: Config) => { + return new Redis.Redis(config.redisForRateLimit); + }, + inject: [DI.config], +}; + const $meta: Provider = { provide: DI.meta, useFactory: async (db: DataSource, redisForSub: Redis.Redis) => { @@ -152,8 +160,8 @@ const $meta: Provider = { @Global() @Module({ imports: [RepositoryModule], - providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions], - exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule], + providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, $redisForRateLimit], + exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, $redisForRateLimit, RepositoryModule], }) export class GlobalModule implements OnApplicationShutdown { constructor( @@ -163,6 +171,7 @@ export class GlobalModule implements OnApplicationShutdown { @Inject(DI.redisForSub) private redisForSub: Redis.Redis, @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, @Inject(DI.redisForReactions) private redisForReactions: Redis.Redis, + @Inject(DI.redisForRateLimit) private redisForRateLimit: Redis.Redis, ) { } public async dispose(): Promise { @@ -176,6 +185,7 @@ export class GlobalModule implements OnApplicationShutdown { this.redisForSub.disconnect(), this.redisForTimelines.disconnect(), this.redisForReactions.disconnect(), + this.redisForRateLimit.disconnect(), ]); } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index d35befdc2b..45aa1c1e22 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -53,6 +53,7 @@ type Source = { redisForJobQueue?: RedisOptionsSource; redisForTimelines?: RedisOptionsSource; redisForReactions?: RedisOptionsSource; + redisForRateLimit?: RedisOptionsSource; fulltextSearch?: { provider?: FulltextSearchProvider; }; @@ -225,6 +226,7 @@ export type Config = { redisForJobQueue: RedisOptions & RedisOptionsSource; redisForTimelines: RedisOptions & RedisOptionsSource; redisForReactions: RedisOptions & RedisOptionsSource; + redisForRateLimit: RedisOptions & RedisOptionsSource; sentryForBackend: { options: Partial; enableNodeProfiling: boolean; } | undefined; sentryForFrontend: { options: Partial } | undefined; perChannelMaxNoteCacheCount: number; @@ -333,6 +335,7 @@ export function loadConfig(): Config { redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis, redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis, redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis, + redisForRateLimit: config.redisForRateLimit ? convertRedisOptions(config.redisForRateLimit, host) : redis, sentryForBackend: config.sentryForBackend, sentryForFrontend: config.sentryForFrontend, id: config.id, @@ -518,7 +521,7 @@ function applyEnvOverrides(config: Source) { _apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]); _apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]); _apply_top([ - ['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions'], + ['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions', 'redisForRateLimit'], ['host', 'port', 'username', 'pass', 'db', 'prefix'], ]); _apply_top(['fulltextSearch', 'provider']); diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 296cc4815b..71f627dc4e 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -13,6 +13,7 @@ export const DI = { redisForSub: Symbol('redisForSub'), redisForTimelines: Symbol('redisForTimelines'), redisForReactions: Symbol('redisForReactions'), + redisForRateLimit: Symbol('redisForRateLimit'), //#region Repositories usersRepository: Symbol('usersRepository'), diff --git a/packages/backend/src/server/SkRateLimiterService.md b/packages/backend/src/server/SkRateLimiterService.md index fb007538fa..c8a2b4e85c 100644 --- a/packages/backend/src/server/SkRateLimiterService.md +++ b/packages/backend/src/server/SkRateLimiterService.md @@ -39,6 +39,7 @@ The first call is read-only, while the others perform at least one write operati Two integer keys are stored per client/subject, and both expire together after the maximum duration of the limit. While performance has not been formally tested, it's expected that SkRateLimiterService has an impact roughly on par with the legacy RateLimiterService. Redis memory usage should be notably lower due to the reduced number of keys and avoidance of set / array constructions. +If redis load does become a concern, then a dedicated node can be assigned via the `redisForRateLimit` config setting. ## Concurrency and Multi-Node Correctness diff --git a/packages/backend/src/server/SkRateLimiterService.ts b/packages/backend/src/server/SkRateLimiterService.ts index 038f12cb25..30bf092e4f 100644 --- a/packages/backend/src/server/SkRateLimiterService.ts +++ b/packages/backend/src/server/SkRateLimiterService.ts @@ -27,7 +27,7 @@ export class SkRateLimiterService { @Inject('TimeService') private readonly timeService: TimeService, - @Inject(DI.redis) + @Inject(DI.redisForRateLimit) private readonly redisClient: Redis.Redis, @Inject('RoleService') -- cgit v1.2.3-freya From 9e3667b2a3c2c73185d5dc511568b20d5f477444 Mon Sep 17 00:00:00 2001 From: dakkar Date: Tue, 18 Feb 2025 20:49:36 +0000 Subject: fix names that annoy @dakkar --- packages/backend/src/core/CustomEmojiService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 165d58d1b2..2e4eddf797 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -520,11 +520,11 @@ export class CustomEmojiService implements OnApplicationShutdown { const q = params.query; if (q.updatedAtFrom) { // noIndexScan - builder.andWhere('CAST(emoji.updatedAt AS DATE) >= :updateAtFrom', { updateAtFrom: q.updatedAtFrom }); + builder.andWhere('CAST(emoji.updatedAt AS DATE) >= :updatedAtFrom', { updatedAtFrom: q.updatedAtFrom }); } if (q.updatedAtTo) { // noIndexScan - builder.andWhere('CAST(emoji.updatedAt AS DATE) <= :updateAtTo', { updateAtTo: q.updatedAtTo }); + builder.andWhere('CAST(emoji.updatedAt AS DATE) <= :updatedAtTo', { updatedAtTo: q.updatedAtTo }); } if (q.name) { builder.andWhere('emoji.name ~~ ANY(ARRAY[:...name])', { name: multipleWordsToQuery(q.name) }); -- cgit v1.2.3-freya From d82c8e8e971d1e563dd93c6f8dea6edcba2337ae Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Wed, 19 Feb 2025 14:55:50 +0100 Subject: Implement tsvector search support --- .config/example.yml | 8 +++++ packages/backend/src/config.ts | 2 +- packages/backend/src/core/SearchService.ts | 54 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/.config/example.yml b/.config/example.yml index 07c613f62d..297f794324 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -222,6 +222,14 @@ fulltextSearch: # You need to install pgroonga and configure it as a PostgreSQL extension. # In addition to the above, you need to create a pgroonga index on the text column of the note table. # see: https://pgroonga.github.io/tutorial/ + # - tsvector + # Use Postgres tsvectors. + # You need to create a generated column and index on the note table to use this, followed by an ANALYZE on the table. Beware, this will take a while to be created and the database will remain locked during this process. + # This also enables advanced search syntax, see documentation of websearch_to_tsquery: https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES + # Support for non-English languages is currently rather poor and will be improved once post languages become a feature. + # ALTER TABLE note ADD COLUMN tsvector_embedding tsvector GENERATED ALWAYS AS ( to_tsvector('english', COALESCE(text, '') || ' ' || COALESCE(cw, '') || ' ' || COALESCE(name, ''))) STORED; + # CREATE INDEX vector_idx ON note USING GIN (tsvector_embedding); + # ANALYZE note; # - meilisearch # Use Meilisearch. # You need to install Meilisearch and configure. diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 3c76c76469..938f44c024 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -254,7 +254,7 @@ export type Config = { }; }; -export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch'; +export type FulltextSearchProvider = 'sqlLike' | 'sqlPgroonga' | 'meilisearch' | 'tsvector'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 6e46fb798c..efef87cfb7 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -248,6 +248,9 @@ export class SearchService { case 'meilisearch': { return this.searchNoteByMeiliSearch(q, me, opts, pagination); } + case 'tsvector': { + return this.searchNoteByTsvector(q, me, opts, pagination); + } default: { // eslint-disable-next-line @typescript-eslint/no-unused-vars const typeCheck: never = this.provider; @@ -256,6 +259,57 @@ export class SearchService { } } + @bindThis + private async searchNoteByTsvector(q: string, + me: MiUser | null, + opts: SearchOpts, + pagination: SearchPagination, + ): Promise { + const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId); + + if (opts.userId) { + query.andWhere('note.userId = :userId', { userId: opts.userId }); + } else if (opts.channelId) { + query.andWhere('note.channelId = :channelId', { channelId: opts.channelId }); + } + + query + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + query.andWhere('note.tsvector @@ websearch_to_tsquery(:q)', { q }); + + if (opts.order === 'asc') { + query + .addSelect('ts_rank_cd(note.tsvector_embedding, websearch_to_tsquery(:q))', 'rank') + .orderBy('rank', 'DESC'); + } else { + query + .orderBy('note.created_at', 'DESC'); + } + + if (opts.host) { + if (opts.host === '.') { + query.andWhere('note.userHost IS NULL'); + } else { + query.andWhere('note.userHost = :host', { host: opts.host }); + } + } + + if (opts.filetype) { + query.andWhere('note."attachedFileTypes" && :types', { types: fileTypes[opts.filetype] }); + } + + this.queryService.generateVisibilityQuery(query, me); + if (me) this.queryService.generateMutedUserQuery(query, me); + if (me) this.queryService.generateBlockedUserQuery(query, me); + + return await query.limit(pagination.limit).getMany(); + } + @bindThis private async searchNoteByLike( q: string, -- cgit v1.2.3-freya From 4fde14d1cc6343ceb0fce6ec005f260dde972d5c Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Wed, 19 Feb 2025 15:12:16 +0100 Subject: fix wrong column name --- packages/backend/src/core/SearchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index efef87cfb7..c99944fa51 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -280,7 +280,7 @@ export class SearchService { .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser'); - query.andWhere('note.tsvector @@ websearch_to_tsquery(:q)', { q }); + query.andWhere('note.tsvector_embedding @@ websearch_to_tsquery(:q)', { q }); if (opts.order === 'asc') { query -- cgit v1.2.3-freya From b5208c2ad052ed318d75b8c85cd0463b25cf1ace Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Wed, 19 Feb 2025 16:26:02 +0100 Subject: simplify tsvector implementation, remove cover density --- packages/backend/src/core/SearchService.ts | 61 +++--------------------------- 1 file changed, 5 insertions(+), 56 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index c99944fa51..2934d08c2d 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -240,7 +240,8 @@ export class SearchService { ): Promise { switch (this.provider) { case 'sqlLike': - case 'sqlPgroonga': { + case 'sqlPgroonga': + case 'tsvector': { // ほとんど内容に差がないのでsqlLikeとsqlPgroongaを同じ処理にしている. // 今後の拡張で差が出る用であれば関数を分ける. return this.searchNoteByLike(q, me, opts, pagination); @@ -248,9 +249,6 @@ export class SearchService { case 'meilisearch': { return this.searchNoteByMeiliSearch(q, me, opts, pagination); } - case 'tsvector': { - return this.searchNoteByTsvector(q, me, opts, pagination); - } default: { // eslint-disable-next-line @typescript-eslint/no-unused-vars const typeCheck: never = this.provider; @@ -259,57 +257,6 @@ export class SearchService { } } - @bindThis - private async searchNoteByTsvector(q: string, - me: MiUser | null, - opts: SearchOpts, - pagination: SearchPagination, - ): Promise { - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId); - - if (opts.userId) { - query.andWhere('note.userId = :userId', { userId: opts.userId }); - } else if (opts.channelId) { - query.andWhere('note.channelId = :channelId', { channelId: opts.channelId }); - } - - query - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); - - query.andWhere('note.tsvector_embedding @@ websearch_to_tsquery(:q)', { q }); - - if (opts.order === 'asc') { - query - .addSelect('ts_rank_cd(note.tsvector_embedding, websearch_to_tsquery(:q))', 'rank') - .orderBy('rank', 'DESC'); - } else { - query - .orderBy('note.created_at', 'DESC'); - } - - if (opts.host) { - if (opts.host === '.') { - query.andWhere('note.userHost IS NULL'); - } else { - query.andWhere('note.userHost = :host', { host: opts.host }); - } - } - - if (opts.filetype) { - query.andWhere('note."attachedFileTypes" && :types', { types: fileTypes[opts.filetype] }); - } - - this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); - - return await query.limit(pagination.limit).getMany(); - } - @bindThis private async searchNoteByLike( q: string, @@ -333,7 +280,9 @@ export class SearchService { .leftJoinAndSelect('renote.user', 'renoteUser'); if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { - query.andWhere('note.text &@~ :q', { q }); + query.andWhere('note.text &@~ :q', {q}); + } else if (this.config.fulltextSearch?.provider === "tsvector") { + query.andWhere('note.tsvector_embedding @@ websearch_to_tsquery(:q)', { q }); } else { query.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }); } -- cgit v1.2.3-freya From e6464906e6f96efcc93ef2d89c29065160a70243 Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Wed, 19 Feb 2025 16:26:02 +0100 Subject: change to sqlTsvector --- packages/backend/src/core/SearchService.ts | 61 +++--------------------------- 1 file changed, 5 insertions(+), 56 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index c99944fa51..2934d08c2d 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -240,7 +240,8 @@ export class SearchService { ): Promise { switch (this.provider) { case 'sqlLike': - case 'sqlPgroonga': { + case 'sqlPgroonga': + case 'tsvector': { // ほとんど内容に差がないのでsqlLikeとsqlPgroongaを同じ処理にしている. // 今後の拡張で差が出る用であれば関数を分ける. return this.searchNoteByLike(q, me, opts, pagination); @@ -248,9 +249,6 @@ export class SearchService { case 'meilisearch': { return this.searchNoteByMeiliSearch(q, me, opts, pagination); } - case 'tsvector': { - return this.searchNoteByTsvector(q, me, opts, pagination); - } default: { // eslint-disable-next-line @typescript-eslint/no-unused-vars const typeCheck: never = this.provider; @@ -259,57 +257,6 @@ export class SearchService { } } - @bindThis - private async searchNoteByTsvector(q: string, - me: MiUser | null, - opts: SearchOpts, - pagination: SearchPagination, - ): Promise { - const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId); - - if (opts.userId) { - query.andWhere('note.userId = :userId', { userId: opts.userId }); - } else if (opts.channelId) { - query.andWhere('note.channelId = :channelId', { channelId: opts.channelId }); - } - - query - .innerJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') - .leftJoinAndSelect('note.renote', 'renote') - .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); - - query.andWhere('note.tsvector_embedding @@ websearch_to_tsquery(:q)', { q }); - - if (opts.order === 'asc') { - query - .addSelect('ts_rank_cd(note.tsvector_embedding, websearch_to_tsquery(:q))', 'rank') - .orderBy('rank', 'DESC'); - } else { - query - .orderBy('note.created_at', 'DESC'); - } - - if (opts.host) { - if (opts.host === '.') { - query.andWhere('note.userHost IS NULL'); - } else { - query.andWhere('note.userHost = :host', { host: opts.host }); - } - } - - if (opts.filetype) { - query.andWhere('note."attachedFileTypes" && :types', { types: fileTypes[opts.filetype] }); - } - - this.queryService.generateVisibilityQuery(query, me); - if (me) this.queryService.generateMutedUserQuery(query, me); - if (me) this.queryService.generateBlockedUserQuery(query, me); - - return await query.limit(pagination.limit).getMany(); - } - @bindThis private async searchNoteByLike( q: string, @@ -333,7 +280,9 @@ export class SearchService { .leftJoinAndSelect('renote.user', 'renoteUser'); if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { - query.andWhere('note.text &@~ :q', { q }); + query.andWhere('note.text &@~ :q', {q}); + } else if (this.config.fulltextSearch?.provider === "tsvector") { + query.andWhere('note.tsvector_embedding @@ websearch_to_tsquery(:q)', { q }); } else { query.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }); } -- cgit v1.2.3-freya From 285bbcb81f9b34cdd8d41e69c3be9e885511b824 Mon Sep 17 00:00:00 2001 From: PrivateGER Date: Wed, 19 Feb 2025 16:46:59 +0100 Subject: fix linter... --- packages/backend/src/core/SearchService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/backend/src') diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts index 3aafac1599..4782a6c7b0 100644 --- a/packages/backend/src/core/SearchService.ts +++ b/packages/backend/src/core/SearchService.ts @@ -280,8 +280,8 @@ export class SearchService { .leftJoinAndSelect('renote.user', 'renoteUser'); if (this.config.fulltextSearch?.provider === 'sqlPgroonga') { - query.andWhere('note.text &@~ :q', {q}); - } else if (this.config.fulltextSearch?.provider === "sqlTsvector") { + query.andWhere('note.text &@~ :q', { q }); + } else if (this.config.fulltextSearch?.provider === 'sqlTsvector') { query.andWhere('note.tsvector_embedding @@ websearch_to_tsquery(:q)', { q }); } else { query.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` }); -- cgit v1.2.3-freya From 292d3b92295d194856cb73c66ac097180f70deb8 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 15 Feb 2025 23:08:02 -0500 Subject: add "reject quotes" toggle at user and instance level + improve, cleanup, and de-duplicate quote resolution + add warning message when quote cannot be loaded + add "process error" framework to display warnings when a note cannot be correctly loaded from another instance --- locales/index.d.ts | 34 +++++ .../1739671352784-add_note_processErrors.js | 11 ++ .../1739671777344-add_user_rejectQuotes.js | 11 ++ .../1739671847942-add_instance_rejectQuotes.js | 11 ++ packages/backend/src/core/NoteCreateService.ts | 30 +++++ packages/backend/src/core/NoteEditService.ts | 5 + .../src/core/activitypub/models/ApNoteService.ts | 143 ++++++++++----------- .../src/core/entities/InstanceEntityService.ts | 1 + .../backend/src/core/entities/NoteEntityService.ts | 1 + .../backend/src/core/entities/UserEntityService.ts | 1 + packages/backend/src/models/Instance.ts | 9 ++ packages/backend/src/models/Note.ts | 11 ++ packages/backend/src/models/User.ts | 9 ++ .../src/models/json-schema/federation-instance.ts | 5 + packages/backend/src/models/json-schema/note.ts | 8 ++ packages/backend/src/models/json-schema/user.ts | 4 + .../endpoints/admin/federation/update-instance.ts | 10 ++ .../server/api/endpoints/admin/reject-quotes.ts | 63 +++++++++ .../src/server/api/endpoints/notes/create.ts | 8 ++ .../backend/src/server/api/endpoints/notes/edit.ts | 8 ++ packages/backend/src/types.ts | 22 ++++ packages/frontend/src/components/MkNote.vue | 2 +- .../frontend/src/components/MkNoteDetailed.vue | 2 +- packages/frontend/src/components/MkNoteSub.vue | 2 +- packages/frontend/src/components/MkPostForm.vue | 2 +- packages/frontend/src/components/SkErrorList.vue | 43 +++++++ packages/frontend/src/components/SkNote.vue | 2 +- .../frontend/src/components/SkNoteDetailed.vue | 2 +- packages/frontend/src/components/SkNoteSub.vue | 2 +- packages/frontend/src/pages/admin-user.vue | 19 +++ .../frontend/src/pages/admin/modlog.ModLog.vue | 12 ++ packages/frontend/src/pages/instance-info.vue | 12 ++ packages/frontend/src/pages/note.vue | 8 +- packages/misskey-js/src/consts.ts | 19 +++ packages/misskey-js/src/entities.ts | 12 ++ sharkey-locales/en-US.yml | 10 ++ 36 files changed, 466 insertions(+), 88 deletions(-) create mode 100644 packages/backend/migration/1739671352784-add_note_processErrors.js create mode 100644 packages/backend/migration/1739671777344-add_user_rejectQuotes.js create mode 100644 packages/backend/migration/1739671847942-add_instance_rejectQuotes.js create mode 100644 packages/backend/src/server/api/endpoints/admin/reject-quotes.ts create mode 100644 packages/frontend/src/components/SkErrorList.vue (limited to 'packages/backend/src') diff --git a/locales/index.d.ts b/locales/index.d.ts index bf49869bf8..cc7884b8c1 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -8566,6 +8566,10 @@ export interface Locale extends ILocale { * Un-silence users */ "write:admin:unsilence-user": string; + /** + * Allow/Reject quote posts from a user + */ + "write:admin:reject-quotes": string; /** * View your list of scheduled notes */ @@ -10242,6 +10246,14 @@ export interface Locale extends ILocale { * Accepted reports from remote instance */ "acceptRemoteInstanceReports": string; + /** + * Rejected quotes from user + */ + "rejectQuotesUser": string; + /** + * Allowed quotes from user + */ + "allowQuotesUser": string; }; "_fileViewer": { /** @@ -11240,6 +11252,22 @@ export interface Locale extends ILocale { * Reject reports from this instance */ "rejectReports": string; + /** + * Reject quote posts from this instance + */ + "rejectQuotesInstance": string; + /** + * Reject quote posts from this user + */ + "rejectQuotesUser": string; + /** + * Are you sure you wish to reject quote posts? + */ + "rejectQuotesConfirm": string; + /** + * Are you sure you wish to allow quote posts? + */ + "allowQuotesConfirm": string; /** * This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s). */ @@ -12109,6 +12137,12 @@ export interface Locale extends ILocale { * Applies a content warning to all posts created by this user. If the post already has a CW, then this is appended to the end. */ "mandatoryCWDescription": string; + "_processErrors": { + /** + * Unable to process quote. This post may be missing context. + */ + "quoteUnavailable": string; + }; } declare const locales: { [lang: string]: Locale; diff --git a/packages/backend/migration/1739671352784-add_note_processErrors.js b/packages/backend/migration/1739671352784-add_note_processErrors.js new file mode 100644 index 0000000000..0be10125e1 --- /dev/null +++ b/packages/backend/migration/1739671352784-add_note_processErrors.js @@ -0,0 +1,11 @@ +export class AddNoteProcessErrors1739671352784 { + name = 'AddNoteProcessErrors1739671352784' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" ADD "processErrors" text array`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "processErrors"`); + } +} diff --git a/packages/backend/migration/1739671777344-add_user_rejectQuotes.js b/packages/backend/migration/1739671777344-add_user_rejectQuotes.js new file mode 100644 index 0000000000..29ed90c8ff --- /dev/null +++ b/packages/backend/migration/1739671777344-add_user_rejectQuotes.js @@ -0,0 +1,11 @@ +export class AddUserRejectQuotes1739671777344 { + name = 'AddUserRejectQuotes1739671777344' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "rejectQuotes" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "rejectQuotes"`); + } +} diff --git a/packages/backend/migration/1739671847942-add_instance_rejectQuotes.js b/packages/backend/migration/1739671847942-add_instance_rejectQuotes.js new file mode 100644 index 0000000000..89774eb991 --- /dev/null +++ b/packages/backend/migration/1739671847942-add_instance_rejectQuotes.js @@ -0,0 +1,11 @@ +export class AddInstanceRejectQuotes1739671847942 { + name = 'AddInstanceRejectQuotes1739671847942' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" ADD "rejectQuotes" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "rejectQuotes"`); + } +} diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 8291db9b42..df31cb4247 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -144,6 +144,7 @@ type Option = { uri?: string | null; url?: string | null; app?: MiApp | null; + processErrors?: string[] | null; }; export type PureRenoteOption = Option & { renote: MiNote } & ({ text?: null } | { cw?: null } | { reply?: null } | { poll?: null } | { files?: null | [] }); @@ -309,6 +310,9 @@ export class NoteCreateService implements OnApplicationShutdown { } } + // Check quote permissions + await this.checkQuotePermissions(data, user); + // Check blocking if (this.isRenote(data) && !this.isQuote(data)) { if (data.renote.userHost === null) { @@ -482,6 +486,7 @@ export class NoteCreateService implements OnApplicationShutdown { renoteUserId: data.renote ? data.renote.userId : null, renoteUserHost: data.renote ? data.renote.userHost : null, userHost: user.host, + processErrors: data.processErrors, }); // should really not happen, but better safe than sorry @@ -1147,4 +1152,29 @@ export class NoteCreateService implements OnApplicationShutdown { public async onApplicationShutdown(signal?: string | undefined): Promise { await this.dispose(); } + + @bindThis + public async checkQuotePermissions(data: Option, user: MiUser): Promise { + // Not a quote + if (!this.isRenote(data) || !this.isQuote(data)) return; + + // User cannot quote + if (user.rejectQuotes) { + if (user.host == null) { + throw new IdentifiableError('1c0ea108-d1e3-4e8e-aa3f-4d2487626153', 'QUOTE_DISABLED_FOR_USER'); + } else { + (data as Option).renote = null; + (data.processErrors ??= []).push('quoteUnavailable'); + } + } + + // Instance cannot quote + if (user.host) { + const instance = await this.federatedInstanceService.fetch(user.host); + if (instance?.rejectQuotes) { + (data as Option).renote = null; + (data.processErrors ??= []).push('quoteUnavailable'); + } + } + } } diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 24a99156d2..7851af86b7 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -140,6 +140,7 @@ type Option = { app?: MiApp | null; updatedAt?: Date | null; editcount?: boolean | null; + processErrors?: string[] | null; }; @Injectable() @@ -337,6 +338,9 @@ export class NoteEditService implements OnApplicationShutdown { } } + // Check quote permissions + await this.noteCreateService.checkQuotePermissions(data, user); + // Check blocking if (this.isRenote(data) && !this.isQuote(data)) { if (data.renote.userHost === null) { @@ -529,6 +533,7 @@ export class NoteEditService implements OnApplicationShutdown { if (data.uri != null) note.uri = data.uri; if (data.url != null) note.url = data.url; + if (data.processErrors !== undefined) note.processErrors = data.processErrors; if (mentionedUsers.length > 0) { note.mentions = mentionedUsers.map(u => u.id); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 2995b1e764..8470285e93 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -296,44 +296,8 @@ export class ApNoteService { : null; // 引用 - let quote: MiNote | undefined | null = null; - - if (note._misskey_quote ?? note.quoteUrl ?? note.quoteUri) { - const tryResolveNote = async (uri: unknown): Promise< - | { status: 'ok'; res: MiNote } - | { status: 'permerror' | 'temperror' } - > => { - if (typeof uri !== 'string' || !/^https?:/.test(uri)) { - this.logger.warn(`Failed to resolve quote ${uri} for note ${entryUri}: URI is invalid`); - return { status: 'permerror' }; - } - try { - const res = await this.resolveNote(uri, { resolver }); - if (res == null) { - this.logger.warn(`Failed to resolve quote ${uri} for note ${entryUri}: resolution error`); - return { status: 'permerror' }; - } - return { status: 'ok', res }; - } catch (e) { - const error = e instanceof Error ? `${e.name}: ${e.message}` : String(e); - this.logger.warn(`Failed to resolve quote ${uri} for note ${entryUri}: ${error}`); - - return { - status: (e instanceof StatusError && !e.isRetryable) ? 'permerror' : 'temperror', - }; - } - }; - - const uris = unique([note._misskey_quote, note.quoteUrl, note.quoteUri].filter(x => x != null)); - const results = await Promise.all(uris.map(tryResolveNote)); - - quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0); - if (!quote) { - if (results.some(x => x.status === 'temperror')) { - throw new Error(`temporary error resolving quote for ${entryUri}`); - } - } - } + const quote = await this.getQuote(note, entryUri, resolver); + const processErrors = quote === null ? ['quoteUnavailable'] : null; // vote if (reply && reply.hasPoll) { @@ -369,7 +333,8 @@ export class ApNoteService { createdAt: note.published ? new Date(note.published) : null, files, reply, - renote: quote, + renote: quote ?? null, + processErrors, name: note.name, cw, text, @@ -538,44 +503,8 @@ export class ApNoteService { : null; // 引用 - let quote: MiNote | undefined | null = null; - - if (note._misskey_quote ?? note.quoteUrl ?? note.quoteUri) { - const tryResolveNote = async (uri: unknown): Promise< - | { status: 'ok'; res: MiNote } - | { status: 'permerror' | 'temperror' } - > => { - if (typeof uri !== 'string' || !/^https?:/.test(uri)) { - this.logger.warn(`Failed to resolve quote ${uri} for note ${entryUri}: URI is invalid`); - return { status: 'permerror' }; - } - try { - const res = await this.resolveNote(uri, { resolver }); - if (res == null) { - this.logger.warn(`Failed to resolve quote ${uri} for note ${entryUri}: resolution error`); - return { status: 'permerror' }; - } - return { status: 'ok', res }; - } catch (e) { - const error = e instanceof Error ? `${e.name}: ${e.message}` : String(e); - this.logger.warn(`Failed to resolve quote ${uri} for note ${entryUri}: ${error}`); - - return { - status: (e instanceof StatusError && !e.isRetryable) ? 'permerror' : 'temperror', - }; - } - }; - - const uris = unique([note._misskey_quote, note.quoteUrl, note.quoteUri].filter(x => x != null)); - const results = await Promise.all(uris.map(tryResolveNote)); - - quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0); - if (!quote) { - if (results.some(x => x.status === 'temperror')) { - throw new Error(`temporary error resolving quote for ${entryUri}`); - } - } - } + const quote = await this.getQuote(note, entryUri, resolver); + const processErrors = quote === null ? ['quoteUnavailable'] : null; // vote if (reply && reply.hasPoll) { @@ -611,7 +540,8 @@ export class ApNoteService { createdAt: note.published ? new Date(note.published) : null, files, reply, - renote: quote, + renote: quote ?? null, + processErrors, name: note.name, cw, text, @@ -734,6 +664,63 @@ export class ApNoteService { }); })); } + + /** + * Fetches the note's quoted post. + * On success - returns the note. + * On skip (no quote) - returns undefined. + * On permanent error - returns null. + * On temporary error - throws an exception. + */ + private async getQuote(note: IPost, entryUri: string, resolver: Resolver): Promise { + const quoteUris = new Set(); + if (note._misskey_quote) quoteUris.add(note._misskey_quote); + if (note.quoteUrl) quoteUris.add(note.quoteUrl); + if (note.quoteUri) quoteUris.add(note.quoteUri); + + // No quote, return undefined + if (quoteUris.size < 1) return undefined; + + /** + * Attempts to resolve a quote by URI. + * Returns the note if successful, true if there's a retryable error, and false if there's a permanent error. + */ + const resolveQuote = async (uri: unknown): Promise => { + if (typeof(uri) !== 'string' || !/^https?:/.test(uri)) { + this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}": URI is invalid`); + return false; + } + + try { + const quote = await this.resolveNote(uri, { resolver }); + + if (quote == null) { + this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}": request error`); + return false; + } + + return quote; + } catch (e) { + const error = e instanceof Error ? `${e.name}: ${e.message}` : String(e); + this.logger.warn(`Failed to resolve quote "${uri}" for note "${entryUri}": ${error}`); + + return (e instanceof StatusError && e.isRetryable); + } + }; + + const results = await Promise.all(Array.from(quoteUris).map(u => resolveQuote(u))); + + // Success - return the quote + const quote = results.find(r => typeof(r) === 'object'); + if (quote) return quote; + + // Temporary / retryable error - throw error + const tempError = results.find(r => r === true); + if (tempError) throw new Error(`temporary error resolving quote for "${entryUri}"`); + + // Permanent error - return null + return null; + } } function getBestIcon(note: IObject): IObject | null { diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index 63e5923255..fcc9bed3bd 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -60,6 +60,7 @@ export class InstanceEntityService { latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null, isNSFW: instance.isNSFW, rejectReports: instance.rejectReports, + rejectQuotes: instance.rejectQuotes, moderationNote: iAmModerator ? instance.moderationNote : null, }; } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index dca73567cc..1c51aba09b 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -490,6 +490,7 @@ export class NoteEntityService implements OnModuleInit { ...(opts.detail ? { clippedCount: note.clippedCount, + processErrors: note.processErrors, reply: note.replyId ? this.pack(note.reply ?? note.replyId, me, { detail: false, diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 4fbbbdd379..5d539ea264 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -674,6 +674,7 @@ export class UserEntityService implements OnModuleInit { securityKeys: profile!.twoFactorEnabled ? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1) : false, + rejectQuotes: user.rejectQuotes, } : {}), ...(isDetailed && isMe ? { diff --git a/packages/backend/src/models/Instance.ts b/packages/backend/src/models/Instance.ts index ba93190c57..c64ebb1b3b 100644 --- a/packages/backend/src/models/Instance.ts +++ b/packages/backend/src/models/Instance.ts @@ -164,6 +164,15 @@ export class MiInstance { }) public rejectReports: boolean; + /** + * If true, quote posts from this instance will be downgraded to normal posts. + * The quote will be stripped and a process error will be generated. + */ + @Column('boolean', { + default: false, + }) + public rejectQuotes: boolean; + @Column('varchar', { length: 16384, default: '', }) diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts index 8b5265e8fe..2dabb75d83 100644 --- a/packages/backend/src/models/Note.ts +++ b/packages/backend/src/models/Note.ts @@ -203,6 +203,17 @@ export class MiNote { @JoinColumn() public channel: MiChannel | null; + /** + * List of non-fatal errors encountered while processing (creating or updating) this note. + * Entries can be a translation key (which will be queried from the "_processErrors" section) or a raw string. + * Errors will be displayed to the user when viewing the note. + */ + @Column('text', { + array: true, + nullable: true, + }) + public processErrors: string[] | null; + //#region Denormalized fields @Index() @Column('varchar', { diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 8a3ad1003d..5d87c7fa12 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -348,6 +348,15 @@ export class MiUser { }) public mandatoryCW: string | null; + /** + * If true, quote posts from this user will be downgraded to normal posts. + * The quote will be stripped and a process error will be generated. + */ + @Column('boolean', { + default: false, + }) + public rejectQuotes: boolean; + constructor(data: Partial) { if (data == null) return; diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index 7960e748e9..57d4466ffa 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -126,6 +126,11 @@ export const packedFederationInstanceSchema = { optional: false, nullable: false, }, + rejectQuotes: { + type: 'boolean', + optional: false, + nullable: false, + }, moderationNote: { type: 'string', optional: true, nullable: true, diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index 432c096e48..51d23fe5e7 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -256,6 +256,14 @@ export const packedNoteSchema = { type: 'number', optional: true, nullable: false, }, + processErrors: { + type: 'array', + optional: true, nullable: true, + items: { + type: 'string', + optional: false, nullable: false, + }, + }, myReaction: { type: 'string', diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 1c2ba538c1..3d0bf44c2e 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -445,6 +445,10 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'boolean', nullable: false, optional: true, }, + rejectQuotes: { + type: 'boolean', + nullable: false, optional: true, + }, //#region relations isFollowing: { type: 'boolean', diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index daf19c4435..24d0b8527c 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -27,6 +27,7 @@ export const paramDef = { isNSFW: { type: 'boolean' }, rejectReports: { type: 'boolean' }, moderationNote: { type: 'string' }, + rejectQuotes: { type: 'boolean' }, }, required: ['host'], } as const; @@ -59,6 +60,7 @@ export default class extends Endpoint { // eslint- suspensionState, isNSFW: ps.isNSFW, rejectReports: ps.rejectReports, + rejectQuotes: ps.rejectQuotes, moderationNote: ps.moderationNote, }); @@ -92,6 +94,14 @@ export default class extends Endpoint { // eslint- }); } + if (ps.rejectQuotes != null && instance.rejectQuotes !== ps.rejectQuotes) { + const message = ps.rejectReports ? 'rejectQuotesInstance' : 'acceptQuotesInstance'; + this.moderationLogService.log(me, message, { + id: instance.id, + host: instance.host, + }); + } + if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) { this.moderationLogService.log(me, 'updateRemoteInstanceNote', { id: instance.id, diff --git a/packages/backend/src/server/api/endpoints/admin/reject-quotes.ts b/packages/backend/src/server/api/endpoints/admin/reject-quotes.ts new file mode 100644 index 0000000000..78f94ceeff --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/reject-quotes.ts @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { CacheService } from '@/core/CacheService.js'; +import { ModerationLogService } from '@/core/ModerationLogService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'write:admin:reject-quotes', +} as const; + +export const paramDef = { + type: 'object', + properties: { + userId: { type: 'string', format: 'misskey:id' }, + rejectQuotes: { type: 'boolean', nullable: false }, + }, + required: ['userId', 'rejectQuotes'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.usersRepository) + private readonly usersRepository: UsersRepository, + + private readonly globalEventService: GlobalEventService, + private readonly cacheService: CacheService, + private readonly moderationLogService: ModerationLogService, + ) { + super(meta, paramDef, async (ps, me) => { + const user = await this.cacheService.findUserById(ps.userId); + + // Skip if there's nothing to do + if (user.rejectQuotes === ps.rejectQuotes) return; + + // Log event first. + // This ensures that we don't "lose" the log if an error occurs + await this.moderationLogService.log(me, ps.rejectQuotes ? 'rejectQuotesUser' : 'acceptQuotesUser', { + userId: user.id, + userUsername: user.username, + userHost: user.host, + }); + + await this.usersRepository.update(ps.userId, { + rejectQuotes: ps.rejectQuotes, + }); + + // Synchronize caches and other processes + this.globalEventService.publishInternalEvent('localUserUpdated', { id: ps.userId }); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index d1cf0123dc..b0f32bfda8 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -143,6 +143,12 @@ export const meta = { code: 'CONTAINS_TOO_MANY_MENTIONS', id: '4de0363a-3046-481b-9b0f-feff3e211025', }, + + quoteDisabledForUser: { + message: 'You do not have permission to create quote posts.', + code: 'QUOTE_DISABLED_FOR_USER', + id: '1c0ea108-d1e3-4e8e-aa3f-4d2487626153', + }, }, } as const; @@ -415,6 +421,8 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.containsProhibitedWords); } else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') { throw new ApiError(meta.errors.containsTooManyMentions); + } else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') { + throw new ApiError(meta.errors.quoteDisabledForUser); } } throw e; diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts index dc94c78e75..cc2293c5d6 100644 --- a/packages/backend/src/server/api/endpoints/notes/edit.ts +++ b/packages/backend/src/server/api/endpoints/notes/edit.ts @@ -176,6 +176,12 @@ export const meta = { id: '33510210-8452-094c-6227-4a6c05d99f02', }, + quoteDisabledForUser: { + message: 'You do not have permission to create quote posts.', + code: 'QUOTE_DISABLED_FOR_USER', + id: '1c0ea108-d1e3-4e8e-aa3f-4d2487626153', + }, + containsProhibitedWords: { message: 'Cannot post because it contains prohibited words.', code: 'CONTAINS_PROHIBITED_WORDS', @@ -469,6 +475,8 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.containsProhibitedWords); } else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') { throw new ApiError(meta.errors.containsTooManyMentions); + } else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') { + throw new ApiError(meta.errors.quoteDisabledForUser); } } throw e; diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index b359fa5a39..b5d982e3a5 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -132,6 +132,10 @@ export const moderationLogTypes = [ 'deletePage', 'deleteFlash', 'deleteGalleryPost', + 'acceptQuotesUser', + 'rejectQuotesUser', + 'acceptQuotesInstance', + 'rejectQuotesInstance', ] as const; export type ModerationLogPayloads = { @@ -417,6 +421,24 @@ export type ModerationLogPayloads = { postUserUsername: string; post: any; }; + acceptQuotesUser: { + userId: string, + userUsername: string, + userHost: string | null, + }; + rejectQuotesUser: { + userId: string, + userUsername: string, + userHost: string | null, + }; + acceptQuotesInstance: { + id: string; + host: string; + }; + rejectQuotesInstance: { + id: string; + host: string; + }; }; export type Serialized = { diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 0bac6a67b9..4b174d7336 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -142,7 +142,7 @@ SPDX-License-Identifier: AGPL-3.0-only