soundbirder/controllers/quizz.js

172 lines
5.5 KiB
JavaScript
Executable File

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
}