Add source filters and a full-window media view
This commit is contained in:
@@ -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
@@ -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
@@ -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 }) });
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user