// A one-off BitChute import (import-video with a bitchute.com URL), made polite. // // BitChute rate-limits, so an import of one of its videos — into whatever // channel — runs the way a BitChute channel's own downloads do: on BitChute's // queue (`platform:bitchute`, one transfer at a time), with BitChute's yt-dlp // args (`PLATFORM_ARGS.bitchute`), never while BitChute is held or in a // rate-limit cooldown, never for a video already on disk, and what BitChute // answered is recorded on its shared pacing state, so a 429 backs every // BitChute path off and a clean download settles it. The editor's // importVideoAction does the wiring; these are its pure rules. import { classifyDownloadFailure } from "../lib/availability"; import { defaultWebpageUrl } from "../lib/platform"; import { extractVideoId } from "../lib/videoId"; import type { DownloadOutcomeRecord } from "../lib/downloadOutcome"; export type BitchuteImportUrl = | { ok: true; id: string; url: string } | { ok: false; error: string }; // The canonical page of the video a BitChute URL names (www., old. and embed // URLs all become https://www.bitchute.com/video//). A channel, playlist or // profile page names no one video: it is refused with the way to list one. export function resolveBitchuteImportUrl(url: string): BitchuteImportUrl { const id = extractVideoId(url); if (!id || !/^[\w-]+$/.test(id)) { return { ok: false, error: "A BitChute import takes one video's page (https://www.bitchute.com/video//). " + "To take a whole BitChute channel, make it a channel with that URL and sync it.", }; } return { ok: true, id, url: defaultWebpageUrl("bitchute", id) }; } // What one import's outcome tells BitChute's pacing state: a rate limit or a // network failure backs the platform off, a download that came down settles // it, and anything else (a per-video failure, a filter skip) tells it nothing. export function importPlatformSignal( outcome: Pick, ): "rate_limit" | "network" | "clean" | null { if (outcome.status.startsWith("ok")) { return outcome.failureClass === "subs_rate_limit" ? null : "clean"; } if (outcome.status !== "failed") return null; const last = outcome.attempts[outcome.attempts.length - 1]; const cls = outcome.failureClass ?? classifyDownloadFailure(last?.error ?? "", last?.availabilityClass); return cls === "rate_limit" || cls === "network" ? cls : null; }