Store embed titles and search them

This commit is contained in:
Fisto
2026-09-17 13:10:16 +02:00
parent c6caa168be
commit 557ba5ca7e
13 changed files with 200 additions and 10 deletions
+37 -1
View File
@@ -291,6 +291,11 @@
if (value === "" || !Number.isFinite(number) || number < 0) return "";
return String(Math.round(number * (unit ? sizeUnit(unit) : 1)));
};
const title = state.filters.title.trim()
? `title=${encodeURIComponent(state.filters.title.trim())}&${
state.filters.titleExact ? "titleExact=1&" : ""
}`
: "";
const sizeLt = state.filters.sizeLt !== "" ? `sizeLt=${amount(state.filters.sizeLt, state.filters.sizeLtUnit)}&` : "";
const sizeGt = state.filters.sizeGt !== "" ? `sizeGt=${amount(state.filters.sizeGt, state.filters.sizeGtUnit)}&` : "";
const lenLt = state.filters.lenLt !== "" ? `lenLt=${amount(state.filters.lenLt)}&` : "";
@@ -301,7 +306,7 @@
const before = state.filters.before ? `before=${state.filters.before}T23:59:59.999Z&` : "";
return (
`${kind}${source}${channel}${authors}${origin}${cached}${exts}${excludeHosts}` +
`${sort}${excludeProviders}${excludeSources}${excludeExts}` +
`${sort}${title}${excludeProviders}${excludeSources}${excludeExts}` +
`${includeProviders}${includeSources}${sizeLt}${sizeGt}${lenLt}${lenGt}` +
`${aspect}${resolution}${after}${before}`
);
@@ -1044,6 +1049,7 @@
function renderCard(item) {
const figure = el("figure", "vk-media-figure");
if (item.title) figure.title = item.title;
const media = el("div", "vk-media-media");
const imageSource = item.localUrl || item.thumbUrl;
const fallbackSource = item.source !== "embed" ? item.url : null;
@@ -1174,6 +1180,8 @@
exts: [],
excludeHosts: ["4chan.org", "i.4cdn.org"],
sort: "newest",
title: "",
titleExact: false,
excludeProviders: [],
excludeSources: [],
includeProviders: [],
@@ -1204,6 +1212,8 @@
excludeSources: [...state.filters.excludeSources],
includeProviders: [...state.filters.includeProviders],
includeSources: [...state.filters.includeSources],
title: state.filters.title,
titleExact: state.filters.titleExact,
excludeExts: [...state.filters.excludeExts]
};
}
@@ -1224,6 +1234,8 @@
excludeSources: [...state.draft.excludeSources],
includeProviders: [...state.draft.includeProviders],
includeSources: [...state.draft.includeSources],
title: state.draft.title,
titleExact: state.draft.titleExact,
excludeExts: [...state.draft.excludeExts],
sizeLt: state.draft.sizeLt,
sizeGt: state.draft.sizeGt,
@@ -1382,6 +1394,30 @@
draft.sort = value;
}
),
(() => {
const row = el("div", "vk-media-toolbar");
const input = document.createElement("input");
input.type = "text";
input.className = "vk-media-input";
input.placeholder = "Video title (Ma Ca Ju finds Man ... car jumps)";
input.value = draft.title;
input.addEventListener("input", () => {
draft.title = input.value;
});
row.append(input);
const exact = el("label", "vk-media-check");
const box = document.createElement("input");
box.type = "checkbox";
box.checked = draft.titleExact;
box.title = "Match the whole phrase instead of per-word prefixes";
box.addEventListener("change", () => {
draft.titleExact = box.checked;
});
exact.append(box, document.createTextNode(" Exact match (quotes)"));
row.append(exact);
return row;
})(),
filterSelect(
[
["", "Any ratio"],
+9
View File
@@ -904,6 +904,11 @@ function buildServer(): McpServer {
.array(z.string())
.optional()
.describe("Keep only these row sources (attachment, sticker, embed)"),
titleQuery: z
.string()
.optional()
.describe("Search embed titles (YouTube). Multi-word prefix match by default"),
titleExact: z.boolean().optional().describe("Treat titleQuery as one exact phrase"),
sizeLessThan: z.number().optional().describe("Only items smaller than this many bytes"),
sizeGreaterThan: z.number().optional().describe("Only items larger than this many bytes"),
lengthLessThan: z.number().optional().describe("Only items shorter than this many seconds"),
@@ -941,6 +946,8 @@ function buildServer(): McpServer {
excludeSources,
includeProviders,
includeSources,
titleQuery,
titleExact,
sizeLessThan,
sizeGreaterThan,
lengthLessThan,
@@ -969,6 +976,8 @@ function buildServer(): McpServer {
excludeSources,
includeProviders,
includeSources,
titleQuery,
titleExact,
sizeLessThan,
sizeGreaterThan,
lengthLessThan,
+11
View File
@@ -135,6 +135,8 @@ export interface MediaItemResult {
thumbUrl: string | null;
provider: string | null;
downloaded: boolean;
/** Embed title (YouTube and friends), when Discord provided one. */
title: string | null;
createdAt: string;
}
@@ -719,6 +721,8 @@ export class MediaService {
excludeSources?: string[];
includeProviders?: string[];
includeSources?: string[];
titleQuery?: string;
titleExact?: boolean;
sizeLessThan?: number;
sizeGreaterThan?: number;
lengthLessThan?: number;
@@ -850,6 +854,8 @@ export class MediaService {
excludeSources?: string[];
includeProviders?: string[];
includeSources?: string[];
titleQuery?: string;
titleExact?: boolean;
sizeLessThan?: number;
sizeGreaterThan?: number;
lengthLessThan?: number;
@@ -887,6 +893,8 @@ export class MediaService {
excludeSources: options.excludeSources,
includeProviders: options.includeProviders,
includeSources: options.includeSources,
titleQuery: options.titleQuery,
titleExact: options.titleExact,
sizeLessThan: options.sizeLessThan,
sizeGreaterThan: options.sizeGreaterThan,
lengthLessThan: options.lengthLessThan,
@@ -949,6 +957,8 @@ export class MediaService {
excludeSources?: string[];
includeProviders?: string[];
includeSources?: string[];
titleQuery?: string;
titleExact?: boolean;
sizeLessThan?: number;
sizeGreaterThan?: number;
lengthLessThan?: number;
@@ -993,6 +1003,7 @@ export class MediaService {
thumbUrl: row.preview_hash ? `/api/media/${meta.id}/${row.id}/thumb` : null,
provider: row.provider,
downloaded: row.downloaded === 1,
title: row.title,
createdAt: row.created_at
}));
}
+2
View File
@@ -213,6 +213,8 @@ function parseMediaFilters(query: Record<string, unknown>) {
excludeSources: list(query.excludeSources),
includeProviders: list(query.includeProviders),
includeSources: list(query.includeSources),
titleQuery: query.title ? String(query.title).slice(0, 200) : undefined,
titleExact: query.titleExact === "1" || query.titleExact === "true",
sizeLessThan: amount(query.sizeLt),
sizeGreaterThan: amount(query.sizeGt),
lengthLessThan: amount(query.lenLt),
+9
View File
@@ -125,6 +125,15 @@ try {
await page.getByRole("button", { name: "Advanced search" }).click();
await page.waitForSelector(".vk-media-drawer:not([hidden])", { timeout: 5000 });
check(
"title search field present",
(await page.locator('.vk-media-drawer input[type="text"]').count()) >= 1
);
check(
"exact-match toggle present",
(await page.locator(".vk-media-drawer .vk-media-check", { hasText: "Exact match" }).count()) === 1
);
const sourceBoxes = await page
.locator(".vk-media-exclude-group")
.first()