feat: アンテナでセンシティブなチャンネルからのノートを除外できるように (#15346)
* feat(db): マイグレーションを追加 * feat(backend): カラムの定義を追加 * wip * feat: フラグを設定出来るように * feat: /notesエンドポイントを対応 * feat: websocketを対応 * test: テストを追加 * docs: CHANGELOGを更新 * docs: CHANGELOGの追加場所を修正 * chore: api.jsonを更新 * docs(CHANGELOG): General欄に移動 * docs: フォーマットを揃える * chore: クエリを削除 * revert: 英訳を消す * chore: note.channelを追加するところを変える * docs: CHANGELOGを更新する * docs(CHANGELOG): 2025.3.2に移動 * chore: changelogを下に移動 * ci: CI再実行用の空コミット --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
0bf49818c4
commit
98eadd7093
15 changed files with 93 additions and 1 deletions
|
|
@ -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"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -532,7 +532,10 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
|
||||
this.pushToTl(note, user);
|
||||
|
||||
this.antennaService.addNoteToAntennas(note, user);
|
||||
this.antennaService.addNoteToAntennas({
|
||||
...note,
|
||||
channel: data.channel ?? null,
|
||||
}, user);
|
||||
|
||||
if (data.reply) {
|
||||
this.saveReply(data.reply, note);
|
||||
|
|
|
|||
|
|
@ -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, // 後方互換性のため
|
||||
|
|
|
|||
|
|
@ -100,4 +100,9 @@ export class MiAntenna {
|
|||
default: false,
|
||||
})
|
||||
public localOnly: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public hideNotesInSensitiveChannel: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,5 +100,10 @@ export const packedAntennaSchema = {
|
|||
optional: false, nullable: false,
|
||||
default: false,
|
||||
},
|
||||
hideNotesInSensitiveChannel: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
.leftJoinAndSelect('reply.user', 'replyUser')
|
||||
.leftJoinAndSelect('renote.user', 'renoteUser');
|
||||
|
||||
// NOTE: センシティブ除外の設定はこのエンドポイントでは無視する。
|
||||
// https://github.com/misskey-dev/misskey/pull/15346#discussion_r1929950255
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
this.queryService.generateMutedUserQueryForNotes(query, me);
|
||||
this.queryService.generateBlockedUserQueryForNotes(query, me);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export const paramDef = {
|
|||
excludeBots: { type: 'boolean' },
|
||||
withReplies: { type: 'boolean' },
|
||||
withFile: { type: 'boolean' },
|
||||
hideNotesInSensitiveChannel: { type: 'boolean' },
|
||||
},
|
||||
required: ['antennaId'],
|
||||
} as const;
|
||||
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ describe('アンテナ', () => {
|
|||
caseSensitive: false,
|
||||
createdAt: new Date(response.createdAt).toISOString(),
|
||||
excludeKeywords: [['']],
|
||||
hideNotesInSensitiveChannel: false,
|
||||
hasUnreadNote: false,
|
||||
isActive: true,
|
||||
keywords: [['keyword']],
|
||||
|
|
@ -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({
|
||||
|
|
@ -626,6 +629,42 @@ describe('アンテナ', () => {
|
|||
assert.deepStrictEqual(response, expected);
|
||||
});
|
||||
|
||||
test('が取得できること(センシティブチャンネルのノートを除く)', async () => {
|
||||
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' },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue