mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-22 17:16:19 -07:00
- Add backend selection, setup gating, Anki integration, and external host support - Add launcher flags, documentation, packaging, and focused tests - Open on-demand overlay modals on the first attempt
41 lines
1.9 KiB
JavaScript
41 lines
1.9 KiB
JavaScript
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Browser-native source rules adapted from GSM PR #549's hoshidicts_audio.py
|
|
// and hoshidicts_audio_profile.py. Configuration remains global reader options.
|
|
|
|
function httpUrl(value) {
|
|
if (typeof value !== "string" || /[\u0000-\u001f]/u.test(value)) {
|
|
throw new Error("Audio URLs must be absolute HTTP(S) URLs.");
|
|
}
|
|
const url = new URL(value);
|
|
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.port === "0") {
|
|
throw new Error("Audio URLs must use HTTP(S) without a username or password.");
|
|
}
|
|
if (/[{}]/u.test(url.host)) throw new Error("Audio URL placeholders cannot appear in the host.");
|
|
return url.href;
|
|
}
|
|
|
|
function encodeValue(value) {
|
|
return encodeURIComponent(value).replace(/[!'()*]/gu, char => `%${char.codePointAt(0).toString(16).toUpperCase()}`);
|
|
}
|
|
|
|
export function audioSourceUrl(template, { expression, reading }) {
|
|
httpUrl(template);
|
|
const encodedTerm = encodeValue(expression);
|
|
const values = { term: encodedTerm, expression: encodedTerm, reading: encodeValue(reading), language: "ja" };
|
|
return httpUrl(template.replace(/\{([^{}]*)\}/gu, (match, key) => Object.hasOwn(values, key) ? values[key] : match));
|
|
}
|
|
|
|
export function parseAudioSourceList(value) {
|
|
if (value?.type !== "audioSourceList" || !Array.isArray(value.audioSources)
|
|
|| Object.keys(value).some(key => !["type", "audioSources"].includes(key))) {
|
|
throw new Error("The audio provider returned an invalid Yomitan audioSourceList.");
|
|
}
|
|
return value.audioSources.map(candidate => {
|
|
if (!candidate || typeof candidate.url !== "string" || (candidate.name !== undefined && typeof candidate.name !== "string")
|
|
|| Object.keys(candidate).some(key => !["url", "name"].includes(key))) {
|
|
throw new Error("The audio provider returned an invalid pronunciation candidate.");
|
|
}
|
|
return { url: httpUrl(candidate.url), name: candidate.name ?? "" };
|
|
});
|
|
}
|