diff --git a/CHANGELOG_YOJO.md b/CHANGELOG_YOJO.md index bd44bb9c0f..f52ca02295 100644 --- a/CHANGELOG_YOJO.md +++ b/CHANGELOG_YOJO.md @@ -25,6 +25,7 @@ Cherrypick 4.11.1 - Enhance: ノートにつけられたリアクションを対象にした検索ができるように - Opensearchのみ対応 - Opensearchの設定で` reactionSearchLocalOnly: true`にすることでリモートのカスタム絵文字リアクションをインデックス対象外にできます +- Enhance: 高度な検索でフォロー中/フォロー外を検索条件にできるように - Fix: 照会かリモートユーザーの投稿取得で作成されたノートの場合通知を発行しないように - Enhance(Opensearch): 表記ゆれがヒットしないようにするオプションを追加 diff --git a/locales/index.d.ts b/locales/index.d.ts index 7ebb9ae38e..128a90b568 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -11891,6 +11891,24 @@ export interface Locale extends ILocale { */ "endDate": string; }; + "_followingFilter": { + /** + * フォローフィルター + */ + "title": string; + /** + * フィルタしない + */ + "combined": string; + /** + * フォロー中 + */ + "following": string; + /** + * フォロー外 + */ + "notFollowing": string; + }; "_description": { /** * その他の設定 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 562ae36714..b3a5a5d4a9 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -3169,6 +3169,11 @@ _advancedSearch: _specifyDate: startDate: "から" endDate: "まで" + _followingFilter: + title: "フォローフィルター" + combined: "フィルタしない" + following: "フォロー中" + notFollowing: "フォロー外" _description: other: "その他の設定" _fileNsfwOption: diff --git a/packages/backend/src/core/AdvancedSearchService.ts b/packages/backend/src/core/AdvancedSearchService.ts index d831fd9dc6..d1b16ef0e9 100644 --- a/packages/backend/src/core/AdvancedSearchService.ts +++ b/packages/backend/src/core/AdvancedSearchService.ts @@ -782,6 +782,7 @@ export class AdvancedSearchService { excludeReply?: boolean; excludeQuote?: boolean; sensitiveFilter?: string | null; + followingFilter?: string | null; offset?: number | null; useStrictSearch?: boolean | null; }, pagination: { @@ -920,7 +921,7 @@ export class AdvancedSearchService { }); } - const Result = await this.search(Option, pagination.untilId ? 1 : 0, me ? me.id : undefined); + const Result = await this.search(Option, pagination.untilId ? 1 : 0, opts.followingFilter ?? 'combined', me ? me.id : undefined); if (Result.length === 0) { return []; } @@ -997,7 +998,11 @@ export class AdvancedSearchService { } } - this.queryService.generateVisibilityQuery(query, me); + if (opts.followingFilter) { + this.queryService.generateVisibilityQuery(query, me, opts.followingFilter); + } else { + this.queryService.generateVisibilityQuery(query, me); + } this.queryService.generateSearchableQuery(query, me); if (me) this.queryService.generateMutedUserQuery(query, me); if (me) this.queryService.generateBlockedUserQuery(query, me); @@ -1010,6 +1015,7 @@ export class AdvancedSearchService { private async search( OpenSearchOption: any, untilAvail: number, + followingFilter: string, meUserId?: string, ): Promise { if (!this.opensearch) throw new Error(); @@ -1030,7 +1036,7 @@ export class AdvancedSearchService { notes = res.body.hits.hits as OpenSearchHit[]; if (notes.length === 0) break;//これ以上探してもない - const resultPromises = notes.map(x => this.filter(x, Filter, Followings, meUserId)); + const resultPromises = notes.map(x => this.filter(x, Filter, Followings, followingFilter, meUserId)); const Results = (await Promise.all(resultPromises)).filter( (x) => x !== null); if (Results.length > 0) { @@ -1060,12 +1066,16 @@ export class AdvancedSearchService { Note: OpenSearchHit, Filter: string[], Followings: string[], - meUserId?: string): Promise { + followingFilter: string, + meUserId?: string ): Promise { if (meUserId) {//ミュートしているか、ブロックされている if (Filter.includes(Note._source.userId) ) return null; if (Note._source.referenceUserId) { if (Filter.includes(Note._source.referenceUserId)) return null; } + if (followingFilter === 'following' && !Followings.includes(Note._source.userId)) return null; + if (followingFilter === 'notFollowing' && Followings.includes(Note._source.userId)) return null; + if (Note._source.userId === meUserId) {//自分のノート return Note; } diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts index 2faee8a460..eb7f9518e0 100644 --- a/packages/backend/src/core/QueryService.ts +++ b/packages/backend/src/core/QueryService.ts @@ -188,7 +188,7 @@ export class QueryService { } @bindThis - public generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: MiUser['id'] } | null): void { + public generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: MiUser['id'] } | null, followingFilter?: string): void { // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { q.andWhere(new Brackets(qb => { @@ -201,6 +201,11 @@ export class QueryService { .select('following.followeeId') .where('following.followerId = :meId'); + if (followingFilter) { + if (followingFilter === 'following') q.andWhere(`note.userId IN (${ followingQuery.getQuery() })`); + if (followingFilter === 'notFollowing') q.andWhere(`note.userId NOT IN (${ followingQuery.getQuery() })`); + } + q.andWhere(new Brackets(qb => { qb // 公開投稿である diff --git a/packages/backend/src/server/api/endpoints/notes/advanced-search.ts b/packages/backend/src/server/api/endpoints/notes/advanced-search.ts index 63410cffd6..352f7e1b72 100644 --- a/packages/backend/src/server/api/endpoints/notes/advanced-search.ts +++ b/packages/backend/src/server/api/endpoints/notes/advanced-search.ts @@ -100,6 +100,12 @@ export const paramDef = { default: 'combined', description: '添付ファイルのセンシティブ状態', }, + followingFilter: { + type: 'string', + enum: ['following', 'notFollowing', 'combined'], + default: 'combined', + description: 'ユーザーのフォロー状態', + }, offset: { type: 'integer', default: 0, @@ -163,6 +169,7 @@ export default class extends Endpoint { origin: ps.origin, fileOption: ps.fileOption, sensitiveFilter: ps.sensitiveFilter, + followingFilter: ps.followingFilter, excludeCW: ps.excludeCW, excludeReply: ps.excludeReply, excludeQuote: ps.excludeQuote, diff --git a/packages/cherrypick-js/src/autogen/types.ts b/packages/cherrypick-js/src/autogen/types.ts index c16ea14855..0f3d2ab3ca 100644 --- a/packages/cherrypick-js/src/autogen/types.ts +++ b/packages/cherrypick-js/src/autogen/types.ts @@ -23621,6 +23621,12 @@ export type operations = { * @enum {string} */ sensitiveFilter?: 'includeSensitive' | 'withOutSensitive' | 'sensitiveOnly' | 'combined'; + /** + * @description ユーザーのフォロー状態 + * @default combined + * @enum {string} + */ + followingFilter?: 'following' | 'notFollowing' | 'combined'; /** * @description 指定された件数の以降のノートを返します * @default 0 diff --git a/packages/frontend/src/pages/search.anote.vue b/packages/frontend/src/pages/search.anote.vue index 61a463a89a..955619327b 100644 --- a/packages/frontend/src/pages/search.anote.vue +++ b/packages/frontend/src/pages/search.anote.vue @@ -66,6 +66,16 @@ SPDX-License-Identifier: AGPL-3.0-only + + +
+ + + + + +
+
@@ -125,6 +135,7 @@ const excludeCW = ref(false); const excludeReply = ref(false); const excludeQuote = ref(false); const sensitiveFilter = ref('combined'); +const followingFilter = ref('combined'); const hostInput = ref(''); const emojiSearchQuery = ref(''); const emojiExcludeSearchQuery = ref(''); @@ -228,6 +239,7 @@ async function search() { excludeReply: excludeReply.value, excludeQuote: excludeQuote.value, sensitiveFilter: sensitiveFilter.value, + followingFilter: followingFilter.value, useStrictSearch: strictSearch.value, }, };