diff --git a/locales/en-US.yml b/locales/en-US.yml index 662ae24e45..4acb0fc5b1 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -197,6 +197,7 @@ perHour: "Per Hour" perDay: "Per Day" stopActivityDelivery: "Stop sending activities" blockThisInstance: "Block this instance" +silenceThisInstance: "Silence this instance" operations: "Operations" software: "Software" version: "Version" @@ -218,10 +219,13 @@ clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote blockedInstances: "Blocked Instances" blockedInstancesDescription: "List the hostnames of the instances that you want to\ \ block. Listed instances will no longer be able to communicate with this instance." +silencedInstances: "Silenced Instances" +silencedInstancesDescription: "List the hostnames of the instances that you want to\ + \ silence. Accounts in the listed instances are treated as \"Silenced\", can only make follow requests, and cannot mention local accounts if not followed. This will not affect the blocked instances." hiddenTags: "Hidden Hashtags" hiddenTagsDescription: "List the hashtags (without the #) of the hashtags you wish\ \ to hide from trending and explore. Hidden hashtags are still discoverable via\ - \ other means." + \ other means. Blocked instances are not affected even if listed here." muteAndBlock: "Mutes and Blocks" mutedUsers: "Muted users" blockedUsers: "Blocked users" @@ -240,6 +244,7 @@ noCustomEmojis: "There are no emoji" noJobs: "There are no jobs" federating: "Federating" blocked: "Blocked" +silenced: "Silenced" suspended: "Suspended" all: "All" subscribing: "Subscribing" @@ -829,7 +834,7 @@ active: "Active" offline: "Offline" notRecommended: "Not recommended" botProtection: "Bot Protection" -instanceBlocking: "Blocked Instances" +instanceBlocking: "Federation Block/Silence" selectAccount: "Select account" switchAccount: "Switch account" enabled: "Enabled" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index bbe90d6f2c..8ae43cdb9b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -183,6 +183,7 @@ perHour: "1時間ごと" perDay: "1日ごと" stopActivityDelivery: "アクティビティの配送を停止" blockThisInstance: "このインスタンスをブロック" +silenceThisInstance: "このインスタンスをサイレンス" operations: "操作" software: "ソフトウェア" version: "バージョン" @@ -202,6 +203,8 @@ clearCachedFiles: "キャッシュをクリア" clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?" blockedInstances: "ブロックしたインスタンス" blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。" +silencedInstances: "サイレンスしたインスタンス" +silencedInstancesDescription: "サイレンスしたいインスタンスのホストを改行で区切って設定します。サイレンスされたインスタンスに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。" muteAndBlock: "ミュートとブロック" mutedUsers: "ミュートしたユーザー" blockedUsers: "ブロックしたユーザー" @@ -220,6 +223,7 @@ noCustomEmojis: "絵文字はありません" noJobs: "ジョブはありません" federating: "連合中" blocked: "ブロック中" +silenced: "サイレンス中" suspended: "配信停止" all: "全て" subscribing: "購読中" @@ -768,7 +772,7 @@ active: "アクティブ" offline: "オフライン" notRecommended: "非推奨" botProtection: "Botプロテクション" -instanceBlocking: "インスタンスブロック" +instanceBlocking: "連合ブロック・サイレンス" selectAccount: "アカウントを選択" switchAccount: "アカウントを切り替え" enabled: "有効" diff --git a/packages/backend/migration/1682891890317-InstanceSilence.js b/packages/backend/migration/1682891890317-InstanceSilence.js new file mode 100644 index 0000000000..f487111f74 --- /dev/null +++ b/packages/backend/migration/1682891890317-InstanceSilence.js @@ -0,0 +1,165 @@ +export class InstanceSilence1682891890317 { + name = "InstanceSilence1682891890317"; + + async up(queryRunner) { + await queryRunner.query( + `ALTER TABLE "abuse_user_report" DROP CONSTRAINT "fk_7f4e851a35d81b64dda28eee0"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_renote_muting_createdAt"`, + ); + await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muteeId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_renote_muting_muterId"`); + await queryRunner.query( + `ALTER TABLE "meta" DROP COLUMN "useStarForReactionFallback"`, + ); + await queryRunner.query( + `ALTER TABLE "meta" DROP COLUMN "enableGuestTimeline"`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ADD "silencedHosts" character varying(256) array NOT NULL DEFAULT '{}'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "notification"."isRead" IS 'Whether the notification was read.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "meta"."defaultReaction" IS NULL`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "secureMode" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "privateMode" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "allowedHosts" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "pinnedPages" SET DEFAULT '{/featured,/channels,/explore,/pages,/about-calckey}'`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://codeberg.org/calckey/calckey'`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://codeberg.org/calckey/calckey/issues/new'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "renote_muting"."createdAt" IS 'The created date of the Muting.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "renote_muting"."muteeId" IS 'The mutee user ID.'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "renote_muting"."muterId" IS 'The muter user ID.'`, + ); + await queryRunner.query( + `ALTER TABLE "page" ALTER COLUMN "isPublic" DROP DEFAULT`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_d1259a2c2b7bb413ff449e8711" ON "renote_muting" ("createdAt") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7eac97594bcac5ffcf2068089b" ON "renote_muting" ("muteeId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7aa72a5fe76019bfe8e5e0e8b7" ON "renote_muting" ("muterId") `, + ); + await queryRunner.query( + `CREATE UNIQUE INDEX "IDX_0d801c609cec4e9eb4b6b4490c" ON "renote_muting" ("muterId", "muteeId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId") `, + ); + await queryRunner.query( + `ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6" FOREIGN KEY ("muteeId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "renote_muting" ADD CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d" FOREIGN KEY ("muterId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + async down(queryRunner) { + await queryRunner.query( + `ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`, + ); + await queryRunner.query( + `ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7aa72a5fe76019bfe8e5e0e8b7d"`, + ); + await queryRunner.query( + `ALTER TABLE "renote_muting" DROP CONSTRAINT "FK_7eac97594bcac5ffcf2068089b6"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_a9021cc2e1feb5f72d3db6e9f5"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_0d801c609cec4e9eb4b6b4490c"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7aa72a5fe76019bfe8e5e0e8b7"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_7eac97594bcac5ffcf2068089b"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_d1259a2c2b7bb413ff449e8711"`, + ); + await queryRunner.query( + `ALTER TABLE "page" ALTER COLUMN "isPublic" SET DEFAULT true`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "renote_muting"."muterId" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "renote_muting"."muteeId" IS NULL`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "renote_muting"."createdAt" IS NULL`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://github.com/misskey-dev/misskey/issues/new'`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://github.com/misskey-dev/misskey'`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "pinnedPages" SET DEFAULT '{/featured,/channels,/explore,/pages,/about-misskey}'`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "allowedHosts" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "privateMode" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ALTER COLUMN "secureMode" DROP NOT NULL`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "meta"."defaultReaction" IS 'The fallback reaction for emoji reacts'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "notification"."isRead" IS 'Whether the Notification is read.'`, + ); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`); + await queryRunner.query( + `ALTER TABLE "meta" ADD "enableGuestTimeline" boolean NOT NULL DEFAULT false`, + ); + await queryRunner.query( + `ALTER TABLE "meta" ADD "useStarForReactionFallback" boolean NOT NULL DEFAULT false`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_renote_muting_muterId" ON "muting" ("muterId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_renote_muting_muteeId" ON "muting" ("muteeId") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_renote_muting_createdAt" ON "muting" ("createdAt") `, + ); + await queryRunner.query( + `ALTER TABLE "abuse_user_report" ADD CONSTRAINT "fk_7f4e851a35d81b64dda28eee0" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } +} diff --git a/packages/backend/src/misc/should-block-instance.ts b/packages/backend/src/misc/should-block-instance.ts index 6e46232428..35ed307931 100644 --- a/packages/backend/src/misc/should-block-instance.ts +++ b/packages/backend/src/misc/should-block-instance.ts @@ -18,3 +18,21 @@ export async function shouldBlockInstance( (blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`), ); } + +/** + * Returns whether a specific host (punycoded) should be limited. + * + * @param host punycoded instance host + * @param meta a resolved Meta table + * @returns whether the given host should be limited + */ +export async function shouldSilenceInstance( + host: Instance["host"], + meta?: Meta, +): Promise { + const { silencedHosts } = meta ?? (await fetchMeta()); + return silencedHosts.some( + (silencedHost) => + host === silencedHost || host.endsWith(`.${silencedHost}`), + ); +} diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 2f77796c4b..84f9af4793 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -97,6 +97,11 @@ export class Meta { }) public blockedHosts: string[]; + @Column('varchar', { + length: 256, array: true, default: '{}', + }) + public silencedHosts: string[]; + @Column('boolean', { default: false, }) diff --git a/packages/backend/src/models/repositories/instance.ts b/packages/backend/src/models/repositories/instance.ts index fb4498911a..667ec948de 100644 --- a/packages/backend/src/models/repositories/instance.ts +++ b/packages/backend/src/models/repositories/instance.ts @@ -1,12 +1,13 @@ import { db } from "@/db/postgre.js"; import { Instance } from "@/models/entities/instance.js"; import type { Packed } from "@/misc/schema.js"; -import { fetchMeta } from "@/misc/fetch-meta.js"; -import { shouldBlockInstance } from "@/misc/should-block-instance.js"; +import { + shouldBlockInstance, + shouldSilenceInstance, +} from "@/misc/should-block-instance.js"; export const InstanceRepository = db.getRepository(Instance).extend({ async pack(instance: Instance): Promise> { - const meta = await fetchMeta(); return { id: instance.id, caughtAt: instance.caughtAt.toISOString(), @@ -22,6 +23,7 @@ export const InstanceRepository = db.getRepository(Instance).extend({ isNotResponding: instance.isNotResponding, isSuspended: instance.isSuspended, isBlocked: await shouldBlockInstance(instance.host), + isSilenced: await shouldSilenceInstance(instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, openRegistrations: instance.openRegistrations, diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts index ed3369bf11..f793d40f62 100644 --- a/packages/backend/src/models/schema/federation-instance.ts +++ b/packages/backend/src/models/schema/federation-instance.ts @@ -68,6 +68,11 @@ export const packedFederationInstanceSchema = { optional: false, nullable: false, }, + isSilenced: { + type: "boolean", + optional: false, + nullable: false, + }, softwareName: { type: "string", optional: false, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index f0ac57892d..89928af11c 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -259,6 +259,16 @@ export const meta = { nullable: false, }, }, + silencedHosts: { + type: "array", + optional: true, + nullable: false, + items: { + type: "string", + optional: false, + nullable: false, + }, + }, allowedHosts: { type: "array", optional: true, @@ -524,6 +534,7 @@ export default define(meta, paramDef, async (ps, me) => { customSplashIcons: instance.customSplashIcons, hiddenTags: instance.hiddenTags, blockedHosts: instance.blockedHosts, + silencedHosts: instance.silencedHosts, allowedHosts: instance.allowedHosts, privateMode: instance.privateMode, secureMode: instance.secureMode, 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 a230007323..7f92e5e29e 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -61,6 +61,13 @@ export const paramDef = { type: "string", }, }, + silencedHosts: { + type: "array", + nullable: true, + items: { + type: "string", + }, + }, allowedHosts: { type: "array", nullable: true, @@ -219,6 +226,15 @@ export default define(meta, paramDef, async (ps, me) => { }); } + if (Array.isArray(ps.silencedHosts)) { + let lastValue = ""; + set.silencedHosts = ps.silencedHosts.sort().filter((h) => { + const lv = lastValue; + lastValue = h; + return h !== "" && h !== lv; + }); + } + if (ps.themeColor !== undefined) { set.themeColor = ps.themeColor; } diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 8f6184b196..646f38282b 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -34,6 +34,7 @@ export const paramDef = { notResponding: { type: "boolean", nullable: true }, suspended: { type: "boolean", nullable: true }, federating: { type: "boolean", nullable: true }, + silenced: { type: "boolean", nullable: true }, subscribing: { type: "boolean", nullable: true }, publishing: { type: "boolean", nullable: true }, limit: { type: "integer", minimum: 1, maximum: 100, default: 30 }, @@ -115,6 +116,22 @@ export default define(meta, paramDef, async (ps, me) => { } } + if (typeof ps.silenced === "boolean") { + const meta = await fetchMeta(true); + if (ps.silenced) { + if (meta.silencedHosts.length === 0) { + return []; + } + query.andWhere("instance.host IN (:...silences)", { + silences: meta.silencedHosts, + }); + } else if (meta.silencedHosts.length > 0) { + query.andWhere("instance.host NOT IN (:...silences)", { + silences: meta.silencedHosts, + }); + } + } + if (typeof ps.notResponding === "boolean") { if (ps.notResponding) { query.andWhere("instance.isNotResponding = TRUE"); diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index f6545b131c..e2dd3fc332 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -6,11 +6,13 @@ import { NoteThreadMutings, UserProfiles, Users, + Followings, } from "@/models/index.js"; import { genId } from "@/misc/gen-id.js"; import type { User } from "@/models/entities/user.js"; import type { Notification } from "@/models/entities/notification.js"; import { sendEmailNotification } from "./send-email-notification.js"; +import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; export async function createNotification( notifieeId: User["id"], @@ -21,6 +23,26 @@ export async function createNotification( return null; } + if ( + data.notifierId && + ["mention", "reply", "renote", "quote", "reaction"].includes(type) + ) { + const notifier = await Users.findOneBy({ id: data.notifierId }); + // suppress if the notifier does not exist or is silenced. + if (!notifier) return null; + + // suppress if the notifier is silenced or in a silenced instance, and not followed by the notifiee. + if ( + (notifier.isSilenced || + (Users.isRemoteUser(notifier) && + (await shouldSilenceInstance(notifier.host)))) && + !(await Followings.exist({ + where: { followerId: notifieeId, followeeId: data.notifierId }, + })) + ) + return null; + } + const profile = await UserProfiles.findOneBy({ userId: notifieeId }); const isMuted = profile?.mutingNotificationTypes.includes(type); diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 61a8c6b268..3a77676b38 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -27,6 +27,7 @@ import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js import type { Packed } from "@/misc/schema.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { webhookDeliver } from "@/queue/index.js"; +import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; const logger = new Logger("following/create"); @@ -226,13 +227,19 @@ export default async function ( }); // フォロー対象が鍵アカウントである or + // The follower is silenced, or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or - // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである + // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or + // The follower is remote, the followee is local, and the follower is in a silenced instance. // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく if ( followee.isLocked || + follower.isSilenced || (followeeProfile.carefulBot && follower.isBot) || - (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) + (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) || + (Users.isRemoteUser(follower) && + Users.isLocalUser(followee) && + (await shouldSilenceInstance(follower.host))) ) { let autoAccept = false; diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index 27f9144d0e..50dbd9b3be 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -80,7 +80,13 @@ export default async function ( } if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { - const content = renderActivity(renderFollow(follower, followee, requestId ?? `${config.url}/follows/${followRequest.id}`)); + const content = renderActivity( + renderFollow( + follower, + followee, + requestId ?? `${config.url}/follows/${followRequest.id}`, + ), + ); deliver(follower, content, followee.inbox); } } diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 5dd324d89a..f1164c9c6e 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -39,7 +39,7 @@ import { } from "@/models/index.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; import type { App } from "@/models/entities/app.js"; -import { Not, In } from "typeorm"; +import { Not, In, IsNull } from "typeorm"; import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import { genId } from "@/misc/gen-id.js"; import { @@ -66,6 +66,7 @@ import { Cache } from "@/misc/cache.js"; import type { UserProfile } from "@/models/entities/user-profile.js"; import { db } from "@/db/postgre.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; +import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; const mutedWordsCache = new Cache< { userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[] @@ -166,6 +167,7 @@ export default async ( data: Option, silent = false, ) => + // rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME new Promise(async (res, rej) => { // If you reply outside the channel, match the scope of the target. // TODO (I think it's a process that could be done on the client side, but it's server side for now.) @@ -203,6 +205,15 @@ export default async ( data.visibility = "home"; } + // Enforce home visibility if the user is in a silenced instance. + if ( + data.visibility === "public" && + Users.isRemoteUser(user) && + (await shouldSilenceInstance(user.host)) + ) { + data.visibility = "home"; + } + // Reject if the target of the renote is a public range other than "Home or Entire". if ( data.renote && diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 1a3c52eb51..277393eb41 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -118,7 +118,7 @@ export default async ( userId: user.id, }); - // リアクションされたユーザーがローカルユーザーなら通知を作成 + // Create notification if the reaction target is a local user. if (note.userHost === null) { createNotification(note.userId, "reaction", { notifierId: user.id, @@ -143,7 +143,7 @@ export default async ( } }); - //#region 配信 + //#region deliver if (Users.isLocalUser(user) && !note.localOnly) { const content = renderActivity(await renderLike(record, note)); const dm = new DeliverManager(user, content); diff --git a/packages/calckey-js/src/api.types.ts b/packages/calckey-js/src/api.types.ts index bef00da4ea..478b86721c 100644 --- a/packages/calckey-js/src/api.types.ts +++ b/packages/calckey-js/src/api.types.ts @@ -55,6 +55,7 @@ export type Endpoints = { "admin/get-table-stats": { req: TODO; res: TODO }; "admin/invite": { req: TODO; res: TODO }; "admin/logs": { req: TODO; res: TODO }; + "admin/meta": { req: TODO; res: TODO }; "admin/reset-password": { req: TODO; res: TODO }; "admin/resolve-abuse-user-report": { req: TODO; res: TODO }; "admin/resync-chart": { req: TODO; res: TODO }; diff --git a/packages/client/src/components/MkInstanceCardMini.vue b/packages/client/src/components/MkInstanceCardMini.vue index 0a3fbbea2f..6bc46c0e4c 100644 --- a/packages/client/src/components/MkInstanceCardMini.vue +++ b/packages/client/src/components/MkInstanceCardMini.vue @@ -5,6 +5,7 @@ { yellow: instance.isNotResponding, red: instance.isBlocked, + purple: instance.isSilenced, gray: instance.isSuspended, }, ]" @@ -23,13 +24,13 @@