Add source filters and a full-window media view

This commit is contained in:
Fisto
2026-09-16 11:11:50 +02:00
parent 2573dd4d63
commit 902646224f
12 changed files with 179 additions and 32 deletions
+7 -2
View File
@@ -823,13 +823,18 @@ function buildServer(): McpServer {
description: "Query media items across scrapes, newest scrape first.",
inputSchema: {
kind: z.enum(["image", "gif", "video", "audio"]).optional(),
source: z
.enum(["attachment", "sticker", "embed"])
.optional()
.describe("Where the item came from"),
scrapeId: z.string().optional().describe("Restrict to one scrape"),
channelId: z.string().optional().describe("Restrict to one source channel"),
limit: z.number().int().min(1).max(500).optional(),
offset: z.number().int().min(0).optional()
}
},
async ({ kind, scrapeId, limit, offset }) =>
jsonText(media.queryMedia({ kind, scrapeId, limit, offset }))
async ({ kind, source, scrapeId, channelId, limit, offset }) =>
jsonText(media.queryMedia({ kind, source, scrapeId, channelId, limit, offset }))
);
server.registerTool(
+16 -2
View File
@@ -92,6 +92,7 @@ export interface MediaItemResult {
itemId: number;
channelId: string;
kind: MediaKind;
source: string | null;
url: string;
localUrl: string | null;
thumbUrl: string | null;
@@ -352,7 +353,14 @@ export class MediaService {
/** Read media rows across every scrape, newest scrape first. */
queryMedia(
options: { kind?: MediaKind; scrapeId?: string; limit?: number; offset?: number } = {}
options: {
kind?: MediaKind;
source?: string;
scrapeId?: string;
channelId?: string;
limit?: number;
offset?: number;
} = {}
): MediaItemResult[] {
const limit = Math.max(1, Math.min(options.limit ?? 60, 500));
const offset = Math.max(0, options.offset ?? 0);
@@ -362,10 +370,15 @@ export class MediaService {
for (const meta of this.engine.list()) {
if (meta.state === "deleted") continue;
if (options.scrapeId && meta.id !== options.scrapeId) continue;
if (options.channelId && meta.channelId !== options.channelId) continue;
const store = new ScrapeStore(scrapeDir(this.rootDir, meta.id));
try {
const rows = store.queryMedia({ kind: options.kind, limit: limit + offset });
const rows = store.queryMedia({
kind: options.kind,
source: options.source,
limit: limit + offset
});
for (const row of rows) {
if (skipped++ < offset) continue;
if (results.length >= limit) break;
@@ -374,6 +387,7 @@ export class MediaService {
itemId: row.id,
channelId: meta.channelId,
kind: row.kind,
source: row.source,
url: row.url,
localUrl: row.file_path ? `/api/media/${meta.id}/${row.id}/file` : null,
thumbUrl: row.preview_hash ? `/api/media/${meta.id}/${row.id}/thumb` : null,
+5 -1
View File
@@ -795,10 +795,14 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
const kind = ["image", "gif", "video", "audio"].includes(String(req.query.kind ?? ""))
? (String(req.query.kind) as "image" | "gif" | "video" | "audio")
: undefined;
const source = ["attachment", "sticker", "embed"].includes(String(req.query.source ?? ""))
? String(req.query.source)
: undefined;
const channelId = req.query.channelId ? String(req.query.channelId) : undefined;
const limit = clampLimit(req.query.limit, 60, 500);
const offset = Math.max(0, Number(req.query.offset ?? 0) || 0);
res.json({ ok: true, result: deps.media.queryMedia({ kind, limit, offset }) });
res.json({ ok: true, result: deps.media.queryMedia({ kind, source, channelId, limit, offset }) });
})
);