diff --git a/locales/en-US.yml b/locales/en-US.yml index afcb0e24ae..173404e7c4 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -32,6 +32,7 @@ uploading: "Uploading..." save: "Save" users: "Users" addUser: "Add a user" +addInstance: "Add an instance" favorite: "Add to bookmarks" favorites: "Bookmarks" unfavorite: "Remove from bookmarks" @@ -160,6 +161,7 @@ proxyAccount: "Proxy account" proxyAccountDescription: "A proxy account is an account that acts as a remote follower for users under certain conditions. For example, when a user adds a remote user to the list, the remote user's activity will not be delivered to the instance if no local user is following that user, so the proxy account will follow instead." host: "Host" selectUser: "Select a user" +selectInstance: "Select an instance" recipient: "Recipient(s)" annotation: "Comments" federation: "Federation" @@ -197,6 +199,7 @@ muteAndBlock: "Mutes and Blocks" mutedUsers: "Muted users" blockedUsers: "Blocked users" noUsers: "There are no users" +noInstances: "There are no instances" editProfile: "Edit profile" noteDeleteConfirm: "Are you sure you want to delete this post?" pinLimitExceeded: "You cannot pin any more posts" @@ -363,6 +366,7 @@ notifyAntenna: "Notify about new posts" withFileAntenna: "Only posts with files" enableServiceworker: "Enable Push-Notifications for your Browser" antennaUsersDescription: "List one username per line" +antennaInstancesDescription: "List one instance host per line" caseSensitive: "Case sensitive" withReplies: "Include replies" connectedTo: "Following account(s) are connected" @@ -1301,6 +1305,7 @@ _antennaSources: users: "Posts from specific users" userList: "Posts from a specified list of users" userGroup: "Posts from users in a specified group" + instances: "Posts from all users on an instance" _weekday: sunday: "Sunday" monday: "Monday" diff --git a/packages/backend/migration/1676093997212-AntennaInstances.js b/packages/backend/migration/1676093997212-AntennaInstances.js new file mode 100644 index 0000000000..7553e12557 --- /dev/null +++ b/packages/backend/migration/1676093997212-AntennaInstances.js @@ -0,0 +1,17 @@ +export class AntennaInstances1676093997212 { + name = 'AntennaInstances1676093997212' + + async up(queryRunner) { + await queryRunner.query(`ALTER TYPE "antenna_src_enum" ADD VALUE 'instances'`); + await queryRunner.query(`ALTER TABLE "antenna" ADD "instances" jsonb NOT NULL DEFAULT '[]'`); + } + + async down(queryRunner) { + await queryRunner.query(`DELETE FROM "antenna" WHERE "src" = 'instances'`); + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "instances"`); + await queryRunner.query(`CREATE TYPE "public"."antenna_src_enum_old" AS ENUM('home', 'all', 'users', 'list', 'group')`); + await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "public"."antenna_src_enum_old" USING "src"::"text"::"public"."antenna_src_enum_old"`); + await queryRunner.query(`DROP TYPE "public"."antenna_src_enum"`); + await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum_old" RENAME TO "antenna_src_enum"`); + } +} diff --git a/packages/backend/src/misc/check-hit-antenna.ts b/packages/backend/src/misc/check-hit-antenna.ts index aa38d9e275..73d9d095af 100644 --- a/packages/backend/src/misc/check-hit-antenna.ts +++ b/packages/backend/src/misc/check-hit-antenna.ts @@ -79,7 +79,14 @@ export async function checkHitAntenna( getFullApAccount(noteUser.username, noteUser.host).toLowerCase(), ) ) - return false; + return false; + } else if (antenna.src === "instances") { + const instances = antenna.instances + .filter(x => x !== "") + .map(host => { + return host.toLowerCase(); + }); + if (!instances.includes(noteUser.host?.toLowerCase() ?? "")) return false; } const keywords = antenna.keywords diff --git a/packages/backend/src/models/entities/antenna.ts b/packages/backend/src/models/entities/antenna.ts index 45d9553e4e..c653b2a051 100644 --- a/packages/backend/src/models/entities/antenna.ts +++ b/packages/backend/src/models/entities/antenna.ts @@ -40,8 +40,8 @@ export class Antenna { }) public name: string; - @Column('enum', { enum: ['home', 'all', 'users', 'list', 'group'] }) - public src: "home" | "all" | "users" | "list" | "group"; + @Column('enum', { enum: ['home', 'all', 'users', 'list', 'group', 'instances'] }) + public src: "home" | "all" | "users" | "list" | "group" | "instances"; @Column({ ...id(), @@ -73,6 +73,11 @@ export class Antenna { }) public users: string[]; + @Column('jsonb', { + default: [], + }) + public instances: string[]; + @Column('jsonb', { default: [], }) diff --git a/packages/backend/src/models/repositories/antenna.ts b/packages/backend/src/models/repositories/antenna.ts index 57ce2fc9e8..c325e25895 100644 --- a/packages/backend/src/models/repositories/antenna.ts +++ b/packages/backend/src/models/repositories/antenna.ts @@ -25,6 +25,7 @@ export const AntennaRepository = db.getRepository(Antenna).extend({ userListId: antenna.userListId, userGroupId: userGroupJoining ? userGroupJoining.userGroupId : null, users: antenna.users, + instances: antenna.instances, caseSensitive: antenna.caseSensitive, notify: antenna.notify, withReplies: antenna.withReplies, diff --git a/packages/backend/src/models/schema/antenna.ts b/packages/backend/src/models/schema/antenna.ts index c7eed092e9..990e2daa2c 100644 --- a/packages/backend/src/models/schema/antenna.ts +++ b/packages/backend/src/models/schema/antenna.ts @@ -52,7 +52,7 @@ export const packedAntennaSchema = { type: "string", optional: false, nullable: false, - enum: ["home", "all", "users", "list", "group"], + enum: ["home", "all", "users", "list", "group", "instances"], }, userListId: { type: "string", @@ -76,6 +76,16 @@ export const packedAntennaSchema = { nullable: false, }, }, + instances: { + type: "array", + optional: false, + nullable: false, + items: { + type: "string", + optional: false, + nullable: false, + }, + }, caseSensitive: { type: "boolean", optional: false, diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 171fc2c643..1df712f368 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -37,7 +37,7 @@ export const paramDef = { type: "object", properties: { name: { type: "string", minLength: 1, maxLength: 100 }, - src: { type: "string", enum: ["home", "all", "users", "list", "group"] }, + src: { type: "string", enum: ["home", "all", "users", "list", "group", "instances"] }, userListId: { type: "string", format: "misskey:id", nullable: true }, userGroupId: { type: "string", format: "misskey:id", nullable: true }, keywords: { @@ -64,6 +64,12 @@ export const paramDef = { type: "string", }, }, + instances: { + type: "array", + items: { + type: "string", + }, + }, caseSensitive: { type: "boolean" }, withReplies: { type: "boolean" }, withFile: { type: "boolean" }, @@ -75,6 +81,7 @@ export const paramDef = { "keywords", "excludeKeywords", "users", + "instances", "caseSensitive", "withReplies", "withFile", @@ -118,6 +125,7 @@ export default define(meta, paramDef, async (ps, user) => { keywords: ps.keywords, excludeKeywords: ps.excludeKeywords, users: ps.users, + instances: ps.instances, caseSensitive: ps.caseSensitive, withReplies: ps.withReplies, withFile: ps.withFile, diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index 6c48cd3696..c9a32988e8 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -43,7 +43,7 @@ export const paramDef = { properties: { antennaId: { type: "string", format: "misskey:id" }, name: { type: "string", minLength: 1, maxLength: 100 }, - src: { type: "string", enum: ["home", "all", "users", "list", "group"] }, + src: { type: "string", enum: ["home", "all", "users", "list", "group", "instances"] }, userListId: { type: "string", format: "misskey:id", nullable: true }, userGroupId: { type: "string", format: "misskey:id", nullable: true }, keywords: { @@ -70,6 +70,12 @@ export const paramDef = { type: "string", }, }, + instances: { + type: "array", + items: { + type: "string", + }, + }, caseSensitive: { type: "boolean" }, withReplies: { type: "boolean" }, withFile: { type: "boolean" }, @@ -82,6 +88,7 @@ export const paramDef = { "keywords", "excludeKeywords", "users", + "instances", "caseSensitive", "withReplies", "withFile", @@ -131,6 +138,7 @@ export default define(meta, paramDef, async (ps, user) => { keywords: ps.keywords, excludeKeywords: ps.excludeKeywords, users: ps.users, + instances: ps.instances, caseSensitive: ps.caseSensitive, withReplies: ps.withReplies, withFile: ps.withFile, diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts index 3ab37ff6f2..8f6184b196 100644 --- a/packages/backend/src/server/api/endpoints/federation/instances.ts +++ b/packages/backend/src/server/api/endpoints/federation/instances.ts @@ -102,10 +102,13 @@ export default define(meta, paramDef, async (ps, me) => { if (typeof ps.blocked === "boolean") { const meta = await fetchMeta(true); if (ps.blocked) { + if (meta.blockedHosts.length === 0) { + return []; + } query.andWhere("instance.host IN (:...blocks)", { blocks: meta.blockedHosts, }); - } else { + } else if (meta.blockedHosts.length > 0) { query.andWhere("instance.host NOT IN (:...blocks)", { blocks: meta.blockedHosts, }); diff --git a/packages/client/src/components/MkInstanceSelectDialog.vue b/packages/client/src/components/MkInstanceSelectDialog.vue new file mode 100644 index 0000000000..b2349c47e1 --- /dev/null +++ b/packages/client/src/components/MkInstanceSelectDialog.vue @@ -0,0 +1,153 @@ + + + {{ i18n.ts.selectInstance }} + + + + {{ i18n.ts.instance }} + + + + + + + + {{ item.host }} + + + + + {{ i18n.ts.noInstances }} + + + + + + + + + diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 0784b68dea..a0c2635729 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -545,6 +545,21 @@ export async function selectUser() { }); } +export async function selectInstance(): Promise { + return new Promise((resolve, reject) => { + popup( + defineAsyncComponent(() => import("@/components/MkInstanceSelectDialog.vue")), + {}, + { + ok: (instance) => { + resolve(instance); + }, + }, + "closed", + ); + }); +} + export async function selectDriveFile(multiple: boolean) { return new Promise((resolve, reject) => { popup( diff --git a/packages/client/src/pages/my-antennas/create.vue b/packages/client/src/pages/my-antennas/create.vue index 794b743703..337816e003 100644 --- a/packages/client/src/pages/my-antennas/create.vue +++ b/packages/client/src/pages/my-antennas/create.vue @@ -5,7 +5,6 @@