UNPKG

booru

Version:

Search (and do other things) on a bunch of different boorus!

237 lines 8.28 kB
"use strict"; /** * @packageDocumentation * @module Boorus */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Booru = void 0; const Constants_1 = require("../Constants"); const Utils_1 = require("../Utils"); const undici_1 = require("undici"); const Post_1 = __importDefault(require("../structures/Post")); const SearchResults_1 = __importDefault(require("../structures/SearchResults")); const resolvedFetch = typeof window !== 'undefined' ? window.fetch.bind(window) : undici_1.fetch; /* - new Booru => Constructor, params {name, {nsfw, {search, postView, ...}, random}, {apiTokens...}} => .search([tags...], {limit, random}) => .postView(id) => .site */ /** * A basic, JSON booru * @example * ``` * const Booru = require('booru') * // Aliases are supported * const e9 = Booru('e9') * * // You can then search the site * const imgs = await e9.search(['cat', 'cute'], {limit: 3}) * * // And use the images * imgs.forEach(i => console.log(i.fileUrl)) * * // Or access other methods on the Booru * e9.postView(imgs[0].id) * ``` */ class Booru { /** The domain of the booru */ domain; /** The site object representing this booru */ site; /** The credentials to use for this booru */ credentials; /** * Create a new booru from a site * * @private * @param site The site to use * @param credentials Credentials for the API (Currently not used) */ constructor(site, credentials) { const domain = (0, Utils_1.resolveSite)(site.domain); if (domain === null) { throw new Error(`Invalid site passed: ${site.domain}`); } this.domain = domain; this.site = site; this.credentials = credentials; } normalizeTags(tags) { if (!Array.isArray(tags)) { return [tags]; } return tags.slice(); } /** * Search for images on this booru * @param {String|String[]} tags The tag(s) to search for * @param {SearchParameters} searchArgs The arguments for the search * @return {Promise<SearchResults>} The results as an array of Posts */ async search(tags, { limit = 1, random = false, page = 0, showUnavailable = false, } = {}) { const fakeLimit = random && !this.site.random ? 100 : 0; const tagArray = this.normalizeTags(tags); try { const searchResult = await this.doSearchRequest(tagArray, { limit, random, page, showUnavailable, }); return this.parseSearchResult(searchResult, { fakeLimit, tags: tagArray, limit, random, page, showUnavailable, }); } catch (err) { if (err instanceof Error) { throw new Constants_1.BooruError(err); } throw err; } } /** * Gets the url you'd see in your browser from a post id for this booru * * @param {String} id The id to get the postView for * @return {String} The url to the post */ postView(id) { if (typeof id === 'string' && Number.isNaN(Number.parseInt(id, 10))) { throw new Constants_1.BooruError(`Not a valid id for postView: ${id}`); } return `http${this.site.insecure ? '' : 's'}://${this.domain}${this.site.api.postView}${id}`; } /** * The internal & common searching logic, pls dont use this use .search instead * * @protected * @param {String[]|String} tags The tags to search with * @param {InternalSearchParameters} searchArgs The arguments for the search * @return {Promise<Object>} */ async doSearchRequest(tags, { uri = null, limit = 1, random = false, page = 0, } = {}) { // Used for random on sites without order:random let fakeLimit; let searchTags = tags.slice(); if (random) { if (this.site.random) { searchTags.push('order:random'); } else { fakeLimit = 100; } } if (this.site.defaultTags) { searchTags = searchTags.concat(this.site.defaultTags.filter((v) => !searchTags.includes(v))); } const fetchuri = uri ?? this.getSearchUrl({ tags: searchTags, limit: fakeLimit ?? limit, page }); const options = Constants_1.defaultOptions; const xml = this.site.type === 'xml'; try { const response = await resolvedFetch(fetchuri, options); // Check for CloudFlare ratelimiting if (response.status === 503) { const body = await response.clone().text(); if (body.includes('cf-browser-verification')) { throw new Constants_1.BooruError("Received a CloudFlare browser verification request. Can't proceed."); } } const data = await response.text(); const posts = xml ? (0, Utils_1.jsonfy)(data) : (0, Utils_1.tryParseJSON)(data); if (!response.ok) { throw new Constants_1.BooruError(`Received HTTP ${response.status} ` + `from booru: '${posts.error ?? posts.message ?? JSON.stringify(posts)}'`); } return posts; } catch (err) { if (err.type === 'invalid-json') return ''; throw err; } } /** * Generates a URL to search the booru with, mostly for debugging purposes * @param opt * @param {string[]} [opt.tags] The tags to search for * @param {number} [opt.limit] The limit of results to return * @param {number} [opt.page] The page of results to return * @returns A URL to search the booru */ getSearchUrl({ tags = [], limit = 100, page = 1, } = {}) { return (0, Constants_1.searchURI)(this.site, tags, limit, page, this.credentials); } /** * Parse the response from the booru * * @protected * @param {Object} result The response of the booru * @param {InternalSearchParameters} searchArgs The arguments used for the search * @return {SearchResults} The results of this search */ parseSearchResult(result, { fakeLimit, tags, limit, random, page, showUnavailable, }) { let outResult = result; if (outResult.success === false) { throw new Constants_1.BooruError(outResult.message ?? outResult.reason); } // Gelbooru if (outResult['@attributes']) { const attributes = outResult['@attributes']; if (attributes.count === '0' || !outResult.post) { outResult = []; } else if (Array.isArray(outResult.post)) { outResult = outResult.post; } else { outResult = [outResult.post]; } } if (outResult.posts) { outResult = outResult.posts; } if (outResult.images) { outResult = outResult.images; } let r; // If gelbooru/other booru decides to return *nothing* instead of an empty array if (outResult === '') { r = []; } else if (fakeLimit) { r = (0, Utils_1.shuffle)(outResult); } else if (outResult.constructor === Object) { // For XML based sites r = [outResult]; } let posts = (r ?? outResult) .slice(0, limit) .map((v) => new Post_1.default(v, this)); const options = { limit, random, page, showUnavailable }; if (tags === undefined) { tags = []; } if (!showUnavailable) { posts = posts.filter((p) => p.available); } return new SearchResults_1.default(posts, tags, options, this); } } exports.Booru = Booru; exports.default = Booru; //# sourceMappingURL=Booru.js.map