-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.js
323 lines (302 loc) · 10.8 KB
/
utils.js
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/**
* Generates a random string of a desired length, containing any of the desired characters.
* @param {number} length The resulting string's length
* @param {string|string[]} chars A list of possible characters to use
* @returns {string} The resulting random string
*/
function randomString(length, chars = '0123456789abcdefghijklmnopqrstuvwxyz') {
let str = '';
for (let i = 0; i < length; i++) {
str += chars[randomInt(0, chars.length-1)];
}
return str;
}
/**
* Generates a lowercase hexadecimal string of a desired length.
* @param {number} length The length of the random
* @returns {String} The resulting string
*/
function randomHex(length = 8) {
return randomString(length, '0123456789abcdef');
}
/**
* Generates a pseudorandom integer between a minimum and maximum.
* @param {number} min The minimum
* @param {number} max The maximum
* @returns {number} The resulting integer
*/
function randomInt(min, max) {
return Math.round(min+(Math.random()*(max-min)));
}
/**
* Generates a pseudorandom float between a minimum and maximum.
* @param {number} min The minimum
* @param {number} max The maximum
* @returns {number} The resulting float
*/
function randomFloat(min, max) {
return min+(Math.random()*(max-min));
}
/**
* Selects a random element from an array.
* @param {array} arr The input array
* @returns {*} A randomly selected array element
*/
const getRandomElement = (arr) => arr[(Math.ceil(Math.random()*arr.length)-1)];
/**
* @typedef itemWithWeight
* @property {*} value The return value for this item
* @property {number} weight The weight of this item
*/
/**
* Selects a random item from a provided list, where each item has a specific weight.
* @param {itemWithWeight[]} args An array of items and their weights
* @returns A randomly selected value
*/
const getRandomWeighted = (args) => {
// Get the total weight
let total = 0;
args.forEach((arg) => {
total += arg.weight;
});
// Sort options from lightest to heaviest
args.sort((a, b) => {
return a.weight-b.weight;
});
// Get our random number
const rand = randomFloat(0, total);
// Iterate through options until the current weight (num)
// is greater than our number (rand)
let num = 0;
for (const arg of args) {
num += arg.weight;
if (rand < num) return arg.value;
}
}
/**
* Keeps a number within a range by preventing it from going above/below its maximum/minimum.
* @param {number} num The input number
* @param {number} min The minimum number
* @param {number} max The maximum number
* @returns {number} The resulting number
*/
function clamp(num, min, max) {
if (num < min) return min;
if (num > max) return max;
return num;
}
/**
* Keeps a number within a range by underflowing/overflowing.
* @param {number} num The input number
* @param {number} min The minimum number
* @param {number} max The maximum number
* @returns {number} The resulting number
*/
function overflow(num, min, max) {
if (num < min) return max;
if (num > max) return min;
return num;
}
/**
* Rounds a float to the desired amount of decimal places, clipping any trailing zeros.
* @param {number} number The input number
* @param {number} decimalPlaces The maximum number of decimal places
* @returns {number} The resulting number
*/
function roundSmart(number, decimalPlaces = 0) {
const factorOfTen = Math.pow(10, decimalPlaces);
return Math.round(number * factorOfTen) / factorOfTen;
}
/**
* Separates a string into its words and returns an array of those words.
* @param {string} s The input string
* @returns {String[]} An array of words
*/
function getWords(s){
s = s.replace(/(^\s*)|(\s*$)/gi, '');
s = s.replace(/[ ]{2,}/gi, ' ');
s = s.replace(/\n/g, ' ');
return s.split(' ').filter(String);
}
/**
* Returns the number of words in a string.
* @param {string} s The input string
* @returns {number} The number of words
*/
function countWords(s){
return getWords(s).length;
}
/**
* Pause execution for a desired amount of time. Use this in `async` functions with `await sleep(ms)`.
* @param {Number} ms The number of milliseconds to sleep for
* @returns {Promise<undefined>}
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Shuffles an array using `Math.random()` to swap elements and returns the result.
* @param {array} arr The input array
* @returns {array} The shuffled array
*/
function shuffle(arr) {
let i = 0;
while (i < arr.length) {
let tmp = arr[i];
let num = Math.round(Math.random()*(arr.length-1));
arr[i] = arr[num];
arr[num] = tmp;
i++;
}
return arr;
}
/**
* Converts a number of seconds into `[hh]:[m]m:ss` format.
* @param {number} s The number of seconds to format
* @returns {String}
*/
function formatSeconds(s) {
s = Math.floor(s || 0);
let hours = 0;
let minutes = 0;
let seconds = s;
if (s >= 3600) {
hours = Math.floor(s / 3600);
s = s % 3600;
}
if (s >= 60) {
minutes = Math.floor(s / 60);
s = s % 60;
}
seconds = s;
let timeString = '';
timeString += (hours > 0) ? hours.toString() + ':' : '';
timeString += minutes.toString().padStart((hours > 0) ? 2 : 1, '0') + ':';
timeString += seconds.toString().padStart(2, '0');
return timeString;
}
/**
* Get age as an integer from a Date object.
* @param {Date} date The target date
* @returns {number}
*/
function getAgeFromDate(date) {
const now = new Date();
let age = now.getFullYear()-date.getFullYear();
if (now.getMonth() < date.getMonth() || (now.getMonth() == date.getMonth() && now.getDate() < date.getDate())) {
age--;
}
return age;
}
/**
* Returns a string describing the relative time between two dates, like "3 days ago" or "2 months from now".
* @param {number} target A millisecond-timestamp of the target date.
* @param {number} [anchor] A millisecond-timestamp of the date to look from. Defaults to `Date.now()`.
* @returns {string} The resulting relative description.
*/
function getRelativeDate(target, anchor = Date.now()) {
const isFuture = (anchor-target < 0) ? true : false;
let diff = Math.abs(anchor-target);
diff = Math.round(diff/1000);
if (diff < 120) // Less than 120 seconds
return (isFuture) ? `In a moment` : `Moments ago`;
diff = Math.round(diff/60);
if (diff < 120) // Less than 120 minutes
return (isFuture) ? `${diff} mins from now` : `${diff} mins ago`;
diff = Math.round(diff/60);
if (diff < 72) // Less than 72 hours
return (isFuture) ? `${diff} hours from now` : `${diff} hours ago`;
diff = Math.round(diff/24);
const days = diff;
if (diff < 90) // Less than 90 days
return (isFuture) ? `${diff} days from now` : `${diff} days ago`;
diff = Math.round(diff/30.43685);
if (diff < 36) // Less than 36 months
return (isFuture) ? `${diff} months from now` : `${diff} months ago`;
diff = Math.round(days/365.2422);
return (isFuture) ? `${diff} years from now` : `${diff} years ago`;
}
/**
* Converts a number of bytes to a human-readable size, like "230.2 MB" or "7.27 GB".
* @param {number} bytes A number of bytes to convert
* @returns {string} The human-readable size string
*/
function formatSize(bytes) {
if (bytes < 1000) return `${bytes} B`;
bytes /= 1024;
if (bytes < (1000)) return `${roundSmart(bytes, 0)} KB`;
bytes /= 1024;
if (bytes < (1000)) return `${roundSmart(bytes, 1)} MB`;
bytes /= 1024;
if (bytes < (1000)) return `${roundSmart(bytes, 2)} GB`;
bytes /= 1024;
if (bytes < (1000)) return `${roundSmart(bytes, 2)} TB`;
return "-";
}
/**
* Determines of an input string is a valid URL.
* @param {string} string The input string
* @returns {Boolean}
*/
function isValidUrl(string) {
let url;
try {
url = new URL(string);
} catch (error) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
/**
* Determines if a string is a valid hostname.
* @param {string} string The input string
* @returns {Boolean}
*/
function isValidHostname(string) {
return string.match(/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/) && !string.match(/^localhost$/);
}
/**
* Determines if a string is a valid IPv4 or IPv6 address.
* @param {string} string The input string
* @returns {Boolean}
*/
function isValidIp(string) {
return string.match(/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/) && !string.match(/(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/) && !string.match(/^::1$/);
}
/**
* Escapes HTML entities present in the input string and returns the result.
* @param {string} text The input string
* @returns {string} The escaped string
*/
function escapeHTML(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
try {
module.exports = {
randomHex: randomHex,
randomInt: randomInt,
randomFloat: randomFloat,
getRandomElement: getRandomElement,
getRandomWeighted: getRandomWeighted,
clamp: clamp,
overflow: overflow,
roundSmart: roundSmart,
getWords: getWords,
countWords: countWords,
sleep: sleep,
shuffle: shuffle,
formatSeconds: formatSeconds,
getAgeFromDate: getAgeFromDate,
getRelativeDate: getRelativeDate,
formatSize: formatSize,
isValidUrl: isValidUrl,
isValidHostname: isValidHostname,
isValidIp: isValidIp,
escapeHTML: escapeHTML
};
} catch (error) {}