booru 2.6.2 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Constants.js CHANGED
@@ -1 +1,91 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(r){return r&&r.__esModule?r:{default:r}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.defaultOptions=exports.searchURI=exports.USER_AGENT=exports.BooruError=exports.sites=void 0;const sites_json_1=__importDefault(require("./sites.json")),Utils_1=require("./Utils"),expandedTags={"rating:e":"rating:explicit","rating:q":"rating:questionable","rating:s":"rating:safe"};exports.sites=sites_json_1.default;class BooruError extends Error{constructor(r){super(r instanceof Error?r.message:r),r instanceof Error?this.stack=r.stack:Error.captureStackTrace(this,BooruError),this.name="BooruError"}}function expandTags(r){return r.map((r=>expandedTags[r.toLowerCase()]??r))}function searchURI(r,t=[],e=100,s=0,o={}){const a=(0,Utils_1.querystring)({[r.tagQuery]:expandTags(t),limit:e,[r.paginate]:s,...o},{arrayJoin:r.tagJoin});return`http${r.insecure?"":"s"}://${r.domain}${r.api.search}`+a}exports.BooruError=BooruError,exports.USER_AGENT="booru (https://github.com/AtoraSuunva/booru)",exports.searchURI=searchURI,exports.defaultOptions={headers:{Accept:"application/json, application/xml;q=0.9, */*;q=0.8","User-Agent":exports.USER_AGENT}};
1
+ "use strict";
2
+ /**
3
+ * @packageDocumentation
4
+ * @module Constants
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.defaultOptions = exports.searchURI = exports.USER_AGENT = exports.BooruError = exports.sites = void 0;
11
+ const sites_json_1 = __importDefault(require("./sites.json"));
12
+ const Utils_1 = require("./Utils");
13
+ const expandedTags = {
14
+ 'rating:e': 'rating:explicit',
15
+ 'rating:q': 'rating:questionable',
16
+ 'rating:s': 'rating:safe',
17
+ };
18
+ /**
19
+ * A map of site url/{@link SiteInfo}
20
+ */
21
+ exports.sites = sites_json_1.default;
22
+ /**
23
+ * Custom error type for when the boorus error or for user-side error, not my code (probably)
24
+ * <p>The name of the error is 'BooruError'
25
+ * @type {Error}
26
+ */
27
+ class BooruError extends Error {
28
+ constructor(message) {
29
+ super(message instanceof Error ? message.message : message);
30
+ if (message instanceof Error) {
31
+ this.stack = message.stack;
32
+ }
33
+ else {
34
+ Error.captureStackTrace(this, BooruError);
35
+ }
36
+ this.name = 'BooruError';
37
+ }
38
+ }
39
+ exports.BooruError = BooruError;
40
+ /**
41
+ * The user-agent to use for searches
42
+ * @private
43
+ */
44
+ exports.USER_AGENT = `booru (https://github.com/AtoraSuunva/booru)`;
45
+ /**
46
+ * Expands tags based on a simple map, used for gelbooru/safebooru/etc compat :(
47
+ *
48
+ * @private
49
+ * @param {String[]} tags The tags to expand
50
+ */
51
+ function expandTags(tags) {
52
+ return tags.map((v) => expandedTags[v.toLowerCase()] ?? v);
53
+ }
54
+ /**
55
+ * Create a full uri to search with
56
+ *
57
+ * @private
58
+ * @param {string} domain The domain to search
59
+ * @param {Site} site The site to search
60
+ * @param {string[]} [tags=[]] The tags to search for
61
+ * @param {number} [limit=100] The limit for images to return
62
+ * @param {number} [page=0] The page to get
63
+ * @param {BooryCredentials} [credentials] The credentials to use for the search, appended to the querystring
64
+ */
65
+ function searchURI(site, tags = [], limit = 100, page = 0, credentials = {}) {
66
+ const query = (0, Utils_1.querystring)({
67
+ [site.tagQuery]: expandTags(tags),
68
+ limit,
69
+ [site.paginate]: page,
70
+ ...credentials,
71
+ }, {
72
+ arrayJoin: site.tagJoin,
73
+ });
74
+ return (`http${site.insecure ? '' : 's'}://` +
75
+ `${site.domain}${site.api.search}` +
76
+ query);
77
+ }
78
+ exports.searchURI = searchURI;
79
+ /**
80
+ * The default options to use for requests
81
+ * <p>I could document this better but meh
82
+ *
83
+ * @private
84
+ */
85
+ exports.defaultOptions = {
86
+ headers: {
87
+ Accept: 'application/json, application/xml;q=0.9, */*;q=0.8',
88
+ 'User-Agent': exports.USER_AGENT,
89
+ },
90
+ };
91
+ //# sourceMappingURL=Constants.js.map
package/dist/Utils.js CHANGED
@@ -1 +1,185 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.encodeURIQueryValue=exports.querystring=exports.compareArrays=exports.validateSearchParams=exports.randInt=exports.shuffle=exports.tryParseJSON=exports.jsonfy=exports.resolveSite=void 0;const Constants_1=require("./Constants"),fast_xml_parser_1=require("fast-xml-parser");function resolveSite(r){if("string"!=typeof r)return null;r=r.toLowerCase();for(const[e,t]of Object.entries(Constants_1.sites))if(e===r||t.domain===r||t.aliases.includes(r))return e;return null}exports.resolveSite=resolveSite;const xmlParser=new fast_xml_parser_1.XMLParser({ignoreAttributes:!1,attributeNamePrefix:""});function jsonfy(r){if("object"==typeof r)return r;const e=xmlParser.parse(r);if(e.html||e["!doctype"]){const r=e.html||e["!doctype"]?.html,t=[];throw r.body.h1&&t.push(r.body.h1),r.body.p&&t.push(r.body.p["#text"]),new Constants_1.BooruError(`The Booru sent back an error: '${t.join(": ")}'`)}return e.posts.post?e.posts.post:e.posts.tag?Array.isArray(e.posts.tag)?e.posts.tag:[e.posts.tag]:[]}function tryParseJSON(r){return""===r?[]:JSON.parse(r)}function shuffle(r){let e,t,o=r.length;for(;0!==o;)t=Math.floor(Math.random()*o),o-=1,e=r[o],r[o]=r[t],r[t]=e;return r}function randInt(r,e){return r=Math.ceil(r),e=Math.floor(e),Math.floor(Math.random()*(e-r+1))+r}function validateSearchParams(r,e){const t=resolveSite(r);if("number"!=typeof e&&(e=parseInt(e,10)),null===t)throw new Constants_1.BooruError("Site not supported");if("number"!=typeof e||Number.isNaN(e))throw new Constants_1.BooruError("`limit` should be an int");return{site:t,limit:e}}function compareArrays(r,e){return r.filter((r=>e.some((e=>r.toLowerCase()===e.toLowerCase()))))}function querystring(r,{arrayJoin:e="+"}={}){return Object.entries(r).map((([r,t])=>`${encodeURIComponent(r)}=${encodeURIQueryValue(t,{arrayJoin:e})}`)).join("&")}function encodeURIQueryValue(r,{arrayJoin:e="+"}={}){return Array.isArray(r)?r.map(encodeURIComponent).join(e):encodeURIComponent(r)}exports.jsonfy=jsonfy,exports.tryParseJSON=tryParseJSON,exports.shuffle=shuffle,exports.randInt=randInt,exports.validateSearchParams=validateSearchParams,exports.compareArrays=compareArrays,exports.querystring=querystring,exports.encodeURIQueryValue=encodeURIQueryValue;
1
+ "use strict";
2
+ /**
3
+ * @packageDocumentation
4
+ * @module Utils
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.encodeURIQueryValue = exports.querystring = exports.compareArrays = exports.validateSearchParams = exports.randInt = exports.shuffle = exports.tryParseJSON = exports.jsonfy = exports.resolveSite = void 0;
8
+ const Constants_1 = require("./Constants");
9
+ const fast_xml_parser_1 = require("fast-xml-parser");
10
+ /**
11
+ * Check if `site` is a supported site (and check if it's an alias and return the sites's true name)
12
+ *
13
+ * @param {String} domain The site to resolveSite
14
+ * @return {String?} null if site is not supported, the site otherwise
15
+ */
16
+ function resolveSite(domain) {
17
+ if (typeof domain !== 'string') {
18
+ return null;
19
+ }
20
+ domain = domain.toLowerCase();
21
+ for (const [site, info] of Object.entries(Constants_1.sites)) {
22
+ if (site === domain ||
23
+ info.domain === domain ||
24
+ info.aliases.includes(domain)) {
25
+ return site;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ exports.resolveSite = resolveSite;
31
+ const xmlParser = new fast_xml_parser_1.XMLParser({
32
+ ignoreAttributes: false,
33
+ attributeNamePrefix: '',
34
+ });
35
+ /**
36
+ * Parses xml to json, which can be used with js
37
+ *
38
+ * @private
39
+ * @param {String} xml The xml to convert to json
40
+ * @return {Object[]} A Promise with an array of objects created from the xml
41
+ */
42
+ function jsonfy(xml) {
43
+ if (typeof xml === 'object')
44
+ return xml;
45
+ const data = xmlParser.parse(xml);
46
+ if (data.html || data['!doctype']) {
47
+ // Some boorus return HTML error pages instead of JSON responses on errors
48
+ // So try scraping off what we can in that case
49
+ const page = data.html || data['!doctype']?.html;
50
+ const message = [];
51
+ if (page.body.h1) {
52
+ message.push(page.body.h1);
53
+ }
54
+ if (page.body.p) {
55
+ message.push(page.body.p['#text']);
56
+ }
57
+ throw new Constants_1.BooruError(`The Booru sent back an error: '${message.join(': ')}'`);
58
+ }
59
+ if (data.posts.post) {
60
+ return data.posts.post;
61
+ }
62
+ if (data.posts.tag) {
63
+ return Array.isArray(data.posts.tag) ? data.posts.tag : [data.posts.tag];
64
+ }
65
+ return [];
66
+ }
67
+ exports.jsonfy = jsonfy;
68
+ /**
69
+ * Try to parse JSON, and then return an empty array if data is an empty string, or the parsed JSON
70
+ *
71
+ * Blame rule34.xxx for returning literally an empty response with HTTP 200 for this
72
+ * @param data The data to try and parse
73
+ * @returns Either the parsed data, or an empty array
74
+ */
75
+ function tryParseJSON(data) {
76
+ if (data === '') {
77
+ return [];
78
+ }
79
+ return JSON.parse(data);
80
+ }
81
+ exports.tryParseJSON = tryParseJSON;
82
+ /**
83
+ * Yay fisher-bates
84
+ * Taken from http://stackoverflow.com/a/2450976
85
+ *
86
+ * @private
87
+ * @param {Array} array Array of something
88
+ * @return {Array} Shuffled array of something
89
+ */
90
+ function shuffle(array) {
91
+ let currentIndex = array.length;
92
+ let temporaryValue;
93
+ let randomIndex;
94
+ while (currentIndex !== 0) {
95
+ randomIndex = Math.floor(Math.random() * currentIndex);
96
+ currentIndex -= 1;
97
+ temporaryValue = array[currentIndex];
98
+ array[currentIndex] = array[randomIndex];
99
+ array[randomIndex] = temporaryValue;
100
+ }
101
+ return array;
102
+ }
103
+ exports.shuffle = shuffle;
104
+ // Thanks mdn and damnit derpibooru
105
+ /**
106
+ * Generate a random int between [min, max]
107
+ *
108
+ * @private
109
+ * @param {Number} min The minimum (inclusive)
110
+ * @param {Number} max The maximum (inclusive)
111
+ */
112
+ function randInt(min, max) {
113
+ min = Math.ceil(min);
114
+ max = Math.floor(max);
115
+ return Math.floor(Math.random() * (max - min + 1)) + min;
116
+ }
117
+ exports.randInt = randInt;
118
+ /**
119
+ * Performs some basic search validation
120
+ *
121
+ * @private
122
+ * @param {String} site The site to resolve
123
+ * @param {Number|String} limit The limit for the amount of images to fetch
124
+ */
125
+ function validateSearchParams(site, limit) {
126
+ const resolvedSite = resolveSite(site);
127
+ if (typeof limit !== 'number') {
128
+ limit = parseInt(limit, 10);
129
+ }
130
+ if (resolvedSite === null) {
131
+ throw new Constants_1.BooruError('Site not supported');
132
+ }
133
+ if (typeof limit !== 'number' || Number.isNaN(limit)) {
134
+ throw new Constants_1.BooruError('`limit` should be an int');
135
+ }
136
+ return { site: resolvedSite, limit };
137
+ }
138
+ exports.validateSearchParams = validateSearchParams;
139
+ /**
140
+ * Finds the matching strings between two arrays
141
+ *
142
+ * @private
143
+ * @param {String[]} arr1 The first array
144
+ * @param {String[]} arr2 The second array
145
+ * @return {String[]} The shared strings between the arrays
146
+ */
147
+ function compareArrays(arr1, arr2) {
148
+ return arr1.filter((e1) => arr2.some((e2) => e1.toLowerCase() === e2.toLowerCase()));
149
+ }
150
+ exports.compareArrays = compareArrays;
151
+ /**
152
+ * Turns an object into a query string, correctly encoding uri components
153
+ *
154
+ * @example
155
+ * const options = { page: 10, limit: 100 }
156
+ * const query = querystring(options) // 'page=10&limit=100'
157
+ * console.log(`https://example.com?${query}`)
158
+ *
159
+ * @param query An object with key/value pairs that will be turned into a string
160
+ * @returns A string that can be appended to a url (after `?`)
161
+ */
162
+ function querystring(query, { arrayJoin = '+' } = {}) {
163
+ return Object.entries(query)
164
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIQueryValue(value, {
165
+ arrayJoin,
166
+ })}`)
167
+ .join('&');
168
+ }
169
+ exports.querystring = querystring;
170
+ /**
171
+ * Encodes a single value or an array of values to be usable in as a URI component,
172
+ * joining array elements with '+'
173
+ * @param value The value to encode
174
+ * @returns An encoded value that can be passed to a querystring
175
+ */
176
+ function encodeURIQueryValue(value, { arrayJoin = '+' } = {}) {
177
+ if (Array.isArray(value)) {
178
+ return value.map(encodeURIComponent).join(arrayJoin);
179
+ }
180
+ else {
181
+ return encodeURIComponent(value);
182
+ }
183
+ }
184
+ exports.encodeURIQueryValue = encodeURIQueryValue;
185
+ //# sourceMappingURL=Utils.js.map
@@ -1 +1,237 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.Booru=void 0;const undici_1=require("undici"),Constants_1=require("../Constants"),Utils_1=require("../Utils"),Post_1=__importDefault(require("../structures/Post")),SearchResults_1=__importDefault(require("../structures/SearchResults")),resolvedFetch="undefined"!=typeof window?window.fetch.bind(window):undici_1.fetch;class Booru{domain;site;credentials;constructor(t,e){const s=(0,Utils_1.resolveSite)(t.domain);if(null===s)throw new Error(`Invalid site passed: ${t}`);this.domain=s,this.site=t,this.credentials=e}async search(t,{limit:e=1,random:s=!1,page:r=0,showUnavailable:i=!1}={}){const o=s&&!this.site.random?100:0;try{const a=await this.doSearchRequest(t,{limit:e,random:s,page:r,showUnavailable:i});return this.parseSearchResult(a,{fakeLimit:o,tags:t,limit:e,random:s,page:r,showUnavailable:i})}catch(t){throw t instanceof Error?new Constants_1.BooruError(t):t}}postView(t){if("string"==typeof t&&Number.isNaN(parseInt(t,10)))throw new Constants_1.BooruError(`Not a valid id for postView: ${t}`);return`http${this.site.insecure?"":"s"}://${this.domain}${this.site.api.postView}${t}`}async doSearchRequest(t,{uri:e=null,limit:s=1,random:r=!1,page:i=0}={}){let o;Array.isArray(t)||(t=[t]),r&&(this.site.random?t.push("order:random"):o=100),this.site.defaultTags&&(t=t.concat(this.site.defaultTags.filter((e=>!t.includes(e)))));const a=e||this.getSearchUrl({tags:t,limit:o||s,page:i}),n=Constants_1.defaultOptions,l="xml"===this.site.type;try{const t=await resolvedFetch(a,n);if(503===t.status){if((await t.clone().text()).includes("cf-browser-verification"))throw new Constants_1.BooruError("Received a CloudFlare browser verification request. Can't proceed.")}const e=await t.text(),s=l?(0,Utils_1.jsonfy)(e):(0,Utils_1.tryParseJSON)(e);if(t.ok)return s;throw new Constants_1.BooruError(`Received HTTP ${t.status} from booru: '${s.error||s.message||JSON.stringify(s)}'`)}catch(t){if("invalid-json"===t.type)return"";throw t}}getSearchUrl({tags:t=[],limit:e=100,page:s=1}={}){return(0,Constants_1.searchURI)(this.site,t,e,s,this.credentials)}parseSearchResult(t,{fakeLimit:e,tags:s,limit:r,random:i,page:o,showUnavailable:a}){if(!1===t.success)throw new Constants_1.BooruError(t.message||t.reason);if(t["@attributes"]){t="0"!==t["@attributes"].count&&t.post?Array.isArray(t.post)?t.post:[t.post]:[]}let n;t.posts&&(t=t.posts),t.images&&(t=t.images),""===t?n=[]:e?n=(0,Utils_1.shuffle)(t):t.constructor===Object&&(n=[t]);let l=(n||t).slice(0,r).map((t=>new Post_1.default(t,this)));const u={limit:r,random:i,page:o,showUnavailable:a};return void 0===s&&(s=[]),Array.isArray(s)||(s=[s]),a||(l=l.filter((t=>t.available))),new SearchResults_1.default(l,s,u,this)}}exports.Booru=Booru,exports.default=Booru;
1
+ "use strict";
2
+ /**
3
+ * @packageDocumentation
4
+ * @module Boorus
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.Booru = void 0;
11
+ const undici_1 = require("undici");
12
+ const Constants_1 = require("../Constants");
13
+ const Utils_1 = require("../Utils");
14
+ const Post_1 = __importDefault(require("../structures/Post"));
15
+ const SearchResults_1 = __importDefault(require("../structures/SearchResults"));
16
+ const resolvedFetch = typeof window !== 'undefined' ? window.fetch.bind(window) : undici_1.fetch;
17
+ /*
18
+ - new Booru
19
+ => Constructor, params {name, {nsfw, {search, postView, ...}, random}, {apiTokens...}}
20
+ => .search([tags...], {limit, random})
21
+ => .postView(id)
22
+ => .site
23
+ */
24
+ /**
25
+ * A basic, JSON booru
26
+ * @example
27
+ * ```
28
+ * const Booru = require('booru')
29
+ * // Aliases are supported
30
+ * const e9 = Booru('e9')
31
+ *
32
+ * // You can then search the site
33
+ * const imgs = await e9.search(['cat', 'cute'], {limit: 3})
34
+ *
35
+ * // And use the images
36
+ * imgs.forEach(i => console.log(i.fileUrl))
37
+ *
38
+ * // Or access other methods on the Booru
39
+ * e9.postView(imgs[0].id)
40
+ * ```
41
+ */
42
+ class Booru {
43
+ /** The domain of the booru */
44
+ domain;
45
+ /** The site object representing this booru */
46
+ site;
47
+ /** The credentials to use for this booru */
48
+ credentials;
49
+ /**
50
+ * Create a new booru from a site
51
+ *
52
+ * @private
53
+ * @param site The site to use
54
+ * @param credentials Credentials for the API (Currently not used)
55
+ */
56
+ constructor(site, credentials) {
57
+ const domain = (0, Utils_1.resolveSite)(site.domain);
58
+ if (domain === null) {
59
+ throw new Error(`Invalid site passed: ${site}`);
60
+ }
61
+ this.domain = domain;
62
+ this.site = site;
63
+ this.credentials = credentials;
64
+ }
65
+ /**
66
+ * Search for images on this booru
67
+ * @param {String|String[]} tags The tag(s) to search for
68
+ * @param {SearchParameters} searchArgs The arguments for the search
69
+ * @return {Promise<SearchResults>} The results as an array of Posts
70
+ */
71
+ async search(tags, { limit = 1, random = false, page = 0, showUnavailable = false, } = {}) {
72
+ const fakeLimit = random && !this.site.random ? 100 : 0;
73
+ try {
74
+ const searchResult = await this.doSearchRequest(tags, {
75
+ limit,
76
+ random,
77
+ page,
78
+ showUnavailable,
79
+ });
80
+ return this.parseSearchResult(searchResult, {
81
+ fakeLimit,
82
+ tags,
83
+ limit,
84
+ random,
85
+ page,
86
+ showUnavailable,
87
+ });
88
+ }
89
+ catch (err) {
90
+ if (err instanceof Error) {
91
+ throw new Constants_1.BooruError(err);
92
+ }
93
+ else {
94
+ throw err;
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * Gets the url you'd see in your browser from a post id for this booru
100
+ *
101
+ * @param {String} id The id to get the postView for
102
+ * @return {String} The url to the post
103
+ */
104
+ postView(id) {
105
+ if (typeof id === 'string' && Number.isNaN(parseInt(id, 10))) {
106
+ throw new Constants_1.BooruError(`Not a valid id for postView: ${id}`);
107
+ }
108
+ return `http${this.site.insecure ? '' : 's'}://${this.domain}${this.site.api.postView}${id}`;
109
+ }
110
+ /**
111
+ * The internal & common searching logic, pls dont use this use .search instead
112
+ *
113
+ * @protected
114
+ * @param {String[]|String} tags The tags to search with
115
+ * @param {InternalSearchParameters} searchArgs The arguments for the search
116
+ * @return {Promise<Object>}
117
+ */
118
+ async doSearchRequest(tags, { uri = null, limit = 1, random = false, page = 0, } = {}) {
119
+ if (!Array.isArray(tags))
120
+ tags = [tags];
121
+ // Used for random on sites without order:random
122
+ let fakeLimit;
123
+ if (random) {
124
+ if (this.site.random) {
125
+ tags.push('order:random');
126
+ }
127
+ else {
128
+ fakeLimit = 100;
129
+ }
130
+ }
131
+ if (this.site.defaultTags) {
132
+ tags = tags.concat(this.site.defaultTags.filter((v) => !tags.includes(v)));
133
+ }
134
+ const fetchuri = uri || this.getSearchUrl({ tags, limit: fakeLimit || limit, page });
135
+ const options = Constants_1.defaultOptions;
136
+ const xml = this.site.type === 'xml';
137
+ try {
138
+ const response = await resolvedFetch(fetchuri, options);
139
+ // Check for CloudFlare ratelimiting
140
+ if (response.status === 503) {
141
+ const body = await response.clone().text();
142
+ if (body.includes('cf-browser-verification')) {
143
+ throw new Constants_1.BooruError("Received a CloudFlare browser verification request. Can't proceed.");
144
+ }
145
+ }
146
+ const data = await response.text();
147
+ const posts = xml ? (0, Utils_1.jsonfy)(data) : (0, Utils_1.tryParseJSON)(data);
148
+ if (!response.ok) {
149
+ throw new Constants_1.BooruError(`Received HTTP ${response.status} ` +
150
+ `from booru: '${posts.error ||
151
+ posts.message ||
152
+ JSON.stringify(posts)}'`);
153
+ }
154
+ else {
155
+ return posts;
156
+ }
157
+ }
158
+ catch (err) {
159
+ if (err.type === 'invalid-json')
160
+ return '';
161
+ throw err;
162
+ }
163
+ }
164
+ /**
165
+ * Generates a URL to search the booru with, mostly for debugging purposes
166
+ * @param opt
167
+ * @param {string[]} [opt.tags] The tags to search for
168
+ * @param {number} [opt.limit] The limit of results to return
169
+ * @param {number} [opt.page] The page of results to return
170
+ * @returns A URL to search the booru
171
+ */
172
+ getSearchUrl({ tags = [], limit = 100, page = 1, } = {}) {
173
+ return (0, Constants_1.searchURI)(this.site, tags, limit, page, this.credentials);
174
+ }
175
+ /**
176
+ * Parse the response from the booru
177
+ *
178
+ * @protected
179
+ * @param {Object} result The response of the booru
180
+ * @param {InternalSearchParameters} searchArgs The arguments used for the search
181
+ * @return {SearchResults} The results of this search
182
+ */
183
+ parseSearchResult(result, { fakeLimit, tags, limit, random, page, showUnavailable, }) {
184
+ if (result.success === false) {
185
+ throw new Constants_1.BooruError(result.message || result.reason);
186
+ }
187
+ // Gelbooru
188
+ if (result['@attributes']) {
189
+ const attributes = result['@attributes'];
190
+ if (attributes.count === '0' || !result.post) {
191
+ result = [];
192
+ }
193
+ else if (Array.isArray(result.post)) {
194
+ result = result.post;
195
+ }
196
+ else {
197
+ result = [result.post];
198
+ }
199
+ }
200
+ if (result.posts) {
201
+ result = result.posts;
202
+ }
203
+ if (result.images) {
204
+ result = result.images;
205
+ }
206
+ let r;
207
+ // If gelbooru/other booru decides to return *nothing* instead of an empty array
208
+ if (result === '') {
209
+ r = [];
210
+ }
211
+ else if (fakeLimit) {
212
+ r = (0, Utils_1.shuffle)(result);
213
+ }
214
+ else if (result.constructor === Object) {
215
+ // For XML based sites
216
+ r = [result];
217
+ }
218
+ const results = r || result;
219
+ let posts = results
220
+ .slice(0, limit)
221
+ .map((v) => new Post_1.default(v, this));
222
+ const options = { limit, random, page, showUnavailable };
223
+ if (tags === undefined) {
224
+ tags = [];
225
+ }
226
+ if (!Array.isArray(tags)) {
227
+ tags = [tags];
228
+ }
229
+ if (!showUnavailable) {
230
+ posts = posts.filter((p) => p.available);
231
+ }
232
+ return new SearchResults_1.default(posts, tags, options, this);
233
+ }
234
+ }
235
+ exports.Booru = Booru;
236
+ exports.default = Booru;
237
+ //# sourceMappingURL=Booru.js.map
@@ -1 +1,49 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const Constants_1=require("../Constants"),Booru_1=__importDefault(require("./Booru"));class Derpibooru extends Booru_1.default{constructor(e,t){super(e,t)}search(e,{limit:t=1,random:r=!1,page:s=0}={}){Array.isArray(e)||(e=[e]),void 0===e[0]&&(e[0]="*"),s+=1;const o=this.getSearchUrl({tags:e,limit:t,page:s})+(r&&"string"===this.site.random?`&${this.site.random}`:"")+(this.credentials?`&key=${this.credentials.token}`:"");return super.doSearchRequest(e,{limit:t,random:r,page:s,uri:o}).then((o=>super.parseSearchResult(o,{fakeLimit:0,tags:e,limit:t,random:r,page:s}))).catch((e=>Promise.reject(new Constants_1.BooruError(e))))}}exports.default=Derpibooru;
1
+ "use strict";
2
+ /**
3
+ * @packageDocumentation
4
+ * @module Boorus
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const Constants_1 = require("../Constants");
11
+ const Booru_1 = __importDefault(require("./Booru"));
12
+ /**
13
+ * A class designed for Derpibooru
14
+ * >:(
15
+ * @private
16
+ * @extends Booru
17
+ * @inheritDoc
18
+ */
19
+ class Derpibooru extends Booru_1.default {
20
+ /**
21
+ * Create a new booru for Derpibooru from a site
22
+ * @param site The site to use
23
+ * @param credentials Credentials for the API (Currently not used)
24
+ */
25
+ constructor(site, credentials) {
26
+ super(site, credentials);
27
+ }
28
+ /** @inheritDoc */
29
+ search(tags, { limit = 1, random = false, page = 0 } = {}) {
30
+ if (!Array.isArray(tags)) {
31
+ tags = [tags];
32
+ }
33
+ // For any image, you must supply *
34
+ if (tags[0] === undefined) {
35
+ tags[0] = '*';
36
+ }
37
+ // Derpibooru offsets the pages by 1
38
+ page += 1;
39
+ const uri = this.getSearchUrl({ tags, limit, page }) +
40
+ (random && this.site.random === 'string' ? `&${this.site.random}` : '') +
41
+ (this.credentials ? `&key=${this.credentials.token}` : '');
42
+ return super
43
+ .doSearchRequest(tags, { limit, random, page, uri })
44
+ .then((r) => super.parseSearchResult(r, { fakeLimit: 0, tags, limit, random, page }))
45
+ .catch((e) => Promise.reject(new Constants_1.BooruError(e)));
46
+ }
47
+ }
48
+ exports.default = Derpibooru;
49
+ //# sourceMappingURL=Derpibooru.js.map
@@ -1 +1,28 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0});const Booru_1=__importDefault(require("./Booru"));class XmlBooru extends Booru_1.default{constructor(e,t){super(e,t)}}exports.default=XmlBooru;
1
+ "use strict";
2
+ /**
3
+ * @packageDocumentation
4
+ * @module Boorus
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const Booru_1 = __importDefault(require("./Booru"));
11
+ /**
12
+ * A class designed for Xml-returning boorus
13
+ *
14
+ * @extends Booru
15
+ * @inheritDoc
16
+ */
17
+ class XmlBooru extends Booru_1.default {
18
+ /**
19
+ * Create a new booru using XML from a site
20
+ * @param {Site} site The site to use
21
+ * @param {Object?} credentials Credentials for the API (Currently not used)
22
+ */
23
+ constructor(site, credentials) {
24
+ super(site, credentials);
25
+ }
26
+ }
27
+ exports.default = XmlBooru;
28
+ //# sourceMappingURL=XmlBooru.js.map
package/dist/index.d.ts CHANGED
@@ -3,9 +3,12 @@
3
3
  * @module Index
4
4
  */
5
5
  import Booru, { BooruCredentials } from './boorus/Booru';
6
+ import Derpibooru from './boorus/Derpibooru';
7
+ import XmlBooru from './boorus/XmlBooru';
6
8
  import Post from './structures/Post';
7
9
  import SearchParameters from './structures/SearchParameters';
8
10
  import SearchResults from './structures/SearchResults';
11
+ import Site from './structures/Site';
9
12
  /**
10
13
  * Create a new booru to search with
11
14
  *
@@ -50,3 +53,5 @@ export { Booru as BooruClass } from './boorus/Booru';
50
53
  export { sites } from './Constants';
51
54
  export { resolveSite } from './Utils';
52
55
  export { BooruError } from './Constants';
56
+ export { Derpibooru, XmlBooru, Post, SearchResults, Site };
57
+ export type { BooruCredentials, SearchParameters };