mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-06 19:57:26 -08:00
build: expand typecheck coverage beyond src
This commit is contained in:
@@ -166,8 +166,10 @@ test('buildSubminerScriptOpts includes aniskip payload fields', () => {
|
||||
assert.match(opts, /subminer-aniskip_intro_end=62/);
|
||||
assert.match(opts, /subminer-aniskip_lookup_status=ready/);
|
||||
assert.ok(payloadMatch !== null);
|
||||
assert.equal(payloadMatch[1].includes('%'), false);
|
||||
const payloadJson = Buffer.from(payloadMatch[1], 'base64url').toString('utf-8');
|
||||
const encodedPayload = payloadMatch[1];
|
||||
assert.ok(encodedPayload !== undefined);
|
||||
assert.equal(encodedPayload.includes('%'), false);
|
||||
const payloadJson = Buffer.from(encodedPayload, 'base64url').toString('utf-8');
|
||||
const payload = JSON.parse(payloadJson);
|
||||
assert.equal(payload.found, true);
|
||||
const first = payload.results?.[0];
|
||||
|
||||
@@ -53,6 +53,13 @@ interface AniSkipPayloadResponse {
|
||||
results?: unknown;
|
||||
}
|
||||
|
||||
const ROMAN_SEASON_ALIASES: Record<number, readonly string[]> = {
|
||||
2: [' ii ', ' second season ', ' 2nd season '],
|
||||
3: [' iii ', ' third season ', ' 3rd season '],
|
||||
4: [' iv ', ' fourth season ', ' 4th season '],
|
||||
5: [' v ', ' fifth season ', ' 5th season '],
|
||||
};
|
||||
|
||||
const MAL_PREFIX_API = 'https://myanimelist.net/search/prefix.json?type=anime&keyword=';
|
||||
const ANISKIP_PAYLOAD_API = 'https://api.aniskip.com/v1/skip-times/';
|
||||
const MAL_USER_AGENT = 'SubMiner-launcher/ani-skip';
|
||||
@@ -188,14 +195,7 @@ function seasonSignalScore(requestedSeason: number | null, candidateTitle: strin
|
||||
return 40;
|
||||
}
|
||||
|
||||
const romanAliases = {
|
||||
2: [' ii ', ' second season ', ' 2nd season '],
|
||||
3: [' iii ', ' third season ', ' 3rd season '],
|
||||
4: [' iv ', ' fourth season ', ' 4th season '],
|
||||
5: [' v ', ' fifth season ', ' 5th season '],
|
||||
} as const;
|
||||
|
||||
const aliases = romanAliases[season] ?? [];
|
||||
const aliases = ROMAN_SEASON_ALIASES[season] ?? [];
|
||||
return aliases.some((alias) => normalized.includes(alias))
|
||||
? 40
|
||||
: hasAnySequelMarker(candidateTitle)
|
||||
|
||||
@@ -284,8 +284,10 @@ export function parseEpisodePathFromDisplay(
|
||||
const normalized = display.trim().replace(/\s+/g, ' ');
|
||||
const match = normalized.match(/^(.*?)\s+S(\d{1,2})E\d{1,3}\b/i);
|
||||
if (!match) return null;
|
||||
const seriesName = match[1].trim();
|
||||
const seasonNumber = Number.parseInt(match[2], 10);
|
||||
const seriesName = match[1]?.trim();
|
||||
const seasonText = match[2];
|
||||
if (!seriesName || !seasonText) return null;
|
||||
const seasonNumber = Number.parseInt(seasonText, 10);
|
||||
if (!seriesName || !Number.isFinite(seasonNumber) || seasonNumber < 0) return null;
|
||||
return { seriesName, seasonNumber };
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ function getRetryAfter(headers: http.IncomingHttpHeaders): number | undefined {
|
||||
const value = headers['x-ratelimit-reset-after'];
|
||||
if (!value) return undefined;
|
||||
const raw = Array.isArray(value) ? value[0] : value;
|
||||
if (!raw) return undefined;
|
||||
const parsed = Number.parseFloat(raw);
|
||||
if (!Number.isFinite(parsed)) return undefined;
|
||||
return parsed;
|
||||
@@ -72,9 +73,14 @@ export function matchEpisodeFromName(name: string): {
|
||||
} {
|
||||
const seasonEpisode = name.match(/S(\d{1,2})E(\d{1,3})/i);
|
||||
if (seasonEpisode && seasonEpisode.index !== undefined) {
|
||||
const seasonText = seasonEpisode[1];
|
||||
const episodeText = seasonEpisode[2];
|
||||
if (!seasonText || !episodeText) {
|
||||
return { season: null, episode: null, index: null, confidence: 'low' };
|
||||
}
|
||||
return {
|
||||
season: Number.parseInt(seasonEpisode[1], 10),
|
||||
episode: Number.parseInt(seasonEpisode[2], 10),
|
||||
season: Number.parseInt(seasonText, 10),
|
||||
episode: Number.parseInt(episodeText, 10),
|
||||
index: seasonEpisode.index,
|
||||
confidence: 'high',
|
||||
};
|
||||
@@ -82,9 +88,14 @@ export function matchEpisodeFromName(name: string): {
|
||||
|
||||
const alt = name.match(/(\d{1,2})x(\d{1,3})/i);
|
||||
if (alt && alt.index !== undefined) {
|
||||
const seasonText = alt[1];
|
||||
const episodeText = alt[2];
|
||||
if (!seasonText || !episodeText) {
|
||||
return { season: null, episode: null, index: null, confidence: 'low' };
|
||||
}
|
||||
return {
|
||||
season: Number.parseInt(alt[1], 10),
|
||||
episode: Number.parseInt(alt[2], 10),
|
||||
season: Number.parseInt(seasonText, 10),
|
||||
episode: Number.parseInt(episodeText, 10),
|
||||
index: alt.index,
|
||||
confidence: 'high',
|
||||
};
|
||||
@@ -92,9 +103,13 @@ export function matchEpisodeFromName(name: string): {
|
||||
|
||||
const epOnly = name.match(/(?:^|[\s._-])E(?:P)?(\d{1,3})(?:\b|[\s._-])/i);
|
||||
if (epOnly && epOnly.index !== undefined) {
|
||||
const episodeText = epOnly[1];
|
||||
if (!episodeText) {
|
||||
return { season: null, episode: null, index: null, confidence: 'low' };
|
||||
}
|
||||
return {
|
||||
season: null,
|
||||
episode: Number.parseInt(epOnly[1], 10),
|
||||
episode: Number.parseInt(episodeText, 10),
|
||||
index: epOnly.index,
|
||||
confidence: 'medium',
|
||||
};
|
||||
@@ -102,9 +117,13 @@ export function matchEpisodeFromName(name: string): {
|
||||
|
||||
const numeric = name.match(/(?:^|[-–—]\s*)(\d{1,3})\s*[-–—]/);
|
||||
if (numeric && numeric.index !== undefined) {
|
||||
const episodeText = numeric[1];
|
||||
if (!episodeText) {
|
||||
return { season: null, episode: null, index: null, confidence: 'low' };
|
||||
}
|
||||
return {
|
||||
season: null,
|
||||
episode: Number.parseInt(numeric[1], 10),
|
||||
episode: Number.parseInt(episodeText, 10),
|
||||
index: numeric.index,
|
||||
confidence: 'medium',
|
||||
};
|
||||
@@ -117,7 +136,9 @@ function detectSeasonFromDir(mediaPath: string): number | null {
|
||||
const parent = path.basename(path.dirname(mediaPath));
|
||||
const match = parent.match(/(?:Season|S)\s*(\d{1,2})/i);
|
||||
if (!match) return null;
|
||||
const parsed = Number.parseInt(match[1], 10);
|
||||
const seasonText = match[1];
|
||||
if (!seasonText) return null;
|
||||
const parsed = Number.parseInt(seasonText, 10);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -427,7 +427,7 @@ export async function startMpv(
|
||||
appPath: string,
|
||||
preloadedSubtitles?: { primaryPath?: string; secondaryPath?: string },
|
||||
options?: { startPaused?: boolean },
|
||||
): void {
|
||||
): Promise<void> {
|
||||
if (targetKind === 'file' && (!fs.existsSync(target) || !fs.statSync(target).isFile())) {
|
||||
fail(`Video file not found: ${target}`);
|
||||
}
|
||||
|
||||
@@ -207,7 +207,8 @@ export function pickLibrary(
|
||||
iconPath: ensureIcon(session, lib.id) || undefined,
|
||||
}));
|
||||
const idx = showRofiIconMenu(entries, 'Jellyfin Library', initialQuery, themePath);
|
||||
return idx >= 0 ? visibleLibraries[idx].id : '';
|
||||
const selected = idx >= 0 ? visibleLibraries[idx] : undefined;
|
||||
return selected?.id ?? '';
|
||||
}
|
||||
|
||||
const lines = visibleLibraries.map((lib) => `${lib.id}\t${lib.name} [${lib.kind}]`);
|
||||
@@ -244,7 +245,8 @@ export function pickItem(
|
||||
iconPath: ensureIcon(session, item.id) || undefined,
|
||||
}));
|
||||
const idx = showRofiIconMenu(entries, 'Jellyfin Item', initialQuery, themePath);
|
||||
return idx >= 0 ? visibleItems[idx].id : '';
|
||||
const selected = idx >= 0 ? visibleItems[idx] : undefined;
|
||||
return selected?.id ?? '';
|
||||
}
|
||||
|
||||
const lines = visibleItems.map((item) => `${item.id}\t${item.display}`);
|
||||
@@ -281,7 +283,8 @@ export function pickGroup(
|
||||
iconPath: ensureIcon(session, group.id) || undefined,
|
||||
}));
|
||||
const idx = showRofiIconMenu(entries, 'Jellyfin Anime/Folder', initialQuery, themePath);
|
||||
return idx >= 0 ? visibleGroups[idx].id : '';
|
||||
const selected = idx >= 0 ? visibleGroups[idx] : undefined;
|
||||
return selected?.id ?? '';
|
||||
}
|
||||
|
||||
const lines = visibleGroups.map((group) => `${group.id}\t${group.display}`);
|
||||
|
||||
@@ -58,7 +58,7 @@ function pickBestCandidate(candidates: SubtitleCandidate[]): SubtitleCandidate |
|
||||
if (srtA !== srtB) return srtB - srtA;
|
||||
return b.size - a.size;
|
||||
});
|
||||
return scored[0];
|
||||
return scored[0] ?? null;
|
||||
}
|
||||
|
||||
function scanSubtitleCandidates(
|
||||
@@ -120,7 +120,7 @@ function findAudioFile(tempDir: string, preferredExt: string): string | null {
|
||||
const preferred = audioFiles.find((entry) => entry.ext === `.${preferredExt.toLowerCase()}`);
|
||||
if (preferred) return preferred.path;
|
||||
audioFiles.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
||||
return audioFiles[0].path;
|
||||
return audioFiles[0]?.path ?? null;
|
||||
}
|
||||
|
||||
async function runWhisper(
|
||||
|
||||
Reference in New Issue
Block a user