mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-01 19:21:34 -07:00
f5e98dfa9d
Addresses CodeRabbit review feedback on the Anime Browser: - reject repository package/apk names that are not plain identifiers, and verify the install target resolves inside the extensions directory - count mpv's %n% option escape in UTF-8 bytes, and escape backslashes in header values so a trailing one cannot eat the list separator - key the bridge extension-id cache by APK content, so an in-place upgrade re-uploads instead of running the previous build - bound every bridge, release-listing, and download request with a timeout - enforce the APK size limit while streaming rather than after buffering - read APK bytes on demand instead of holding a base64 copy per extension for the lifetime of the browser - serialize preference mutations and write the file atomically - handle the sidecar spawn error event, and wait for the child to exit in stop() before returning - report a failed Anime Browser bootstrap instead of showing the starting banner forever - keep the preferences panel's save confirmation and in-flight multi-select edits by re-rendering only on a structural schema change
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import type { BridgeVideo, OkHttpHeaders, ResolvedStream } from './types';
|
|
|
|
/**
|
|
* Flatten OkHttp's alternating `[name, value, name, value]` array into a map.
|
|
* A trailing name with no value is dropped rather than mapped to undefined.
|
|
*/
|
|
export function parseOkHttpHeaders(headers: OkHttpHeaders | undefined): Record<string, string> {
|
|
const flat = headers?.['namesAndValues$okhttp'];
|
|
if (!Array.isArray(flat)) return {};
|
|
|
|
const parsed: Record<string, string> = {};
|
|
for (let i = 0; i + 1 < flat.length; i += 2) {
|
|
const name = flat[i];
|
|
const value = flat[i + 1];
|
|
if (typeof name === 'string' && typeof value === 'string') parsed[name] = value;
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
/**
|
|
* Render headers as mpv's `--http-header-fields` string list. mpv splits
|
|
* entries on commas, so commas inside a value must be escaped — and the
|
|
* backslash that does the escaping has to be escaped first, or a value ending
|
|
* in `\` would neutralise the separator and swallow the next header.
|
|
*/
|
|
export function toMpvHeaderFields(headers: Record<string, string>): string {
|
|
return Object.entries(headers)
|
|
.map(([name, value]) => `${name}: ${value.replace(/\\/g, '\\\\').replace(/,/g, '\\,')}`)
|
|
.join(',');
|
|
}
|
|
|
|
function normalizeTracks(
|
|
tracks: Array<{ url?: string; lang?: string }> | undefined,
|
|
): Array<{ url: string; lang: string }> {
|
|
if (!Array.isArray(tracks)) return [];
|
|
return tracks
|
|
.filter((track): track is { url: string; lang?: string } => typeof track.url === 'string')
|
|
.map((track) => ({ url: track.url, lang: track.lang ?? '' }));
|
|
}
|
|
|
|
/**
|
|
* Normalize a bridge video into a playable stream. Returns null when the
|
|
* extension produced no `videoUrl`, which happens for entries it failed to
|
|
* resolve.
|
|
*/
|
|
export function resolveStream(video: BridgeVideo): ResolvedStream | null {
|
|
if (typeof video.videoUrl !== 'string' || video.videoUrl.length === 0) return null;
|
|
|
|
return {
|
|
url: video.videoUrl,
|
|
quality: video.quality ?? '',
|
|
headers: parseOkHttpHeaders(video.headers),
|
|
subtitles: normalizeTracks(video.subtitleTracks),
|
|
audios: normalizeTracks(video.audioTracks),
|
|
};
|
|
}
|