mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
145 lines
5.9 KiB
JavaScript
145 lines
5.9 KiB
JavaScript
import nconf from 'nconf';
|
|
import fs from 'fs';
|
|
import { langCodes } from '../../libs/i18n';
|
|
import { CONTENT_CACHE_PATH, getLocalizedContentResponse } from '../../libs/content';
|
|
|
|
const IS_PROD = nconf.get('IS_PROD');
|
|
|
|
const api = {};
|
|
|
|
const CACHED_HASHES = [
|
|
|
|
];
|
|
|
|
const MOBILE_FILTER = `achievements,questSeriesAchievements,animalColorAchievements,animalSetAchievements,stableAchievements,
|
|
mystery,bundles,loginIncentives,pets,premiumPets,specialPets,questPets,wackyPets,mounts,premiumMounts,specialMounts,questMounts,
|
|
events,dropEggs,questEggs,dropHatchingPotions,premiumHatchingPotions,wackyHatchingPotions,backgroundsFlat,questsByLevel,gear.tree,
|
|
tasksByCategory,userDefaults,timeTravelStable,gearTypes,cardTypes`;
|
|
|
|
function hashForFilter (filter) {
|
|
let hash = 0;
|
|
let i; let
|
|
chr;
|
|
if (filter.length === 0) return '';
|
|
for (i = 0; i < filter.length; i++) { // eslint-disable-line
|
|
chr = filter.charCodeAt(i);
|
|
hash = ((hash << 5) - hash) + chr; // eslint-disable-line
|
|
hash |= 0; // eslint-disable-line
|
|
}
|
|
return String(hash);
|
|
}
|
|
|
|
/**
|
|
* @api {get} /api/v3/content Get all available content objects
|
|
* @apiDescription Does not require authentication.
|
|
* @apiName ContentGet
|
|
* @apiGroup Content
|
|
*
|
|
* @apiParam (Query) {String="bg","cs","da","de",
|
|
* "en","en@pirate","en_GB",
|
|
* "es","es_419","fr","he","hu",
|
|
* "id","it","ja","nl","pl","pt","pt_BR",
|
|
* "ro","ru","sk","sr","sv",
|
|
* "uk","zh","zh_TW"} [language=en] Language code used for the items'
|
|
* strings. If the authenticated user makes
|
|
* the request, the content will return with
|
|
* the user's configured language.
|
|
*
|
|
* @apiSuccess {Object} data Various data about the content of Habitica. The content route
|
|
* contains many keys, but the data listed below are the recommended data to use.
|
|
* @apiSuccess {Object} data.mystery The mystery sets awarded to paying subscribers.
|
|
* @apiSuccess {Object} data.gear The gear that can be equipped.
|
|
* @apiSuccess {Object} data.gear.tree Detailed information about the gear, organized by type.
|
|
* @apiSuccess {Object} data.gear.flat The full key of each equipment.
|
|
* @apiSuccess {Object} data.spells The skills organized by class. Includes cards and visual buffs.
|
|
* @apiSuccess {Object} data.potion Data about the health potion.
|
|
* @apiSuccess {Object} data.armoire Data about the armoire.
|
|
* @apiSuccess {Array} data.classes The available classes.
|
|
* @apiSuccess {Object} data.eggs All available eggs.
|
|
* @apiSuccess {Object} data.timeTravelStable The animals available
|
|
* in the Time Traveler's stable, separated
|
|
* into pets and mounts.
|
|
* @apiSuccess {Object} data.hatchingPotions All the hatching potions.
|
|
* @apiSuccess {Object} data.petInfo All the pets with extra info.
|
|
* @apiSuccess {Object} data.mountInfo All the mounts with extra info.
|
|
* @apiSuccess {Object} data.food All the food.
|
|
* @apiSuccess {Array} data.userCanOwnQuestCategories The types of quests that a user can own.
|
|
* @apiSuccess {Object} data.quests Data about the quests.
|
|
* @apiSuccess {Object} data.appearances Data about the appearance properties.
|
|
* @apiSuccess {Object} data.appearances.hair Data about available hair options.
|
|
* @apiSuccess {Object} data.appearances.shirt Data about available shirt options.
|
|
* @apiSuccess {Object} data.appearances.size Data about available body size options.
|
|
* @apiSuccess {Object} data.appearances.skin Data about available skin options.
|
|
* @apiSuccess {Object} data.appearances.chair Data about available chair options.
|
|
* @apiSuccess {Object} data.appearances.background Data about available background options.
|
|
* @apiSuccess {Object} data.backgrounds Data about the background sets.
|
|
* @apiSuccess {Object} data.subscriptionBlocks Data about the various subscriptions blocks.
|
|
*
|
|
*/
|
|
api.getContent = {
|
|
method: 'GET',
|
|
url: '/content',
|
|
noLanguage: true,
|
|
async handler (req, res) {
|
|
let language = 'en';
|
|
const proposedLang = req.query.language;
|
|
|
|
if (proposedLang && langCodes.includes(proposedLang)) {
|
|
language = proposedLang;
|
|
}
|
|
|
|
let filter = req.query.filter || '';
|
|
// apply defaults for mobile clients
|
|
if (filter === '') {
|
|
if (req.headers['x-client'] === 'habitica-android') {
|
|
filter = `${MOBILE_FILTER},appearance.background`;
|
|
} else if (req.headers['x-client'] === 'habitica-ios') {
|
|
filter = `${MOBILE_FILTER},backgrounds`;
|
|
}
|
|
}
|
|
|
|
// Build usable filter object
|
|
const filterObj = {};
|
|
filter.split(',').forEach(item => {
|
|
if (item.includes('.')) {
|
|
const [key, subkey] = item.split('.');
|
|
if (!filterObj[key]) {
|
|
filterObj[key] = {};
|
|
}
|
|
filterObj[key][subkey.trim()] = true;
|
|
} else {
|
|
filterObj[item.trim()] = true;
|
|
}
|
|
});
|
|
|
|
if (IS_PROD) {
|
|
const filterHash = language + hashForFilter(filter);
|
|
if (CACHED_HASHES.includes(filterHash)) {
|
|
// Content is already cached, so just send it.
|
|
res.sendFile(`${CONTENT_CACHE_PATH}${filterHash}.json`);
|
|
} else {
|
|
// Content is not cached, so cache it and send it.
|
|
res.set({
|
|
'Content-Type': 'application/json',
|
|
});
|
|
const jsonResString = getLocalizedContentResponse(language, filterObj);
|
|
fs.writeFileSync(
|
|
`${CONTENT_CACHE_PATH}${filterHash}.json`,
|
|
jsonResString,
|
|
'utf8',
|
|
);
|
|
CACHED_HASHES.push(filterHash);
|
|
res.status(200).send(jsonResString);
|
|
}
|
|
} else {
|
|
res.set({
|
|
'Content-Type': 'application/json',
|
|
});
|
|
const jsonResString = getLocalizedContentResponse(language, filterObj);
|
|
res.status(200).send(jsonResString);
|
|
}
|
|
},
|
|
};
|
|
|
|
export default api;
|