require('dotenv').config(); const debug = require('debug')('soundbirder:api:quizz'); const debugResponses = require('debug')('soundbirder:api:responses'); const cache = require('./cache'); const eBird = require('@unclesamulus/ebird-api')(process.env.EBIRD_API_KEY); const XenoCanto = require('@unclesamulus/xeno-canto-api'); const { choices, choice } = require('../utils/choices'); function questionKey(region, locale, size) { return `${region}:${locale}#${size}`; } async function getQuestionCached(region, locale, size) { let key = questionKey(region, locale, size); let quizz = await cache.pop(key); if (!quizz) { quizz = generateQuestion(region, locale, size); cache.push(key, quizz); } return quizz; } async function cacheQuestion(region, locale, size) { let quizz = await generateQuestion(region, locale, size); let key = questionKey(region, locale, size); cache.push(key, quizz); return quizz; } async function generateQuestion(region, locale, nb_propositions) { const quizz = {} try { let speciesSelection = []; do { speciesSelection = await getSpeciesSelection(region, nb_propositions); } while (speciesSelection == []); debugResponses(`Species selection: ${speciesSelection}`); const speciesSelectionLocalized = await getLocalizedNames(speciesSelection, locale); if (!speciesSelectionLocalized) { throw 'Not localized'; } debugResponses('Localized species selection:', speciesSelectionLocalized); quizz.species = speciesSelectionLocalized; debug("Got species selection", quizz.species); let answer; do { answer = choice(speciesSelectionLocalized); quizz.answer = answer; quizz.audio = await getAudio(answer.sciName); if (quizz.audio === undefined) { debug("No audio found for species", answer.sciName); debug("Trying again..."); } } while (quizz.audio === undefined); debug("Got answer", answer); debug("Got audio", quizz.audio); } catch (error) { debug("Error raised while generating quizz"); console.error(error); } return quizz; } async function generateQuizz(region, locale, nb_questions) { let questions = []; let answers = []; const species_in_region = await getSpeciesList(region); const species_in_region_localized = await getLocalizedNames(species_in_region, locale); console.error(species_in_region_localized); for (let i=0; i < nb_questions; i++) { try { let question_species = choice(species_in_region_localize); let question_audio = await getAudio(question_species.sciName); questions.push(question_audio); answers.push(question_species); } catch (error) { console.error(error); } } return {questions: questions, answers: answers, propositions: species_in_region_localized}; } async function getSpeciesSelection(region, number) { const regionCode = region; const speciesList = await getSpeciesList(regionCode); const speciesSelection = choices(speciesList, number); return speciesSelection; } async function getLocalizedNames(speciesCodes, locale) { const localizedNames = []; for (const speciesCode of speciesCodes) { const localized = await getLocalizedName(speciesCode, locale); if (!localized.sciName.includes(" x ")) localizedNames.push(localized); } return localizedNames; } function getLocalizedName(speciesCode, locale) { return eBird.ref.taxonomy.ebird({ fmt: 'json', locale: locale, species: speciesCode }).then( response => { const names = response[0]; debug("Got localized species names"); debugResponses(names); return names; } ).catch(error => { throw error; }); } async function getSpeciesList(regionCode) { const cached = await cache.getCached(`spplist-${regionCode}`); if (cached) { if (cached != []) { return cached; } else { console.log("Empty cached species list, retrieving again"); } } else { return eBird.product.spplist.in(regionCode)() .then(species => { cache.cacheResponse(`spplist-${regionCode}`, species); return species; }) .catch(error => { throw error; }); } } function getAudio(speciesScientificName) { return XenoCanto.search({ name: speciesScientificName, quality: 'A' }).then(response => { const { recordings } = response; const randomRecord = choice(recordings); const audio = randomRecord.file; return audio; }).catch(error => { throw error; }); } async function getSpeciesCompletion(region, locale, term) { let speciesList = await getSpeciesList(region); let localizedSpeciesList = await getLocalizedNames(speciesList, locale); let termLower = term.toLowerCase(); let speciesCompletion = localizedSpeciesList.filter(speciesNames => { let { comName, sciName } = speciesNames; let name = comName.toLowerCase(); let sci = sciName.toLowerCase(); return name.includes(termLower) || sci.includes(termLower); }); return speciesCompletion; } module.exports = { generateQuestion, getQuestionCached, getSpeciesCompletion, cacheQuestion, generateQuizz }