forked from naskya/firefish
feat: search posts by cw and alt texts
Co-authored-by: sup39 <dev@sup39.dev>
This commit is contained in:
parent
90a30f82be
commit
949084e218
12 changed files with 84 additions and 11 deletions
|
@ -1178,6 +1178,7 @@ useCdn: "Get assets from CDN"
|
|||
useCdnDescription: "Load some static assets like Twemoji from the JSDelivr CDN instead of this Firefish server."
|
||||
suggested: "Suggested"
|
||||
noLanguage: "No language"
|
||||
searchCwAndAlt: "Include content warnings and alt texts"
|
||||
|
||||
_sensitiveMediaDetection:
|
||||
description: "Reduces the effort of server moderation through automatically recognizing
|
||||
|
|
|
@ -2057,3 +2057,4 @@ searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@e
|
|||
searchRange: "投稿期間(オプション)"
|
||||
searchRangeDescription: "投稿検索で投稿期間を絞りたい場合、20220615-20231031 のような形式で投稿期間を入力してください。今年の日付を指定する場合には年の指定を省略できます(0105-0106 や 20231105-0110 のように)。\n\n開始日と終了日のどちらか一方は省略可能です。例えば -0102 とすると今年1月2日までの投稿のみを、20231026- とすると2023年10月26日以降の投稿のみを検索します。"
|
||||
searchPostsWithFiles: "添付ファイルのある投稿のみ"
|
||||
searchCwAndAlt: "閲覧注意の注釈と添付ファイルの代替テキストも検索する"
|
||||
|
|
|
@ -2050,3 +2050,4 @@ searchUsersDescription: "如欲搜尋特定使用者的貼文,請以「@user@e
|
|||
searchWords: "搜尋關鍵字 / 查詢ID或URL"
|
||||
searchWordsDescription: "如欲搜尋貼文,請在此欄位輸入欲搜尋的關鍵字。以空格分隔關鍵字以進行AND搜尋、在關鍵字之間插入「OR」以進行OR搜尋。\n舉例來說,輸入「早上 晚上」會搜尋包含「早上」和「晚上」的貼文,「早上 OR 晚上」會搜尋包含「早上」或「晚上」(或兩者皆包含)的貼文。\n\n如欲前往特定使用者或貼文的頁面,請在此欄位輸入使用者ID(@user@example.com)或貼文的URL,並點擊「查詢」按鈕。點擊「搜尋」按鈕則會搜尋字面上包含輸入的ID或URL的貼文。"
|
||||
suggested: "建議"
|
||||
searchCwAndAlt: "包含內容警告及替代文字"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
BEGIN;
|
||||
|
||||
DELETE FROM "migrations" WHERE name IN (
|
||||
'IndexAltTextAndCw1708872574733',
|
||||
'SeparateHardMuteWordsAndPatterns1706413792769',
|
||||
'RenameMetaColumns1705944717480',
|
||||
'RemoveNativeUtilsMigration1705877093218',
|
||||
|
@ -13,6 +14,10 @@ DELETE FROM "migrations" WHERE name IN (
|
|||
'EmojiModerator1692825433698'
|
||||
);
|
||||
|
||||
-- index-alt-text-and-cw
|
||||
DROP INDEX "IDX_f4f7b93d05958527300d79ac82";
|
||||
DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f";
|
||||
|
||||
-- separate-hard-mute-words-and-patterns
|
||||
UPDATE "user_profile" SET "mutedWords" = "mutedWords" || array_to_json("mutedPatterns")::jsonb;
|
||||
ALTER TABLE "user_profile" DROP "mutedPatterns";
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
export class IndexAltTextAndCw1708872574733 {
|
||||
name = "IndexAltTextAndCw1708872574733";
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2)`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_f4f7b93d05958527300d79ac82" ON "drive_file" USING "pgroonga" ("comment" pgroonga_varchar_full_text_search_ops_v2)`,
|
||||
);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`DROP INDEX "IDX_f4f7b93d05958527300d79ac82"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_8e3bbbeb3df04d1a8105da4c8f"`);
|
||||
}
|
||||
}
|
|
@ -70,6 +70,7 @@ export class DriveFile {
|
|||
})
|
||||
public size: number;
|
||||
|
||||
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
|
||||
@Column("varchar", {
|
||||
length: DB_MAX_IMAGE_COMMENT_LENGTH,
|
||||
nullable: true,
|
||||
|
|
|
@ -79,6 +79,7 @@ export class Note {
|
|||
})
|
||||
public name: string | null;
|
||||
|
||||
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
|
||||
@Column("varchar", {
|
||||
length: 512,
|
||||
nullable: true,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// import { FindManyOptions, In } from "typeorm";
|
||||
import { Brackets } from "typeorm";
|
||||
import { Notes } from "@/models/index.js";
|
||||
import { Note } from "@/models/entities/note.js";
|
||||
// import config from "@/config/index.js";
|
||||
|
@ -56,6 +57,7 @@ export const paramDef = {
|
|||
default: null,
|
||||
},
|
||||
withFiles: { type: "boolean", nullable: true },
|
||||
searchCwAndAlt: { type: "boolean", nullable: true },
|
||||
channelId: {
|
||||
type: "string",
|
||||
format: "misskey:id",
|
||||
|
@ -92,7 +94,28 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
}
|
||||
|
||||
if (ps.query != null) {
|
||||
query.andWhere("note.text &@~ :q", { q: `${sqlLikeEscape(ps.query)}` });
|
||||
const q = sqlLikeEscape(ps.query);
|
||||
|
||||
if (ps.searchCwAndAlt) {
|
||||
query.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where("note.text &@~ :q", { q })
|
||||
.orWhere("note.cw &@~ :q", { q })
|
||||
.orWhere(
|
||||
`EXISTS (
|
||||
SELECT FROM "drive_file"
|
||||
WHERE
|
||||
comment &@~ :q
|
||||
AND
|
||||
drive_file."id" = ANY(note."fileIds")
|
||||
)`,
|
||||
{ q },
|
||||
);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
query.andWhere("note.text &@~ :q", { q });
|
||||
}
|
||||
}
|
||||
|
||||
query.innerJoinAndSelect("note.user", "user");
|
||||
|
|
|
@ -68,6 +68,12 @@
|
|||
:class="$style.input"
|
||||
>{{ i18n.ts.searchPostsWithFiles }}</FormSwitch
|
||||
>
|
||||
<FormSwitch
|
||||
v-model="searchCwAndAlt"
|
||||
class="form-switch"
|
||||
:class="$style.input"
|
||||
>{{ i18n.ts.searchCwAndAlt }}</FormSwitch
|
||||
>
|
||||
<div :class="$style.buttons">
|
||||
<MkButton inline primary @click="search"
|
||||
>{{ i18n.ts.search }}
|
||||
|
@ -128,7 +134,8 @@ const searchRange = ref(
|
|||
}`
|
||||
: "",
|
||||
);
|
||||
const searchPostsWithFiles = ref(searchParams.get("withFiles") === "true");
|
||||
const searchPostsWithFiles = ref(searchParams.get("withFiles") === "1");
|
||||
const searchCwAndAlt = ref(searchParams.get("detailed") === "1");
|
||||
|
||||
function done(canceled: boolean, result?: searchQuery) {
|
||||
emit("done", { canceled, result });
|
||||
|
@ -149,6 +156,7 @@ function search() {
|
|||
from: searchUsers.value === "" ? undefined : searchUsers.value,
|
||||
range: searchRange.value === "" ? undefined : searchRange.value,
|
||||
withFiles: searchPostsWithFiles.value,
|
||||
searchCwAndAlt: searchCwAndAlt.value,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,8 @@ const props = defineProps<{
|
|||
since?: string;
|
||||
until?: string;
|
||||
channel?: string;
|
||||
withFiles: "false" | "true";
|
||||
withFiles: "0" | "1";
|
||||
searchCwAndAlt: "0" | "1";
|
||||
}>();
|
||||
|
||||
const userId = props.user == null ? undefined : await getUserId(props.user);
|
||||
|
@ -79,7 +80,8 @@ const notesPagination = {
|
|||
props.since == null ? undefined : getUnixTime(props.since, false),
|
||||
untilDate:
|
||||
props.until == null ? undefined : getUnixTime(props.until, true),
|
||||
withFiles: props.withFiles === "true",
|
||||
withFiles: props.withFiles === "1",
|
||||
searchCwAndAlt: props.searchCwAndAlt === "1",
|
||||
channelId: props.channel,
|
||||
})),
|
||||
};
|
||||
|
|
|
@ -310,6 +310,7 @@ export const routes = [
|
|||
until: "until",
|
||||
withFiles: "withFiles",
|
||||
channel: "channel",
|
||||
detailed: "searchCwAndAlt",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -21,6 +21,7 @@ export async function search() {
|
|||
from?: string;
|
||||
range?: string;
|
||||
withFiles: boolean;
|
||||
searchCwAndAlt: boolean;
|
||||
};
|
||||
}
|
||||
>((resolve, _) => {
|
||||
|
@ -69,24 +70,35 @@ export async function search() {
|
|||
}
|
||||
|
||||
if (result.action === "search") {
|
||||
let paramString = `withFiles=${result.withFiles ? "true" : "false"}`;
|
||||
const params = new URLSearchParams();
|
||||
|
||||
params.append("withFiles", result.withFiles ? "1" : "0");
|
||||
|
||||
if (result.query != null) {
|
||||
paramString += `&q=${encodeURIComponent(result.query)}`;
|
||||
params.append("q", result.query);
|
||||
}
|
||||
|
||||
if (result.from != null) {
|
||||
if (result.from === "me" || result.from.includes("@"))
|
||||
paramString += `&user=${encodeURIComponent(result.from)}`;
|
||||
else paramString += `&host=${encodeURIComponent(result.from)}`;
|
||||
params.append("user", result.from);
|
||||
else params.append("host", result.from);
|
||||
}
|
||||
|
||||
if (result.range != null) {
|
||||
const split = result.range.split("-");
|
||||
if (split[0] !== "") paramString += `&since=${split[0]}`;
|
||||
if (split[1] !== "") paramString += `&until=${split[1]}`;
|
||||
if (split.length === 1) {
|
||||
params.append("since", result.range);
|
||||
params.append("until", result.range);
|
||||
} else {
|
||||
if (split[0] !== "") params.append("since", split[0]);
|
||||
if (split[1] !== "") params.append("until", split[1]);
|
||||
}
|
||||
}
|
||||
|
||||
mainRouter.push(`/search?${paramString}`);
|
||||
if (result.searchCwAndAlt) {
|
||||
params.append("detailed", "1");
|
||||
}
|
||||
|
||||
mainRouter.push(`/search?${params.toString()}`);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue