enhance(antenna): Botの投稿を除外できるように (#13603)

* enhance(antenna): Botの投稿を除外できるように (MisskeyIO#545)

(cherry picked from commit a95ce067c6cf0a93647e358aabc984bdbe99e952)

* Update Changelog

* remove translations

* spdx

---------

Co-authored-by: まっちゃとーにゅ <17376330+u1-liquid@users.noreply.github.com>
This commit is contained in:
かっこかり 2024-03-21 07:51:01 +09:00 committed by GitHub
parent 7795045b23
commit f4838e50b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 55 additions and 2 deletions

View File

@ -1,6 +1,8 @@
## Unreleased ## Unreleased
### General ### General
- Enhance: アンテナでBotによるートを除外できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545)
- Fix: Play作成時に設定した公開範囲が機能していない問題を修正 - Fix: Play作成時に設定した公開範囲が機能していない問題を修正
### Client ### Client

4
locales/index.d.ts vendored
View File

@ -1616,6 +1616,10 @@ export interface Locale extends ILocale {
* *
*/ */
"antennaExcludeKeywords": string; "antennaExcludeKeywords": string;
/**
* Botアカウントを除外
*/
"antennaExcludeBots": string;
/** /**
* AND指定になりOR指定になります * AND指定になりOR指定になります
*/ */

View File

@ -400,6 +400,7 @@ name: "名前"
antennaSource: "受信ソース" antennaSource: "受信ソース"
antennaKeywords: "受信キーワード" antennaKeywords: "受信キーワード"
antennaExcludeKeywords: "除外キーワード" antennaExcludeKeywords: "除外キーワード"
antennaExcludeBots: "Botアカウントを除外"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります" antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しいノートを通知する" notifyAntenna: "新しいノートを通知する"
withFileAntenna: "ファイルが添付されたノートのみ" withFileAntenna: "ファイルが添付されたノートのみ"

View File

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class AntennaExcludeBots1710919614510 {
name = 'AntennaExcludeBots1710919614510'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" ADD "excludeBots" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "excludeBots"`);
}
}

View File

@ -92,7 +92,7 @@ export class AntennaService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise<void> { public async addNoteToAntennas(note: MiNote, noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<void> {
const antennas = await this.getAntennas(); const antennas = await this.getAntennas();
const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const))); const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const)));
const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna); const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna);
@ -110,10 +110,12 @@ export class AntennaService implements OnApplicationShutdown {
// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
@bindThis @bindThis
public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; }): Promise<boolean> { public async checkHitAntenna(antenna: MiAntenna, note: (MiNote | Packed<'Note'>), noteUser: { id: MiUser['id']; username: string; host: string | null; isBot: boolean; }): Promise<boolean> {
if (note.visibility === 'specified') return false; if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') return false; if (note.visibility === 'followers') return false;
if (antenna.excludeBots && noteUser.isBot) return false;
if (antenna.localOnly && noteUser.host != null) return false; if (antenna.localOnly && noteUser.host != null) return false;
if (!antenna.withReplies && note.replyId != null) return false; if (!antenna.withReplies && note.replyId != null) return false;

View File

@ -39,6 +39,7 @@ export class AntennaEntityService {
caseSensitive: antenna.caseSensitive, caseSensitive: antenna.caseSensitive,
localOnly: antenna.localOnly, localOnly: antenna.localOnly,
notify: antenna.notify, notify: antenna.notify,
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies, withReplies: antenna.withReplies,
withFile: antenna.withFile, withFile: antenna.withFile,
isActive: antenna.isActive, isActive: antenna.isActive,

View File

@ -72,6 +72,11 @@ export class MiAntenna {
}) })
public caseSensitive: boolean; public caseSensitive: boolean;
@Column('boolean', {
default: false,
})
public excludeBots: boolean;
@Column('boolean', { @Column('boolean', {
default: false, default: false,
}) })

View File

@ -76,6 +76,11 @@ export const packedAntennaSchema = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
excludeBots: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
withReplies: { withReplies: {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,

View File

@ -81,6 +81,7 @@ export class ExportAntennasProcessorService {
}) : null, }) : null,
caseSensitive: antenna.caseSensitive, caseSensitive: antenna.caseSensitive,
localOnly: antenna.localOnly, localOnly: antenna.localOnly,
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies, withReplies: antenna.withReplies,
withFile: antenna.withFile, withFile: antenna.withFile,
notify: antenna.notify, notify: antenna.notify,

View File

@ -44,6 +44,7 @@ const validate = new Ajv().compile({
} }, } },
caseSensitive: { type: 'boolean' }, caseSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' }, localOnly: { type: 'boolean' },
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' }, withReplies: { type: 'boolean' },
withFile: { type: 'boolean' }, withFile: { type: 'boolean' },
notify: { type: 'boolean' }, notify: { type: 'boolean' },
@ -88,6 +89,7 @@ export class ImportAntennasProcessorService {
users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean), users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean),
caseSensitive: antenna.caseSensitive, caseSensitive: antenna.caseSensitive,
localOnly: antenna.localOnly, localOnly: antenna.localOnly,
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies, withReplies: antenna.withReplies,
withFile: antenna.withFile, withFile: antenna.withFile,
notify: antenna.notify, notify: antenna.notify,

View File

@ -64,6 +64,7 @@ export const paramDef = {
} }, } },
caseSensitive: { type: 'boolean' }, caseSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' }, localOnly: { type: 'boolean' },
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' }, withReplies: { type: 'boolean' },
withFile: { type: 'boolean' }, withFile: { type: 'boolean' },
notify: { type: 'boolean' }, notify: { type: 'boolean' },
@ -124,6 +125,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
users: ps.users, users: ps.users,
caseSensitive: ps.caseSensitive, caseSensitive: ps.caseSensitive,
localOnly: ps.localOnly, localOnly: ps.localOnly,
excludeBots: ps.excludeBots,
withReplies: ps.withReplies, withReplies: ps.withReplies,
withFile: ps.withFile, withFile: ps.withFile,
notify: ps.notify, notify: ps.notify,

View File

@ -63,6 +63,7 @@ export const paramDef = {
} }, } },
caseSensitive: { type: 'boolean' }, caseSensitive: { type: 'boolean' },
localOnly: { type: 'boolean' }, localOnly: { type: 'boolean' },
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' }, withReplies: { type: 'boolean' },
withFile: { type: 'boolean' }, withFile: { type: 'boolean' },
notify: { type: 'boolean' }, notify: { type: 'boolean' },
@ -120,6 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
users: ps.users, users: ps.users,
caseSensitive: ps.caseSensitive, caseSensitive: ps.caseSensitive,
localOnly: ps.localOnly, localOnly: ps.localOnly,
excludeBots: ps.excludeBots,
withReplies: ps.withReplies, withReplies: ps.withReplies,
withFile: ps.withFile, withFile: ps.withFile,
notify: ps.notify, notify: ps.notify,

View File

@ -44,6 +44,7 @@ describe('アンテナ', () => {
users: [''], users: [''],
withFile: false, withFile: false,
withReplies: false, withReplies: false,
excludeBots: false,
}; };
let root: User; let root: User;
@ -156,6 +157,7 @@ describe('アンテナ', () => {
users: [''], users: [''],
withFile: false, withFile: false,
withReplies: false, withReplies: false,
excludeBots: false,
localOnly: false, localOnly: false,
}; };
assert.deepStrictEqual(response, expected); assert.deepStrictEqual(response, expected);

View File

@ -26,6 +26,7 @@ const draft = ref({
users: [], users: [],
keywords: [], keywords: [],
excludeKeywords: [], excludeKeywords: [],
excludeBots: false,
withReplies: false, withReplies: false,
caseSensitive: false, caseSensitive: false,
localOnly: false, localOnly: false,

View File

@ -26,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.users }}</template> <template #label>{{ i18n.ts.users }}</template>
<template #caption>{{ i18n.ts.antennaUsersDescription }} <button class="_textButton" @click="addUser">{{ i18n.ts.addUser }}</button></template> <template #caption>{{ i18n.ts.antennaUsersDescription }} <button class="_textButton" @click="addUser">{{ i18n.ts.addUser }}</button></template>
</MkTextarea> </MkTextarea>
<MkSwitch v-model="excludeBots">{{ i18n.ts.antennaExcludeBots }}</MkSwitch>
<MkSwitch v-model="withReplies">{{ i18n.ts.withReplies }}</MkSwitch> <MkSwitch v-model="withReplies">{{ i18n.ts.withReplies }}</MkSwitch>
<MkTextarea v-model="keywords"> <MkTextarea v-model="keywords">
<template #label>{{ i18n.ts.antennaKeywords }}</template> <template #label>{{ i18n.ts.antennaKeywords }}</template>
@ -78,6 +79,7 @@ const keywords = ref<string>(props.antenna.keywords.map(x => x.join(' ')).join('
const excludeKeywords = ref<string>(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n')); const excludeKeywords = ref<string>(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
const caseSensitive = ref<boolean>(props.antenna.caseSensitive); const caseSensitive = ref<boolean>(props.antenna.caseSensitive);
const localOnly = ref<boolean>(props.antenna.localOnly); const localOnly = ref<boolean>(props.antenna.localOnly);
const excludeBots = ref<boolean>(props.antenna.excludeBots);
const withReplies = ref<boolean>(props.antenna.withReplies); const withReplies = ref<boolean>(props.antenna.withReplies);
const withFile = ref<boolean>(props.antenna.withFile); const withFile = ref<boolean>(props.antenna.withFile);
const notify = ref<boolean>(props.antenna.notify); const notify = ref<boolean>(props.antenna.notify);
@ -94,6 +96,7 @@ async function saveAntenna() {
name: name.value, name: name.value,
src: src.value, src: src.value,
userListId: userListId.value, userListId: userListId.value,
excludeBots: excludeBots.value,
withReplies: withReplies.value, withReplies: withReplies.value,
withFile: withFile.value, withFile: withFile.value,
notify: notify.value, notify: notify.value,

View File

@ -4434,6 +4434,8 @@ export type components = {
localOnly: boolean; localOnly: boolean;
notify: boolean; notify: boolean;
/** @default false */ /** @default false */
excludeBots: boolean;
/** @default false */
withReplies: boolean; withReplies: boolean;
withFile: boolean; withFile: boolean;
isActive: boolean; isActive: boolean;
@ -9654,6 +9656,7 @@ export type operations = {
users: string[]; users: string[];
caseSensitive: boolean; caseSensitive: boolean;
localOnly?: boolean; localOnly?: boolean;
excludeBots?: boolean;
withReplies: boolean; withReplies: boolean;
withFile: boolean; withFile: boolean;
notify: boolean; notify: boolean;
@ -9935,6 +9938,7 @@ export type operations = {
users?: string[]; users?: string[];
caseSensitive?: boolean; caseSensitive?: boolean;
localOnly?: boolean; localOnly?: boolean;
excludeBots?: boolean;
withReplies?: boolean; withReplies?: boolean;
withFile?: boolean; withFile?: boolean;
notify?: boolean; notify?: boolean;