Files
SubMiner/src/main/cli-runtime.ts
sudacode 5feed360ca feat: add app-owned YouTube subtitle flow with absPlayer-style parsing (#31)
* fix: harden preload argv parsing for popup windows

* fix: align youtube playback with shared overlay startup

* fix: unwrap mpv youtube streams for anki media mining

* docs: update docs for youtube subtitle and mining flow

* refactor: unify cli and runtime wiring for startup and youtube flow

* feat: update subtitle sidebar overlay behavior

* chore: add shared log-file source for diagnostics

* fix(ci): add changelog fragment for immersion changes

* fix: address CodeRabbit review feedback

* fix: persist canonical title from youtube metadata

* style: format stats library tab

* fix: address latest review feedback

* style: format stats library files

* test: stub launcher youtube deps in CI

* test: isolate launcher youtube flow deps

* test: stub launcher youtube deps in failing case

* test: force x11 backend in launcher ci harness

* test: address latest review feedback

* fix(launcher): preserve user YouTube ytdl raw options

* docs(backlog): update task tracking notes

* fix(immersion): special-case youtube media paths in runtime and tracking

* feat(stats): improve YouTube media metadata and picker key handling

* fix(ci): format stats media library hook

* fix: address latest CodeRabbit review items

* docs: update youtube release notes and docs

* feat: auto-load youtube subtitles before manual picker

* fix: restore app-owned youtube subtitle flow

* docs: update youtube playback docs and config copy

* refactor: remove legacy youtube launcher mode plumbing

* fix: refine youtube subtitle startup binding

* docs: clarify youtube subtitle startup behavior

* fix: address PR #31 latest review follow-ups

* fix: address PR #31 follow-up review comments

* test: harden youtube picker test harness

* udpate backlog

* fix: add timeout to youtube metadata probe

* docs: refresh youtube and stats docs

* update backlog

* update backlog

* chore: release v0.9.0
2026-03-24 00:01:24 -07:00

145 lines
5.8 KiB
TypeScript

import { handleCliCommand, createCliCommandDepsRuntime } from '../core/services';
import type { CliArgs, CliCommandSource } from '../cli/args';
import {
createCliCommandRuntimeServiceDeps,
CliCommandRuntimeServiceDepsParams,
} from './dependencies';
export interface CliCommandRuntimeServiceContext {
setLogLevel?: (level: NonNullable<CliArgs['logLevel']>) => void;
getSocketPath: () => string;
setSocketPath: (socketPath: string) => void;
getClient: CliCommandRuntimeServiceDepsParams['mpv']['getClient'];
showOsd: CliCommandRuntimeServiceDepsParams['mpv']['showOsd'];
getTexthookerPort: () => number;
setTexthookerPort: (port: number) => void;
shouldOpenBrowser: () => boolean;
openInBrowser: (url: string) => void;
isOverlayInitialized: () => boolean;
initializeOverlay: () => void;
toggleVisibleOverlay: () => void;
openFirstRunSetup: () => void;
setVisibleOverlay: (visible: boolean) => void;
copyCurrentSubtitle: () => void;
startPendingMultiCopy: (timeoutMs: number) => void;
mineSentenceCard: () => Promise<void>;
startPendingMineSentenceMultiple: (timeoutMs: number) => void;
updateLastCardFromClipboard: () => Promise<void>;
refreshKnownWordCache: () => Promise<void>;
triggerFieldGrouping: () => Promise<void>;
triggerSubsyncFromConfig: () => Promise<void>;
markLastCardAsAudioCard: () => Promise<void>;
getAnilistStatus: CliCommandRuntimeServiceDepsParams['anilist']['getStatus'];
clearAnilistToken: CliCommandRuntimeServiceDepsParams['anilist']['clearToken'];
openAnilistSetup: CliCommandRuntimeServiceDepsParams['anilist']['openSetup'];
getAnilistQueueStatus: CliCommandRuntimeServiceDepsParams['anilist']['getQueueStatus'];
retryAnilistQueueNow: CliCommandRuntimeServiceDepsParams['anilist']['retryQueueNow'];
generateCharacterDictionary: CliCommandRuntimeServiceDepsParams['dictionary']['generate'];
openJellyfinSetup: CliCommandRuntimeServiceDepsParams['jellyfin']['openSetup'];
runStatsCommand: CliCommandRuntimeServiceDepsParams['jellyfin']['runStatsCommand'];
runJellyfinCommand: CliCommandRuntimeServiceDepsParams['jellyfin']['runCommand'];
runYoutubePlaybackFlow: CliCommandRuntimeServiceDepsParams['app']['runYoutubePlaybackFlow'];
openYomitanSettings: () => void;
cycleSecondarySubMode: () => void;
openRuntimeOptionsPalette: () => void;
printHelp: () => void;
stopApp: () => void;
hasMainWindow: () => boolean;
getMultiCopyTimeoutMs: () => number;
schedule: (fn: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
log: (message: string) => void;
warn: (message: string) => void;
error: (message: string, err: unknown) => void;
}
export interface CliCommandRuntimeServiceContextHandlers {
texthookerService: CliCommandRuntimeServiceDepsParams['texthooker']['service'];
}
function createCliCommandDepsFromContext(
context: CliCommandRuntimeServiceContext & CliCommandRuntimeServiceContextHandlers,
): CliCommandRuntimeServiceDepsParams {
return {
setLogLevel: context.setLogLevel,
mpv: {
getSocketPath: context.getSocketPath,
setSocketPath: context.setSocketPath,
getClient: context.getClient,
showOsd: context.showOsd,
},
texthooker: {
service: context.texthookerService,
getPort: context.getTexthookerPort,
setPort: context.setTexthookerPort,
shouldOpenBrowser: context.shouldOpenBrowser,
openInBrowser: context.openInBrowser,
},
overlay: {
isInitialized: context.isOverlayInitialized,
initialize: context.initializeOverlay,
toggleVisible: context.toggleVisibleOverlay,
setVisible: context.setVisibleOverlay,
},
mining: {
copyCurrentSubtitle: context.copyCurrentSubtitle,
startPendingMultiCopy: context.startPendingMultiCopy,
mineSentenceCard: context.mineSentenceCard,
startPendingMineSentenceMultiple: context.startPendingMineSentenceMultiple,
updateLastCardFromClipboard: context.updateLastCardFromClipboard,
refreshKnownWords: context.refreshKnownWordCache,
triggerFieldGrouping: context.triggerFieldGrouping,
triggerSubsyncFromConfig: context.triggerSubsyncFromConfig,
markLastCardAsAudioCard: context.markLastCardAsAudioCard,
},
anilist: {
getStatus: context.getAnilistStatus,
clearToken: context.clearAnilistToken,
openSetup: context.openAnilistSetup,
getQueueStatus: context.getAnilistQueueStatus,
retryQueueNow: context.retryAnilistQueueNow,
},
dictionary: {
generate: context.generateCharacterDictionary,
},
jellyfin: {
openSetup: context.openJellyfinSetup,
runStatsCommand: context.runStatsCommand,
runCommand: context.runJellyfinCommand,
},
app: {
stop: context.stopApp,
hasMainWindow: context.hasMainWindow,
runYoutubePlaybackFlow: context.runYoutubePlaybackFlow,
},
ui: {
openFirstRunSetup: context.openFirstRunSetup,
openYomitanSettings: context.openYomitanSettings,
cycleSecondarySubMode: context.cycleSecondarySubMode,
openRuntimeOptionsPalette: context.openRuntimeOptionsPalette,
printHelp: context.printHelp,
},
getMultiCopyTimeoutMs: context.getMultiCopyTimeoutMs,
schedule: context.schedule,
log: context.log,
warn: context.warn,
error: context.error,
};
}
export function handleCliCommandRuntimeService(
args: CliArgs,
source: CliCommandSource,
params: CliCommandRuntimeServiceDepsParams,
): void {
const deps = createCliCommandDepsRuntime(createCliCommandRuntimeServiceDeps(params));
handleCliCommand(args, source, deps);
}
export function handleCliCommandRuntimeServiceWithContext(
args: CliArgs,
source: CliCommandSource,
context: CliCommandRuntimeServiceContext & CliCommandRuntimeServiceContextHandlers,
): void {
handleCliCommandRuntimeService(args, source, createCliCommandDepsFromContext(context));
}