diff options
| -rw-r--r-- | ext/bg/js/audio.js | 104 | ||||
| -rw-r--r-- | ext/bg/js/backend.js | 8 | 
2 files changed, 64 insertions, 48 deletions
| diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 361a19cc..80e9cb9a 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -18,8 +18,49 @@  /*global jpIsStringEntirelyKana*/ -const audioUrlBuilders = new Map([ -    ['jpod101', async (definition) => { +class AudioUriBuilder { +    constructor() { +        this._getUrlHandlers = new Map([ +            ['jpod101', this._getUriJpod101.bind(this)], +            ['jpod101-alternate', this._getUriJpod101Alternate.bind(this)], +            ['jisho', this._getUriJisho.bind(this)], +            ['text-to-speech', this._getUriTextToSpeech.bind(this)], +            ['text-to-speech-reading', this._getUriTextToSpeechReading.bind(this)], +            ['custom', this._getUriCustom.bind(this)] +        ]); +    } + +    normalizeUrl(url, baseUrl, basePath) { +        if (url) { +            if (url[0] === '/') { +                if (url.length >= 2 && url[1] === '/') { +                    // Begins with "//" +                    url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url; +                } else { +                    // Begins with "/" +                    url = baseUrl + url; +                } +            } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) { +                // No URI scheme => relative path +                url = baseUrl + basePath + url; +            } +        } +        return url; +    } + +    async getUri(mode, definition, options) { +        const handler = this._getUrlHandlers.get(mode); +        if (typeof handler === 'function') { +            try { +                return await handler(definition, options); +            } catch (e) { +                // NOP +            } +        } +        return null; +    } + +    async _getUriJpod101(definition) {          let kana = definition.reading;          let kanji = definition.expression; @@ -37,8 +78,9 @@ const audioUrlBuilders = new Map([          }          return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; -    }], -    ['jpod101-alternate', async (definition) => { +    } + +    async _getUriJpod101Alternate(definition) {          const response = await new Promise((resolve, reject) => {              const xhr = new XMLHttpRequest();              xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); @@ -54,7 +96,7 @@ const audioUrlBuilders = new Map([                  const url = row.querySelector('audio>source[src]').getAttribute('src');                  const reading = row.getElementsByClassName('dc-vocab_kana').item(0).textContent;                  if (url && reading && (!definition.reading || definition.reading === reading)) { -                    return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); +                    return this.normalizeUrl(url, 'https://www.japanesepod101.com', '/learningcenter/reference/');                  }              } catch (e) {                  // NOP @@ -62,8 +104,9 @@ const audioUrlBuilders = new Map([          }          throw new Error('Failed to find audio URL'); -    }], -    ['jisho', async (definition) => { +    } + +    async _getUriJisho(definition) {          const response = await new Promise((resolve, reject) => {              const xhr = new XMLHttpRequest();              xhr.open('GET', `https://jisho.org/search/${definition.expression}`); @@ -78,7 +121,7 @@ const audioUrlBuilders = new Map([              if (audio !== null) {                  const url = audio.getElementsByTagName('source').item(0).getAttribute('src');                  if (url) { -                    return audioUrlNormalize(url, 'https://jisho.org', '/search/'); +                    return this.normalizeUrl(url, 'https://jisho.org', '/search/');                  }              }          } catch (e) { @@ -86,55 +129,28 @@ const audioUrlBuilders = new Map([          }          throw new Error('Failed to find audio URL'); -    }], -    ['text-to-speech', async (definition, options) => { +    } + +    async _getUriTextToSpeech(definition, options) {          const voiceURI = options.audio.textToSpeechVoice;          if (!voiceURI) {              throw new Error('No voice');          }          return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; -    }], -    ['text-to-speech-reading', async (definition, options) => { +    } + +    async _getUriTextToSpeechReading(definition, options) {          const voiceURI = options.audio.textToSpeechVoice;          if (!voiceURI) {              throw new Error('No voice');          }          return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`; -    }], -    ['custom', async (definition, options) => { -        const customSourceUrl = options.audio.customSourceUrl; -        return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0)); -    }] -]); - -async function audioGetUrl(definition, mode, options, download) { -    const handler = audioUrlBuilders.get(mode); -    if (typeof handler === 'function') { -        try { -            return await handler(definition, options, download); -        } catch (e) { -            // NOP -        }      } -    return null; -} -function audioUrlNormalize(url, baseUrl, basePath) { -    if (url) { -        if (url[0] === '/') { -            if (url.length >= 2 && url[1] === '/') { -                // Begins with "//" -                url = baseUrl.substring(0, baseUrl.indexOf(':') + 1) + url; -            } else { -                // Begins with "/" -                url = baseUrl + url; -            } -        } else if (!/^[a-z][a-z0-9\-+.]*:/i.test(url)) { -            // No URI scheme => relative path -            url = baseUrl + basePath + url; -        } +    async _getUriCustom(definition, options) { +        const customSourceUrl = options.audio.customSourceUrl; +        return customSourceUrl.replace(/\{([^}]*)\}/g, (m0, m1) => (hasOwn(definition, m1) ? `${definition[m1]}` : m0));      } -    return url;  } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 1fdc4c70..66378b0c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -21,9 +21,8 @@ conditionsTestValue, profileConditionsDescriptor  handlebarsRenderDynamic  requestText, requestJson, optionsLoad  dictConfigured, dictTermsSort, dictEnabledSet -audioGetUrl  jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana -AnkiNoteBuilder, AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ +AnkiNoteBuilder, AudioSystem, AudioUriBuilder, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/  class Backend {      constructor() { @@ -36,6 +35,7 @@ class Backend {          this.optionsSchema = null;          this.defaultAnkiFieldTemplates = null;          this.audioSystem = new AudioSystem({getAudioUri: this._getAudioUri.bind(this)}); +        this.audioUriBuilder = new AudioUriBuilder();          this.optionsContext = {              depth: 0,              url: window.location.href @@ -515,7 +515,7 @@ class Backend {      async _onApiAudioGetUrl({definition, source, optionsContext}) {          const options = this.getOptions(optionsContext); -        return await audioGetUrl(definition, source, options); +        return await this.audioUriBuilder.getUri(source, definition, options);      }      _onApiScreenshotGet({options}, sender) { @@ -771,7 +771,7 @@ class Backend {          }          const options = this.getOptions(optionsContext); -        return await audioGetUrl(definition, source, options); +        return await this.audioUriBuilder.getUri(source, definition, options);      }      async _audioInject(definition, fields, sources, optionsContext) { |