Vibe coding

This commit is contained in:
2026-04-05 14:46:59 +01:00
parent b00871fc07
commit 60e7984501
3 changed files with 777 additions and 15 deletions
+2
View File
@@ -32,3 +32,5 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Finder (MacOS) folder config
.DS_Store
downloads/
+771 -12
View File
@@ -1,20 +1,779 @@
import {
ASCIIFont,
Box,
BoxRenderable,
createCliRenderer,
Text,
SelectRenderable,
TextAttributes,
TextRenderable,
type SelectOption,
} from "@opentui/core";
import { mkdir } from "node:fs/promises";
const renderer = await createCliRenderer({ exitOnCtrlC: true });
type ShowSummary = {
title: string;
slug: string;
url: string;
};
renderer.root.add(
Box(
{ alignItems: "center", justifyContent: "center", flexGrow: 1 },
Box(
{ justifyContent: "center", alignItems: "flex-end" },
ASCIIFont({ font: "tiny", text: "OpenTUI" }),
Text({ content: "What will you build?", attributes: TextAttributes.DIM }),
type ShowDetails = {
title: string;
synopsis: string;
};
type ReleaseDownload = {
res: string;
magnet: string;
};
type ReleaseEntry = {
episode: string;
downloads: ReleaseDownload[];
};
type ShowDownloadData = {
sid: string;
episodes: ReleaseEntry[];
batches: ReleaseEntry[];
qualities: string[];
};
type CachedShowData = {
title: string;
synopsis: string;
downloadData: ShowDownloadData | null;
};
const SUBSPLEASE_SHOWS_URL = "https://subsplease.org/shows";
const renderer = await createCliRenderer({
exitOnCtrlC: true,
autoFocus: true,
});
const root = new BoxRenderable(renderer, {
flexDirection: "row",
flexGrow: 1,
width: "100%",
height: "100%",
padding: 1,
gap: 1,
});
const leftPane = new BoxRenderable(renderer, {
width: "42%",
border: true,
title: "SubsPlease Shows",
padding: 1,
flexDirection: "column",
gap: 1,
});
const rightPane = new BoxRenderable(renderer, {
flexGrow: 1,
border: true,
title: "Show Details",
padding: 1,
});
const showsSelect = new SelectRenderable(renderer, {
width: "100%",
flexGrow: 1,
showScrollIndicator: true,
wrapSelection: true,
showDescription: true,
options: [{ name: "Loading shows...", description: "Please wait" }],
onKeyDown: (key) => {
if (key.name === "q" || key.name === "escape") {
renderer.destroy();
return;
}
if (key.name === "tab") {
cycleQuality(key.shift ? -1 : 1);
key.preventDefault();
return;
}
if (key.ctrl && key.name === "d") {
key.preventDefault();
void downloadFirstThreeEpisodes();
return;
}
if (key.ctrl && key.name === "b") {
key.preventDefault();
void downloadEntireSeries();
return;
}
if (key.ctrl && key.name === "u") {
searchQuery = "";
applyShowFilter();
key.preventDefault();
return;
}
if (key.name === "backspace") {
searchQuery = searchQuery.slice(0, -1);
applyShowFilter();
key.preventDefault();
return;
}
if (!key.ctrl && !key.meta) {
const nextChar =
key.name === "space"
? " "
: key.name && key.name.length === 1
? key.name
: "";
if (nextChar) {
searchQuery += nextChar;
applyShowFilter();
key.preventDefault();
return;
}
}
// Select handles navigation keys internally; defer to read the updated item.
queueMicrotask(() => {
const selected = showsSelect.getSelectedOption() as
| (SelectOption & { value?: ShowSummary })
| null;
const show = selected?.value;
if (show) {
void loadAndShowDetails(show);
}
});
},
});
const searchText = new TextRenderable(renderer, {
content: "Search: ",
attributes: TextAttributes.DIM,
});
const detailsTitle = new TextRenderable(renderer, {
content: "Loading shows...",
attributes: TextAttributes.BOLD,
});
const detailsBody = new TextRenderable(renderer, {
content: "Fetching show list from SubsPlease.",
marginTop: 1,
});
const qualityText = new TextRenderable(renderer, {
content: "Quality: (loading)",
marginTop: 1,
attributes: TextAttributes.BOLD,
});
const downloadActionsText = new TextRenderable(renderer, {
content: "Actions: Ctrl+D first 3 episodes, Ctrl+B entire series",
marginTop: 1,
attributes: TextAttributes.DIM,
});
const downloadStatusText = new TextRenderable(renderer, {
content: "Download status: Waiting for show data.",
marginTop: 1,
attributes: TextAttributes.DIM,
});
const helpText = new TextRenderable(renderer, {
content:
"Keys: Type filter, Backspace delete, Ctrl+U clear, Tab quality+, Shift+Tab quality-, Ctrl+D first 3, Ctrl+B series, Q/Esc quit",
marginTop: 1,
attributes: TextAttributes.DIM,
});
leftPane.add(searchText);
leftPane.add(showsSelect);
rightPane.add(detailsTitle);
rightPane.add(detailsBody);
rightPane.add(qualityText);
rightPane.add(downloadActionsText);
rightPane.add(downloadStatusText);
rightPane.add(helpText);
root.add(leftPane);
root.add(rightPane);
renderer.root.add(root);
const detailsCache = new Map<string, CachedShowData>();
let allShows: ShowSummary[] = [];
let filteredShows: ShowSummary[] = [];
let searchQuery = "";
let activeShow: ShowSummary | null = null;
let activeShowDownloadData: ShowDownloadData | null = null;
let selectedQuality = "";
let currentDetailsRequest = 0;
function sortQualities(qualities: string[]): string[] {
return [...qualities].sort((a, b) => Number(b) - Number(a));
}
function getDownloadByQuality(
entry: ReleaseEntry,
quality: string,
): ReleaseDownload | null {
return entry.downloads.find((download) => download.res === quality) ?? null;
}
function parseEpisodeStart(episodeText: string): number {
const match = episodeText.match(/\d+/);
if (!match) {
return Number.POSITIVE_INFINITY;
}
return Number(match[0]);
}
function getEpisodesSortedAscending(episodes: ReleaseEntry[]): ReleaseEntry[] {
return [...episodes].sort(
(a, b) => parseEpisodeStart(b.episode) - parseEpisodeStart(a.episode),
);
}
function getBestBatchEntry(batches: ReleaseEntry[]): ReleaseEntry | null {
if (batches.length === 0) {
return null;
}
const scored = [...batches].sort((a, b) => {
const aBounds = a.episode.match(/(\d+)\D+(\d+)/);
const bBounds = b.episode.match(/(\d+)\D+(\d+)/);
const aSpan = aBounds ? Number(aBounds[2]) - Number(aBounds[1]) : -1;
const bSpan = bBounds ? Number(bBounds[2]) - Number(bBounds[1]) : -1;
return bSpan - aSpan;
});
return scored[0] ?? null;
}
function sanitizeFileSegment(value: string): string {
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
}
function setDownloadStatus(message: string): void {
downloadStatusText.content = `Download status: ${message}`;
}
function updateQualityDisplay(): void {
const qualities = activeShowDownloadData?.qualities ?? [];
const hasData = qualities.length > 0;
if (!hasData) {
qualityText.content = "Quality: unavailable";
return;
}
const currentIndex = Math.max(0, qualities.indexOf(selectedQuality));
qualityText.content = `Quality: ${selectedQuality}p (${currentIndex + 1}/${qualities.length})`;
}
function cycleQuality(direction: 1 | -1): void {
const qualities = activeShowDownloadData?.qualities ?? [];
if (qualities.length === 0) {
setDownloadStatus("No qualities available for this show.");
return;
}
const currentIndex = qualities.indexOf(selectedQuality);
const startIndex = currentIndex >= 0 ? currentIndex : 0;
const nextIndex =
(startIndex + direction + qualities.length) % qualities.length;
selectedQuality = qualities[nextIndex] ?? qualities[0] ?? "";
updateQualityDisplay();
setDownloadStatus(`Selected ${selectedQuality}p.`);
}
async function writeMagnetFile(
filePath: string,
magnet: string,
): Promise<void> {
await Bun.write(filePath, `${magnet}\n`);
}
async function downloadFirstThreeEpisodes(): Promise<void> {
const show = activeShow;
const data = activeShowDownloadData;
const quality = selectedQuality;
if (!show || !data) {
setDownloadStatus("Select a show first.");
return;
}
if (!quality) {
setDownloadStatus("No quality selected.");
return;
}
const episodes = getEpisodesSortedAscending(data.episodes).slice(0, 3);
if (episodes.length === 0) {
setDownloadStatus("No episodes available.");
return;
}
const baseDir = `downloads/${sanitizeFileSegment(show.slug)}`;
await mkdir(baseDir, { recursive: true });
let writtenCount = 0;
for (const entry of episodes) {
const download = getDownloadByQuality(entry, quality);
if (!download) {
continue;
}
const episodeNumber = parseEpisodeStart(entry.episode);
const episodeSuffix = Number.isFinite(episodeNumber)
? `e${String(episodeNumber).padStart(2, "0")}`
: sanitizeFileSegment(entry.episode.toLowerCase());
const filePath = `${baseDir}/${sanitizeFileSegment(show.slug)}-${episodeSuffix}-${quality}p.magnet`;
await writeMagnetFile(filePath, download.magnet);
writtenCount += 1;
}
if (writtenCount === 0) {
setDownloadStatus(`No first-3 episode magnets found for ${quality}p.`);
return;
}
setDownloadStatus(`Saved ${writtenCount} magnet file(s) to ${baseDir}.`);
}
async function downloadEntireSeries(): Promise<void> {
const show = activeShow;
const data = activeShowDownloadData;
const quality = selectedQuality;
if (!show || !data) {
setDownloadStatus("Select a show first.");
return;
}
if (!quality) {
setDownloadStatus("No quality selected.");
return;
}
const baseDir = `downloads/${sanitizeFileSegment(show.slug)}`;
await mkdir(baseDir, { recursive: true });
const bestBatch = getBestBatchEntry(data.batches);
if (bestBatch) {
const batchDownload = getDownloadByQuality(bestBatch, quality);
if (batchDownload) {
const filePath = `${baseDir}/${sanitizeFileSegment(show.slug)}-batch-${quality}p.magnet`;
await writeMagnetFile(filePath, batchDownload.magnet);
setDownloadStatus(`Saved batch magnet to ${filePath}.`);
return;
}
}
let savedEpisodeCount = 0;
const episodes = getEpisodesSortedAscending(data.episodes);
for (const entry of episodes) {
const download = getDownloadByQuality(entry, quality);
if (!download) {
continue;
}
const episodeNumber = parseEpisodeStart(entry.episode);
const episodeSuffix = Number.isFinite(episodeNumber)
? `e${String(episodeNumber).padStart(2, "0")}`
: sanitizeFileSegment(entry.episode.toLowerCase());
const filePath = `${baseDir}/${sanitizeFileSegment(show.slug)}-${episodeSuffix}-${quality}p.magnet`;
await writeMagnetFile(filePath, download.magnet);
savedEpisodeCount += 1;
}
if (savedEpisodeCount === 0) {
setDownloadStatus(`No series magnets found for ${quality}p.`);
return;
}
setDownloadStatus(
`Saved ${savedEpisodeCount} episode magnet links to ${baseDir}.`,
);
}
function updateSearchText(): void {
const suffix = searchQuery.length > 0 ? searchQuery : "(all)";
searchText.content = `Search: ${suffix}`;
}
function showToOption(
show: ShowSummary,
): SelectOption & { value: ShowSummary } {
return {
name: show.title,
description: show.slug,
value: show,
};
}
function applyShowFilter(): void {
const normalized = searchQuery.trim().toLocaleLowerCase();
const previousSelected = showsSelect.getSelectedOption() as
| (SelectOption & { value?: ShowSummary })
| null;
const previousSlug = previousSelected?.value?.slug;
filteredShows =
normalized.length === 0
? allShows
: allShows.filter((show) => {
const haystack = `${show.title} ${show.slug}`.toLocaleLowerCase();
return haystack.includes(normalized);
});
updateSearchText();
leftPane.title = `SubsPlease Shows (${filteredShows.length})`;
if (filteredShows.length === 0) {
showsSelect.options = [
{
name: "No matches",
description: searchQuery.length > 0 ? `for \"${searchQuery}\"` : "",
},
];
updateDetailsPane(
"No matching show",
searchQuery.length > 0
? `No shows match \"${searchQuery}\". Try a different search.`
: "No shows available.",
);
return;
}
showsSelect.options = filteredShows.map(showToOption);
const nextIndex = previousSlug
? Math.max(
0,
filteredShows.findIndex((show) => show.slug === previousSlug),
)
: 0;
showsSelect.setSelectedIndex(nextIndex);
const selected = filteredShows[nextIndex] ?? filteredShows[0];
if (selected) {
void loadAndShowDetails(selected);
}
}
function decodeHtmlEntities(value: string): string {
const named: Record<string, string> = {
amp: "&",
quot: '"',
apos: "'",
lt: "<",
gt: ">",
nbsp: " ",
mdash: "-",
ndash: "-",
hellip: "...",
};
return value
.replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)))
.replace(/&#x([0-9a-fA-F]+);/g, (_, code) =>
String.fromCodePoint(parseInt(code, 16)),
)
.replace(/&([a-zA-Z]+);/g, (full, name) => named[name] ?? full);
}
function sanitizeText(value: string): string {
return decodeHtmlEntities(value)
.replace(/<br\s*\/?>/gi, "\n")
.replace(/<\/p>/gi, "\n\n")
.replace(/<[^>]+>/g, " ")
.replace(/[\t ]+\n/g, "\n")
.replace(/\n{3,}/g, "\n\n")
.replace(/[\t ]{2,}/g, " ")
.trim();
}
function parseShowsPage(html: string): ShowSummary[] {
const showRegex =
/<div class="all-shows-link">\s*<a href="([^"]+)"[^>]*title="([^"]+)"[^>]*>/g;
const dedupe = new Map<string, ShowSummary>();
for (const match of html.matchAll(showRegex)) {
const rawHref = match[1];
const rawTitle = match[2];
if (!rawHref || !rawTitle) {
continue;
}
const absoluteHref = rawHref.startsWith("http")
? rawHref
: `https://subsplease.org${rawHref.startsWith("/") ? "" : "/"}${rawHref}`;
const url = absoluteHref.replace(/\/$/, "");
if (!url.includes("/shows/")) {
continue;
}
const slug = url.split("/").filter(Boolean).pop();
if (!slug) {
continue;
}
const title = sanitizeText(rawTitle);
if (!title) {
continue;
}
const key = `${slug}::${title}`;
if (!dedupe.has(key)) {
dedupe.set(key, { title, slug, url });
}
}
return [...dedupe.values()].sort((a, b) => a.title.localeCompare(b.title));
}
function parseShowDetails(html: string): ShowDetails {
const titleMatch = html.match(/<h1 class="entry-title">([\s\S]*?)<\/h1>/i);
const title = sanitizeText(titleMatch?.[1] ?? "Unknown show");
const synopsisBlockMatch = html.match(
/<div class="series-syn">([\s\S]*?)<\/div>/i,
);
const synopsisBlock = synopsisBlockMatch?.[1] ?? "";
const paragraphMatches = [...synopsisBlock.matchAll(/<p>([\s\S]*?)<\/p>/gi)];
const synopsis = paragraphMatches
.map((match) => sanitizeText(match[1] ?? ""))
.filter(Boolean)
.join("\n\n");
return {
title,
synopsis: synopsis || "No synopsis available for this show.",
};
}
function parseShowSid(html: string): string | null {
const sidMatch = html.match(/id="show-release-table"[^>]*\ssid="(\d+)"/i);
return sidMatch?.[1] ?? null;
}
function normalizeReleaseEntries(rawSection: unknown): ReleaseEntry[] {
if (!rawSection || typeof rawSection !== "object") {
return [];
}
const entries = Array.isArray(rawSection)
? rawSection
: Object.values(rawSection as Record<string, unknown>);
const normalized: ReleaseEntry[] = [];
for (const entry of entries) {
if (!entry || typeof entry !== "object") {
continue;
}
const maybeEpisode = (entry as { episode?: unknown }).episode;
const maybeDownloads = (entry as { downloads?: unknown }).downloads;
if (typeof maybeEpisode !== "string" || !Array.isArray(maybeDownloads)) {
continue;
}
const downloads: ReleaseDownload[] = maybeDownloads
.map((download) => {
if (!download || typeof download !== "object") {
return null;
}
const res = (download as { res?: unknown }).res;
const magnet = (download as { magnet?: unknown }).magnet;
if (typeof res !== "string" || typeof magnet !== "string") {
return null;
}
return { res, magnet };
})
.filter((item): item is ReleaseDownload => item !== null);
if (downloads.length === 0) {
continue;
}
normalized.push({ episode: maybeEpisode, downloads });
}
return normalized;
}
async function fetchShowDownloadData(
sid: string,
): Promise<ShowDownloadData | null> {
const apiUrl = `https://subsplease.org/api/?f=show&tz=UTC&sid=${sid}`;
const response = await Bun.fetch(apiUrl, {
headers: {
"User-Agent": "subs-please-browser/1.0",
},
});
if (!response.ok) {
return null;
}
const raw = (await response.json()) as {
episode?: unknown;
batch?: unknown;
};
const episodes = normalizeReleaseEntries(raw.episode);
const batches = normalizeReleaseEntries(raw.batch);
const qualities = sortQualities([
...new Set(
[...episodes, ...batches].flatMap((entry) =>
entry.downloads.map((download) => download.res),
),
),
);
]);
return {
sid,
episodes,
batches,
qualities,
};
}
function updateDetailsPane(showTitle: string, bodyText: string): void {
detailsTitle.content = showTitle;
detailsBody.content = bodyText;
}
async function fetchShows(): Promise<ShowSummary[]> {
const response = await Bun.fetch(SUBSPLEASE_SHOWS_URL, {
headers: {
"User-Agent": "subs-please-browser/1.0",
},
});
if (!response.ok) {
throw new Error(`Failed to fetch shows list (${response.status})`);
}
const html = await response.text();
return parseShowsPage(html);
}
async function loadAndShowDetails(show: ShowSummary): Promise<void> {
const requestId = ++currentDetailsRequest;
activeShow = show;
const cached = detailsCache.get(show.slug);
if (cached) {
updateDetailsPane(cached.title, cached.synopsis);
activeShowDownloadData = cached.downloadData;
if (activeShowDownloadData?.qualities.length) {
if (!activeShowDownloadData.qualities.includes(selectedQuality)) {
selectedQuality = activeShowDownloadData.qualities[0] ?? "";
}
updateQualityDisplay();
setDownloadStatus(
`Ready. Episodes: ${activeShowDownloadData.episodes.length}, batches: ${activeShowDownloadData.batches.length}.`,
);
} else {
updateQualityDisplay();
setDownloadStatus("No downloadable magnets available.");
}
return;
}
updateDetailsPane(show.title, "Loading synopsis...");
activeShowDownloadData = null;
updateQualityDisplay();
setDownloadStatus("Loading download data...");
try {
const response = await Bun.fetch(show.url, {
headers: {
"User-Agent": "subs-please-browser/1.0",
},
});
if (!response.ok) {
throw new Error(`Failed to fetch show page (${response.status})`);
}
const html = await response.text();
const details = parseShowDetails(html);
const sid = parseShowSid(html);
const downloadData = sid ? await fetchShowDownloadData(sid) : null;
detailsCache.set(show.slug, {
...details,
downloadData,
});
// Ignore stale responses if the user moved to another item quickly.
if (requestId !== currentDetailsRequest) {
return;
}
updateDetailsPane(details.title, details.synopsis);
activeShowDownloadData = downloadData;
const qualities = sortQualities(downloadData?.qualities ?? []);
if (qualities.length > 0) {
selectedQuality = qualities.includes(selectedQuality)
? selectedQuality
: (qualities[0] ?? "");
updateQualityDisplay();
setDownloadStatus(
`Ready. Episodes: ${downloadData?.episodes.length ?? 0}, batches: ${downloadData?.batches.length ?? 0}.`,
);
} else {
selectedQuality = "";
updateQualityDisplay();
setDownloadStatus("No downloadable magnets available for this show.");
}
} catch (error) {
if (requestId !== currentDetailsRequest) {
return;
}
const message = error instanceof Error ? error.message : "Unknown error";
updateDetailsPane(show.title, `Could not load details. ${message}`);
activeShowDownloadData = null;
selectedQuality = "";
updateQualityDisplay();
setDownloadStatus(`Unable to load show downloads. ${message}`);
}
}
try {
const shows = await fetchShows();
allShows = shows;
if (shows.length === 0) {
showsSelect.options = [
{ name: "No shows found", description: "Try again later" },
];
updateDetailsPane("No data", "SubsPlease returned no show entries.");
} else {
applyShowFilter();
}
} catch (error) {
showsSelect.options = [
{ name: "Failed to load shows", description: "Network error" },
];
const message = error instanceof Error ? error.message : "Unknown error";
updateDetailsPane("Error", `Could not fetch show list. ${message}`);
}
updateSearchText();
showsSelect.focus();
+2 -1
View File
@@ -1,7 +1,8 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"lib": ["ESNext", "DOM"],
"types": ["bun-types"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",