diff --git a/.config/docker_example.env b/.config/docker_example.env index 7a0261524b..4fe8e76b78 100644 --- a/.config/docker_example.env +++ b/.config/docker_example.env @@ -2,3 +2,4 @@ POSTGRES_PASSWORD=example-misskey-pass POSTGRES_USER=example-misskey-user POSTGRES_DB=misskey +DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c5755315fc..d4678ec5e0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,7 +17,7 @@ updates: directory: "/" schedule: interval: daily - open-pull-requests-limit: 5 + open-pull-requests-limit: 10 # List dependencies required to be updated together, sharing the same version numbers. # Those who simply have the common owner (e.g. @fastify) don't need to be listed. groups: diff --git a/CHANGELOG.md b/CHANGELOG.md index ea34fa9ef7..474fcad674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,21 @@ ## 202x.x.x (Unreleased) +### General +- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加 +- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正 + ### Client +- Feat: 新しいゲームを追加 - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように +- Fix: ネイティブモードの絵文字がモノクロにならないように +- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正 +- Enhance: チャンネルノートのピン留めをノートのメニューからできるよ + +### Server +- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました +- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916) +- Enhance: クリップをエクスポートできるように ## 2023.12.2 diff --git a/ROADMAP.md b/ROADMAP.md index 3077c41e73..509ecb9fe7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,6 +6,7 @@ Also, the later tasks are more indefinite and are subject to change as developme This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development. - ~~Make the number of type errors zero (backend)~~ → Done ✔️ +- Make the number of type errors zero (frontend) - Improve CI - ~~Fix tests~~ → Done ✔️ - Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986 diff --git a/docker-compose_example.yml b/docker-compose_example.yml index 60ba4dc8ca..5cebbe4164 100644 --- a/docker-compose_example.yml +++ b/docker-compose_example.yml @@ -7,6 +7,7 @@ services: links: - db - redis +# - mcaptcha # - meilisearch depends_on: db: @@ -48,6 +49,36 @@ services: interval: 5s retries: 20 +# mcaptcha: +# restart: always +# image: mcaptcha/mcaptcha:latest +# networks: +# internal_network: +# external_network: +# aliases: +# - localhost +# ports: +# - 7493:7493 +# env_file: +# - .config/docker.env +# environment: +# PORT: 7493 +# MCAPTCHA_redis_URL: "redis://mcaptcha_redis/" +# depends_on: +# db: +# condition: service_healthy +# mcaptcha_redis: +# condition: service_healthy +# +# mcaptcha_redis: +# image: mcaptcha/cache:latest +# networks: +# - internal_network +# healthcheck: +# test: "redis-cli ping" +# interval: 5s +# retries: 20 + # meilisearch: # restart: always # image: getmeili/meilisearch:v1.3.4 diff --git a/locales/index.d.ts b/locales/index.d.ts index 60e88a2192..7c73caaac9 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -382,6 +382,11 @@ export interface Locale { "enableHcaptcha": string; "hcaptchaSiteKey": string; "hcaptchaSecretKey": string; + "mcaptcha": string; + "enableMcaptcha": string; + "mcaptchaSiteKey": string; + "mcaptchaSecretKey": string; + "mcaptchaInstanceUrl": string; "recaptcha": string; "enableRecaptcha": string; "recaptchaSiteKey": string; @@ -672,6 +677,7 @@ export interface Locale { "other": string; "regenerateLoginToken": string; "regenerateLoginTokenDescription": string; + "theKeywordWhenSearchingForCustomEmoji": string; "setMultipleBySeparatingWithSpace": string; "fileIdOrUrl": string; "behavior": string; @@ -1186,6 +1192,7 @@ export interface Locale { "decorate": string; "addMfmFunction": string; "enableQuickAddMfmFunction": string; + "bubbleGame": string; "_announcement": { "forExistingUsers": string; "forExistingUsersDescription": string; @@ -1650,6 +1657,15 @@ export interface Locale { "title": string; "description": string; }; + "_bubbleGameExplodingHead": { + "title": string; + "description": string; + }; + "_bubbleGameDoubleExplodingHead": { + "title": string; + "description": string; + "flavor": string; + }; }; }; "_role": { @@ -2250,6 +2266,7 @@ export interface Locale { "_exportOrImport": { "allNotes": string; "favoritedNotes": string; + "clips": string; "followingList": string; "muteList": string; "blockingList": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3d31dcef49..55ff3201f0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -379,6 +379,11 @@ hcaptcha: "hCaptcha" enableHcaptcha: "hCaptchaを有効にする" hcaptchaSiteKey: "サイトキー" hcaptchaSecretKey: "シークレットキー" +mcaptcha: "mCaptcha" +enableMcaptcha: "mCaptchaを有効にする" +mcaptchaSiteKey: "サイトキー" +mcaptchaSecretKey: "シークレットキー" +mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL" recaptcha: "reCAPTCHA" enableRecaptcha: "reCAPTCHAを有効にする" recaptchaSiteKey: "サイトキー" @@ -669,6 +674,7 @@ useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使 other: "その他" regenerateLoginToken: "ログイントークンを再生成" regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。" +theKeywordWhenSearchingForCustomEmoji: "カスタム絵文字を検索する時のキーワードになります。" setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。" fileIdOrUrl: "ファイルIDまたはURL" behavior: "動作" @@ -1183,6 +1189,7 @@ seasonalScreenEffect: "季節に応じた画面の演出" decorate: "デコる" addMfmFunction: "装飾を追加" enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する" +bubbleGame: "バブルゲーム" _announcement: forExistingUsers: "既存ユーザーのみ" @@ -1561,6 +1568,13 @@ _achievements: _tutorialCompleted: title: "Misskey初心者講座 修了証" description: "チュートリアルを完了した" + _bubbleGameExplodingHead: + title: "🤯" + description: "バブルゲームで最も大きいモノを出した" + _bubbleGameDoubleExplodingHead: + title: "ダブル🤯" + description: "バブルゲームで最も大きいモノを2つ同時に出した" + flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて" _role: new: "ロールの作成" @@ -2153,6 +2167,7 @@ _profile: _exportOrImport: allNotes: "全てのノート" favoritedNotes: "お気に入りにしたノート" + clips: "クリップ" followingList: "フォロー" muteList: "ミュート" blockingList: "ブロック" diff --git a/packages/backend/migration/1704373210054-support-mcaptcha.js b/packages/backend/migration/1704373210054-support-mcaptcha.js new file mode 100644 index 0000000000..ce42b90716 --- /dev/null +++ b/packages/backend/migration/1704373210054-support-mcaptcha.js @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SupportMcaptcha1704373210054 { + name = 'SupportMcaptcha1704373210054' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableMcaptcha" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSitekey" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSecretKey" character varying(1024)`); + await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaInstanceUrl" character varying(1024)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaInstanceUrl"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSecretKey"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSitekey"`); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableMcaptcha"`); + } +} diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 3e9d19f825..c83845b94c 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { setTimeout } from 'node:timers/promises'; import { Global, Inject, Module } from '@nestjs/common'; import * as Redis from 'ioredis'; import { DataSource } from 'typeorm'; @@ -12,6 +11,7 @@ import { DI } from './di-symbols.js'; import { Config, loadConfig } from './config.js'; import { createPostgresDataSource } from './postgres.js'; import { RepositoryModule } from './models/RepositoryModule.js'; +import { allSettled } from './misc/promise-tracker.js'; import type { Provider, OnApplicationShutdown } from '@nestjs/common'; const $config: Provider = { @@ -33,7 +33,7 @@ const $meilisearch: Provider = { useFactory: (config: Config) => { if (config.meilisearch) { return new MeiliSearch({ - host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`, + host: `${config.meilisearch.ssl ? 'https' : 'http'}://${config.meilisearch.host}:${config.meilisearch.port}`, apiKey: config.meilisearch.apiKey, }); } else { @@ -91,17 +91,12 @@ export class GlobalModule implements OnApplicationShutdown { @Inject(DI.redisForPub) private redisForPub: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, - ) {} + ) { } public async dispose(): Promise { - if (process.env.NODE_ENV === 'test') { - // XXX: - // Shutting down the existing connections causes errors on Jest as - // Misskey has asynchronous postgres/redis connections that are not - // awaited. - // Let's wait for some random time for them to finish. - await setTimeout(5000); - } + // Wait for all potential DB queries + await allSettled(); + // And then disconnect from DB await Promise.all([ this.db.destroy(), this.redisClient.disconnect(), diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index 88fc033859..a28b68ee86 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -87,6 +87,8 @@ export const ACHIEVEMENT_TYPES = [ 'brainDiver', 'smashTestNotificationButton', 'tutorialCompleted', + 'bubbleGameExplodingHead', + 'bubbleGameDoubleExplodingHead', ] as const; @Injectable() diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index f64196f4fc..6c5ee4835d 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -73,6 +73,37 @@ export class CaptchaService { } } + // https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go + @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'); + } + + const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost); + const result = await this.httpRequestService.send(endpointUrl.toString(), { + method: 'POST', + body: JSON.stringify({ + key: siteKey, + secret: secret, + token: response, + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (result.status !== 200) { + throw new Error('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'); + } + } + @bindThis public async verifyTurnstile(secret: string, response: string | null | undefined): Promise { if (response == null) { diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index ed8d51df16..97fb80ab39 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -58,6 +58,7 @@ import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; import { isReply } from '@/misc/is-reply.js'; +import { trackPromise } from '@/misc/promise-tracker.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -676,7 +677,7 @@ export class NoteCreateService implements OnApplicationShutdown { this.relayService.deliverToRelays(user, noteActivity); } - dm.execute(); + trackPromise(dm.execute()); })(); } //#endregion diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index 03c1735e04..c73cf76592 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -14,6 +14,7 @@ import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; +import { trackPromise } from '@/misc/promise-tracker.js'; @Injectable() export class NoteReadService implements OnApplicationShutdown { @@ -107,7 +108,7 @@ export class NoteReadService implements OnApplicationShutdown { // TODO: ↓まとめてクエリしたい - this.noteUnreadsRepository.countBy({ + trackPromise(this.noteUnreadsRepository.countBy({ userId: userId, isMentioned: true, }).then(mentionsCount => { @@ -115,9 +116,9 @@ export class NoteReadService implements OnApplicationShutdown { // 全て既読になったイベントを発行 this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions'); } - }); + })); - this.noteUnreadsRepository.countBy({ + trackPromise(this.noteUnreadsRepository.countBy({ userId: userId, isSpecified: true, }).then(specifiedCount => { @@ -125,7 +126,7 @@ export class NoteReadService implements OnApplicationShutdown { // 全て既読になったイベントを発行 this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes'); } - }); + })); } } diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts index ad7be83e5b..765fcae063 100644 --- a/packages/backend/src/core/NotificationService.ts +++ b/packages/backend/src/core/NotificationService.ts @@ -20,6 +20,7 @@ import { CacheService } from '@/core/CacheService.js'; import type { Config } from '@/config.js'; import { UserListService } from '@/core/UserListService.js'; import type { FilterUnionByProperty } from '@/types.js'; +import { trackPromise } from '@/misc/promise-tracker.js'; @Injectable() export class NotificationService implements OnApplicationShutdown { @@ -74,7 +75,18 @@ export class NotificationService implements OnApplicationShutdown { } @bindThis - public async createNotification( + public createNotification( + notifieeId: MiUser['id'], + type: T, + data: Omit, 'type' | 'id' | 'createdAt' | 'notifierId'>, + notifierId?: MiUser['id'] | null, + ) { + trackPromise( + this.#createNotificationInternal(notifieeId, type, data, notifierId), + ); + } + + async #createNotificationInternal( notifieeId: MiUser['id'], type: T, data: Omit, 'type' | 'id' | 'createdAt' | 'notifierId'>, diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts index 4444dc9787..20a53ff282 100644 --- a/packages/backend/src/core/QueueModule.ts +++ b/packages/backend/src/core/QueueModule.ts @@ -3,12 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { setTimeout } from 'node:timers/promises'; import { Inject, Module, OnApplicationShutdown } from '@nestjs/common'; import * as Bull from 'bullmq'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { QUEUE, baseQueueOptions } from '@/queue/const.js'; +import { allSettled } from '@/misc/promise-tracker.js'; import type { Provider } from '@nestjs/common'; import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js'; @@ -106,14 +106,9 @@ export class QueueModule implements OnApplicationShutdown { ) {} public async dispose(): Promise { - if (process.env.NODE_ENV === 'test') { - // XXX: - // Shutting down the existing connections causes errors on Jest as - // Misskey has asynchronous postgres/redis connections that are not - // awaited. - // Let's wait for some random time for them to finish. - await setTimeout(5000); - } + // Wait for all potential queue jobs + await allSettled(); + // And then close all queues await Promise.all([ this.systemQueue.close(), this.endedPollNotificationQueue.close(), diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index be378a899b..dc3f248da4 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -16,6 +16,7 @@ import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, Obj import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; import type * as Bull from 'bullmq'; +import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; @Injectable() export class QueueService { @@ -74,11 +75,15 @@ export class QueueService { if (content == null) return null; if (to == null) return null; + const contentBody = JSON.stringify(content); + const digest = ApRequestCreator.createDigest(contentBody); + const data: DeliverJobData = { user: { id: user.id, }, - content, + content: contentBody, + digest, to, isSharedInbox, }; @@ -103,6 +108,8 @@ export class QueueService { @bindThis public async deliverMany(user: ThinUser, content: IActivity | null, inboxes: Map) { if (content == null) return null; + const contentBody = JSON.stringify(content); + const digest = ApRequestCreator.createDigest(contentBody); const opts = { attempts: this.config.deliverJobMaxAttempts ?? 12, @@ -117,7 +124,8 @@ export class QueueService { name: d[0], data: { user, - content, + content: contentBody, + digest, to: d[0], isSharedInbox: d[1], } as DeliverJobData, @@ -174,6 +182,16 @@ export class QueueService { }); } + @bindThis + public createExportClipsJob(user: ThinUser) { + return this.dbQueue.add('exportClips', { + user: { id: user.id }, + }, { + removeOnComplete: true, + removeOnFail: true, + }); + } + @bindThis public createExportFavoritesJob(user: ThinUser) { return this.dbQueue.add('exportFavorites', { diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 3ca12551b1..2e8f76fa8a 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -28,6 +28,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { RoleService } from '@/core/RoleService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; +import { trackPromise } from '@/misc/promise-tracker.js'; const FALLBACK = '❤'; const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16; @@ -268,7 +269,7 @@ export class ReactionService { } } - dm.execute(); + trackPromise(dm.execute()); } //#endregion } @@ -316,7 +317,7 @@ export class ReactionService { dm.addDirectRecipe(reactee as MiRemoteUser); } dm.addFollowersRecipe(); - dm.execute(); + trackPromise(dm.execute()); } //#endregion } diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 81003bcf1c..d7414e9c99 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -144,7 +144,7 @@ class DeliverManager { } // deliver - this.queueService.deliverMany(this.actor, this.activity, inboxes); + await this.queueService.deliverMany(this.actor, this.activity, inboxes); } } diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index baaab67e48..a0c63bdbf7 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -97,6 +97,8 @@ export class ApInboxService { } catch (err) { if (err instanceof Error || typeof err === 'string') { this.logger.error(err); + } else { + throw err; } } } @@ -256,7 +258,7 @@ export class ApInboxService { const targetUri = getApId(activity.object); - this.announceNote(actor, activity, targetUri); + await this.announceNote(actor, activity, targetUri); } @bindThis @@ -288,7 +290,7 @@ export class ApInboxService { } catch (err) { // 対象が4xxならスキップ if (err instanceof StatusError) { - if (err.isClientError) { + if (!err.isRetryable) { this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`); return; } @@ -373,7 +375,7 @@ export class ApInboxService { }); if (isPost(object)) { - this.createNote(resolver, actor, object, false, activity); + await this.createNote(resolver, actor, object, false, activity); } else { this.logger.warn(`Unknown type: ${getApType(object)}`); } @@ -404,7 +406,7 @@ export class ApInboxService { await this.apNoteService.createNote(note, resolver, silent); return 'ok'; } catch (err) { - if (err instanceof StatusError && err.isClientError) { + if (err instanceof StatusError && !err.isRetryable) { return `skip ${err.statusCode}`; } else { throw err; diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index b59ce5241f..e165c5e960 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -34,9 +34,9 @@ type PrivateKey = { }; export class ApRequestCreator { - static createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record }): Signed { + static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); - const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`; + const digestHeader = args.digest ?? this.createDigest(args.body); const request: Request = { url: u.href, @@ -59,6 +59,10 @@ export class ApRequestCreator { }; } + static createDigest(body: string) { + return `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`; + } + static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record }): Signed { const u = new URL(args.url); @@ -145,8 +149,8 @@ export class ApRequestService { } @bindThis - public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown): Promise { - const body = JSON.stringify(object); + public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown, digest?: string): Promise { + const body = typeof object === 'string' ? object : JSON.stringify(object); const keypair = await this.userKeypairService.getUserKeypair(user.id); @@ -157,6 +161,7 @@ export class ApRequestService { }, url, body, + digest, additionalHeaders: { }, }); diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 05d5ca15db..e3eccd5405 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -216,7 +216,7 @@ export class ApNoteService { return { status: 'ok', res }; } catch (e) { return { - status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror', + status: (e instanceof StatusError && !e.isRetryable) ? 'permerror' : 'temperror', }; } }; diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index c49dad8e79..1777e2cf54 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -351,6 +351,7 @@ export class NoteEntityService implements OnModuleInit { color: channel.color, isSensitive: channel.isSensitive, allowRenoteToExternal: channel.allowRenoteToExternal, + userId: channel.userId, } : undefined, mentions: note.mentions.length > 0 ? note.mentions : undefined, uri: note.uri ?? undefined, diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts index c5ef9b2fa3..4c55acea5a 100644 --- a/packages/backend/src/daemons/ServerStatsService.ts +++ b/packages/backend/src/daemons/ServerStatsService.ts @@ -37,7 +37,7 @@ export class ServerStatsService implements OnApplicationShutdown { const log = [] as any[]; ev.on('requestServerStatsLog', x => { - ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length ?? 50)); + ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length)); }); const tick = async () => { diff --git a/packages/backend/src/misc/promise-tracker.ts b/packages/backend/src/misc/promise-tracker.ts new file mode 100644 index 0000000000..c7166c6de9 --- /dev/null +++ b/packages/backend/src/misc/promise-tracker.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +const promiseRefs: Set>> = new Set(); + +/** + * This tracks promises that other modules decided not to wait for, + * and makes sure they are all settled before fully closing down the server. + */ +export function trackPromise(promise: Promise) { + if (process.env.NODE_ENV !== 'test') { + return; + } + const ref = new WeakRef(promise); + promiseRefs.add(ref); + promise.finally(() => promiseRefs.delete(ref)); +} + +export async function allSettled(): Promise { + await Promise.allSettled([...promiseRefs].map(r => r.deref())); +} diff --git a/packages/backend/src/misc/status-error.ts b/packages/backend/src/misc/status-error.ts index 4285685d24..be213088a8 100644 --- a/packages/backend/src/misc/status-error.ts +++ b/packages/backend/src/misc/status-error.ts @@ -7,6 +7,7 @@ export class StatusError extends Error { public statusCode: number; public statusMessage?: string; public isClientError: boolean; + public isRetryable: boolean; constructor(message: string, statusCode: number, statusMessage?: string) { super(message); @@ -14,5 +15,6 @@ export class StatusError extends Error { this.statusCode = statusCode; this.statusMessage = statusMessage; this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500; + this.isRetryable = !this.isClientError || this.statusCode === 429; } } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index f5a75ed28a..3265e85dd7 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -191,6 +191,29 @@ export class MiMeta { }) public hcaptchaSecretKey: string | null; + @Column('boolean', { + default: false, + }) + public enableMcaptcha: boolean; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public mcaptchaSitekey: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public mcaptchaSecretKey: string | null; + + @Column('varchar', { + length: 1024, + nullable: true, + }) + public mcaptchaInstanceUrl: string | null; + @Column('boolean', { default: false, }) @@ -467,7 +490,7 @@ export class MiMeta { nullable: true, }) public truemailInstance: string | null; - + @Column('varchar', { length: 1024, nullable: true, diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts index aa749943f0..2b7722129b 100644 --- a/packages/backend/src/models/json-schema/note.ts +++ b/packages/backend/src/models/json-schema/note.ts @@ -148,6 +148,10 @@ export const packedNoteSchema = { type: 'boolean', optional: false, nullable: false, }, + userId: { + type: 'string', + optional: false, nullable: true, + }, }, }, localOnly: { diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index e6327002c5..9c52c7d76a 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -24,6 +24,7 @@ import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmo import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js'; import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; +import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js'; import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js'; import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js'; import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js'; @@ -53,6 +54,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor DeleteDriveFilesProcessorService, ExportCustomEmojisProcessorService, ExportNotesProcessorService, + ExportClipsProcessorService, ExportFavoritesProcessorService, ExportFollowingProcessorService, ExportMutingProcessorService, diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index b872dd65f7..bcc1a69f80 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -16,6 +16,7 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js'; import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js'; +import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js'; import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js'; import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js'; import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js'; @@ -91,6 +92,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private deleteDriveFilesProcessorService: DeleteDriveFilesProcessorService, private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService, private exportNotesProcessorService: ExportNotesProcessorService, + private exportClipsProcessorService: ExportClipsProcessorService, private exportFavoritesProcessorService: ExportFavoritesProcessorService, private exportFollowingProcessorService: ExportFollowingProcessorService, private exportMutingProcessorService: ExportMutingProcessorService, @@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job); case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job); case 'exportNotes': return this.exportNotesProcessorService.process(job); + case 'exportClips': return this.exportClipsProcessorService.process(job); case 'exportFavorites': return this.exportFavoritesProcessorService.process(job); case 'exportFollowing': return this.exportFollowingProcessorService.process(job); case 'exportMuting': return this.exportMutingProcessorService.process(job); diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 4a1d9f28b4..64c3445552 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -72,7 +72,7 @@ export class DeliverProcessorService { } try { - await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content); + await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest); // Update stats this.federatedInstanceService.fetch(host).then(i => { @@ -111,7 +111,7 @@ export class DeliverProcessorService { if (res instanceof StatusError) { // 4xx - if (res.isClientError) { + if (!res.isRetryable) { // 相手が閉鎖していることを明示しているため、配送停止する if (job.data.isSharedInbox && res.statusCode === 410) { this.federatedInstanceService.fetch(host).then(i => { diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts new file mode 100644 index 0000000000..5221497bd3 --- /dev/null +++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts @@ -0,0 +1,206 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as fs from 'node:fs'; +import { Writable } from 'node:stream'; +import { Inject, Injectable, StreamableFile } from '@nestjs/common'; +import { MoreThan } from 'typeorm'; +import { format as dateFormat } from 'date-fns'; +import { DI } from '@/di-symbols.js'; +import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { DriveService } from '@/core/DriveService.js'; +import { createTemp } from '@/misc/create-temp.js'; +import type { MiPoll } from '@/models/Poll.js'; +import type { MiNote } from '@/models/Note.js'; +import { bindThis } from '@/decorators.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import { Packed } from '@/misc/json-schema.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; +import type { DbJobDataWithUser } from '../types.js'; + +@Injectable() +export class ExportClipsProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.pollsRepository) + private pollsRepository: PollsRepository, + + @Inject(DI.clipsRepository) + private clipsRepository: ClipsRepository, + + @Inject(DI.clipNotesRepository) + private clipNotesRepository: ClipNotesRepository, + + private driveService: DriveService, + private queueLoggerService: QueueLoggerService, + private idService: IdService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('export-clips'); + } + + @bindThis + public async process(job: Bull.Job): Promise { + this.logger.info(`Exporting clips of ${job.data.user.id} ...`); + + const user = await this.usersRepository.findOneBy({ id: job.data.user.id }); + if (user == null) { + return; + } + + // Create temp file + const [path, cleanup] = await createTemp(); + + this.logger.info(`Temp file is ${path}`); + + try { + const stream = Writable.toWeb(fs.createWriteStream(path, { flags: 'a' })); + const writer = stream.getWriter(); + writer.closed.catch(this.logger.error); + + await writer.write('['); + + await this.processClips(writer, user, job); + + await writer.write(']'); + await writer.close(); + + this.logger.succ(`Exported to: ${path}`); + + const fileName = 'clips-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' }); + + this.logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } + } + + async processClips(writer: WritableStreamDefaultWriter, user: MiUser, job: Bull.Job) { + let exportedClipsCount = 0; + let cursor: MiClip['id'] | null = null; + + while (true) { + const clips = await this.clipsRepository.find({ + where: { + userId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }); + + if (clips.length === 0) { + job.updateProgress(100); + break; + } + + cursor = clips.at(-1)?.id ?? null; + + for (const clip of clips) { + // Stringify but remove the last `]}` + const content = JSON.stringify(this.serializeClip(clip)).slice(0, -2); + const isFirst = exportedClipsCount === 0; + await writer.write(isFirst ? content : ',\n' + content); + + await this.processClipNotes(writer, clip.id); + + await writer.write(']}'); + exportedClipsCount++; + } + + const total = await this.clipsRepository.countBy({ + userId: user.id, + }); + + job.updateProgress(exportedClipsCount / total); + } + } + + async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise { + let exportedClipNotesCount = 0; + let cursor: MiClipNote['id'] | null = null; + + while (true) { + const clipNotes = await this.clipNotesRepository.find({ + where: { + clipId, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + relations: ['note', 'note.user'], + }) as (MiClipNote & { note: MiNote & { user: MiUser } })[]; + + if (clipNotes.length === 0) { + break; + } + + cursor = clipNotes.at(-1)?.id ?? null; + + for (const clipNote of clipNotes) { + let poll: MiPoll | undefined; + if (clipNote.note.hasPoll) { + poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id }); + } + const content = JSON.stringify(this.serializeClipNote(clipNote, poll)); + const isFirst = exportedClipNotesCount === 0; + await writer.write(isFirst ? content : ',\n' + content); + + exportedClipNotesCount++; + } + } + } + + private serializeClip(clip: MiClip): Record { + return { + id: clip.id, + name: clip.name, + description: clip.description, + lastClippedAt: clip.lastClippedAt?.toISOString(), + clipNotes: [], + }; + } + + private serializeClipNote(clip: MiClipNote & { note: MiNote & { user: MiUser } }, poll: MiPoll | undefined): Record { + return { + id: clip.id, + createdAt: this.idService.parse(clip.id).date.toISOString(), + note: { + id: clip.note.id, + text: clip.note.text, + createdAt: this.idService.parse(clip.note.id).date.toISOString(), + fileIds: clip.note.fileIds, + replyId: clip.note.replyId, + renoteId: clip.note.renoteId, + poll: poll, + cw: clip.note.cw, + visibility: clip.note.visibility, + visibleUserIds: clip.note.visibleUserIds, + localOnly: clip.note.localOnly, + reactionAcceptance: clip.note.reactionAcceptance, + uri: clip.note.uri, + url: clip.note.url, + user: { + id: clip.note.user.id, + name: clip.note.user.name, + username: clip.note.user.username, + host: clip.note.user.host, + uri: clip.note.user.uri, + }, + }, + }; + } +} diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 89d4ea503e..50e7a1631e 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -85,7 +85,7 @@ export class InboxProcessorService { } catch (err) { // 対象が4xxならスキップ if (err instanceof StatusError) { - if (err.isClientError) { + if (!err.isRetryable) { throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`); } throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`); diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts index a41f5565c8..7a0d533846 100644 --- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts @@ -71,7 +71,7 @@ export class WebhookDeliverProcessorService { if (res instanceof StatusError) { // 4xx - if (res.isClientError) { + if (!res.isRetryable) { throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`); } diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 9330c01528..5a9952e807 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -15,7 +15,9 @@ export type DeliverJobData = { /** Actor */ user: ThinUser; /** Activity */ - content: unknown; + content: string; + /** Digest header */ + digest: string; /** inbox URL to deliver */ to: string; /** whether it is sharedInbox */ diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index 86a64d7121..a3a9805444 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -208,6 +208,7 @@ 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'; @@ -569,6 +570,7 @@ const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass: 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 }; @@ -934,6 +936,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportFollowing, $i_exportMute, $i_exportNotes, + $i_exportClips, $i_exportFavorites, $i_exportUserLists, $i_exportAntennas, @@ -1293,6 +1296,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention $i_exportFollowing, $i_exportMute, $i_exportNotes, + $i_exportClips, $i_exportFavorites, $i_exportUserLists, $i_exportAntennas, diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 753984ef52..6b4d9d9f70 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -65,6 +65,7 @@ export class SignupApiService { 'hcaptcha-response'?: string; 'g-recaptcha-response'?: string; 'turnstile-response'?: string; + 'm-captcha-response'?: string; } }>, reply: FastifyReply, @@ -82,6 +83,12 @@ export class SignupApiService { }); } + if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) { + await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } + if (instance.enableRecaptcha && instance.recaptchaSecretKey) { await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { throw new FastifyReplyError(400, err); diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 41232091c6..bd8aa4af72 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Schema } from '@/misc/json-schema.js'; import { permissions } from 'misskey-js'; +import type { Schema } from '@/misc/json-schema.js'; import { RolePolicies } from '@/core/RoleService.js'; import * as ep___admin_meta from './endpoints/admin/meta.js'; @@ -209,6 +209,7 @@ 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'; @@ -568,6 +569,7 @@ const eps = [ ['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], diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 281f6c484c..0627c5055c 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -41,6 +41,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableMcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + mcaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + mcaptchaInstanceUrl: { + type: 'string', + optional: false, nullable: true, + }, enableRecaptcha: { type: 'boolean', optional: false, nullable: false, @@ -163,6 +175,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + mcaptchaSecretKey: { + type: 'string', + optional: false, nullable: true, + }, recaptchaSecretKey: { type: 'string', optional: false, nullable: true, @@ -468,6 +484,9 @@ export default class extends Endpoint { // eslint- emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableMcaptcha: instance.enableMcaptcha, + mcaptchaSiteKey: instance.mcaptchaSitekey, + mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl, enableRecaptcha: instance.enableRecaptcha, recaptchaSiteKey: instance.recaptchaSiteKey, enableTurnstile: instance.enableTurnstile, @@ -498,6 +517,7 @@ export default class extends Endpoint { // eslint- sensitiveWords: instance.sensitiveWords, preservedUsernames: instance.preservedUsernames, hcaptchaSecretKey: instance.hcaptchaSecretKey, + mcaptchaSecretKey: instance.mcaptchaSecretKey, recaptchaSecretKey: instance.recaptchaSecretKey, turnstileSecretKey: instance.turnstileSecretKey, sensitiveMediaDetection: instance.sensitiveMediaDetection, 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 3a6426435d..d76d3dfeea 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -63,6 +63,10 @@ export const paramDef = { enableHcaptcha: { type: 'boolean' }, hcaptchaSiteKey: { type: 'string', nullable: true }, hcaptchaSecretKey: { type: 'string', nullable: true }, + enableMcaptcha: { type: 'boolean' }, + mcaptchaSiteKey: { type: 'string', nullable: true }, + mcaptchaInstanceUrl: { type: 'string', nullable: true }, + mcaptchaSecretKey: { type: 'string', nullable: true }, enableRecaptcha: { type: 'boolean' }, recaptchaSiteKey: { type: 'string', nullable: true }, recaptchaSecretKey: { type: 'string', nullable: true }, @@ -269,6 +273,22 @@ export default class extends Endpoint { // eslint- set.hcaptchaSecretKey = ps.hcaptchaSecretKey; } + if (ps.enableMcaptcha !== undefined) { + set.enableMcaptcha = ps.enableMcaptcha; + } + + if (ps.mcaptchaSiteKey !== undefined) { + set.mcaptchaSitekey = ps.mcaptchaSiteKey; + } + + if (ps.mcaptchaInstanceUrl !== undefined) { + set.mcaptchaInstanceUrl = ps.mcaptchaInstanceUrl; + } + + if (ps.mcaptchaSecretKey !== undefined) { + set.mcaptchaSecretKey = ps.mcaptchaSecretKey; + } + if (ps.enableRecaptcha !== undefined) { set.enableRecaptcha = ps.enableRecaptcha; } @@ -472,7 +492,7 @@ export default class extends Endpoint { // eslint- set.verifymailAuthKey = ps.verifymailAuthKey; } } - + if (ps.enableTruemailApi !== undefined) { set.enableTruemailApi = ps.enableTruemailApi; } diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts index 0bf2688b4a..7293c2e39b 100644 --- a/packages/backend/src/server/api/endpoints/antennas/notes.ts +++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts @@ -14,6 +14,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { IdService } from '@/core/IdService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; +import { trackPromise } from '@/misc/promise-tracker.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -92,7 +93,7 @@ export default class extends Endpoint { // eslint- antenna.isActive = true; antenna.lastUsedAt = new Date(); - this.antennasRepository.update(antenna.id, antenna); + trackPromise(this.antennasRepository.update(antenna.id, antenna)); if (needPublishEvent) { this.globalEventService.publishInternalEvent('antennaUpdated', antenna); diff --git a/packages/backend/src/server/api/endpoints/i/export-clips.ts b/packages/backend/src/server/api/endpoints/i/export-clips.ts new file mode 100644 index 0000000000..9435a2b23c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-clips.ts @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import ms from 'ms'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QueueService } from '@/core/QueueService.js'; + +export const meta = { + secure: true, + requireCredential: true, + limit: { + duration: ms('1day'), + max: 1, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + this.queueService.createExportClipsJob(me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index f7c2962bc2..529e82678d 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -108,6 +108,18 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableMcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, + mcaptchaSiteKey: { + type: 'string', + optional: false, nullable: true, + }, + mcaptchaInstanceUrl: { + type: 'string', + optional: false, nullable: true, + }, enableRecaptcha: { type: 'boolean', optional: false, nullable: false, @@ -351,6 +363,9 @@ export default class extends Endpoint { // eslint- emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, hcaptchaSiteKey: instance.hcaptchaSiteKey, + enableMcaptcha: instance.enableMcaptcha, + mcaptchaSiteKey: instance.mcaptchaSitekey, + mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl, enableRecaptcha: instance.enableRecaptcha, recaptchaSiteKey: instance.recaptchaSiteKey, enableTurnstile: instance.enableTurnstile, diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index 909b5a5e03..e0245814c4 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -21,6 +21,7 @@ class UserListChannel extends Channel { private membershipsMap: Record | undefined> = {}; private listUsersClock: NodeJS.Timeout; private withFiles: boolean; + private withRenotes: boolean; constructor( private userListsRepository: UserListsRepository, @@ -39,6 +40,7 @@ class UserListChannel extends Channel { public async init(params: any) { this.listId = params.listId as string; this.withFiles = params.withFiles ?? false; + this.withRenotes = params.withRenotes ?? true; // Check existence and owner const listExist = await this.userListsRepository.exist({ @@ -104,6 +106,8 @@ class UserListChannel extends Channel { } } + if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index ed967d2620..dfed8b2fce 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -24,7 +24,7 @@ import type * as misskey from 'misskey-js'; describe('2要素認証', () => { let app: INestApplicationContext; - let alice: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; const config = loadConfig(); const password = 'test'; diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index c0317f1435..9bac5122d4 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -37,7 +37,7 @@ describe('アンテナ', () => { // - srcのenumにgroupが残っている // - userGroupIdが残っている, isActiveがない type Antenna = misskey.entities.Antenna | Packed<'Antenna'>; - type User = misskey.entities.MeSignup; + type User = misskey.entities.SignupResponse; type Note = misskey.entities.Note; // アンテナを作成できる最小のパラメタ diff --git a/packages/backend/test/e2e/api-visibility.ts b/packages/backend/test/e2e/api-visibility.ts index 33c8d03fdb..afe4f9c05a 100644 --- a/packages/backend/test/e2e/api-visibility.ts +++ b/packages/backend/test/e2e/api-visibility.ts @@ -24,15 +24,15 @@ describe('API visibility', () => { describe('Note visibility', () => { //#region vars /** ヒロイン */ - let alice: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; /** フォロワー */ - let follower: misskey.entities.MeSignup; + let follower: misskey.entities.SignupResponse; /** 非フォロワー */ - let other: misskey.entities.MeSignup; + let other: misskey.entities.SignupResponse; /** 非フォロワーでもリプライやメンションをされた人 */ - let target: misskey.entities.MeSignup; + let target: misskey.entities.SignupResponse; /** specified mentionでmentionを飛ばされる人 */ - let target2: misskey.entities.MeSignup; + let target2: misskey.entities.SignupResponse; /** public-post */ let pub: any; diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts index cf24228b83..ad351eebbb 100644 --- a/packages/backend/test/e2e/api.ts +++ b/packages/backend/test/e2e/api.ts @@ -13,9 +13,9 @@ import type * as misskey from 'misskey-js'; describe('API', () => { let app: INestApplicationContext; - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; - let carol: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + let carol: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/block.ts b/packages/backend/test/e2e/block.ts index 4445d9036c..25ff9f11ac 100644 --- a/packages/backend/test/e2e/block.ts +++ b/packages/backend/test/e2e/block.ts @@ -14,9 +14,9 @@ describe('Block', () => { let app: INestApplicationContext; // alice blocks bob - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; - let carol: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + let carol: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index 2ef3434bca..bfd0b0272d 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -17,10 +17,10 @@ import type * as misskey from 'misskey-js'; describe('Endpoints', () => { let app: INestApplicationContext; - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; - let carol: misskey.entities.MeSignup; - let dave: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + let carol: misskey.entities.SignupResponse; + let dave: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts new file mode 100644 index 0000000000..9686f2b7fd --- /dev/null +++ b/packages/backend/test/e2e/exports.ts @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import { signup, api, startServer, startJobQueue, port, post } from '../utils.js'; +import type { INestApplicationContext } from '@nestjs/common'; +import type * as misskey from 'misskey-js'; + +describe('export-clips', () => { + let app: INestApplicationContext; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + + // XXX: Any better way to get the result? + async function pollFirstDriveFile() { + while (true) { + const files = (await api('/drive/files', {}, alice)).body; + if (!files.length) { + await new Promise(r => setTimeout(r, 100)); + continue; + } + if (files.length > 1) { + throw new Error('Too many files?'); + } + const file = (await api('/drive/files/show', { fileId: files[0].id }, alice)).body; + const res = await fetch(new URL(new URL(file.url).pathname, `http://127.0.0.1:${port}`)); + return await res.json(); + } + } + + beforeAll(async () => { + app = await startServer(); + await startJobQueue(); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + }, 1000 * 60 * 2); + + afterAll(async () => { + await app.close(); + }); + + beforeEach(async () => { + // Clean all clips and files of alice + const clips = (await api('/clips/list', {}, alice)).body; + for (const clip of clips) { + const res = await api('/clips/delete', { clipId: clip.id }, alice); + if (res.status !== 204) { + throw new Error('Failed to delete clip'); + } + } + const files = (await api('/drive/files', {}, alice)).body; + for (const file of files) { + const res = await api('/drive/files/delete', { fileId: file.id }, alice); + if (res.status !== 204) { + throw new Error('Failed to delete file'); + } + } + }); + + test('basic export', async () => { + let res = await api('/clips/create', { + name: 'foo', + description: 'bar', + }, alice); + assert.strictEqual(res.status, 200); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'foo'); + assert.strictEqual(exported[0].description, 'bar'); + assert.strictEqual(exported[0].clipNotes.length, 0); + }); + + test('export with notes', async () => { + let res = await api('/clips/create', { + name: 'foo', + description: 'bar', + }, alice); + assert.strictEqual(res.status, 200); + const clip = res.body; + + const note1 = await post(alice, { + text: 'baz1', + }); + + const note2 = await post(alice, { + text: 'baz2', + poll: { + choices: ['sakura', 'izumi', 'ako'], + }, + }); + + for (const note of [note1, note2]) { + res = await api('/clips/add-note', { + clipId: clip.id, + noteId: note.id, + }, alice); + assert.strictEqual(res.status, 204); + } + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'foo'); + assert.strictEqual(exported[0].description, 'bar'); + assert.strictEqual(exported[0].clipNotes.length, 2); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1'); + assert.strictEqual(exported[0].clipNotes[1].note.text, 'baz2'); + assert.deepStrictEqual(exported[0].clipNotes[1].note.poll.choices[0], 'sakura'); + }); + + test('multiple clips', async () => { + let res = await api('/clips/create', { + name: 'kawaii', + description: 'kawaii', + }, alice); + assert.strictEqual(res.status, 200); + const clip1 = res.body; + + res = await api('/clips/create', { + name: 'yuri', + description: 'yuri', + }, alice); + assert.strictEqual(res.status, 200); + const clip2 = res.body; + + const note1 = await post(alice, { + text: 'baz1', + }); + + const note2 = await post(alice, { + text: 'baz2', + }); + + res = await api('/clips/add-note', { + clipId: clip1.id, + noteId: note1.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/clips/add-note', { + clipId: clip2.id, + noteId: note2.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'kawaii'); + assert.strictEqual(exported[0].clipNotes.length, 1); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1'); + assert.strictEqual(exported[1].name, 'yuri'); + assert.strictEqual(exported[1].clipNotes.length, 1); + assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2'); + }); + + test('Clipping other user\'s note', async () => { + let res = await api('/clips/create', { + name: 'kawaii', + description: 'kawaii', + }, alice); + assert.strictEqual(res.status, 200); + const clip = res.body; + + const note = await post(bob, { + text: 'baz', + visibility: 'followers', + }); + + res = await api('/clips/add-note', { + clipId: clip.id, + noteId: note.id, + }, alice); + assert.strictEqual(res.status, 204); + + res = await api('/i/export-clips', {}, alice); + assert.strictEqual(res.status, 204); + + const exported = await pollFirstDriveFile(); + assert.strictEqual(exported[0].name, 'kawaii'); + assert.strictEqual(exported[0].clipNotes.length, 1); + assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz'); + assert.strictEqual(exported[0].clipNotes[0].note.user.username, 'bob'); + }); +}); diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index 251d662760..28affe7768 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -25,7 +25,7 @@ const JSON_UTF8 = 'application/json; charset=utf-8'; describe('Webリソース', () => { let app: INestApplicationContext; - let alice: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; let aliceUploadedFile: any; let alicesPost: any; let alicePage: any; @@ -34,7 +34,7 @@ describe('Webリソース', () => { let aliceGalleryPost: any; let aliceChannel: any; - let bob: misskey.entities.MeSignup; + let bob: misskey.entities.SignupResponse; type Request = { path: string, diff --git a/packages/backend/test/e2e/ff-visibility.ts b/packages/backend/test/e2e/ff-visibility.ts index 1fbd45c741..4d323e14e7 100644 --- a/packages/backend/test/e2e/ff-visibility.ts +++ b/packages/backend/test/e2e/ff-visibility.ts @@ -13,8 +13,8 @@ import type * as misskey from 'misskey-js'; describe('FF visibility', () => { let app: INestApplicationContext; - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index b009ef124a..f7da66a27c 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -20,12 +20,12 @@ describe('Account Move', () => { let url: URL; let root: any; - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; - let carol: misskey.entities.MeSignup; - let dave: misskey.entities.MeSignup; - let eve: misskey.entities.MeSignup; - let frank: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + let carol: misskey.entities.SignupResponse; + let dave: misskey.entities.SignupResponse; + let eve: misskey.entities.SignupResponse; + let frank: misskey.entities.SignupResponse; let Users: UsersRepository; diff --git a/packages/backend/test/e2e/mute.ts b/packages/backend/test/e2e/mute.ts index a4b57a1eba..3b5542dfe0 100644 --- a/packages/backend/test/e2e/mute.ts +++ b/packages/backend/test/e2e/mute.ts @@ -14,9 +14,9 @@ describe('Mute', () => { let app: INestApplicationContext; // alice mutes carol - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; - let carol: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + let carol: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts index 961df99cc2..8d33c63485 100644 --- a/packages/backend/test/e2e/note.ts +++ b/packages/backend/test/e2e/note.ts @@ -16,8 +16,8 @@ describe('Note', () => { let app: INestApplicationContext; let Notes: any; - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts index 3a5e4ebdae..3ca1f8b542 100644 --- a/packages/backend/test/e2e/oauth.ts +++ b/packages/backend/test/e2e/oauth.ts @@ -75,7 +75,7 @@ function getMeta(html: string): { transactionId: string | undefined, clientName: }; } -function fetchDecision(transactionId: string, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise { +function fetchDecision(transactionId: string, user: misskey.entities.SignupResponse, { cancel }: { cancel?: boolean } = {}): Promise { return fetch(new URL('/oauth/decision', host), { method: 'post', body: new URLSearchParams({ @@ -90,14 +90,14 @@ function fetchDecision(transactionId: string, user: misskey.entities.MeSignup, { }); } -async function fetchDecisionFromResponse(response: Response, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise { +async function fetchDecisionFromResponse(response: Response, user: misskey.entities.SignupResponse, { cancel }: { cancel?: boolean } = {}): Promise { const { transactionId } = getMeta(await response.text()); assert.ok(transactionId); return await fetchDecision(transactionId, user, { cancel }); } -async function fetchAuthorizationCode(user: misskey.entities.MeSignup, scope: string, code_challenge: string): Promise<{ client: AuthorizationCode, code: string }> { +async function fetchAuthorizationCode(user: misskey.entities.SignupResponse, scope: string, code_challenge: string): Promise<{ client: AuthorizationCode, code: string }> { const client = new AuthorizationCode(clientConfig); const response = await fetch(client.authorizeURL({ @@ -150,8 +150,8 @@ describe('OAuth', () => { let app: INestApplicationContext; let fastify: FastifyInstance; - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; let sender: (reply: FastifyReply) => void; diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts index 7d57ba17b6..fededdff32 100644 --- a/packages/backend/test/e2e/renote-mute.ts +++ b/packages/backend/test/e2e/renote-mute.ts @@ -14,9 +14,9 @@ describe('Renote Mute', () => { let app: INestApplicationContext; // alice mutes carol - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; - let carol: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + let carol: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index 288c54bdbc..a447ba94ae 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -32,15 +32,15 @@ describe('Streaming', () => { describe('Streaming', () => { // Local users - let ayano: misskey.entities.MeSignup; - let kyoko: misskey.entities.MeSignup; - let chitose: misskey.entities.MeSignup; - let kanako: misskey.entities.MeSignup; + let ayano: misskey.entities.SignupResponse; + let kyoko: misskey.entities.SignupResponse; + let chitose: misskey.entities.SignupResponse; + let kanako: misskey.entities.SignupResponse; // Remote users - let akari: misskey.entities.MeSignup; - let chinatsu: misskey.entities.MeSignup; - let takumi: misskey.entities.MeSignup; + let akari: misskey.entities.SignupResponse; + let chinatsu: misskey.entities.SignupResponse; + let takumi: misskey.entities.SignupResponse; let kyokoNote: any; let kanakoNote: any; diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts index 0e487976dc..5c68e2b150 100644 --- a/packages/backend/test/e2e/thread-mute.ts +++ b/packages/backend/test/e2e/thread-mute.ts @@ -13,9 +13,9 @@ import type * as misskey from 'misskey-js'; describe('Note thread mute', () => { let app: INestApplicationContext; - let alice: misskey.entities.MeSignup; - let bob: misskey.entities.MeSignup; - let carol: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; + let bob: misskey.entities.SignupResponse; + let carol: misskey.entities.SignupResponse; beforeAll(async () => { app = await startServer(); diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts index b5f00a6327..4f2e7c4cf3 100644 --- a/packages/backend/test/e2e/user-notes.ts +++ b/packages/backend/test/e2e/user-notes.ts @@ -13,7 +13,7 @@ import type * as misskey from 'misskey-js'; describe('users/notes', () => { let app: INestApplicationContext; - let alice: misskey.entities.MeSignup; + let alice: misskey.entities.SignupResponse; let jpgNote: any; let pngNote: any; let jpgPngNote: any; diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 46b8ea9cdd..7c9428d476 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -17,7 +17,7 @@ import { entities } from '../src/postgres.js'; import { loadConfig } from '../src/config.js'; import type * as misskey from 'misskey-js'; -export { server as startServer } from '@/boot/common.js'; +export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js'; interface UserToken { token: string; diff --git a/packages/frontend/assets/drop-and-fusion/bgm_1.mp3 b/packages/frontend/assets/drop-and-fusion/bgm_1.mp3 new file mode 100644 index 0000000000..cafc34ad9c Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/bgm_1.mp3 differ diff --git a/packages/frontend/assets/drop-and-fusion/bubble2.mp3 b/packages/frontend/assets/drop-and-fusion/bubble2.mp3 new file mode 100644 index 0000000000..8b4f8df6e9 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/bubble2.mp3 differ diff --git a/packages/frontend/assets/drop-and-fusion/cold_face.png b/packages/frontend/assets/drop-and-fusion/cold_face.png new file mode 100644 index 0000000000..f5f53e9efc Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/cold_face.png differ diff --git a/packages/frontend/assets/drop-and-fusion/drop-arrow.svg b/packages/frontend/assets/drop-and-fusion/drop-arrow.svg new file mode 100644 index 0000000000..f98bb8a1ac Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/drop-arrow.svg differ diff --git a/packages/frontend/assets/drop-and-fusion/dropper.png b/packages/frontend/assets/drop-and-fusion/dropper.png new file mode 100644 index 0000000000..f4300aa5c0 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/dropper.png differ diff --git a/packages/frontend/assets/drop-and-fusion/exploding_head.png b/packages/frontend/assets/drop-and-fusion/exploding_head.png new file mode 100644 index 0000000000..e8ec5182c8 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/exploding_head.png differ diff --git a/packages/frontend/assets/drop-and-fusion/face_with_open_mouth.png b/packages/frontend/assets/drop-and-fusion/face_with_open_mouth.png new file mode 100644 index 0000000000..c523020f62 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/face_with_open_mouth.png differ diff --git a/packages/frontend/assets/drop-and-fusion/face_with_symbols_on_mouth.png b/packages/frontend/assets/drop-and-fusion/face_with_symbols_on_mouth.png new file mode 100644 index 0000000000..db9e839c84 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/face_with_symbols_on_mouth.png differ diff --git a/packages/frontend/assets/drop-and-fusion/frame-dark.svg b/packages/frontend/assets/drop-and-fusion/frame-dark.svg new file mode 100644 index 0000000000..3fa7c0da81 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/frame-dark.svg differ diff --git a/packages/frontend/assets/drop-and-fusion/frame-light.svg b/packages/frontend/assets/drop-and-fusion/frame-light.svg new file mode 100644 index 0000000000..6052ccbaa0 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/frame-light.svg differ diff --git a/packages/frontend/assets/drop-and-fusion/gameover.png b/packages/frontend/assets/drop-and-fusion/gameover.png new file mode 100644 index 0000000000..8b622577ca Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/gameover.png differ diff --git a/packages/frontend/assets/drop-and-fusion/grinning_squinting_face.png b/packages/frontend/assets/drop-and-fusion/grinning_squinting_face.png new file mode 100644 index 0000000000..fd72d749a1 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/grinning_squinting_face.png differ diff --git a/packages/frontend/assets/drop-and-fusion/heart_suit.png b/packages/frontend/assets/drop-and-fusion/heart_suit.png new file mode 100644 index 0000000000..b0105f8582 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/heart_suit.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_1.png b/packages/frontend/assets/drop-and-fusion/keycap_1.png new file mode 100644 index 0000000000..d672f2854a Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_1.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_10.png b/packages/frontend/assets/drop-and-fusion/keycap_10.png new file mode 100644 index 0000000000..32cf193540 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_10.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_2.png b/packages/frontend/assets/drop-and-fusion/keycap_2.png new file mode 100644 index 0000000000..81c3f58e6e Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_2.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_3.png b/packages/frontend/assets/drop-and-fusion/keycap_3.png new file mode 100644 index 0000000000..424d8c123d Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_3.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_4.png b/packages/frontend/assets/drop-and-fusion/keycap_4.png new file mode 100644 index 0000000000..ea6ae50531 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_4.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_5.png b/packages/frontend/assets/drop-and-fusion/keycap_5.png new file mode 100644 index 0000000000..ad435da69a Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_5.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_6.png b/packages/frontend/assets/drop-and-fusion/keycap_6.png new file mode 100644 index 0000000000..70c9522b43 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_6.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_7.png b/packages/frontend/assets/drop-and-fusion/keycap_7.png new file mode 100644 index 0000000000..5a24307487 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_7.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_8.png b/packages/frontend/assets/drop-and-fusion/keycap_8.png new file mode 100644 index 0000000000..9689d8ecfb Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_8.png differ diff --git a/packages/frontend/assets/drop-and-fusion/keycap_9.png b/packages/frontend/assets/drop-and-fusion/keycap_9.png new file mode 100644 index 0000000000..ac3f638841 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/keycap_9.png differ diff --git a/packages/frontend/assets/drop-and-fusion/logo.png b/packages/frontend/assets/drop-and-fusion/logo.png new file mode 100644 index 0000000000..c6725bea88 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/logo.png differ diff --git a/packages/frontend/assets/drop-and-fusion/pleading_face.png b/packages/frontend/assets/drop-and-fusion/pleading_face.png new file mode 100644 index 0000000000..42f58d411c Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/pleading_face.png differ diff --git a/packages/frontend/assets/drop-and-fusion/poi1.mp3 b/packages/frontend/assets/drop-and-fusion/poi1.mp3 new file mode 100644 index 0000000000..59dae90965 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/poi1.mp3 differ diff --git a/packages/frontend/assets/drop-and-fusion/poi2.mp3 b/packages/frontend/assets/drop-and-fusion/poi2.mp3 new file mode 100644 index 0000000000..a65c653891 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/poi2.mp3 differ diff --git a/packages/frontend/assets/drop-and-fusion/smiling_face_with_hearts.png b/packages/frontend/assets/drop-and-fusion/smiling_face_with_hearts.png new file mode 100644 index 0000000000..416ef0410a Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/smiling_face_with_hearts.png differ diff --git a/packages/frontend/assets/drop-and-fusion/smiling_face_with_sunglasses.png b/packages/frontend/assets/drop-and-fusion/smiling_face_with_sunglasses.png new file mode 100644 index 0000000000..c0f72254c2 Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/smiling_face_with_sunglasses.png differ diff --git a/packages/frontend/assets/drop-and-fusion/zany_face.png b/packages/frontend/assets/drop-and-fusion/zany_face.png new file mode 100644 index 0000000000..f14f9db20b Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/zany_face.png differ diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 864779fd9d..9ef18a56a7 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "watch": "vite", - "dev": "vite --config vite.config.local-dev.ts", + "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", @@ -19,6 +19,7 @@ "dependencies": { "@discordapp/twemoji": "15.0.2", "@github/webauthn-json": "2.1.1", + "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@misskey-dev/browser-image-resizer": "2.2.1-misskey.10", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index f23fb804c5..b01a80d137 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -11,7 +11,8 @@ import { miLocalStorage } from '@/local-storage.js'; import { MenuButton } from '@/types/menu.js'; import { del, get, set } from '@/scripts/idb-proxy.js'; import { apiUrl } from '@/config.js'; -import { waiting, api, popup, popupMenu, success, alert } from '@/os.js'; +import { waiting, popup, popupMenu, success, alert } from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js'; // TODO: 他のタブと永続化されたstateを同期 @@ -23,9 +24,14 @@ const accountData = miLocalStorage.getItem('account'); // TODO: 外部からはreadonlyに export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; -export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator); +export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true); export const iAmAdmin = $i != null && $i.isAdmin; +export function signinRequired() { + if ($i == null) throw new Error('signin required'); + return $i; +} + export let notesCount = $i == null ? 0 : $i.notesCount; export function incNotesCount() { notesCount++; @@ -246,7 +252,7 @@ export async function openAccountMenu(opts: { } const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id)); - const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) }); + const accountsPromise = misskeyApi('users/show', { userIds: storedAccounts.map(x => x.id) }); function createItem(account: Misskey.entities.UserDetailed) { return { diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index ef69eff764..c67911c9c3 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -22,6 +22,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { miLocalStorage } from '@/local-storage.js'; import { fetchCustomEmojis } from '@/custom-emojis.js'; +import { setupRouter } from '@/global/router/definition.js'; export async function common(createVue: () => App) { console.info(`Misskey v${version}`); @@ -241,6 +242,8 @@ export async function common(createVue: () => App) { const app = createVue(); + setupRouter(app); + if (_DEV_) { app.config.performance = true; } diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 0159d0c032..5011ce9e74 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -3,23 +3,23 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { createApp, markRaw, defineAsyncComponent } from 'vue'; +import { createApp, defineAsyncComponent, markRaw } from 'vue'; import { common } from './common.js'; import { ui } from '@/config.js'; import { i18n } from '@/i18n.js'; -import { confirm, alert, post, popup, toast } from '@/os.js'; +import { alert, confirm, popup, post, toast } from '@/os.js'; import { useStream } from '@/stream.js'; import * as sound from '@/scripts/sound.js'; -import { $i, updateAccount, signout } from '@/account.js'; -import { defaultStore, ColdDeviceStorage } from '@/store.js'; +import { $i, signout, updateAccount } from '@/account.js'; +import { ColdDeviceStorage, defaultStore } from '@/store.js'; import { makeHotkey } from '@/scripts/hotkey.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; import { miLocalStorage } from '@/local-storage.js'; import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js'; -import { mainRouter } from '@/router.js'; import { initializeSw } from '@/scripts/initialize-sw.js'; import { deckStore } from '@/ui/deck/deck-store.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; +import { mainRouter } from '@/global/router/main.js'; export async function mainBoot() { const { isClientUpdated } = await common(() => createApp( diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index 25d2b3c15f..20950add80 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -5,9 +5,9 @@ import * as Misskey from 'misskey-js'; import { Cache } from '@/scripts/cache.js'; -import { api } from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; -export const clipsCache = new Cache(1000 * 60 * 30, () => api('clips/list')); -export const rolesCache = new Cache(1000 * 60 * 30, () => api('admin/roles/list')); -export const userListsCache = new Cache(1000 * 60 * 30, () => api('users/lists/list')); -export const antennasCache = new Cache(1000 * 60 * 30, () => api('antennas/list')); +export const clipsCache = new Cache(1000 * 60 * 30, () => misskeyApi('clips/list')); +export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list')); +export const userListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list')); +export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list')); diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index 155d9fe3a9..a5b43d3c6c 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -17,7 +17,7 @@ import * as Misskey from 'misskey-js'; import MkMention from './MkMention.vue'; import { i18n } from '@/i18n.js'; import { host as localHost } from '@/config.js'; -import { api } from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; const user = ref(); @@ -25,7 +25,7 @@ const props = defineProps<{ movedTo: string; // user id }>(); -api('users/show', { userId: props.movedTo }).then(u => user.value = u); +misskeyApi('users/show', { userId: props.movedTo }).then(u => user.value = u); diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index 07b44a1051..e00953c1ff 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -39,7 +39,10 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -82,6 +85,7 @@ import MkInput from '@/components/MkInput.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkFolder from '@/components/MkFolder.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { customEmojiCategories } from '@/custom-emojis.js'; import MkSwitch from '@/components/MkSwitch.vue'; @@ -104,7 +108,7 @@ const rolesThatCanBeUsedThisEmojiAsReaction = ref([]); const file = ref(); watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => { - rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null); + rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null); }, { immediate: true }); const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null); @@ -123,7 +127,7 @@ async function changeImage(ev) { } async function addRole() { - const roles = await os.api('admin/roles/list'); + const roles = await misskeyApi('admin/roles/list'); const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id); const { canceled, result: role } = await os.select({ @@ -185,7 +189,7 @@ async function del() { }); if (canceled) return; - os.api('admin/emoji/delete', { + misskeyApi('admin/emoji/delete', { id: props.emoji.id, }).then(() => { emit('done', { diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue index 9ba9047ca3..ea6947bbba 100644 --- a/packages/frontend/src/pages/emojis.emoji.vue +++ b/packages/frontend/src/pages/emojis.emoji.vue @@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue index 73c2a94fc0..440bd0b945 100644 --- a/packages/frontend/src/pages/explore.users.vue +++ b/packages/frontend/src/pages/explore.users.vue @@ -68,7 +68,7 @@ import * as Misskey from 'misskey-js'; import MkUserList from '@/components/MkUserList.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkTab from '@/components/MkTab.vue'; -import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; const props = defineProps<{ @@ -123,14 +123,14 @@ const recentlyRegisteredUsersF = { endpoint: 'users', limit: 10, noPaging: true, sort: '+createdAt', } }; -os.api('hashtags/list', { +misskeyApi('hashtags/list', { sort: '+attachedLocalUsers', attachedToLocalUserOnly: true, limit: 30, }).then(tags => { tagsLocal.value = tags; }); -os.api('hashtags/list', { +misskeyApi('hashtags/list', { sort: '+attachedRemoteUsers', attachedToRemoteUserOnly: true, limit: 30, diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index 147a381c98..8298dc6d79 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -38,13 +38,14 @@ import { computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkTextarea from '@/components/MkTextarea.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue'; import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; -import { useRouter } from '@/router.js'; +import { useRouter } from '@/global/router/supplier.js'; const PRESET_DEFAULT = `/// @ 0.16.0 @@ -369,7 +370,7 @@ const flash = ref(null); const visibility = ref('public'); if (props.id) { - flash.value = await os.api('flash/show', { + flash.value = await misskeyApi('flash/show', { flashId: props.id, }); } diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue index e0b9f87d46..7852018894 100644 --- a/packages/frontend/src/pages/flash/flash-index.vue +++ b/packages/frontend/src/pages/flash/flash-index.vue @@ -42,9 +42,9 @@ import { computed, ref } from 'vue'; import MkFlashPreview from '@/components/MkFlashPreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; -import { useRouter } from '@/router.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { useRouter } from '@/global/router/supplier.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 6df9bbc241..bcffb5601a 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -62,12 +62,13 @@ import * as Misskey from 'misskey-js'; import { Interpreter, Parser, values } from '@syuilo/aiscript'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { url } from '@/config.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkAsUi from '@/components/MkAsUi.vue'; import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js'; -import { createAiScriptEnv } from '@/scripts/aiscript/api.js'; +import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js'; import MkFolder from '@/components/MkFolder.vue'; import MkCode from '@/components/MkCode.vue'; import { defaultStore } from '@/store.js'; @@ -84,7 +85,7 @@ const error = ref(null); function fetchFlash() { flash.value = null; - os.api('flash/show', { + misskeyApi('flash/show', { flashId: props.id, }).then(_flash => { flash.value = _flash; @@ -162,15 +163,7 @@ async function run() { THIS_ID: values.STR(flash.value.id), THIS_URL: values.STR(`${url}/play/${flash.value.id}`), }, { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ result: a }) => { - ok(a ?? ''); - }); - }); - }, + in: aiScriptReadline, out: (value) => { // nop }, diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index 51f31b1ca5..902e6a4b51 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -41,7 +41,7 @@ import { shallowRef, computed } from 'vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; import { userPage, acct } from '@/filters/user.js'; -import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { infoImageUrl } from '@/instance.js'; @@ -54,13 +54,13 @@ const pagination = { }; function accept(user) { - os.api('following/requests/accept', { userId: user.id }).then(() => { + misskeyApi('following/requests/accept', { userId: user.id }).then(() => { paginationComponent.value.reload(); }); } function reject(user) { - os.api('following/requests/reject', { userId: user.id }).then(() => { + misskeyApi('following/requests/reject', { userId: user.id }).then(() => { paginationComponent.value.reload(); }); } diff --git a/packages/frontend/src/pages/follow.vue b/packages/frontend/src/pages/follow.vue index a0a4a480b5..eefef828bd 100644 --- a/packages/frontend/src/pages/follow.vue +++ b/packages/frontend/src/pages/follow.vue @@ -12,9 +12,10 @@ SPDX-License-Identifier: AGPL-3.0-only import { } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os.js'; -import { mainRouter } from '@/router.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { defaultStore } from "@/store.js"; +import { defaultStore } from '@/store.js'; +import { mainRouter } from '@/global/router/main.js'; async function follow(user): Promise { const { canceled } = await os.confirm({ @@ -42,7 +43,7 @@ if (acct == null) { let promise; if (acct.startsWith('https://')) { - promise = os.api('ap/show', { + promise = misskeyApi('ap/show', { uri: acct, }); promise.then(res => { @@ -60,7 +61,7 @@ if (acct.startsWith('https://')) { } }); } else { - promise = os.api('users/show', Misskey.acct.parse(acct)); + promise = misskeyApi('users/show', Misskey.acct.parse(acct)); promise.then(user => { follow(user); }); diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index d711cb4e88..f7db01ce95 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -47,9 +47,10 @@ import MkSwitch from '@/components/MkSwitch.vue'; import FormSuspense from '@/components/form/suspense.vue'; import { selectFiles } from '@/scripts/select-file.js'; import * as os from '@/os.js'; -import { useRouter } from '@/router.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; +import { useRouter } from '@/global/router/supplier.js'; const router = useRouter(); @@ -107,7 +108,7 @@ async function del() { } watch(() => props.postId, () => { - init.value = () => props.postId ? os.api('gallery/posts/show', { + init.value = () => props.postId ? misskeyApi('gallery/posts/show', { postId: props.postId, }).then(post => { files.value = post.files ?? []; diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue index 8d9ac07805..0198ab9700 100644 --- a/packages/frontend/src/pages/gallery/index.vue +++ b/packages/frontend/src/pages/gallery/index.vue @@ -53,7 +53,7 @@ import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; -import { useRouter } from '@/router.js'; +import { useRouter } from '@/global/router/supplier.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index 77af81cec1..dcd427d6b4 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -66,18 +66,19 @@ import { computed, watch, ref } from 'vue'; import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import MkContainer from '@/components/MkContainer.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; import { url } from '@/config.js'; -import { useRouter } from '@/router.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { defaultStore } from '@/store.js'; import { $i } from '@/account.js'; import { isSupportShare } from '@/scripts/navigator.js'; import copyToClipboard from '@/scripts/copy-to-clipboard.js'; +import { useRouter } from '@/global/router/supplier.js'; const router = useRouter(); @@ -97,7 +98,7 @@ const otherPostsPagination = { function fetchPost() { post.value = null; - os.api('gallery/posts/show', { + misskeyApi('gallery/posts/show', { postId: props.postId, }).then(_post => { post.value = _post; diff --git a/packages/frontend/src/pages/install-extentions.vue b/packages/frontend/src/pages/install-extentions.vue index 8117699849..3815658bd4 100644 --- a/packages/frontend/src/pages/install-extentions.vue +++ b/packages/frontend/src/pages/install-extentions.vue @@ -105,6 +105,7 @@ import MkInfo from '@/components/MkInfo.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js'; import { parseThemeCode, installTheme } from '@/scripts/install-theme.js'; import { unisonReload } from '@/scripts/unison-reload.js'; @@ -159,7 +160,7 @@ async function fetch() { uiPhase.value = 'error'; return; } - const res = await os.api('fetch-external-resources', { + const res = await misskeyApi('fetch-external-resources', { url: url.value, hash: hash.value, }).catch((err) => { diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 97dc0a8633..c8a0eeeeaa 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -129,6 +129,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import number from '@/filters/number.js'; import { iAmModerator, iAmAdmin } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -164,9 +165,9 @@ const usersPagination = { async function fetch(): Promise { if (iAmAdmin) { - meta.value = await os.api('admin/meta'); + meta.value = await misskeyApi('admin/meta'); } - instance.value = await os.api('federation/show-instance', { + instance.value = await misskeyApi('federation/show-instance', { host: props.host, }); suspended.value = instance.value?.isSuspended ?? false; @@ -179,7 +180,7 @@ async function toggleBlock(): Promise { if (!meta.value) throw new Error('No meta?'); if (!instance.value) throw new Error('No instance?'); const { host } = instance.value; - await os.api('admin/update-meta', { + await misskeyApi('admin/update-meta', { blockedHosts: isBlocked.value ? meta.value.blockedHosts.concat([host]) : meta.value.blockedHosts.filter(x => x !== host), }); } @@ -189,14 +190,14 @@ async function toggleSilenced(): Promise { if (!instance.value) throw new Error('No instance?'); const { host } = instance.value; const silencedHosts = meta.value.silencedHosts ?? []; - await os.api('admin/update-meta', { + await misskeyApi('admin/update-meta', { silencedHosts: isSilenced.value ? silencedHosts.concat([host]) : silencedHosts.filter(x => x !== host), }); } async function toggleSuspend(): Promise { if (!instance.value) throw new Error('No instance?'); - await os.api('admin/federation/update-instance', { + await misskeyApi('admin/federation/update-instance', { host: instance.value.host, isSuspended: suspended.value, }); @@ -204,7 +205,7 @@ async function toggleSuspend(): Promise { function refreshMetadata(): void { if (!instance.value) throw new Error('No instance?'); - os.api('admin/federation/refresh-remote-instance-metadata', { + misskeyApi('admin/federation/refresh-remote-instance-metadata', { host: instance.value.host, }); os.alert({ diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue index 25ce38e0ef..61030741fa 100644 --- a/packages/frontend/src/pages/invite.vue +++ b/packages/frontend/src/pages/invite.vue @@ -40,6 +40,7 @@ import { computed, ref, shallowRef } from 'vue'; import type * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import MkButton from '@/components/MkButton.vue'; import MkPagination, { Paging } from '@/components/MkPagination.vue'; import MkInviteCode from '@/components/MkInviteCode.vue'; @@ -68,7 +69,7 @@ const resetCycle = computed(() => { }); async function create() { - const ticket = await os.api('invite/create'); + const ticket = await misskeyApi('invite/create'); os.alert({ type: 'success', title: i18n.ts.inviteCodeCreated, @@ -87,7 +88,7 @@ function deleted(id: string) { } async function update() { - currentInviteLimit.value = (await os.api('invite/limit')).remaining; + currentInviteLimit.value = (await misskeyApi('invite/limit')).remaining; } update(); diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue index 936d078848..03db02a350 100644 --- a/packages/frontend/src/pages/list.vue +++ b/packages/frontend/src/pages/list.vue @@ -37,6 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { watch, computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { userPage } from '@/filters/user.js'; import { i18n } from '@/i18n.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; @@ -53,12 +54,12 @@ const error = ref(); const users = ref([]); function fetchList(): void { - os.api('users/lists/show', { + misskeyApi('users/lists/show', { listId: props.listId, forPublic: true, }).then(_list => { list.value = _list; - os.api('users/show', { + misskeyApi('users/show', { userIds: list.value.userIds, }).then(_users => { users.value = _users; diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue index ad9bea4548..539cb462ad 100644 --- a/packages/frontend/src/pages/miauth.vue +++ b/packages/frontend/src/pages/miauth.vue @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, computed } from 'vue'; import MkSignin from '@/components/MkSignin.vue'; import MkButton from '@/components/MkButton.vue'; -import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { $i, login } from '@/account.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -65,7 +65,7 @@ const state = ref(null); async function accept(): Promise { state.value = 'waiting'; - await os.api('miauth/gen-token', { + await misskeyApi('miauth/gen-token', { session: props.session, name: props.name, iconUrl: props.icon, diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue index c5b1b54222..61b9424bdd 100644 --- a/packages/frontend/src/pages/my-antennas/create.vue +++ b/packages/frontend/src/pages/my-antennas/create.vue @@ -14,8 +14,8 @@ import { ref } from 'vue'; import XAntenna from './editor.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { useRouter } from '@/router.js'; import { antennasCache } from '@/cache.js'; +import { useRouter } from '@/global/router/supplier.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue index 9b3d56ee36..b4ca7cc9f8 100644 --- a/packages/frontend/src/pages/my-antennas/edit.vue +++ b/packages/frontend/src/pages/my-antennas/edit.vue @@ -13,11 +13,11 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref } from 'vue'; import * as Misskey from 'misskey-js'; import XAntenna from './editor.vue'; -import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { useRouter } from '@/router.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { antennasCache } from '@/cache.js'; +import { useRouter } from '@/global/router/supplier.js'; const router = useRouter(); @@ -32,7 +32,7 @@ function onAntennaUpdated() { router.push('/my/antennas'); } -os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => { +misskeyApi('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => { antenna.value = antennaResponse; }); diff --git a/packages/frontend/src/pages/my-antennas/editor.vue b/packages/frontend/src/pages/my-antennas/editor.vue index 9b19206d96..45acbb2158 100644 --- a/packages/frontend/src/pages/my-antennas/editor.vue +++ b/packages/frontend/src/pages/my-antennas/editor.vue @@ -57,6 +57,7 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; const props = defineProps<{ @@ -84,7 +85,7 @@ const userLists = ref(null); watch(() => src.value, async () => { if (src.value === 'list' && userLists.value === null) { - userLists.value = await os.api('users/lists/list'); + userLists.value = await misskeyApi('users/lists/list'); } }); @@ -119,7 +120,7 @@ async function deleteAntenna() { }); if (canceled) return; - await os.api('antennas/delete', { + await misskeyApi('antennas/delete', { antennaId: props.antenna.id, }); diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue index bc09e916e3..850222708e 100644 --- a/packages/frontend/src/pages/my-clips/index.vue +++ b/packages/frontend/src/pages/my-clips/index.vue @@ -32,6 +32,7 @@ import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; import MkClipPreview from '@/components/MkClipPreview.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { clipsCache } from '@/cache.js'; @@ -48,7 +49,7 @@ const favorites = ref(null); const pagingComponent = shallowRef>(); watch(tab, async () => { - favorites.value = await os.api('clips/my-favorites'); + favorites.value = await misskeyApi('clips/my-favorites'); }); async function create() { diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue index 0abfb15d98..14e2315843 100644 --- a/packages/frontend/src/pages/my-lists/index.vue +++ b/packages/frontend/src/pages/my-lists/index.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
{{ list.name }} ({{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i?.policies['userEachUserListsLimit']}` }) }})
+
{{ list.name }} ({{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})
@@ -37,7 +37,9 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { userListsCache } from '@/cache.js'; import { infoImageUrl } from '@/instance.js'; -import { $i } from '@/account.js'; +import { signinRequired } from '@/account.js'; + +const $i = signinRequired(); const items = computed(() => userListsCache.value.value ?? []); diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index cf9da02868..85775a2fdd 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
{{ i18n.ts.addUser }} @@ -57,7 +57,7 @@ import { computed, ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; -import { mainRouter } from '@/router.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { userPage } from '@/filters/user.js'; @@ -66,9 +66,12 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; import { userListsCache } from '@/cache.js'; -import { $i } from '@/account.js'; +import { signinRequired } from '@/account.js'; import { defaultStore } from '@/store.js'; import MkPagination from '@/components/MkPagination.vue'; +import { mainRouter } from '@/global/router/main.js'; + +const $i = signinRequired(); const { enableInfiniteScroll, @@ -91,7 +94,7 @@ const membershipsPagination = { }; function fetchList() { - os.api('users/lists/show', { + misskeyApi('users/lists/show', { listId: props.listId, }).then(_list => { list.value = _list; @@ -119,7 +122,7 @@ async function removeUser(item, ev) { danger: true, action: async () => { if (!list.value) return; - os.api('users/lists/pull', { + misskeyApi('users/lists/pull', { listId: list.value.id, userId: item.userId, }).then(() => { @@ -134,7 +137,7 @@ async function showMembershipMenu(item, ev) { text: item.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline, icon: item.withReplies ? 'ti ti-messages-off' : 'ti ti-messages', action: async () => { - os.api('users/lists/update-membership', { + misskeyApi('users/lists/update-membership', { listId: list.value.id, userId: item.userId, withReplies: !item.withReplies, diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index ff1e80aaab..7e353e629c 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -50,7 +50,7 @@ import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; import MkNotes from '@/components/MkNotes.vue'; import MkRemoteCaution from '@/components/MkRemoteCaution.vue'; import MkButton from '@/components/MkButton.vue'; -import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { dateString } from '@/filters/date.js'; @@ -90,13 +90,13 @@ function fetchNote() { showPrev.value = false; showNext.value = false; note.value = null; - os.api('notes/show', { + misskeyApi('notes/show', { noteId: props.noteId, }).then(res => { note.value = res; // 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く if (note.value.clippedCount > 0 || new Date(note.value.createdAt).getTime() < new Date('2023-10-01').getTime()) { - os.api('notes/clips', { + misskeyApi('notes/clips', { noteId: note.value.id, }).then((_clips) => { clips.value = _clips; diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue index 9d6da653b4..561b0142aa 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue @@ -26,6 +26,7 @@ import * as Misskey from 'misskey-js'; import XContainer from '../page-editor.container.vue'; import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; const props = defineProps<{ @@ -52,7 +53,7 @@ onMounted(async () => { if (props.modelValue.fileId == null) { await choose(); } else { - os.api('drive/files/show', { + misskeyApi('drive/files/show', { fileId: props.modelValue.fileId, }).then(fileResponse => { file.value = fileResponse; diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue index ea9d52c2be..ebabc98d23 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue @@ -30,7 +30,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkNote from '@/components/MkNote.vue'; import MkNoteDetailed from '@/components/MkNoteDetailed.vue'; -import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; const props = defineProps<{ @@ -53,7 +53,7 @@ watch(id, async () => { ...props.modelValue, note: id.value, }); - note.value = await os.api('notes/show', { noteId: id.value }); + note.value = await misskeyApi('notes/show', { noteId: id.value }); }, { immediate: true, }); diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index bcfbf5825f..6db72dccba 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -71,11 +71,12 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkInput from '@/components/MkInput.vue'; import { url } from '@/config.js'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { selectFile } from '@/scripts/select-file.js'; -import { mainRouter } from '@/router.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { $i } from '@/account.js'; +import { mainRouter } from '@/global/router/main.js'; const props = defineProps<{ initPageId?: string; @@ -106,7 +107,7 @@ watch(eyeCatchingImageId, async () => { if (eyeCatchingImageId.value == null) { eyeCatchingImage.value = null; } else { - eyeCatchingImage.value = await os.api('drive/files/show', { + eyeCatchingImage.value = await misskeyApi('drive/files/show', { fileId: eyeCatchingImageId.value, }); } @@ -149,7 +150,7 @@ function save() { if (pageId.value) { options.pageId = pageId.value; - os.api('pages/update', options) + misskeyApi('pages/update', options) .then(page => { currentName.value = name.value.trim(); os.alert({ @@ -158,7 +159,7 @@ function save() { }); }).catch(onError); } else { - os.api('pages/create', options) + misskeyApi('pages/create', options) .then(created => { pageId.value = created.id; currentName.value = name.value.trim(); @@ -177,7 +178,7 @@ function del() { text: i18n.t('removeAreYouSure', { x: title.value.trim() }), }).then(({ canceled }) => { if (canceled) return; - os.api('pages/delete', { + misskeyApi('pages/delete', { pageId: pageId.value, }).then(() => { os.alert({ @@ -192,7 +193,7 @@ function del() { function duplicate() { title.value = title.value + ' - copy'; name.value = name.value + '-copy'; - os.api('pages/create', getSaveOptions()).then(created => { + misskeyApi('pages/create', getSaveOptions()).then(created => { pageId.value = created.id; currentName.value = name.value.trim(); os.alert({ @@ -236,11 +237,11 @@ function removeEyeCatchingImage() { async function init() { if (props.initPageId) { - page.value = await os.api('pages/show', { + page.value = await misskeyApi('pages/show', { pageId: props.initPageId, }); } else if (props.initPageName && props.initUser) { - page.value = await os.api('pages/show', { + page.value = await misskeyApi('pages/show', { name: props.initPageName, username: props.initUser, }); diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 11c8e15e14..7b4f06d5cf 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -81,6 +81,7 @@ import * as Misskey from 'misskey-js'; import XPage from '@/components/page/page.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { url } from '@/config.js'; import MkMediaImage from '@/components/MkMediaImage.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; @@ -113,7 +114,7 @@ const path = computed(() => props.username + '/' + props.pageName); function fetchPage() { page.value = null; - os.api('pages/show', { + misskeyApi('pages/show', { name: props.pageName, username: props.username, }).then(async _page => { diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue index bc51b55c7f..22ab9ced09 100644 --- a/packages/frontend/src/pages/pages.vue +++ b/packages/frontend/src/pages/pages.vue @@ -40,9 +40,9 @@ import { computed, ref } from 'vue'; import MkPagePreview from '@/components/MkPagePreview.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; -import { useRouter } from '@/router.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { useRouter } from '@/global/router/supplier.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue index 822a39c2e8..8ae8495de3 100644 --- a/packages/frontend/src/pages/registry.keys.vue +++ b/packages/frontend/src/pages/registry.keys.vue @@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { watch, computed, ref } from 'vue'; import JSON5 from 'json5'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import FormLink from '@/components/form/link.vue'; @@ -54,7 +55,7 @@ const scope = computed(() => props.path ? props.path.split('/') : []); const keys = ref(null); function fetchKeys() { - os.api('i/registry/keys-with-type', { + misskeyApi('i/registry/keys-with-type', { scope: scope.value, domain: props.domain === '@' ? null : props.domain, }).then(res => { diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue index 243c69eed5..f355550209 100644 --- a/packages/frontend/src/pages/registry.value.vue +++ b/packages/frontend/src/pages/registry.value.vue @@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { watch, computed, ref } from 'vue'; import JSON5 from 'json5'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; @@ -68,7 +69,7 @@ const value = ref(null); const valueForEditor = ref(null); function fetchValue() { - os.api('i/registry/get-detail', { + misskeyApi('i/registry/get-detail', { scope: scope.value, key: key.value, domain: props.domain === '@' ? null : props.domain, diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue index f45f8922ad..ca54d23b6f 100644 --- a/packages/frontend/src/pages/registry.vue +++ b/packages/frontend/src/pages/registry.vue @@ -26,6 +26,7 @@ import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; import JSON5 from 'json5'; import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import FormLink from '@/components/form/link.vue'; @@ -35,7 +36,7 @@ import MkButton from '@/components/MkButton.vue'; const scopesWithDomain = ref(null); function fetchScopes() { - os.api('i/registry/scopes-with-domain').then(res => { + misskeyApi('i/registry/scopes-with-domain').then(res => { scopesWithDomain.value = res; }); } diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue index c9d193b787..d8dec27513 100644 --- a/packages/frontend/src/pages/reset-password.vue +++ b/packages/frontend/src/pages/reset-password.vue @@ -25,8 +25,8 @@ import MkInput from '@/components/MkInput.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { mainRouter } from '@/router.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { mainRouter } from '@/global/router/main.js'; const props = defineProps<{ token?: string; diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue index 10642ddefe..67f18ee87e 100644 --- a/packages/frontend/src/pages/role.vue +++ b/packages/frontend/src/pages/role.vue @@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only