Merge pull request 'feat: Suppress notifications by silenced accounts and instances' (#9965) from nmkj/calckey:instance-silence into develop
Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9965
This commit is contained in:
commit
34a6e1caa4
@ -197,6 +197,7 @@ perHour: "Per Hour"
|
|||||||
perDay: "Per Day"
|
perDay: "Per Day"
|
||||||
stopActivityDelivery: "Stop sending activities"
|
stopActivityDelivery: "Stop sending activities"
|
||||||
blockThisInstance: "Block this instance"
|
blockThisInstance: "Block this instance"
|
||||||
|
silenceThisInstance: "Silence this instance"
|
||||||
operations: "Operations"
|
operations: "Operations"
|
||||||
software: "Software"
|
software: "Software"
|
||||||
version: "Version"
|
version: "Version"
|
||||||
@ -218,10 +219,13 @@ clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote
|
|||||||
blockedInstances: "Blocked Instances"
|
blockedInstances: "Blocked Instances"
|
||||||
blockedInstancesDescription: "List the hostnames of the instances that you want to\
|
blockedInstancesDescription: "List the hostnames of the instances that you want to\
|
||||||
\ block. Listed instances will no longer be able to communicate with this instance."
|
\ 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"
|
hiddenTags: "Hidden Hashtags"
|
||||||
hiddenTagsDescription: "List the hashtags (without the #) of the hashtags you wish\
|
hiddenTagsDescription: "List the hashtags (without the #) of the hashtags you wish\
|
||||||
\ to hide from trending and explore. Hidden hashtags are still discoverable via\
|
\ 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"
|
muteAndBlock: "Mutes and Blocks"
|
||||||
mutedUsers: "Muted users"
|
mutedUsers: "Muted users"
|
||||||
blockedUsers: "Blocked users"
|
blockedUsers: "Blocked users"
|
||||||
@ -240,6 +244,7 @@ noCustomEmojis: "There are no emoji"
|
|||||||
noJobs: "There are no jobs"
|
noJobs: "There are no jobs"
|
||||||
federating: "Federating"
|
federating: "Federating"
|
||||||
blocked: "Blocked"
|
blocked: "Blocked"
|
||||||
|
silenced: "Silenced"
|
||||||
suspended: "Suspended"
|
suspended: "Suspended"
|
||||||
all: "All"
|
all: "All"
|
||||||
subscribing: "Subscribing"
|
subscribing: "Subscribing"
|
||||||
@ -829,7 +834,7 @@ active: "Active"
|
|||||||
offline: "Offline"
|
offline: "Offline"
|
||||||
notRecommended: "Not recommended"
|
notRecommended: "Not recommended"
|
||||||
botProtection: "Bot Protection"
|
botProtection: "Bot Protection"
|
||||||
instanceBlocking: "Blocked Instances"
|
instanceBlocking: "Federation Block/Silence"
|
||||||
selectAccount: "Select account"
|
selectAccount: "Select account"
|
||||||
switchAccount: "Switch account"
|
switchAccount: "Switch account"
|
||||||
enabled: "Enabled"
|
enabled: "Enabled"
|
||||||
|
@ -183,6 +183,7 @@ perHour: "1時間ごと"
|
|||||||
perDay: "1日ごと"
|
perDay: "1日ごと"
|
||||||
stopActivityDelivery: "アクティビティの配送を停止"
|
stopActivityDelivery: "アクティビティの配送を停止"
|
||||||
blockThisInstance: "このインスタンスをブロック"
|
blockThisInstance: "このインスタンスをブロック"
|
||||||
|
silenceThisInstance: "このインスタンスをサイレンス"
|
||||||
operations: "操作"
|
operations: "操作"
|
||||||
software: "ソフトウェア"
|
software: "ソフトウェア"
|
||||||
version: "バージョン"
|
version: "バージョン"
|
||||||
@ -202,6 +203,8 @@ clearCachedFiles: "キャッシュをクリア"
|
|||||||
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
|
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
|
||||||
blockedInstances: "ブロックしたインスタンス"
|
blockedInstances: "ブロックしたインスタンス"
|
||||||
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。"
|
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。"
|
||||||
|
silencedInstances: "サイレンスしたインスタンス"
|
||||||
|
silencedInstancesDescription: "サイレンスしたいインスタンスのホストを改行で区切って設定します。サイレンスされたインスタンスに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
|
||||||
muteAndBlock: "ミュートとブロック"
|
muteAndBlock: "ミュートとブロック"
|
||||||
mutedUsers: "ミュートしたユーザー"
|
mutedUsers: "ミュートしたユーザー"
|
||||||
blockedUsers: "ブロックしたユーザー"
|
blockedUsers: "ブロックしたユーザー"
|
||||||
@ -220,6 +223,7 @@ noCustomEmojis: "絵文字はありません"
|
|||||||
noJobs: "ジョブはありません"
|
noJobs: "ジョブはありません"
|
||||||
federating: "連合中"
|
federating: "連合中"
|
||||||
blocked: "ブロック中"
|
blocked: "ブロック中"
|
||||||
|
silenced: "サイレンス中"
|
||||||
suspended: "配信停止"
|
suspended: "配信停止"
|
||||||
all: "全て"
|
all: "全て"
|
||||||
subscribing: "購読中"
|
subscribing: "購読中"
|
||||||
@ -768,7 +772,7 @@ active: "アクティブ"
|
|||||||
offline: "オフライン"
|
offline: "オフライン"
|
||||||
notRecommended: "非推奨"
|
notRecommended: "非推奨"
|
||||||
botProtection: "Botプロテクション"
|
botProtection: "Botプロテクション"
|
||||||
instanceBlocking: "インスタンスブロック"
|
instanceBlocking: "連合ブロック・サイレンス"
|
||||||
selectAccount: "アカウントを選択"
|
selectAccount: "アカウントを選択"
|
||||||
switchAccount: "アカウントを切り替え"
|
switchAccount: "アカウントを切り替え"
|
||||||
enabled: "有効"
|
enabled: "有効"
|
||||||
|
165
packages/backend/migration/1682891890317-InstanceSilence.js
Normal file
165
packages/backend/migration/1682891890317-InstanceSilence.js
Normal file
@ -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`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -18,3 +18,21 @@ export async function shouldBlockInstance(
|
|||||||
(blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`),
|
(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<boolean> {
|
||||||
|
const { silencedHosts } = meta ?? (await fetchMeta());
|
||||||
|
return silencedHosts.some(
|
||||||
|
(silencedHost) =>
|
||||||
|
host === silencedHost || host.endsWith(`.${silencedHost}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -97,6 +97,11 @@ export class Meta {
|
|||||||
})
|
})
|
||||||
public blockedHosts: string[];
|
public blockedHosts: string[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 256, array: true, default: '{}',
|
||||||
|
})
|
||||||
|
public silencedHosts: string[];
|
||||||
|
|
||||||
@Column('boolean', {
|
@Column('boolean', {
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { db } from "@/db/postgre.js";
|
import { db } from "@/db/postgre.js";
|
||||||
import { Instance } from "@/models/entities/instance.js";
|
import { Instance } from "@/models/entities/instance.js";
|
||||||
import type { Packed } from "@/misc/schema.js";
|
import type { Packed } from "@/misc/schema.js";
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
import {
|
||||||
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
|
shouldBlockInstance,
|
||||||
|
shouldSilenceInstance,
|
||||||
|
} from "@/misc/should-block-instance.js";
|
||||||
|
|
||||||
export const InstanceRepository = db.getRepository(Instance).extend({
|
export const InstanceRepository = db.getRepository(Instance).extend({
|
||||||
async pack(instance: Instance): Promise<Packed<"FederationInstance">> {
|
async pack(instance: Instance): Promise<Packed<"FederationInstance">> {
|
||||||
const meta = await fetchMeta();
|
|
||||||
return {
|
return {
|
||||||
id: instance.id,
|
id: instance.id,
|
||||||
caughtAt: instance.caughtAt.toISOString(),
|
caughtAt: instance.caughtAt.toISOString(),
|
||||||
@ -22,6 +23,7 @@ export const InstanceRepository = db.getRepository(Instance).extend({
|
|||||||
isNotResponding: instance.isNotResponding,
|
isNotResponding: instance.isNotResponding,
|
||||||
isSuspended: instance.isSuspended,
|
isSuspended: instance.isSuspended,
|
||||||
isBlocked: await shouldBlockInstance(instance.host),
|
isBlocked: await shouldBlockInstance(instance.host),
|
||||||
|
isSilenced: await shouldSilenceInstance(instance.host),
|
||||||
softwareName: instance.softwareName,
|
softwareName: instance.softwareName,
|
||||||
softwareVersion: instance.softwareVersion,
|
softwareVersion: instance.softwareVersion,
|
||||||
openRegistrations: instance.openRegistrations,
|
openRegistrations: instance.openRegistrations,
|
||||||
|
@ -68,6 +68,11 @@ export const packedFederationInstanceSchema = {
|
|||||||
optional: false,
|
optional: false,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
|
isSilenced: {
|
||||||
|
type: "boolean",
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
softwareName: {
|
softwareName: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
|
@ -259,6 +259,16 @@ export const meta = {
|
|||||||
nullable: false,
|
nullable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
silencedHosts: {
|
||||||
|
type: "array",
|
||||||
|
optional: true,
|
||||||
|
nullable: false,
|
||||||
|
items: {
|
||||||
|
type: "string",
|
||||||
|
optional: false,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
allowedHosts: {
|
allowedHosts: {
|
||||||
type: "array",
|
type: "array",
|
||||||
optional: true,
|
optional: true,
|
||||||
@ -524,6 +534,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||||||
customSplashIcons: instance.customSplashIcons,
|
customSplashIcons: instance.customSplashIcons,
|
||||||
hiddenTags: instance.hiddenTags,
|
hiddenTags: instance.hiddenTags,
|
||||||
blockedHosts: instance.blockedHosts,
|
blockedHosts: instance.blockedHosts,
|
||||||
|
silencedHosts: instance.silencedHosts,
|
||||||
allowedHosts: instance.allowedHosts,
|
allowedHosts: instance.allowedHosts,
|
||||||
privateMode: instance.privateMode,
|
privateMode: instance.privateMode,
|
||||||
secureMode: instance.secureMode,
|
secureMode: instance.secureMode,
|
||||||
|
@ -61,6 +61,13 @@ export const paramDef = {
|
|||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
silencedHosts: {
|
||||||
|
type: "array",
|
||||||
|
nullable: true,
|
||||||
|
items: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
allowedHosts: {
|
allowedHosts: {
|
||||||
type: "array",
|
type: "array",
|
||||||
nullable: true,
|
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) {
|
if (ps.themeColor !== undefined) {
|
||||||
set.themeColor = ps.themeColor;
|
set.themeColor = ps.themeColor;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ export const paramDef = {
|
|||||||
notResponding: { type: "boolean", nullable: true },
|
notResponding: { type: "boolean", nullable: true },
|
||||||
suspended: { type: "boolean", nullable: true },
|
suspended: { type: "boolean", nullable: true },
|
||||||
federating: { type: "boolean", nullable: true },
|
federating: { type: "boolean", nullable: true },
|
||||||
|
silenced: { type: "boolean", nullable: true },
|
||||||
subscribing: { type: "boolean", nullable: true },
|
subscribing: { type: "boolean", nullable: true },
|
||||||
publishing: { type: "boolean", nullable: true },
|
publishing: { type: "boolean", nullable: true },
|
||||||
limit: { type: "integer", minimum: 1, maximum: 100, default: 30 },
|
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 (typeof ps.notResponding === "boolean") {
|
||||||
if (ps.notResponding) {
|
if (ps.notResponding) {
|
||||||
query.andWhere("instance.isNotResponding = TRUE");
|
query.andWhere("instance.isNotResponding = TRUE");
|
||||||
|
@ -6,11 +6,13 @@ import {
|
|||||||
NoteThreadMutings,
|
NoteThreadMutings,
|
||||||
UserProfiles,
|
UserProfiles,
|
||||||
Users,
|
Users,
|
||||||
|
Followings,
|
||||||
} from "@/models/index.js";
|
} from "@/models/index.js";
|
||||||
import { genId } from "@/misc/gen-id.js";
|
import { genId } from "@/misc/gen-id.js";
|
||||||
import type { User } from "@/models/entities/user.js";
|
import type { User } from "@/models/entities/user.js";
|
||||||
import type { Notification } from "@/models/entities/notification.js";
|
import type { Notification } from "@/models/entities/notification.js";
|
||||||
import { sendEmailNotification } from "./send-email-notification.js";
|
import { sendEmailNotification } from "./send-email-notification.js";
|
||||||
|
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
||||||
|
|
||||||
export async function createNotification(
|
export async function createNotification(
|
||||||
notifieeId: User["id"],
|
notifieeId: User["id"],
|
||||||
@ -21,6 +23,26 @@ export async function createNotification(
|
|||||||
return null;
|
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 profile = await UserProfiles.findOneBy({ userId: notifieeId });
|
||||||
|
|
||||||
const isMuted = profile?.mutingNotificationTypes.includes(type);
|
const isMuted = profile?.mutingNotificationTypes.includes(type);
|
||||||
|
@ -27,6 +27,7 @@ import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js
|
|||||||
import type { Packed } from "@/misc/schema.js";
|
import type { Packed } from "@/misc/schema.js";
|
||||||
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
|
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
|
||||||
import { webhookDeliver } from "@/queue/index.js";
|
import { webhookDeliver } from "@/queue/index.js";
|
||||||
|
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
||||||
|
|
||||||
const logger = new Logger("following/create");
|
const logger = new Logger("following/create");
|
||||||
|
|
||||||
@ -226,13 +227,19 @@ export default async function (
|
|||||||
});
|
});
|
||||||
|
|
||||||
// フォロー対象が鍵アカウントである or
|
// フォロー対象が鍵アカウントである or
|
||||||
|
// The follower is silenced, or
|
||||||
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
|
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
|
||||||
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
|
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or
|
||||||
|
// The follower is remote, the followee is local, and the follower is in a silenced instance.
|
||||||
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
|
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
|
||||||
if (
|
if (
|
||||||
followee.isLocked ||
|
followee.isLocked ||
|
||||||
|
follower.isSilenced ||
|
||||||
(followeeProfile.carefulBot && follower.isBot) ||
|
(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;
|
let autoAccept = false;
|
||||||
|
|
||||||
|
@ -80,7 +80,13 @@ export default async function (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
|
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);
|
deliver(follower, content, followee.inbox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ import {
|
|||||||
} from "@/models/index.js";
|
} from "@/models/index.js";
|
||||||
import type { DriveFile } from "@/models/entities/drive-file.js";
|
import type { DriveFile } from "@/models/entities/drive-file.js";
|
||||||
import type { App } from "@/models/entities/app.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 type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
|
||||||
import { genId } from "@/misc/gen-id.js";
|
import { genId } from "@/misc/gen-id.js";
|
||||||
import {
|
import {
|
||||||
@ -66,6 +66,7 @@ import { Cache } from "@/misc/cache.js";
|
|||||||
import type { UserProfile } from "@/models/entities/user-profile.js";
|
import type { UserProfile } from "@/models/entities/user-profile.js";
|
||||||
import { db } from "@/db/postgre.js";
|
import { db } from "@/db/postgre.js";
|
||||||
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
|
import { getActiveWebhooks } from "@/misc/webhook-cache.js";
|
||||||
|
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
|
||||||
|
|
||||||
const mutedWordsCache = new Cache<
|
const mutedWordsCache = new Cache<
|
||||||
{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
|
{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
|
||||||
@ -166,6 +167,7 @@ export default async (
|
|||||||
data: Option,
|
data: Option,
|
||||||
silent = false,
|
silent = false,
|
||||||
) =>
|
) =>
|
||||||
|
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
|
||||||
new Promise<Note>(async (res, rej) => {
|
new Promise<Note>(async (res, rej) => {
|
||||||
// If you reply outside the channel, match the scope of the target.
|
// 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.)
|
// 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";
|
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".
|
// Reject if the target of the renote is a public range other than "Home or Entire".
|
||||||
if (
|
if (
|
||||||
data.renote &&
|
data.renote &&
|
||||||
|
@ -118,7 +118,7 @@ export default async (
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
// Create notification if the reaction target is a local user.
|
||||||
if (note.userHost === null) {
|
if (note.userHost === null) {
|
||||||
createNotification(note.userId, "reaction", {
|
createNotification(note.userId, "reaction", {
|
||||||
notifierId: user.id,
|
notifierId: user.id,
|
||||||
@ -143,7 +143,7 @@ export default async (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region 配信
|
//#region deliver
|
||||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||||
const content = renderActivity(await renderLike(record, note));
|
const content = renderActivity(await renderLike(record, note));
|
||||||
const dm = new DeliverManager(user, content);
|
const dm = new DeliverManager(user, content);
|
||||||
|
@ -55,6 +55,7 @@ export type Endpoints = {
|
|||||||
"admin/get-table-stats": { req: TODO; res: TODO };
|
"admin/get-table-stats": { req: TODO; res: TODO };
|
||||||
"admin/invite": { req: TODO; res: TODO };
|
"admin/invite": { req: TODO; res: TODO };
|
||||||
"admin/logs": { req: TODO; res: TODO };
|
"admin/logs": { req: TODO; res: TODO };
|
||||||
|
"admin/meta": { req: TODO; res: TODO };
|
||||||
"admin/reset-password": { req: TODO; res: TODO };
|
"admin/reset-password": { req: TODO; res: TODO };
|
||||||
"admin/resolve-abuse-user-report": { req: TODO; res: TODO };
|
"admin/resolve-abuse-user-report": { req: TODO; res: TODO };
|
||||||
"admin/resync-chart": { req: TODO; res: TODO };
|
"admin/resync-chart": { req: TODO; res: TODO };
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
{
|
{
|
||||||
yellow: instance.isNotResponding,
|
yellow: instance.isNotResponding,
|
||||||
red: instance.isBlocked,
|
red: instance.isBlocked,
|
||||||
|
purple: instance.isSilenced,
|
||||||
gray: instance.isSuspended,
|
gray: instance.isSuspended,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@ -23,13 +24,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as misskey from "calckey-js";
|
import * as calckey from "calckey-js";
|
||||||
import MkMiniChart from "@/components/MkMiniChart.vue";
|
import MkMiniChart from "@/components/MkMiniChart.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
instance: misskey.entities.Instance;
|
instance: calckey.entities.Instance;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
let chartValues = $ref<number[] | null>(null);
|
let chartValues = $ref<number[] | null>(null);
|
||||||
@ -135,6 +136,21 @@ function getInstanceIcon(instance): string {
|
|||||||
background-size: 16px 16px;
|
background-size: 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:global(.purple) {
|
||||||
|
--c: rgba(196, 0, 255, 0.15);
|
||||||
|
background-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
var(--c) 16.67%,
|
||||||
|
transparent 16.67%,
|
||||||
|
transparent 50%,
|
||||||
|
var(--c) 50%,
|
||||||
|
var(--c) 66.67%,
|
||||||
|
transparent 66.67%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
background-size: 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
&:global(.gray) {
|
&:global(.gray) {
|
||||||
--c: var(--bg);
|
--c: var(--bg);
|
||||||
background-image: linear-gradient(
|
background-image: linear-gradient(
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
<option value="publishing">{{ i18n.ts.publishing }}</option>
|
<option value="publishing">{{ i18n.ts.publishing }}</option>
|
||||||
<option value="suspended">{{ i18n.ts.suspended }}</option>
|
<option value="suspended">{{ i18n.ts.suspended }}</option>
|
||||||
<option value="blocked">{{ i18n.ts.blocked }}</option>
|
<option value="blocked">{{ i18n.ts.blocked }}</option>
|
||||||
|
<option value="silenced">{{ i18n.ts.silenced }}</option>
|
||||||
<option value="notResponding">
|
<option value="notResponding">
|
||||||
{{ i18n.ts.notResponding }}
|
{{ i18n.ts.notResponding }}
|
||||||
</option>
|
</option>
|
||||||
@ -105,13 +106,11 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
|
||||||
import MkInput from "@/components/form/input.vue";
|
import MkInput from "@/components/form/input.vue";
|
||||||
import MkSelect from "@/components/form/select.vue";
|
import MkSelect from "@/components/form/select.vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import MkInstanceCardMini from "@/components/MkInstanceCardMini.vue";
|
import MkInstanceCardMini from "@/components/MkInstanceCardMini.vue";
|
||||||
import FormSplit from "@/components/form/split.vue";
|
import FormSplit from "@/components/form/split.vue";
|
||||||
import * as os from "@/os";
|
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
let host = $ref("");
|
let host = $ref("");
|
||||||
@ -134,6 +133,8 @@ const pagination = {
|
|||||||
? { suspended: true }
|
? { suspended: true }
|
||||||
: state === "blocked"
|
: state === "blocked"
|
||||||
? { blocked: true }
|
? { blocked: true }
|
||||||
|
: state === "silenced"
|
||||||
|
? { silenced: true }
|
||||||
: state === "notResponding"
|
: state === "notResponding"
|
||||||
? { notResponding: true }
|
? { notResponding: true }
|
||||||
: {}),
|
: {}),
|
||||||
@ -143,6 +144,7 @@ const pagination = {
|
|||||||
function getStatus(instance) {
|
function getStatus(instance) {
|
||||||
if (instance.isSuspended) return "Suspended";
|
if (instance.isSuspended) return "Suspended";
|
||||||
if (instance.isBlocked) return "Blocked";
|
if (instance.isBlocked) return "Blocked";
|
||||||
|
if (instance.isSilenced) return "Silenced";
|
||||||
if (instance.isNotResponding) return "Error";
|
if (instance.isNotResponding) return "Error";
|
||||||
return "Alive";
|
return "Alive";
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header
|
<template #header
|
||||||
><MkPageHeader
|
><MkPageHeader
|
||||||
v-model:tab="tab"
|
|
||||||
:actions="headerActions"
|
:actions="headerActions"
|
||||||
:tabs="headerTabs"
|
:tabs="headerTabs"
|
||||||
:display-back-button="true"
|
:display-back-button="true"
|
||||||
|
@ -7,13 +7,31 @@
|
|||||||
:display-back-button="true"
|
:display-back-button="true"
|
||||||
/></template>
|
/></template>
|
||||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||||
|
<MkTab v-model="tab" class="_formBlock">
|
||||||
|
<option value="block">{{ i18n.ts.blockedInstances }}</option>
|
||||||
|
<option value="silence">{{ i18n.ts.silencedInstances }}</option>
|
||||||
|
</MkTab>
|
||||||
<FormSuspense :p="init">
|
<FormSuspense :p="init">
|
||||||
<FormTextarea v-model="blockedHosts" class="_formBlock">
|
<FormTextarea
|
||||||
|
v-if="tab === 'block'"
|
||||||
|
v-model="blockedHosts"
|
||||||
|
class="_formBlock"
|
||||||
|
>
|
||||||
<span>{{ i18n.ts.blockedInstances }}</span>
|
<span>{{ i18n.ts.blockedInstances }}</span>
|
||||||
<template #caption>{{
|
<template #caption>{{
|
||||||
i18n.ts.blockedInstancesDescription
|
i18n.ts.blockedInstancesDescription
|
||||||
}}</template>
|
}}</template>
|
||||||
</FormTextarea>
|
</FormTextarea>
|
||||||
|
<FormTextarea
|
||||||
|
v-else-if="tab === 'silence'"
|
||||||
|
v-model="silencedHosts"
|
||||||
|
class="_formBlock"
|
||||||
|
>
|
||||||
|
<span>{{ i18n.ts.silencedInstances }}</span>
|
||||||
|
<template #caption>{{
|
||||||
|
i18n.ts.silencedInstancesDescription
|
||||||
|
}}</template>
|
||||||
|
</FormTextarea>
|
||||||
|
|
||||||
<FormButton primary class="_formBlock" @click="save"
|
<FormButton primary class="_formBlock" @click="save"
|
||||||
><i class="ph-floppy-disk-back ph-bold ph-lg"></i>
|
><i class="ph-floppy-disk-back ph-bold ph-lg"></i>
|
||||||
@ -29,21 +47,28 @@ import {} from "vue";
|
|||||||
import FormButton from "@/components/MkButton.vue";
|
import FormButton from "@/components/MkButton.vue";
|
||||||
import FormTextarea from "@/components/form/textarea.vue";
|
import FormTextarea from "@/components/form/textarea.vue";
|
||||||
import FormSuspense from "@/components/form/suspense.vue";
|
import FormSuspense from "@/components/form/suspense.vue";
|
||||||
|
import MkTab from "@/components/MkTab.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { fetchInstance } from "@/instance";
|
import { fetchInstance } from "@/instance";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
|
|
||||||
let blockedHosts: string = $ref("");
|
let blockedHosts: string = $ref("");
|
||||||
|
let silencedHosts: string = $ref("");
|
||||||
|
let tab = $ref("block");
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const meta = await os.api("admin/meta");
|
const meta = await os.api("admin/meta");
|
||||||
blockedHosts = meta.blockedHosts.join("\n");
|
if (meta) {
|
||||||
|
blockedHosts = meta.blockedHosts.join("\n");
|
||||||
|
silencedHosts = meta.silencedHosts.join("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
os.apiWithDialog("admin/update-meta", {
|
os.apiWithDialog("admin/update-meta", {
|
||||||
blockedHosts: blockedHosts.split("\n").map((h) => h.trim()) || [],
|
blockedHosts: blockedHosts.split("\n").map((h) => h.trim()) || [],
|
||||||
|
silencedHosts: silencedHosts.split("\n").map((h) => h.trim()) || [],
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
fetchInstance();
|
fetchInstance();
|
||||||
});
|
});
|
||||||
|
@ -98,6 +98,14 @@
|
|||||||
@update:modelValue="toggleBlock"
|
@update:modelValue="toggleBlock"
|
||||||
>{{ i18n.ts.blockThisInstance }}</FormSwitch
|
>{{ i18n.ts.blockThisInstance }}</FormSwitch
|
||||||
>
|
>
|
||||||
|
<FormSwitch
|
||||||
|
v-model="isSilenced"
|
||||||
|
class="_formBlock"
|
||||||
|
@update:modelValue="toggleSilence"
|
||||||
|
>{{
|
||||||
|
i18n.ts.silenceThisInstance
|
||||||
|
}}</FormSwitch
|
||||||
|
>
|
||||||
</FormSuspense>
|
</FormSuspense>
|
||||||
<MkButton @click="refreshMetadata"
|
<MkButton @click="refreshMetadata"
|
||||||
><i
|
><i
|
||||||
@ -329,7 +337,7 @@
|
|||||||
import { watch } from "vue";
|
import { watch } from "vue";
|
||||||
import { Virtual } from "swiper";
|
import { Virtual } from "swiper";
|
||||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||||
import type * as misskey from "calckey-js";
|
import type * as calckey from "calckey-js";
|
||||||
import MkChart from "@/components/MkChart.vue";
|
import MkChart from "@/components/MkChart.vue";
|
||||||
import MkObjectView from "@/components/MkObjectView.vue";
|
import MkObjectView from "@/components/MkObjectView.vue";
|
||||||
import FormLink from "@/components/form/link.vue";
|
import FormLink from "@/components/form/link.vue";
|
||||||
@ -352,11 +360,13 @@ import "swiper/scss";
|
|||||||
import "swiper/scss/virtual";
|
import "swiper/scss/virtual";
|
||||||
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
|
||||||
|
|
||||||
type AugmentedInstanceMetadata = misskey.entities.DetailedInstanceMetadata & {
|
type AugmentedInstanceMetadata = calckey.entities.DetailedInstanceMetadata & {
|
||||||
blockedHosts: string[];
|
blockedHosts: string[];
|
||||||
|
silencedHosts: string[];
|
||||||
};
|
};
|
||||||
type AugmentedInstance = misskey.entities.Instance & {
|
type AugmentedInstance = calckey.entities.Instance & {
|
||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
|
isSilenced: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -373,6 +383,7 @@ let meta = $ref<AugmentedInstanceMetadata | null>(null);
|
|||||||
let instance = $ref<AugmentedInstance | null>(null);
|
let instance = $ref<AugmentedInstance | null>(null);
|
||||||
let suspended = $ref(false);
|
let suspended = $ref(false);
|
||||||
let isBlocked = $ref(false);
|
let isBlocked = $ref(false);
|
||||||
|
let isSilenced = $ref(false);
|
||||||
let faviconUrl = $ref(null);
|
let faviconUrl = $ref(null);
|
||||||
|
|
||||||
const usersPagination = {
|
const usersPagination = {
|
||||||
@ -386,16 +397,14 @@ const usersPagination = {
|
|||||||
offsetMode: true,
|
offsetMode: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
async function init() {
|
|
||||||
meta = await os.api("admin/meta");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
|
meta = (await os.api("admin/meta")) as AugmentedInstanceMetadata;
|
||||||
instance = (await os.api("federation/show-instance", {
|
instance = (await os.api("federation/show-instance", {
|
||||||
host: props.host,
|
host: props.host,
|
||||||
})) as AugmentedInstance;
|
})) as AugmentedInstance;
|
||||||
suspended = instance.isSuspended;
|
suspended = instance.isSuspended;
|
||||||
isBlocked = instance.isBlocked;
|
isBlocked = instance.isBlocked;
|
||||||
|
isSilenced = instance.isSilenced;
|
||||||
faviconUrl =
|
faviconUrl =
|
||||||
getProxiedImageUrlNullable(instance.faviconUrl, "preview") ??
|
getProxiedImageUrlNullable(instance.faviconUrl, "preview") ??
|
||||||
getProxiedImageUrlNullable(instance.iconUrl, "preview");
|
getProxiedImageUrlNullable(instance.iconUrl, "preview");
|
||||||
@ -417,6 +426,22 @@ async function toggleBlock() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleSilence() {
|
||||||
|
if (meta == null) return;
|
||||||
|
if (!instance) {
|
||||||
|
throw new Error(`Instance info not loaded`);
|
||||||
|
}
|
||||||
|
let silencedHosts: string[];
|
||||||
|
if (isSilenced) {
|
||||||
|
silencedHosts = meta.silencedHosts.concat([instance.host]);
|
||||||
|
} else {
|
||||||
|
silencedHosts = meta.silencedHosts.filter((x) => x !== instance!.host);
|
||||||
|
}
|
||||||
|
await os.api("admin/update-meta", {
|
||||||
|
silencedHosts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function toggleSuspend(v) {
|
async function toggleSuspend(v) {
|
||||||
await os.api("admin/federation/update-instance", {
|
await os.api("admin/federation/update-instance", {
|
||||||
host: instance.host,
|
host: instance.host,
|
||||||
|
Loading…
Reference in New Issue
Block a user