-
Notifications
You must be signed in to change notification settings - Fork 1
/
Utils.ts
191 lines (160 loc) · 4.33 KB
/
Utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/**
* @packageDocumentation
* @module Utils
*/
import { BooruError, sites } from './Constants'
import { XMLParser } from 'fast-xml-parser'
/**
* 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
*/
export function resolveSite(domain: string): string | null {
if (typeof domain !== 'string') {
return null
}
domain = domain.toLowerCase()
for (const site in sites) {
if (
site === domain ||
sites[site].domain === domain ||
sites[site].aliases.includes(domain)
) {
return site
}
}
return null
}
interface XMLPage {
html: any
}
interface XMLPosts {
post?: any[]
tag?: any | any[]
}
interface BooruXML {
html?: XMLPage
'!doctype'?: XMLPage
posts: XMLPosts
}
const xmlParser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '',
})
/**
* Parses 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
*/
export function jsonfy(xml: string): object[] {
if (typeof xml === 'object') return xml
const data = xmlParser.parse(xml) as BooruXML
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 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 []
}
/**
* 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
*/
export function tryParseJSON(data: string): Record<string, unknown>[] {
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
*/
export function shuffle<T>(array: T[]): T[] {
let currentIndex: number = array.length
let temporaryValue: T
let randomIndex: number
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)
*/
export function randInt(min: number, max: number): number {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min
}
/**
* 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
*/
export function validateSearchParams(
site: string,
limit: number | string,
): { site: string; limit: number } {
const resolvedSite = resolveSite(site)
if (typeof limit !== 'number') {
limit = parseInt(limit, 10)
}
if (resolvedSite === null) {
throw new BooruError('Site not supported')
}
if (typeof limit !== 'number' || Number.isNaN(limit)) {
throw new BooruError('`limit` should be an int')
}
return { site: resolvedSite, limit }
}
/**
* 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
*/
export function compareArrays(arr1: string[], arr2: string[]): string[] {
return arr1.filter((e1) =>
arr2.some((e2) => e1.toLowerCase() === e2.toLowerCase()),
)
}