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 +91 -1
- package/dist/Utils.js +185 -1
- package/dist/boorus/Booru.js +237 -1
- package/dist/boorus/Derpibooru.js +49 -1
- package/dist/boorus/XmlBooru.js +28 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +122 -1
- package/dist/index.js.map +1 -1
- package/dist/sites.json +1 -1
- package/dist/structures/InternalSearchParameters.js +7 -1
- package/dist/structures/Post.js +299 -1
- package/dist/structures/SearchParameters.js +7 -1
- package/dist/structures/SearchResults.js +128 -1
- package/dist/structures/Site.js +51 -1
- package/dist/structures/SiteApi.js +7 -1
- package/dist/structures/SiteInfo.js +7 -1
- package/package.json +15 -15
package/dist/Constants.js
CHANGED
|
@@ -1 +1,91 @@
|
|
|
1
|
-
"use strict";
|
|
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";
|
|
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
|
package/dist/boorus/Booru.js
CHANGED
|
@@ -1 +1,237 @@
|
|
|
1
|
-
"use strict";
|
|
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";
|
|
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
|
package/dist/boorus/XmlBooru.js
CHANGED
|
@@ -1 +1,28 @@
|
|
|
1
|
-
"use strict";
|
|
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 };
|