mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-31 19:21:33 -07:00
e64ff1a0ee
- Add `subminer anime` / `--anime` and a tray entry to open a browser that searches installed Aniyomi extension sources, shows cover art and episodes, and plays into mpv with overlay/mining attached - Add an Extensions tab to add repos and install/update/remove sources, and per-source settings for sources needing config - Support searching all sources at once with streaming, per-source results and status - Prefer Japanese audio/subtitle tracks from the source and keep the primary subtitle slot reserved for Japanese - Fix window/tray/Dock handling so the browser and mpv can be switched between without quitting the app or losing the Dock icon - Add anime.repos, anime.extensionsDir, anime.preferredQuality config keys (no bundled repos or discovery)
75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
/**
|
|
* The bridge returns cover art and video URLs pointing at its own loopback
|
|
* media proxy, but the origin it embeds is not always the port we actually
|
|
* started it on. Rebase those onto the live bridge origin, and leave any
|
|
* genuinely remote URL untouched.
|
|
*/
|
|
|
|
const PROXY_ROUTES = new Set(['image', 'video']);
|
|
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);
|
|
|
|
function isLoopbackProxyUrl(candidate: URL): boolean {
|
|
if (candidate.protocol !== 'http:' && candidate.protocol !== 'https:') return false;
|
|
const host = candidate.hostname.toLowerCase();
|
|
if (!LOOPBACK_HOSTS.has(host)) return false;
|
|
const route = candidate.pathname.split('/').filter(Boolean)[0];
|
|
return route !== undefined && PROXY_ROUTES.has(route);
|
|
}
|
|
|
|
/**
|
|
* Rewrite a bridge media URL onto `bridgeBaseUrl`, preserving path and query.
|
|
* Returns the input unchanged when it is not a loopback proxy URL, or when
|
|
* either URL cannot be parsed.
|
|
*/
|
|
export function resolveBridgeMediaUrl(bridgeBaseUrl: string, mediaUrl: string): string {
|
|
let media: URL;
|
|
try {
|
|
media = new URL(mediaUrl);
|
|
} catch {
|
|
return mediaUrl;
|
|
}
|
|
if (!isLoopbackProxyUrl(media)) return mediaUrl;
|
|
|
|
const normalizedBase = bridgeBaseUrl.includes('://') ? bridgeBaseUrl : `http://${bridgeBaseUrl}`;
|
|
let base: URL;
|
|
try {
|
|
base = new URL(normalizedBase);
|
|
} catch {
|
|
return mediaUrl;
|
|
}
|
|
if (base.protocol !== 'http:' && base.protocol !== 'https:') return mediaUrl;
|
|
if (base.hostname.length === 0) return mediaUrl;
|
|
|
|
const rebased = new URL(base.origin);
|
|
rebased.pathname = media.pathname;
|
|
rebased.search = media.search;
|
|
rebased.hash = media.hash;
|
|
return rebased.toString();
|
|
}
|
|
|
|
/** Aniyomi's SAnime status constants. */
|
|
export type AnimeStatus =
|
|
| 'unknown'
|
|
| 'ongoing'
|
|
| 'completed'
|
|
| 'publishing-finished'
|
|
| 'cancelled'
|
|
| 'on-hiatus';
|
|
|
|
export function parseAnimeStatus(status: number | undefined): AnimeStatus {
|
|
switch (status) {
|
|
case 1:
|
|
return 'ongoing';
|
|
case 2:
|
|
return 'completed';
|
|
case 4:
|
|
return 'publishing-finished';
|
|
case 5:
|
|
return 'cancelled';
|
|
case 6:
|
|
return 'on-hiatus';
|
|
default:
|
|
return 'unknown';
|
|
}
|
|
}
|