mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-15 01:55:52 -07:00
2512e0f3af
- 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)
76 lines
3.2 KiB
TypeScript
76 lines
3.2 KiB
TypeScript
import { IPC_CHANNELS } from '../../shared/ipc/contracts';
|
|
import type { AnimeBrowserRuntime } from './anime-browser-runtime';
|
|
import type { AnimeBrowserPlayRequest } from '../../types/anime-browser';
|
|
|
|
export interface AnimeBrowserIpcDeps {
|
|
// Structurally typed so tests can pass a fake without importing Electron.
|
|
ipcMain: {
|
|
handle(channel: string, listener: (event: unknown, ...args: unknown[]) => unknown): unknown;
|
|
};
|
|
runtime: AnimeBrowserRuntime;
|
|
}
|
|
|
|
/**
|
|
* Bridge the renderer to the anime runtime. Page arguments arrive as `unknown`
|
|
* from the renderer, so they are coerced here rather than trusted.
|
|
*/
|
|
export function registerAnimeBrowserIpcHandlers(deps: AnimeBrowserIpcDeps): void {
|
|
const channels = IPC_CHANNELS.request;
|
|
const { runtime } = deps;
|
|
const handle = (
|
|
channel: string,
|
|
listener: (event: unknown, ...args: unknown[]) => unknown,
|
|
): void => {
|
|
deps.ipcMain.handle(channel, listener);
|
|
};
|
|
|
|
handle(channels.animeBrowserGetSnapshot, () => runtime.getSnapshot());
|
|
handle(channels.animeBrowserEnsureBridge, () => runtime.ensureBridge());
|
|
handle(channels.animeBrowserSelectSource, (_event, sourceId) =>
|
|
runtime.selectSource(String(sourceId)),
|
|
);
|
|
handle(channels.animeBrowserSearch, (_event, query, page) =>
|
|
runtime.search(String(query ?? ''), toPage(page)),
|
|
);
|
|
handle(channels.animeBrowserGetPopular, (_event, page) => runtime.getPopular(toPage(page)));
|
|
handle(channels.animeBrowserGetDetails, (_event, animeUrl, sourceId) =>
|
|
runtime.getDetails(String(animeUrl), toOptionalId(sourceId)),
|
|
);
|
|
handle(channels.animeBrowserGetEpisodes, (_event, animeUrl, sourceId) =>
|
|
runtime.getEpisodes(String(animeUrl), toOptionalId(sourceId)),
|
|
);
|
|
handle(channels.animeBrowserListAvailableExtensions, () => runtime.listAvailableExtensions());
|
|
handle(channels.animeBrowserInstallExtension, (_event, pkg) =>
|
|
runtime.installExtension(String(pkg)),
|
|
);
|
|
handle(channels.animeBrowserRemoveExtension, (_event, pkg) =>
|
|
runtime.removeExtension(String(pkg)),
|
|
);
|
|
handle(channels.animeBrowserRescanExtensions, () => runtime.rescanExtensions());
|
|
handle(channels.animeBrowserAddRepo, (_event, url) => runtime.addRepo(String(url)));
|
|
handle(channels.animeBrowserRemoveRepo, (_event, url) => runtime.removeRepo(String(url)));
|
|
handle(channels.animeBrowserPlayEpisode, (_event, request) =>
|
|
runtime.playEpisode(request as AnimeBrowserPlayRequest),
|
|
);
|
|
handle(channels.animeBrowserGetPreferences, (_event, sourceId) =>
|
|
runtime.getPreferences(String(sourceId)),
|
|
);
|
|
handle(channels.animeBrowserSetPreference, (_event, sourceId, key, value) =>
|
|
runtime.setPreference(String(sourceId), String(key), value as string | string[] | boolean),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* A source id the renderer may omit. Absent means "use the current selection",
|
|
* so an empty value must stay undefined rather than becoming the string "".
|
|
*/
|
|
function toOptionalId(value: unknown): string | undefined {
|
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
}
|
|
|
|
/** Bridge pages are 1-based; anything unusable falls back to the first page. */
|
|
function toPage(value: unknown): number {
|
|
const page = Number(value);
|
|
return Number.isFinite(page) && page >= 1 ? Math.floor(page) : 1;
|
|
}
|