mirror of
https://github.com/baairon/torlink.git
synced 2026-07-08 18:28:22 +02:00
- new PromptHints action row shared by the folder and trackers prompts - download-to prompt shows the torrent name and size, enter reads "download" - export torrent rebound from T to s and centralized in the store - footer trimmed to essentials, d/D and z detail live in the ? sheet - z keeps FitGirl and SubsPlease rows: their feeds carry no seeder data, a new per-source reportsHealth flag marks seeders: 0 as unknown, not dead - version 1.3.0
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import { fetchResilient, HttpError, USER_AGENT } from "../util/net";
|
|
import { parseMagnet } from "./magnet";
|
|
import type { SearchOptions, Source, TorrentResult } from "./types";
|
|
|
|
const API = "https://subsplease.org/api/";
|
|
const RES_PREFERENCE = ["1080", "720", "480"];
|
|
|
|
interface SpDownload {
|
|
res?: string;
|
|
magnet?: string;
|
|
}
|
|
interface SpEntry {
|
|
show?: string;
|
|
episode?: string;
|
|
release_date?: string;
|
|
downloads?: SpDownload[];
|
|
}
|
|
|
|
function pickBest(downloads: SpDownload[]): SpDownload | undefined {
|
|
for (const res of RES_PREFERENCE) {
|
|
const d = downloads.find((d) => d.res === res && d.magnet);
|
|
if (d) return d;
|
|
}
|
|
return downloads.find((d) => d.magnet);
|
|
}
|
|
|
|
async function search(query: string, opts: SearchOptions = {}): Promise<TorrentResult[]> {
|
|
const q = query.trim();
|
|
const params = new URLSearchParams({ tz: "UTC" });
|
|
if (q) {
|
|
params.set("f", "search");
|
|
params.set("s", q);
|
|
} else {
|
|
params.set("f", "latest");
|
|
}
|
|
|
|
const res = await fetchResilient(`${API}?${params.toString()}`, {
|
|
headers: { "User-Agent": USER_AGENT },
|
|
signal: opts.signal,
|
|
});
|
|
if (!res.ok) throw new HttpError(res.status, `SubsPlease returned ${res.status}`);
|
|
|
|
const json = (await res.json()) as Record<string, SpEntry> | unknown[];
|
|
if (!json || Array.isArray(json)) return [];
|
|
|
|
const out: TorrentResult[] = [];
|
|
for (const entry of Object.values(json)) {
|
|
const dl = pickBest(entry.downloads ?? []);
|
|
if (!dl?.magnet) continue;
|
|
const parsed = parseMagnet(dl.magnet);
|
|
if (!parsed) continue;
|
|
const show = entry.show ?? "Unknown";
|
|
const ep = entry.episode ? ` - ${entry.episode}` : "";
|
|
const sizeMatch = dl.magnet.match(/[?&]xl=(\d+)/);
|
|
out.push({
|
|
infoHash: parsed.infoHash,
|
|
name: `${show}${ep} [${dl.res ?? "?"}p]`,
|
|
sizeBytes: sizeMatch ? Number(sizeMatch[1]) : 0,
|
|
seeders: 0,
|
|
leechers: 0,
|
|
source: "subsplease",
|
|
magnet: parsed.magnet,
|
|
added: entry.release_date ? new Date(entry.release_date).getTime() / 1000 : undefined,
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export const subsplease: Source = {
|
|
id: "subsplease",
|
|
label: "SubsPlease",
|
|
group: "Anime",
|
|
homepage: "https://subsplease.org",
|
|
// The SubsPlease API has no swarm data; every result reports seeders: 0.
|
|
reportsHealth: false,
|
|
search,
|
|
};
|