diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2023-11-27 12:48:14 -0500 | 
|---|---|---|
| committer | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2023-11-27 12:48:14 -0500 | 
| commit | 4da4827bcbcdd1ef163f635d9b29416ff272b0bb (patch) | |
| tree | a8a0f1a8befdb78a554e1be91f2c6059ca3ad5f9 /ext/js/media/audio-downloader.js | |
| parent | fd6bba8a2a869eaf2b2c1fa49001f933fce3c618 (diff) | |
Add JSDoc type annotations to project (rebased)
Diffstat (limited to 'ext/js/media/audio-downloader.js')
| -rw-r--r-- | ext/js/media/audio-downloader.js | 125 | 
1 files changed, 108 insertions, 17 deletions
| diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 1720a5d9..8bd04b2b 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -23,11 +23,18 @@ import {NativeSimpleDOMParser} from '../dom/native-simple-dom-parser.js';  import {SimpleDOMParser} from '../dom/simple-dom-parser.js';  export class AudioDownloader { +    /** +     * @param {{japaneseUtil: JapaneseUtil, requestBuilder: RequestBuilder}} details +     */      constructor({japaneseUtil, requestBuilder}) { +        /** @type {JapaneseUtil} */          this._japaneseUtil = japaneseUtil; +        /** @type {RequestBuilder} */          this._requestBuilder = requestBuilder; +        /** @type {?JsonSchema} */          this._customAudioListSchema = null; -        this._getInfoHandlers = new Map([ +        /** @type {Map<import('settings').AudioSourceType, import('audio-downloader').GetInfoHandler>} */ +        this._getInfoHandlers = new Map(/** @type {[name: import('settings').AudioSourceType, handler: import('audio-downloader').GetInfoHandler][]} */ ([              ['jpod101', this._getInfoJpod101.bind(this)],              ['jpod101-alternate', this._getInfoJpod101Alternate.bind(this)],              ['jisho', this._getInfoJisho.bind(this)], @@ -35,9 +42,15 @@ export class AudioDownloader {              ['text-to-speech-reading', this._getInfoTextToSpeechReading.bind(this)],              ['custom', this._getInfoCustom.bind(this)],              ['custom-json', this._getInfoCustomJson.bind(this)] -        ]); +        ]));      } +    /** +     * @param {import('audio').AudioSourceInfo} source +     * @param {string} term +     * @param {string} reading +     * @returns {Promise<import('audio-downloader').Info[]>} +     */      async getTermAudioInfoList(source, term, reading) {          const handler = this._getInfoHandlers.get(source.type);          if (typeof handler === 'function') { @@ -50,6 +63,14 @@ export class AudioDownloader {          return [];      } +    /** +     * @param {import('audio').AudioSourceInfo[]} sources +     * @param {?number} preferredAudioIndex +     * @param {string} term +     * @param {string} reading +     * @param {?number} idleTimeout +     * @returns {Promise<import('audio-downloader').AudioBinaryBase64>} +     */      async downloadTermAudio(sources, preferredAudioIndex, term, reading, idleTimeout) {          const errors = [];          for (const source of sources) { @@ -70,28 +91,34 @@ export class AudioDownloader {              }          } -        const error = new Error('Could not download audio'); +        const error = new ExtensionError('Could not download audio');          error.data = {errors};          throw error;      }      // Private +    /** +     * @param {string} url +     * @param {string} base +     * @returns {string} +     */      _normalizeUrl(url, base) {          return new URL(url, base).href;      } +    /** @type {import('audio-downloader').GetInfoHandler} */      async _getInfoJpod101(term, reading) {          if (reading === term && this._japaneseUtil.isStringEntirelyKana(term)) {              reading = term; -            term = null; +            term = '';          }          const params = new URLSearchParams(); -        if (term) { +        if (term.length > 0) {              params.set('kanji', term);          } -        if (reading) { +        if (reading.length > 0) {              params.set('kana', reading);          } @@ -99,6 +126,7 @@ export class AudioDownloader {          return [{type: 'url', url}];      } +    /** @type {import('audio-downloader').GetInfoHandler} */      async _getInfoJpod101Alternate(term, reading) {          const fetchUrl = 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post';          const data = new URLSearchParams({ @@ -149,6 +177,7 @@ export class AudioDownloader {          throw new Error('Failed to find audio URL');      } +    /** @type {import('audio-downloader').GetInfoHandler} */      async _getInfoJisho(term, reading) {          const fetchUrl = `https://jisho.org/search/${term}`;          const response = await this._requestBuilder.fetchAnonymous(fetchUrl, { @@ -181,26 +210,52 @@ export class AudioDownloader {          throw new Error('Failed to find audio URL');      } -    async _getInfoTextToSpeech(term, reading, {voice}) { -        if (!voice) { -            throw new Error('No voice'); +    /** @type {import('audio-downloader').GetInfoHandler} */ +    async _getInfoTextToSpeech(term, reading, details) { +        if (typeof details !== 'object' || details === null) { +            throw new Error('Invalid arguments'); +        } +        const {voice} = details; +        if (typeof voice !== 'string') { +            throw new Error('Invalid voice');          }          return [{type: 'tts', text: term, voice: voice}];      } -    async _getInfoTextToSpeechReading(term, reading, {voice}) { -        if (!voice) { -            throw new Error('No voice'); +    /** @type {import('audio-downloader').GetInfoHandler} */ +    async _getInfoTextToSpeechReading(term, reading, details) { +        if (typeof details !== 'object' || details === null) { +            throw new Error('Invalid arguments'); +        } +        const {voice} = details; +        if (typeof voice !== 'string') { +            throw new Error('Invalid voice');          }          return [{type: 'tts', text: reading, voice: voice}];      } -    async _getInfoCustom(term, reading, {url}) { +    /** @type {import('audio-downloader').GetInfoHandler} */ +    async _getInfoCustom(term, reading, details) { +        if (typeof details !== 'object' || details === null) { +            throw new Error('Invalid arguments'); +        } +        let {url} = details; +        if (typeof url !== 'string') { +            throw new Error('Invalid url'); +        }          url = this._getCustomUrl(term, reading, url);          return [{type: 'url', url}];      } -    async _getInfoCustomJson(term, reading, {url}) { +    /** @type {import('audio-downloader').GetInfoHandler} */ +    async _getInfoCustomJson(term, reading, details) { +        if (typeof details !== 'object' || details === null) { +            throw new Error('Invalid arguments'); +        } +        let {url} = details; +        if (typeof url !== 'string') { +            throw new Error('Invalid url'); +        }          url = this._getCustomUrl(term, reading, url);          const response = await this._requestBuilder.fetchAnonymous(url, { @@ -220,12 +275,14 @@ export class AudioDownloader {          if (this._customAudioListSchema === null) {              const schema = await this._getCustomAudioListSchema(); -            this._customAudioListSchema = new JsonSchema(schema); +            this._customAudioListSchema = new JsonSchema(/** @type {import('json-schema').Schema} */ (schema));          }          this._customAudioListSchema.validate(responseJson); +        /** @type {import('audio-downloader').Info[]} */          const results = [];          for (const {url: url2, name} of responseJson.audioSources) { +            /** @type {import('audio-downloader').Info1} */              const info = {type: 'url', url: url2};              if (typeof name === 'string') { info.name = name; }              results.push(info); @@ -233,17 +290,32 @@ export class AudioDownloader {          return results;      } +    /** +     * @param {string} term +     * @param {string} reading +     * @param {string} url +     * @returns {string} +     * @throws {Error} +     */      _getCustomUrl(term, reading, url) {          if (typeof url !== 'string') {              throw new Error('No custom URL defined');          }          const data = {term, reading}; -        return url.replace(/\{([^}]*)\}/g, (m0, m1) => (Object.prototype.hasOwnProperty.call(data, m1) ? `${data[m1]}` : m0)); +        return url.replace(/\{([^}]*)\}/g, (m0, m1) => (Object.prototype.hasOwnProperty.call(data, m1) ? `${data[/** @type {'term'|'reading'} */ (m1)]}` : m0));      } +    /** +     * @param {string} url +     * @param {import('settings').AudioSourceType} sourceType +     * @param {?number} idleTimeout +     * @returns {Promise<import('audio-downloader').AudioBinaryBase64>} +     */      async _downloadAudioFromUrl(url, sourceType, idleTimeout) {          let signal; +        /** @type {?(done: boolean) => void} */          let onProgress = null; +        /** @type {?number} */          let idleTimer = null;          if (typeof idleTimeout === 'number') {              const abortController = new AbortController(); @@ -252,7 +324,9 @@ export class AudioDownloader {                  abortController.abort('Idle timeout');              };              onProgress = (done) => { -                clearTimeout(idleTimer); +                if (idleTimer !== null) { +                    clearTimeout(idleTimer); +                }                  idleTimer = done ? null : setTimeout(onIdleTimeout, idleTimeout);              };              idleTimer = setTimeout(onIdleTimeout, idleTimeout); @@ -287,6 +361,11 @@ export class AudioDownloader {          return {data, contentType};      } +    /** +     * @param {ArrayBuffer} arrayBuffer +     * @param {import('settings').AudioSourceType} sourceType +     * @returns {Promise<boolean>} +     */      async _isAudioBinaryValid(arrayBuffer, sourceType) {          switch (sourceType) {              case 'jpod101': @@ -304,6 +383,10 @@ export class AudioDownloader {          }      } +    /** +     * @param {ArrayBuffer} arrayBuffer +     * @returns {Promise<string>} +     */      async _arrayBufferDigest(arrayBuffer) {          const hash = new Uint8Array(await crypto.subtle.digest('SHA-256', new Uint8Array(arrayBuffer)));          let digest = ''; @@ -313,6 +396,11 @@ export class AudioDownloader {          return digest;      } +    /** +     * @param {string} content +     * @returns {import('simple-dom-parser').ISimpleDomParser} +     * @throws {Error} +     */      _createSimpleDOMParser(content) {          if (typeof NativeSimpleDOMParser !== 'undefined' && NativeSimpleDOMParser.isSupported()) {              return new NativeSimpleDOMParser(content); @@ -323,6 +411,9 @@ export class AudioDownloader {          }      } +    /** +     * @returns {Promise<unknown>} +     */      async _getCustomAudioListSchema() {          const url = chrome.runtime.getURL('/data/schemas/custom-audio-list-schema.json');          const response = await fetch(url, { |