aboutsummaryrefslogtreecommitdiff
path: root/ext/js/media/audio-downloader.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/media/audio-downloader.js')
-rw-r--r--ext/js/media/audio-downloader.js96
1 files changed, 60 insertions, 36 deletions
diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js
index 2d1bc4ec..d378d043 100644
--- a/ext/js/media/audio-downloader.js
+++ b/ext/js/media/audio-downloader.js
@@ -25,6 +25,16 @@ import {NativeSimpleDOMParser} from '../dom/native-simple-dom-parser.js';
import {SimpleDOMParser} from '../dom/simple-dom-parser.js';
import {isStringEntirelyKana} from '../language/ja/japanese.js';
+/** @type {RequestInit} */
+const DEFAULT_REQUEST_INIT_PARAMS = {
+ method: 'GET',
+ mode: 'cors',
+ cache: 'default',
+ credentials: 'omit',
+ redirect: 'follow',
+ referrerPolicy: 'no-referrer',
+};
+
export class AudioDownloader {
/**
* @param {RequestBuilder} requestBuilder
@@ -39,6 +49,7 @@ export class AudioDownloader {
['jpod101', this._getInfoJpod101.bind(this)],
['jpod101-alternate', this._getInfoJpod101Alternate.bind(this)],
['jisho', this._getInfoJisho.bind(this)],
+ ['lingua-libre', this._getInfoLinguaLibre.bind(this)],
['text-to-speech', this._getInfoTextToSpeech.bind(this)],
['text-to-speech-reading', this._getInfoTextToSpeechReading.bind(this)],
['custom', this._getInfoCustom.bind(this)],
@@ -50,13 +61,14 @@ export class AudioDownloader {
* @param {import('audio').AudioSourceInfo} source
* @param {string} term
* @param {string} reading
+ * @param {import('language').LanguageSummary} languageSummary
* @returns {Promise<import('audio-downloader').Info[]>}
*/
- async getTermAudioInfoList(source, term, reading) {
+ async getTermAudioInfoList(source, term, reading, languageSummary) {
const handler = this._getInfoHandlers.get(source.type);
if (typeof handler === 'function') {
try {
- return await handler(term, reading, source);
+ return await handler(term, reading, source, languageSummary);
} catch (e) {
// NOP
}
@@ -70,12 +82,13 @@ export class AudioDownloader {
* @param {string} term
* @param {string} reading
* @param {?number} idleTimeout
+ * @param {import('language').LanguageSummary} languageSummary
* @returns {Promise<import('audio-downloader').AudioBinaryBase64>}
*/
- async downloadTermAudio(sources, preferredAudioIndex, term, reading, idleTimeout) {
+ async downloadTermAudio(sources, preferredAudioIndex, term, reading, idleTimeout, languageSummary) {
const errors = [];
for (const source of sources) {
- let infoList = await this.getTermAudioInfoList(source, term, reading);
+ let infoList = await this.getTermAudioInfoList(source, term, reading, languageSummary);
if (typeof preferredAudioIndex === 'number') {
infoList = (preferredAudioIndex >= 0 && preferredAudioIndex < infoList.length ? [infoList[preferredAudioIndex]] : []);
}
@@ -137,12 +150,8 @@ export class AudioDownloader {
vulgar: 'true',
});
const response = await this._requestBuilder.fetchAnonymous(fetchUrl, {
+ ...DEFAULT_REQUEST_INIT_PARAMS,
method: 'POST',
- mode: 'cors',
- cache: 'default',
- credentials: 'omit',
- redirect: 'follow',
- referrerPolicy: 'no-referrer',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
@@ -181,14 +190,7 @@ export class AudioDownloader {
/** @type {import('audio-downloader').GetInfoHandler} */
async _getInfoJisho(term, reading) {
const fetchUrl = `https://jisho.org/search/${term}`;
- const response = await this._requestBuilder.fetchAnonymous(fetchUrl, {
- method: 'GET',
- mode: 'cors',
- cache: 'default',
- credentials: 'omit',
- redirect: 'follow',
- referrerPolicy: 'no-referrer',
- });
+ const response = await this._requestBuilder.fetchAnonymous(fetchUrl, DEFAULT_REQUEST_INIT_PARAMS);
const responseText = await response.text();
const dom = this._createSimpleDOMParser(responseText);
@@ -212,6 +214,44 @@ export class AudioDownloader {
}
/** @type {import('audio-downloader').GetInfoHandler} */
+ async _getInfoLinguaLibre(term, _reading, _details, languageSummary) {
+ if (typeof languageSummary !== 'object' || languageSummary === null) {
+ throw new Error('Invalid arguments');
+ }
+ const {iso639_3} = languageSummary;
+ const searchCategory = `incategory:"Lingua_Libre_pronunciation-${iso639_3}"`;
+ const searchString = `-${term}.wav`;
+ const fetchUrl = `https://commons.wikimedia.org/w/api.php?action=query&format=json&list=search&srsearch=intitle:/${searchString}/i+${searchCategory}&srnamespace=6&origin=*`;
+
+ const response = await this._requestBuilder.fetchAnonymous(fetchUrl, DEFAULT_REQUEST_INIT_PARAMS);
+
+ /** @type {import('audio-downloader').LinguaLibreLookupResponse} */
+ const lookupResponse = await readResponseJson(response);
+
+ const lookupResults = lookupResponse.query.search;
+
+ const fetchFileInfos = lookupResults.map(async ({title}) => {
+ const fileInfoURL = `https://commons.wikimedia.org/w/api.php?action=query&format=json&titles=${title}&prop=imageinfo&iiprop=user|url&origin=*`;
+ const response2 = await this._requestBuilder.fetchAnonymous(fileInfoURL, DEFAULT_REQUEST_INIT_PARAMS);
+ /** @type {import('audio-downloader').LinguaLibreFileResponse} */
+ const fileResponse = await readResponseJson(response2);
+ const fileResults = fileResponse.query.pages;
+ const results = [];
+ for (const page of Object.values(fileResults)) {
+ const fileUrl = page.imageinfo[0].url;
+ const fileUser = page.imageinfo[0].user;
+ const validFilenameTest = new RegExp(`^File:LL-Q\\d+\\s+\\(${iso639_3}\\)-(\\w+ \\()?${fileUser}\\)?-${term}\\.wav$`, 'i');
+ if (validFilenameTest.test(title)) {
+ results.push({type: 'url', url: fileUrl, name: fileUser});
+ }
+ }
+ return /** @type {import('audio-downloader').Info1[]} */ (results);
+ });
+
+ return (await Promise.all(fetchFileInfos)).flat();
+ }
+
+ /** @type {import('audio-downloader').GetInfoHandler} */
async _getInfoTextToSpeech(term, reading, details) {
if (typeof details !== 'object' || details === null) {
throw new Error('Invalid arguments');
@@ -259,14 +299,7 @@ export class AudioDownloader {
}
url = this._getCustomUrl(term, reading, url);
- const response = await this._requestBuilder.fetchAnonymous(url, {
- method: 'GET',
- mode: 'cors',
- cache: 'default',
- credentials: 'omit',
- redirect: 'follow',
- referrerPolicy: 'no-referrer',
- });
+ const response = await this._requestBuilder.fetchAnonymous(url, DEFAULT_REQUEST_INIT_PARAMS);
if (!response.ok) {
throw new Error(`Invalid response: ${response.status}`);
@@ -345,12 +378,7 @@ export class AudioDownloader {
}
const response = await this._requestBuilder.fetchAnonymous(url, {
- method: 'GET',
- mode: 'cors',
- cache: 'default',
- credentials: 'omit',
- redirect: 'follow',
- referrerPolicy: 'no-referrer',
+ ...DEFAULT_REQUEST_INIT_PARAMS,
signal,
});
@@ -429,12 +457,8 @@ export class AudioDownloader {
async _getCustomAudioListSchema() {
const url = chrome.runtime.getURL('/data/schemas/custom-audio-list-schema.json');
const response = await fetch(url, {
- method: 'GET',
+ ...DEFAULT_REQUEST_INIT_PARAMS,
mode: 'no-cors',
- cache: 'default',
- credentials: 'omit',
- redirect: 'follow',
- referrerPolicy: 'no-referrer',
});
return await readResponseJson(response);
}