Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: アンテナでセンシティブなチャンネルからのノートを除外できるように #15346

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Unreleased

### General
-
- Enhance: アンテナでセンシティブなチャンネルのノートを除外できるように ( #14177 )

### Client
-
Expand Down
1 change: 1 addition & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ antennaExcludeBots: "Exclude bot accounts"
antennaKeywordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
notifyAntenna: "Notify about new notes"
withFileAntenna: "Only notes with files"
hideNotesInSensitiveChannel: "Hide notes in sensitive channels"
enableServiceworker: "Enable Push-Notifications for your Browser"
antennaUsersDescription: "List one username per line"
caseSensitive: "Case sensitive"
Expand Down
4 changes: 4 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1718,6 +1718,10 @@ export interface Locale extends ILocale {
* ファイルが添付されたノートのみ
*/
"withFileAntenna": string;
/**
* センシティブなチャンネルのノートを非表示
*/
"hideNotesInSensitiveChannel": string;
/**
* ブラウザへのプッシュ通知を有効にする
*/
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ antennaExcludeBots: "Botアカウントを除外"
antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
notifyAntenna: "新しいノートを通知する"
withFileAntenna: "ファイルが添付されたノートのみ"
hideNotesInSensitiveChannel: "センシティブなチャンネルのノートを非表示"
enableServiceworker: "ブラウザへのプッシュ通知を有効にする"
antennaUsersDescription: "ユーザー名を改行で区切って指定します"
caseSensitive: "大文字小文字を区別する"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class AddAntennaHideNotesInSensitiveChannel1736230492103 {
name = 'AddAntennaHideNotesInSensitiveChannel1736230492103'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" ADD "hideNotesInSensitiveChannel" boolean NOT NULL DEFAULT false`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "hideNotesInSensitiveChannel"`);
}
}
2 changes: 2 additions & 0 deletions packages/backend/src/core/AntennaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export class AntennaService implements OnApplicationShutdown {
if (note.visibility === 'specified') return false;
if (note.visibility === 'followers') return false;

if (antenna.hideNotesInSensitiveChannel && note.channel?.isSensitive) return false;

if (antenna.excludeBots && noteUser.isBot) return false;

if (antenna.localOnly && noteUser.host != null) return false;
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ export class NoteCreateService implements OnApplicationShutdown {
replyId: data.reply ? data.reply.id : null,
renoteId: data.renote ? data.renote.id : null,
channelId: data.channel ? data.channel.id : null,
channel: data.channel,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

センシティブチャンネルかどうかの情報を取得するためにここのコンストラクタにチャンネルを渡していますが、もしかしたらORM的にはよくないかもしれません。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

channneldを挿入しているのでここでchannelを追加せずともチャンネル情報はjoinして取得することが可能となってはいないでしょうか

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

センシティブチャンネルかどうかを取得するcheckHitAntennaでjoinのクエリをするとクエリがアンテナの数だけ行われて結構負荷がかかりそうなんですよね。
そもそもここでchannel: data.channelをしておけばクエリ数が増えることも無いのでここに増やしました。

が、これはもしかしたら何らかのしきたりに反するかもしれないな...というのが上のコメントの意図でした(ごめんなさい)。

threadId: data.reply
? data.reply.threadId
? data.reply.threadId
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/core/entities/AntennaEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class AntennaEntityService {
excludeBots: antenna.excludeBots,
withReplies: antenna.withReplies,
withFile: antenna.withFile,
hideNotesInSensitiveChannel: antenna.hideNotesInSensitiveChannel,
isActive: antenna.isActive,
hasUnreadNote: false, // TODO
notify: false, // 後方互換性のため
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/models/Antenna.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,9 @@ export class MiAntenna {
default: false,
})
public localOnly: boolean;

@Column('boolean', {
default: false,
})
public hideNotesInSensitiveChannel: boolean;
}
5 changes: 5 additions & 0 deletions packages/backend/src/models/json-schema/antenna.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,10 @@ export const packedAntennaSchema = {
optional: false, nullable: false,
default: false,
},
hideNotesInSensitiveChannel: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
},
} as const;
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints/antennas/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const paramDef = {
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' },
withFile: { type: 'boolean' },
hideNotesInSensitiveChannel: { type: 'boolean' },
},
required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'],
} as const;
Expand Down Expand Up @@ -133,6 +134,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
excludeBots: ps.excludeBots,
withReplies: ps.withReplies,
withFile: ps.withFile,
hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel,
});

this.globalEventService.publishInternalEvent('antennaCreated', antenna);
Expand Down
12 changes: 12 additions & 0 deletions packages/backend/src/server/api/endpoints/antennas/notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('reply.user', 'replyUser')
.leftJoinAndSelect('renote.user', 'renoteUser');

if (antenna.hideNotesInSensitiveChannel) {
// TypeORMにはRIGHT JOINがないので、サブクエリで代用。
Copy link
Contributor Author

@sevenc-nanashi sevenc-nanashi Jan 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「channelIdがNULL、またはchannelIdの指すchannelのisSensitiveがfalse」を「channelIdがNULL、またはisSensitive = falseなid = channelIdのchannelが存在する」で代用しました。

query
.andWhere('note.channelId IS NULL OR EXISTS(' +
query.subQuery()
.from('channel', 'channel')
.where('channel.id = note.channelId')
.andWhere('channel.isSensitive = false')
.getQuery() +
' )');
}

Comment on lines +116 to +127
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このクエリ、悩ましいですね…
NoteCreateServiceでredisに積む段階で既にフィルタ済みなので、

  • redisに積んでから設定が変わったチャンネルのノートを取り除く
  • redisに積んでからアンテナの設定を変えてたことにより除外対象となったチャンネルのノートを取り除く

というケースでしか作用せず、コスト対効果がいまいちな気がしております。
(ブロック・ミュートとは異なり、どちらもそうそう変えるものでもないと思われるため)

@syuilo どうでしょう

this.queryService.generateVisibilityQuery(query, me);
this.queryService.generateMutedUserQuery(query, me);
this.queryService.generateBlockedUserQuery(query, me);
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/server/api/endpoints/antennas/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const paramDef = {
excludeBots: { type: 'boolean' },
withReplies: { type: 'boolean' },
withFile: { type: 'boolean' },
hideNotesInSensitiveChannel: { type: 'boolean' },
},
required: ['antennaId'],
} as const;
Expand Down Expand Up @@ -129,6 +130,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
excludeBots: ps.excludeBots,
withReplies: ps.withReplies,
withFile: ps.withFile,
hideNotesInSensitiveChannel: ps.hideNotesInSensitiveChannel,
isActive: true,
lastUsedAt: new Date(),
});
Expand Down
39 changes: 39 additions & 0 deletions packages/backend/test/e2e/antennas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ describe('アンテナ', () => {
caseSensitive: false,
createdAt: new Date(response.createdAt).toISOString(),
excludeKeywords: [['']],
hideNotesInSensitiveChannel: false,
hasUnreadNote: false,
isActive: true,
keywords: [['keyword']],
Expand Down Expand Up @@ -217,6 +218,8 @@ describe('アンテナ', () => {
{ parameters: () => ({ withReplies: true }) },
{ parameters: () => ({ withFile: false }) },
{ parameters: () => ({ withFile: true }) },
{ parameters: () => ({ hideNotesInSensitiveChannel: false }) },
{ parameters: () => ({ hideNotesInSensitiveChannel: true }) },
];
test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => {
const response = await successfulApiCall({
Expand Down Expand Up @@ -626,6 +629,42 @@ describe('アンテナ', () => {
assert.deepStrictEqual(response, expected);
});

test('が取得できること(センシティブチャンネルのノートを除く)', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これだけチャンネル作成が必要になってくるので上のeachとは別に書いています。

const keyword = 'キーワード';
const antenna = await successfulApiCall({
endpoint: 'antennas/create',
parameters: { ...defaultParam, keywords: [[keyword]], hideNotesInSensitiveChannel: true },
user: alice,
});
const nonSensitiveChannel = await successfulApiCall({
endpoint: 'channels/create',
parameters: { name: 'test', isSensitive: false },
user: alice,
});
const sensitiveChannel = await successfulApiCall({
endpoint: 'channels/create',
parameters: { name: 'test', isSensitive: true },
user: alice,
});

const noteInLocal = await post(bob, { text: `test ${keyword}` });
const noteInNonSensitiveChannel = await post(bob, { text: `test ${keyword}`, channelId: nonSensitiveChannel.id });
await post(bob, { text: `test ${keyword}`, channelId: sensitiveChannel.id });

const response = await successfulApiCall({
endpoint: 'antennas/notes',
parameters: { antennaId: antenna.id },
user: alice,
});
// 最後に投稿したものが先頭に来る。
const expected = [
noteInNonSensitiveChannel,
noteInLocal,
];
assert.deepStrictEqual(response, expected);
});


test.skip('が取得でき、日付指定のPaginationに一貫性があること', async () => { });
test.each([
{ label: 'ID指定', offsetBy: 'id' },
Expand Down
4 changes: 4 additions & 0 deletions packages/frontend/src/components/MkAntennaEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
<MkSwitch v-model="caseSensitive">{{ i18n.ts.caseSensitive }}</MkSwitch>
<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
<MkSwitch v-model="hideNotesInSensitiveChannel">{{ i18n.ts.hideNotesInSensitiveChannel }}</MkSwitch>
</div>
<div :class="$style.actions">
<div class="_buttons">
Expand Down Expand Up @@ -86,6 +87,7 @@ const initialAntenna = deepMerge<PartialAllowedAntenna>(props.antenna ?? {}, {
caseSensitive: false,
localOnly: false,
withFile: false,
hideNotesInSensitiveChannel: false,
isActive: true,
hasUnreadNote: false,
notify: false,
Expand All @@ -108,6 +110,7 @@ const localOnly = ref<boolean>(initialAntenna.localOnly);
const excludeBots = ref<boolean>(initialAntenna.excludeBots);
const withReplies = ref<boolean>(initialAntenna.withReplies);
const withFile = ref<boolean>(initialAntenna.withFile);
const hideNotesInSensitiveChannel = ref<boolean>(initialAntenna.hideNotesInSensitiveChannel);
const userLists = ref<Misskey.entities.UserList[] | null>(null);

watch(() => src.value, async () => {
Expand All @@ -124,6 +127,7 @@ async function saveAntenna() {
excludeBots: excludeBots.value,
withReplies: withReplies.value,
withFile: withFile.value,
hideNotesInSensitiveChannel: hideNotesInSensitiveChannel.value,
caseSensitive: caseSensitive.value,
localOnly: localOnly.value,
users: users.value.trim().split('\n').map(x => x.trim()),
Expand Down
4 changes: 4 additions & 0 deletions packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4654,6 +4654,8 @@ export type components = {
hasUnreadNote: boolean;
/** @default false */
notify: boolean;
/** @default false */
hideNotesInSensitiveChannel: boolean;
};
Clip: {
/**
Expand Down Expand Up @@ -10918,6 +10920,7 @@ export type operations = {
excludeBots?: boolean;
withReplies: boolean;
withFile: boolean;
hideNotesInSensitiveChannel?: boolean;
};
};
};
Expand Down Expand Up @@ -11199,6 +11202,7 @@ export type operations = {
excludeBots?: boolean;
withReplies?: boolean;
withFile?: boolean;
hideNotesInSensitiveChannel?: boolean;
};
};
};
Expand Down
Loading