Add the picked providers and a Must include group

This commit is contained in:
Fisto
2026-09-17 11:28:27 +02:00
parent 83dd9bc0fa
commit c6caa168be
11 changed files with 229 additions and 119 deletions
+115 -95
View File
@@ -280,6 +280,12 @@
const excludeExts = state.filters.excludeExts.length
? `excludeExt=${state.filters.excludeExts.join(",")}&`
: "";
const includeProviders = state.filters.includeProviders.length
? `includeProviders=${state.filters.includeProviders.join(",")}&`
: "";
const includeSources = state.filters.includeSources.length
? `includeSources=${state.filters.includeSources.join(",")}&`
: "";
const amount = (value, unit) => {
const number = Number(value);
if (value === "" || !Number.isFinite(number) || number < 0) return "";
@@ -295,7 +301,8 @@
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}${sizeLt}${sizeGt}${lenLt}${lenGt}` +
`${sort}${excludeProviders}${excludeSources}${excludeExts}` +
`${includeProviders}${includeSources}${sizeLt}${sizeGt}${lenLt}${lenGt}` +
`${aspect}${resolution}${after}${before}`
);
}
@@ -1169,6 +1176,8 @@
sort: "newest",
excludeProviders: [],
excludeSources: [],
includeProviders: [],
includeSources: [],
excludeExts: [],
sizeLt: "",
sizeGt: "",
@@ -1193,6 +1202,8 @@
excludeHosts: [...state.filters.excludeHosts],
excludeProviders: [...state.filters.excludeProviders],
excludeSources: [...state.filters.excludeSources],
includeProviders: [...state.filters.includeProviders],
includeSources: [...state.filters.includeSources],
excludeExts: [...state.filters.excludeExts]
};
}
@@ -1211,6 +1222,8 @@
sort: state.draft.sort,
excludeProviders: [...state.draft.excludeProviders],
excludeSources: [...state.draft.excludeSources],
includeProviders: [...state.draft.includeProviders],
includeSources: [...state.draft.includeSources],
excludeExts: [...state.draft.excludeExts],
sizeLt: state.draft.sizeLt,
sizeGt: state.draft.sizeGt,
@@ -1434,34 +1447,6 @@
})
);
const typeRow = (label, extensions) => {
const row = el("div", "vk-media-toolbar");
row.append(el("span", "vk-media-status", label));
for (const extension of extensions) {
const chip = el(
"button",
`vk-chip ${draft.exts.includes(extension) ? "vk-chip-on" : "vk-chip-off"}`,
extension
);
chip.type = "button";
chip.addEventListener("click", () => {
draft.exts = draft.exts.includes(extension)
? draft.exts.filter(value => value !== extension)
: [...draft.exts, extension];
renderFilterDrawer();
});
row.append(chip);
}
return row;
};
filters.append(
el("div", "vk-media-status", "File types"),
typeRow("Video", ["mp4", "webm", "mov", "mkv"]),
typeRow("Image", ["png", "jpg", "gif", "webp", "avif"]),
typeRow("Audio", ["mp3", "ogg", "wav", "m4a", "flac"])
);
const cachedRow = el("label", "vk-media-check");
const cachedBox = document.createElement("input");
cachedBox.type = "checkbox";
@@ -1482,15 +1467,41 @@
deadRow.append(deadBox, document.createTextNode(" Hide 4chan links (usually dead)"));
filters.append(deadRow);
// "Do not include": nested collapsibles, so exclusions stay out of the way.
const exclude = document.createElement("details");
exclude.className = "vk-media-exclude";
exclude.append(el("summary", "vk-media-exclude-title", "Do not include"));
// "Do not include" and "Must include": mirrored collapsible groups.
const EXTS = [
"mp4",
"webm",
"mov",
"mkv",
"png",
"jpg",
"gif",
"webp",
"avif",
"mp3",
"ogg",
"wav",
"m4a",
"flac"
];
const SOURCE_KINDS = [
["embed", "Embeds"],
["attachment", "Attachments"],
["sticker", "Stickers"]
];
const group = title => {
const groupBlock = title => {
const detailsNode = document.createElement("details");
detailsNode.className = "vk-media-exclude";
detailsNode.append(el("summary", "vk-media-exclude-title", title));
return detailsNode;
};
const group = (parent, title) => {
const detailsNode = document.createElement("details");
detailsNode.className = "vk-media-exclude-group";
detailsNode.append(el("summary", "vk-media-status", title));
parent.append(detailsNode);
return detailsNode;
};
@@ -1510,63 +1521,65 @@
const toggle = (list, value, on) =>
on ? [...new Set([...list, value])] : list.filter(entry => entry !== value);
const sourceGroup = group("Sources");
for (const value of state.status?.providerFilters ?? []) {
const label = value.charAt(0).toUpperCase() + value.slice(1);
sourceGroup.append(
checkRow(label, draft.excludeProviders.includes(value), on => {
draft.excludeProviders = toggle(draft.excludeProviders, value, on);
})
);
}
for (const [value, label] of [
["embed", "Embeds"],
["attachment", "Attachments"],
["sticker", "Stickers"]
]) {
sourceGroup.append(
checkRow(label, draft.excludeSources.includes(value), on => {
draft.excludeSources = toggle(draft.excludeSources, value, on);
})
);
}
exclude.append(sourceGroup);
const toggleMany = (list, values, on) =>
on ? [...new Set([...list, ...values])] : list.filter(entry => !values.includes(entry));
const extGroup = group("File types");
const extRow = el("div", "vk-media-toolbar");
for (const extension of [
"mp4",
"webm",
"mov",
"mkv",
"png",
"jpg",
"gif",
"webp",
"avif",
"mp3",
"ogg",
"wav",
"m4a",
"flac"
]) {
const chip = el(
"button",
`vk-chip ${draft.excludeExts.includes(extension) ? "vk-chip-off vk-chip-strike" : "vk-chip-on"}`,
extension
);
chip.type = "button";
chip.title = draft.excludeExts.includes(extension)
? `Excluding ${extension}`
: `${extension} included`;
chip.addEventListener("click", () => {
draft.excludeExts = toggle(draft.excludeExts, extension, !draft.excludeExts.includes(extension));
renderFilterDrawer();
});
extRow.append(chip);
}
extGroup.append(extRow);
exclude.append(extGroup);
/** Provider boxes; a list entry may carry several substrings ("giphy,tenor"). */
const providerChecks = (parent, key) => {
const node = group(parent, "Sources");
for (const entry of state.status?.providerFilters ?? []) {
const values = entry.split(",");
const label = values.map(value => value.charAt(0).toUpperCase() + value.slice(1)).join("/");
node.append(
checkRow(
label,
values.every(value => draft[key].includes(value)),
on => {
draft[key] = toggleMany(draft[key], values, on);
}
)
);
}
for (const [value, label] of SOURCE_KINDS) {
node.append(
checkRow(label, draft[key === "excludeProviders" ? "excludeSources" : "includeSources"].includes(value), on => {
const listKey = key === "excludeProviders" ? "excludeSources" : "includeSources";
draft[listKey] = toggle(draft[listKey], value, on);
})
);
}
return node;
};
const extChips = (parent, key, mode) => {
const node = group(parent, "File types");
const row = el("div", "vk-media-toolbar");
for (const extension of EXTS) {
const active = draft[key].includes(extension);
const chip = el(
"button",
mode === "exclude"
? `vk-chip ${active ? "vk-chip-off vk-chip-strike" : "vk-chip-on"}`
: `vk-chip ${active ? "vk-chip-on" : "vk-chip-off"}`,
extension
);
chip.type = "button";
chip.title = active
? mode === "exclude"
? `Excluding ${extension}`
: `Only ${extension}`
: mode === "exclude"
? `${extension} included`
: `${extension} not required`;
chip.addEventListener("click", () => {
draft[key] = toggle(draft[key], extension, !active);
renderFilterDrawer();
});
row.append(chip);
}
node.append(row);
return node;
};
const amountRow = (label, key, unitKey) => {
const row = el("div", "vk-media-toolbar");
@@ -1604,18 +1617,25 @@
return row;
};
const sizeGroup = group("Size");
const exclude = groupBlock("Do not include");
providerChecks(exclude, "excludeProviders");
extChips(exclude, "excludeExts", "exclude");
filters.append(exclude);
const include = groupBlock("Must include");
providerChecks(include, "includeProviders");
extChips(include, "exts", "include");
const sizeGroup = group(include, "Size");
sizeGroup.append(
amountRow("Less than", "sizeLt", "sizeLtUnit"),
amountRow("Greater than", "sizeGt", "sizeGtUnit")
);
exclude.append(sizeGroup);
const lengthGroup = group("Length");
const lengthGroup = group(include, "Length");
lengthGroup.append(amountRow("Less than", "lenLt"), amountRow("Greater than", "lenGt"));
exclude.append(lengthGroup);
filters.append(exclude);
filters.append(include);
filterDrawer.append(filters);
// Zoom applies live; it is not part of the filter draft.
+12
View File
@@ -896,6 +896,14 @@ function buildServer(): McpServer {
.array(z.string())
.optional()
.describe("Hide these row sources (attachment, sticker, embed)"),
includeProviders: z
.array(z.string())
.optional()
.describe("Keep only providers matching these substrings"),
includeSources: z
.array(z.string())
.optional()
.describe("Keep only these row sources (attachment, sticker, embed)"),
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"),
@@ -931,6 +939,8 @@ function buildServer(): McpServer {
excludeExts,
excludeProviders,
excludeSources,
includeProviders,
includeSources,
sizeLessThan,
sizeGreaterThan,
lengthLessThan,
@@ -957,6 +967,8 @@ function buildServer(): McpServer {
excludeExts,
excludeProviders,
excludeSources,
includeProviders,
includeSources,
sizeLessThan,
sizeGreaterThan,
lengthLessThan,
+8
View File
@@ -717,6 +717,8 @@ export class MediaService {
excludeExts?: string[];
excludeProviders?: string[];
excludeSources?: string[];
includeProviders?: string[];
includeSources?: string[];
sizeLessThan?: number;
sizeGreaterThan?: number;
lengthLessThan?: number;
@@ -846,6 +848,8 @@ export class MediaService {
excludeExts?: string[];
excludeProviders?: string[];
excludeSources?: string[];
includeProviders?: string[];
includeSources?: string[];
sizeLessThan?: number;
sizeGreaterThan?: number;
lengthLessThan?: number;
@@ -881,6 +885,8 @@ export class MediaService {
excludeExts: options.excludeExts,
excludeProviders: options.excludeProviders,
excludeSources: options.excludeSources,
includeProviders: options.includeProviders,
includeSources: options.includeSources,
sizeLessThan: options.sizeLessThan,
sizeGreaterThan: options.sizeGreaterThan,
lengthLessThan: options.lengthLessThan,
@@ -941,6 +947,8 @@ export class MediaService {
excludeExts?: string[];
excludeProviders?: string[];
excludeSources?: string[];
includeProviders?: string[];
includeSources?: string[];
sizeLessThan?: number;
sizeGreaterThan?: number;
lengthLessThan?: number;
+2
View File
@@ -211,6 +211,8 @@ function parseMediaFilters(query: Record<string, unknown>) {
excludeExts: list(query.excludeExt),
excludeProviders: list(query.excludeProviders),
excludeSources: list(query.excludeSources),
includeProviders: list(query.includeProviders),
includeSources: list(query.includeSources),
sizeLessThan: amount(query.sizeLt),
sizeGreaterThan: amount(query.sizeGt),
lengthLessThan: amount(query.lenLt),
+11
View File
@@ -136,6 +136,17 @@ try {
sourceBoxes === providerFilters.length + 3,
{ sourceBoxes, providers: providerFilters.length }
);
const includeSources = await page
.locator(".vk-media-exclude-group")
.nth(2)
.locator(".vk-media-check")
.count();
step("must-include source checkboxes", includeSources);
check(
"must-include mirrors the provider list",
includeSources === providerFilters.length + 3,
{ includeSources, providers: providerFilters.length }
);
await page.locator(".vk-media-check", { hasText: "Show only cached previews" }).locator("input").check();
await page.getByRole("button", { name: "Apply filters" }).click();
await page.waitForTimeout(2500);