booru
Version:
Search (and do other things) on a bunch of different boorus!
214 lines • 6.92 kB
JavaScript
;
/**
* @packageDocumentation
* @module Utils
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveSite = resolveSite;
exports.jsonifyPosts = jsonifyPosts;
exports.jsonifyTags = jsonifyTags;
exports.tryParseJSON = tryParseJSON;
exports.shuffle = shuffle;
exports.randInt = randInt;
exports.validateSearchParams = validateSearchParams;
exports.compareArrays = compareArrays;
exports.querystring = querystring;
exports.encodeURIQueryValue = encodeURIQueryValue;
const fast_xml_parser_1 = require("fast-xml-parser");
const Constants_1 = require("./Constants");
/**
* Check if `site` is a supported site (and check if it's an alias and return the sites's true name)
*
* @param {String} domain The site to resolveSite
* @return {String?} null if site is not supported, the site otherwise
*/
function resolveSite(domain) {
if (typeof domain !== 'string') {
return null;
}
const lowerDomain = domain.toLowerCase();
for (const [site, info] of Object.entries(Constants_1.sites)) {
if (site === lowerDomain ||
info.domain === lowerDomain ||
info.aliases.includes(lowerDomain)) {
return site;
}
}
return null;
}
const xmlParser = new fast_xml_parser_1.XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
});
/**
* Parses posts xml to json, which can be used with js
*
* @private
* @param {String} xml The xml to convert to json
* @return {Object[]} A Promise with an array of objects created from the xml
*/
function jsonifyPosts(xml) {
if (typeof xml === 'object')
return xml;
const data = xmlParser.parse(xml);
if (data.html || data['!doctype']) {
// Some boorus return HTML error pages instead of JSON responses on errors
// So try scraping off what we can in that case
const page = data.html ?? data['!doctype']?.html;
const message = [];
if (page.body.h1) {
message.push(page.body.h1);
}
if (page.body.p) {
message.push(page.body.p['#text']);
}
throw new Constants_1.BooruError(`The Booru sent back an error: '${message.join(': ')}'`);
}
if (data.posts.post) {
return data.posts.post;
}
if (data.posts.tag) {
return Array.isArray(data.posts.tag) ? data.posts.tag : [data.posts.tag];
}
return [];
}
/**
* Parses tags xml to json, which can be used with js
*
* @private
* @param {String} xml The xml to convert to json
* @return {Object[]} A Promise with an array of objects created from the xml
*/
function jsonifyTags(xml) {
if (typeof xml === 'object')
return xml;
const data = xmlParser.parse(xml);
if (data.html || data['!doctype']) {
// Some boorus return HTML error pages instead of JSON responses on errors
// So try scraping off what we can in that case
const page = data.html ?? data['!doctype']?.html;
const message = [];
if (page.body.h1) {
message.push(page.body.h1);
}
if (page.body.p) {
message.push(page.body.p['#text']);
}
throw new Constants_1.BooruError(`The Booru sent back an error: '${message.join(': ')}'`);
}
if (data.tags.tag) {
return data.tags.tag
? Array.isArray(data.tags.tag)
? data.tags.tag
: [data.tags.tag]
: [];
}
return [];
}
/**
* Try to parse JSON, and then return an empty array if data is an empty string, or the parsed JSON
*
* Blame rule34.xxx for returning literally an empty response with HTTP 200 for this
* @param data The data to try and parse
* @returns Either the parsed data, or an empty array
*/
function tryParseJSON(data) {
if (data === '') {
return [];
}
return JSON.parse(data);
}
/**
* Yay fisher-bates
* Taken from http://stackoverflow.com/a/2450976
*
* @private
* @param {Array} array Array of something
* @return {Array} Shuffled array of something
*/
function shuffle(array) {
let currentIndex = array.length;
let temporaryValue;
let randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
// Thanks mdn and damnit derpibooru
/**
* Generate a random int between [min, max]
*
* @private
* @param {Number} min The minimum (inclusive)
* @param {Number} max The maximum (inclusive)
*/
function randInt(min, max) {
const nmin = Math.ceil(min);
const nmax = Math.floor(max);
return Math.floor(Math.random() * (nmax - nmin + 1)) + nmin;
}
/**
* Performs some basic search validation
*
* @private
* @param {String} site The site to resolve
* @param {Number|String} limit The limit for the amount of images to fetch
*/
function validateSearchParams(site, limit) {
const resolvedSite = resolveSite(site);
const resolvedLimit = typeof limit !== 'number' ? Number.parseInt(limit, 10) : limit;
if (resolvedSite === null) {
throw new Constants_1.BooruError('Site not supported');
}
if (typeof limit !== 'number' || Number.isNaN(limit)) {
throw new Constants_1.BooruError('`limit` should be an int');
}
return { site: resolvedSite, limit: resolvedLimit };
}
/**
* Finds the matching strings between two arrays
*
* @private
* @param {String[]} arr1 The first array
* @param {String[]} arr2 The second array
* @return {String[]} The shared strings between the arrays
*/
function compareArrays(arr1, arr2) {
return arr1.filter((e1) => arr2.some((e2) => e1.toLowerCase() === e2.toLowerCase()));
}
/**
* Turns an object into a query string, correctly encoding uri components
*
* @example
* const options = { page: 10, limit: 100 }
* const query = querystring(options) // 'page=10&limit=100'
* console.log(`https://example.com?${query}`)
*
* @param query An object with key/value pairs that will be turned into a string
* @returns A string that can be appended to a url (after `?`)
*/
function querystring(query, { arrayJoin = '+' } = {}) {
return Object.entries(query)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIQueryValue(value, {
arrayJoin,
})}`)
.join('&');
}
/**
* Encodes a single value or an array of values to be usable in as a URI component,
* joining array elements with '+'
* @param value The value to encode
* @returns An encoded value that can be passed to a querystring
*/
function encodeURIQueryValue(value, { arrayJoin = '+' } = {}) {
if (Array.isArray(value)) {
return value.map(encodeURIComponent).join(arrayJoin);
}
return encodeURIComponent(value);
}
//# sourceMappingURL=Utils.js.map