@jeniex/utils 1.4.0 → 1.4.2

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/browser/index.js CHANGED
@@ -242,9 +242,9 @@ function capitalize(sentence) {
242
242
  return capitalizeWord(sentence);
243
243
  }
244
244
 
245
- const scriptName = GM.info.script.name;
245
+ const scriptName = typeof GM === 'undefined' ? '' : GM.info.script.name;
246
246
  /** The initial tab URL on the script run. */
247
- const tabURL = window.location.href;
247
+ const tabURL = typeof window === 'undefined' ? '' : window.location.href;
248
248
 
249
249
  function alert(message) {
250
250
  if (message === undefined) {
package/node/index.cts ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Joins an array's items or do nothing if it is joined already.
3
+ *
4
+ * @category Array
5
+ */
6
+ declare function join(object: string[] | string, separator?: string): string;
7
+
8
+ /**
9
+ * Removes an item from an array by mutating it.
10
+ * @throws an error if the object to be removed does not exist inside the array.
11
+ *
12
+ * @category Array
13
+ */
14
+ declare function remove<T>(array: T[], object: T): void;
15
+
16
+ declare function noop(): void;
17
+ declare function toString(object: unknown): string;
18
+
19
+ /**
20
+ * Asserts that an object is neither `null` nor `undefined`.
21
+ *
22
+ * If the object is `null` or `undefined`, a `TypeError` is thrown.
23
+ * Otherwise, TypeScript narrows the object to `T`.
24
+ *
25
+ * @template T The type of the object.
26
+ *
27
+ * @param object The object to validate.
28
+ *
29
+ * @throws {TypeError} If the object is `null` or `undefined`.
30
+ *
31
+ * @example
32
+ * const object: string | undefined = Math.random() < 0.5 ? undefined : 'Hello';
33
+ *
34
+ * assert(object);
35
+ *
36
+ * // `object` is now typed as `string`.
37
+ * console.log(object.length);
38
+ */
39
+ declare function assert<T>(object: T | null | undefined): asserts object is T;
40
+ /**
41
+ * Asserts that an object satisfies the given type guard.
42
+ *
43
+ * If the type guard returns `false`, a `TypeError` is thrown.
44
+ * Otherwise, TypeScript narrows the object to the type accepted by
45
+ * the type guard.
46
+ *
47
+ * @template T The type checked by the type guard.
48
+ *
49
+ * @param object The object to validate.
50
+ * @param guard A type guard used to validate the object.
51
+ *
52
+ * @throws {TypeError} If the object does not satisfy the type guard.
53
+ *
54
+ * @example
55
+ * const object: number | string = Math.random() < 0.5 ? 1 : 'Hello';
56
+ *
57
+ * assertType(object, isNumber);
58
+ *
59
+ * // `object` is now typed as `number`.
60
+ * console.log(object.toFixed(2));
61
+ *
62
+ * @example
63
+ * const object: unknown = 'hello';
64
+ *
65
+ * assertType(object, isString);
66
+ *
67
+ * // `object` is now typed as `string`.
68
+ * console.log(object.toUpperCase());
69
+ */
70
+ declare function assertType<T>(object: unknown, guard: (object: unknown) => object is T): asserts object is T;
71
+ /**
72
+ * Asserts that an object is neither `null` nor `undefined` and returns it.
73
+ *
74
+ * If the object is `null` or `undefined`, a `TypeError` is thrown.
75
+ * Otherwise, the object is returned with its type narrowed to `T`.
76
+ *
77
+ * @template T The type of the object.
78
+ *
79
+ * @param object The object to validate.
80
+ *
81
+ * @returns The validated object, narrowed to `T`.
82
+ *
83
+ * @throws {TypeError} If the object is `null` or `undefined`.
84
+ *
85
+ * @example
86
+ * const object: string | undefined = Math.random() < 0.5 ? undefined : 'Hello';
87
+ *
88
+ * const string = asserted(object);
89
+ *
90
+ * // `object` is now typed as `string`.
91
+ * console.log(string.length);
92
+ */
93
+ declare function asserted<T>(object: T | null | undefined): T;
94
+ /**
95
+ * Asserts that an object satisfies the given type guard and returns it
96
+ * with the narrowed type.
97
+ *
98
+ * If the type guard returns `false`, a `TypeError` is thrown.
99
+ *
100
+ * @template T The type checked by the type guard.
101
+ *
102
+ * @param object The object to validate.
103
+ * @param guard A type guard used to validate the object.
104
+ *
105
+ * @returns The validated object, narrowed to `T`.
106
+ *
107
+ * @throws {TypeError} If the object does not satisfy the type guard.
108
+ *
109
+ * @example
110
+ * const object: number | string = Math.random() < 0.5 ? 1 : 'Hello';
111
+ *
112
+ * const number = assertedType(object, isNumber);
113
+ *
114
+ * // `number` is typed as `number`.
115
+ * console.log(number.toFixed(2));
116
+ *
117
+ * @example
118
+ * const object: unknown = 'hello';
119
+ *
120
+ * const text = assertedType(object, isString);
121
+ *
122
+ * // `text` is typed as `string`.
123
+ * console.log(text.toUpperCase());
124
+ */
125
+ declare function assertedType<T>(object: unknown, guard: (object: unknown) => object is T): T;
126
+ declare function isTruthy<T>(object: T): object is Exclude<T, '' | 0 | false | null | undefined>;
127
+ declare function isFalsy(object: unknown): object is '' | 0 | false | null | undefined;
128
+ declare function isNullish(object: unknown): object is null | undefined;
129
+ declare function isNotNullish<T>(object: T): object is Exclude<T, null | undefined>;
130
+
131
+ declare function isString(object: unknown): object is string;
132
+ declare function isNumber(object: unknown): object is number;
133
+ declare function isBoolean(object: unknown): object is boolean;
134
+ declare function isObject(object: unknown): object is object;
135
+ declare function isFunction<T extends Function>(object: unknown): object is T;
136
+
137
+ /**
138
+ * A wrapper around `setTimeout`.
139
+ *
140
+ * @category Promise
141
+ */
142
+ declare function sleep(milliSeconds: number): Promise<void>;
143
+
144
+ /**
145
+ * Adds leading zeros to a number.
146
+ *
147
+ * @category String
148
+ */
149
+ declare function padZeros(number: number, length: number): string;
150
+
151
+ /**
152
+ * capitalizes a word or a sentence.
153
+ *
154
+ * @category String
155
+ */
156
+ declare function capitalize(sentence: string): string;
157
+
158
+ type Nullable<T> = T | null | undefined;
159
+ type Fn<T = void, U = any> = (param?: U) => T;
160
+ type Prettify<T> = {
161
+ [KeyType in keyof T]: T[KeyType];
162
+ } & {};
163
+
164
+ declare function exists(path: string): Promise<boolean>;
165
+
166
+ declare function existsFile(path: string): Promise<boolean>;
167
+
168
+ declare function existsFileSync(path: string): boolean;
169
+
170
+ declare function existsFolder(path: string): Promise<boolean>;
171
+
172
+ declare function existsFolderSync(path: string): boolean;
173
+
174
+ declare function existsSync(path: string): boolean;
175
+
176
+ interface ListBaseOpt {
177
+ /** The absolute path of the target folder. */
178
+ folderPath: string;
179
+ getFullPath?: boolean;
180
+ }
181
+ interface ListFoldersOpt extends ListBaseOpt {
182
+ }
183
+ interface ListFilesOpt extends ListBaseOpt {
184
+ /**
185
+ * If provided, only files with these extensions will be processed.
186
+ *
187
+ * @example
188
+ * ['.css', '.html'];
189
+ */
190
+ filter?: string[];
191
+ }
192
+ interface RemoveFilesOpt extends Omit<ListFilesOpt, 'getFullPath'> {
193
+ }
194
+
195
+ /**
196
+ * Lists the files that are present in the provided folder path.
197
+ * @throws an error if the provided folder path does not exist.
198
+ *
199
+ * @category File-System
200
+ */
201
+ declare function listFiles({ folderPath, getFullPath, filter }: ListFilesOpt): Promise<string[]>;
202
+
203
+ /**
204
+ * Lists the folders that are present in the provided folder path.
205
+ * @throws an error if the provided folder path does not exist.
206
+ *
207
+ * @category File-System
208
+ */
209
+ declare function listFolders({ folderPath, getFullPath }: ListFoldersOpt): Promise<string[]>;
210
+
211
+ /**
212
+ * Lists the folders that are present in the provided folder path.
213
+ * @throws an error if the provided folder path does not exist.
214
+ *
215
+ * @category File-System
216
+ */
217
+ declare function listFoldersSync({ folderPath, getFullPath }: ListFoldersOpt): string[];
218
+
219
+ /**
220
+ * Removes the files that are present in the provided folder path.
221
+ * @throws an error if the provided folder path does not exist.
222
+ * @throws an error if the files are outside or at the root of the project folder.
223
+ *
224
+ * @category File-System
225
+ */
226
+ declare function removeFiles({ folderPath, filter }: RemoveFilesOpt): Promise<void>;
227
+
228
+ declare function readJSON<T = object>(filePath: string, withComments?: boolean): Promise<T>;
229
+
230
+ interface RqRequest {
231
+ response: () => Promise<Response>;
232
+ blob: () => Promise<Blob>;
233
+ buffer: () => Promise<ArrayBuffer>;
234
+ json: <T = unknown>() => Promise<T>;
235
+ text: () => Promise<string>;
236
+ document: () => Promise<Document>;
237
+ multi: <T = unknown>(types: ResponseType[]) => Promise<Responses<T>>;
238
+ }
239
+ type Transport<TOptions = unknown> = (url: string, options?: TOptions) => Promise<Response>;
240
+ type ResponseType = 'blob' | 'buffer' | 'document' | 'json' | 'text';
241
+ interface Responses<T> {
242
+ response?: Response;
243
+ blob?: Blob;
244
+ buffer?: ArrayBuffer;
245
+ json?: T;
246
+ text?: string;
247
+ document?: Document;
248
+ }
249
+
250
+ declare function rq(url: string, options?: RequestInit): RqRequest;
251
+
252
+ declare function createRequest<TOptions>(transport: Transport<TOptions>, url: string, options?: TOptions): RqRequest;
253
+
254
+ export { Fn, Nullable, Prettify, assert, assertType, asserted, assertedType, capitalize, exists, existsFile, existsFileSync, existsFolder, existsFolderSync, existsSync, isBoolean, isFalsy, isFunction, isNotNullish, isNullish, isNumber, isObject, isString, isTruthy, join, listFiles, listFolders, listFoldersSync, noop, padZeros, readJSON, remove, removeFiles, rq, createRequest as rqc, sleep, toString };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeniex/utils",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "author": "JenieX",
5
5
  "homepage": "https://github.com/JenieX/utils",
6
6
  "type": "module",
@@ -10,13 +10,17 @@
10
10
  "exports": {
11
11
  "./browser": {
12
12
  "types": "./browser/index.d.ts",
13
- "import": "./browser/index.js",
14
- "require": "./browser/index.cjs"
13
+ "import": "./browser/index.js"
15
14
  },
16
15
  "./node": {
17
- "types": "./node/index.d.ts",
18
- "import": "./node/index.js",
19
- "require": "./node/index.cjs"
16
+ "import": {
17
+ "types": "./node/index.d.ts",
18
+ "default": "./node/index.js"
19
+ },
20
+ "require": {
21
+ "types": "./node/index.d.cts",
22
+ "default": "./node/index.cjs"
23
+ }
20
24
  },
21
25
  "./node/inquirer": {
22
26
  "types": "./node/inquirer/index.d.ts",
package/browser/index.cjs DELETED
@@ -1,742 +0,0 @@
1
- 'use strict';
2
-
3
- function noop() { }
4
- function toString(object) {
5
- return Object.prototype.toString.call(object);
6
- }
7
-
8
- function isString(object) {
9
- return typeof object === 'string';
10
- }
11
- function isNumber(object) {
12
- return typeof object === 'number';
13
- }
14
- function isBoolean(object) {
15
- return typeof object === 'boolean';
16
- }
17
- function isObject(object) {
18
- return toString(object) === '[object Object]';
19
- }
20
- // eslint-disable-next-line @typescript-eslint/ban-types
21
- function isFunction(object) {
22
- return typeof object === 'function';
23
- }
24
-
25
- /**
26
- * Joins an array's items or do nothing if it is joined already.
27
- *
28
- * @category Array
29
- */
30
- function join(object, separator = ',') {
31
- if (isString(object)) {
32
- return object;
33
- }
34
- return object.join(separator);
35
- }
36
-
37
- /**
38
- * Removes an item from an array by mutating it.
39
- * @throws an error if the object to be removed does not exist inside the array.
40
- *
41
- * @category Array
42
- */
43
- function remove(array, object) {
44
- const index = array.indexOf(object);
45
- if (index === -1) {
46
- throw new Error('Provided value does not exist inside the array.');
47
- }
48
- array.splice(index, 1);
49
- }
50
-
51
- /**
52
- * Asserts that an object is neither `null` nor `undefined`.
53
- *
54
- * If the object is `null` or `undefined`, a `TypeError` is thrown.
55
- * Otherwise, TypeScript narrows the object to `T`.
56
- *
57
- * @template T The type of the object.
58
- *
59
- * @param object The object to validate.
60
- *
61
- * @throws {TypeError} If the object is `null` or `undefined`.
62
- *
63
- * @example
64
- * const object: string | undefined = Math.random() < 0.5 ? undefined : 'Hello';
65
- *
66
- * assert(object);
67
- *
68
- * // `object` is now typed as `string`.
69
- * console.log(object.length);
70
- */
71
- function assert(object) {
72
- if (object === null || object === undefined) {
73
- throw new TypeError('object is either null or undefined');
74
- }
75
- }
76
- /**
77
- * Asserts that an object satisfies the given type guard.
78
- *
79
- * If the type guard returns `false`, a `TypeError` is thrown.
80
- * Otherwise, TypeScript narrows the object to the type accepted by
81
- * the type guard.
82
- *
83
- * @template T The type checked by the type guard.
84
- *
85
- * @param object The object to validate.
86
- * @param guard A type guard used to validate the object.
87
- *
88
- * @throws {TypeError} If the object does not satisfy the type guard.
89
- *
90
- * @example
91
- * const object: number | string = Math.random() < 0.5 ? 1 : 'Hello';
92
- *
93
- * assertType(object, isNumber);
94
- *
95
- * // `object` is now typed as `number`.
96
- * console.log(object.toFixed(2));
97
- *
98
- * @example
99
- * const object: unknown = 'hello';
100
- *
101
- * assertType(object, isString);
102
- *
103
- * // `object` is now typed as `string`.
104
- * console.log(object.toUpperCase());
105
- */
106
- function assertType(object, guard) {
107
- if (!guard(object)) {
108
- throw new TypeError('object has an invalid type');
109
- }
110
- }
111
- /**
112
- * Asserts that an object is neither `null` nor `undefined` and returns it.
113
- *
114
- * If the object is `null` or `undefined`, a `TypeError` is thrown.
115
- * Otherwise, the object is returned with its type narrowed to `T`.
116
- *
117
- * @template T The type of the object.
118
- *
119
- * @param object The object to validate.
120
- *
121
- * @returns The validated object, narrowed to `T`.
122
- *
123
- * @throws {TypeError} If the object is `null` or `undefined`.
124
- *
125
- * @example
126
- * const object: string | undefined = Math.random() < 0.5 ? undefined : 'Hello';
127
- *
128
- * const string = asserted(object);
129
- *
130
- * // `object` is now typed as `string`.
131
- * console.log(string.length);
132
- */
133
- function asserted(object) {
134
- if (object === null || object === undefined) {
135
- throw new TypeError('object is either null or undefined');
136
- }
137
- return object;
138
- }
139
- /**
140
- * Asserts that an object satisfies the given type guard and returns it
141
- * with the narrowed type.
142
- *
143
- * If the type guard returns `false`, a `TypeError` is thrown.
144
- *
145
- * @template T The type checked by the type guard.
146
- *
147
- * @param object The object to validate.
148
- * @param guard A type guard used to validate the object.
149
- *
150
- * @returns The validated object, narrowed to `T`.
151
- *
152
- * @throws {TypeError} If the object does not satisfy the type guard.
153
- *
154
- * @example
155
- * const object: number | string = Math.random() < 0.5 ? 1 : 'Hello';
156
- *
157
- * const number = assertedType(object, isNumber);
158
- *
159
- * // `number` is typed as `number`.
160
- * console.log(number.toFixed(2));
161
- *
162
- * @example
163
- * const object: unknown = 'hello';
164
- *
165
- * const text = assertedType(object, isString);
166
- *
167
- * // `text` is typed as `string`.
168
- * console.log(text.toUpperCase());
169
- */
170
- function assertedType(object, guard) {
171
- if (!guard(object)) {
172
- throw new TypeError('object has an invalid type');
173
- }
174
- return object;
175
- }
176
- function isTruthy(object) {
177
- return Boolean(object) === true;
178
- }
179
- function isFalsy(object) {
180
- return Boolean(object) === false;
181
- }
182
- function isNullish(object) {
183
- return object === null || object === undefined;
184
- }
185
- function isNotNullish(object) {
186
- return object !== null && object !== undefined;
187
- }
188
-
189
- /**
190
- * A wrapper around `setTimeout`.
191
- *
192
- * @category Promise
193
- */
194
- async function sleep(milliSeconds) {
195
- return new Promise((resolve) => {
196
- setTimeout(resolve, milliSeconds);
197
- });
198
- }
199
-
200
- /**
201
- * Adds leading zeros to a number.
202
- *
203
- * @category String
204
- */
205
- function padZeros(number, length) {
206
- if (Math.abs(number).toString().length > length) {
207
- throw new Error('Provided length can not be less than the digits of the number.');
208
- }
209
- if (number < 0) {
210
- throw new Error('Provided number must be greater than zero.');
211
- }
212
- const padded = '0'.repeat(length - 1) + number;
213
- if (number < 10) {
214
- return padded;
215
- }
216
- return padded.slice(-length);
217
- }
218
-
219
- const lowercaseWords = new Set([
220
- 'a', 'an', 'the',
221
- 'and', 'but', 'for', 'nor', 'or', 'so', 'yet',
222
- 'about', 'across', 'after', 'along', 'among', 'around', 'as', 'at', 'before', 'between', 'by', 'during', 'in', 'into', 'of', 'on', 'over', 'since', 'through', 'throughout', 'to', 'under', 'with', 'within',
223
- 'away', 'back', 'down', 'far', 'off', 'on', 'out', 'up',
224
- 'he', 'her', 'him', 'his', 'it', 'its', 'me', 'my', 'our', 'she', 'their', 'them', 'they', 'us', 'we', 'you', 'your',
225
- ]);
226
- function capitalizeWord(word) {
227
- return word[0].toUpperCase() + word.slice(1).toLowerCase();
228
- }
229
- /**
230
- * capitalizes a word or a sentence.
231
- *
232
- * @category String
233
- */
234
- function capitalize(sentence) {
235
- if (sentence.includes(' ')) {
236
- return sentence.split(' ').map((word, index) => {
237
- const lowercaseWord = word.toLowerCase();
238
- if (index > 0 && lowercaseWords.has(lowercaseWord)) {
239
- return lowercaseWord;
240
- }
241
- return capitalizeWord(word);
242
- }).join(' ');
243
- }
244
- return capitalizeWord(sentence);
245
- }
246
-
247
- const scriptName = GM.info.script.name;
248
- /** The initial tab URL on the script run. */
249
- const tabURL = window.location.href;
250
-
251
- function alert(message) {
252
- if (message === undefined) {
253
- window.alert(`[ ${scriptName} ]`);
254
- return;
255
- }
256
- window.alert(`[ ${scriptName} ]\n\n${message}`);
257
- }
258
- function confirm(message) {
259
- return window.confirm(`[ ${scriptName} ]\n\n${message}`);
260
- }
261
- function prompt(message, _default) {
262
- return window.prompt(`[ ${scriptName} ]\n\n${message}`, _default);
263
- }
264
-
265
- async function fishResponse(url, options) {
266
- const response = await fetch(url, options);
267
- if (!response.ok && !response.url.startsWith('file:///')) {
268
- throw new Error(`Request to ${response.url} ended with ${response.status} status.`);
269
- }
270
- return response;
271
- }
272
-
273
- // Note: to set the 'cookie' header, you have to set 'anonymous' to true.
274
- async function fishXResponse(url, fishOptions) {
275
- const { method, anonymous, headers, body, timeOut, onProgress } = fishOptions ?? {};
276
- return new Promise((resolve, reject) => {
277
- GM.xmlHttpRequest({
278
- url,
279
- method: method ?? 'GET',
280
- headers,
281
- anonymous,
282
- data: body,
283
- responseType: 'blob',
284
- timeout: timeOut,
285
- onprogress: onProgress,
286
- onload({ response, statusText, status, finalUrl }) {
287
- const isFileURL = finalUrl.startsWith('file:///');
288
- const ok = status >= 200 && status < 300;
289
- if (!ok && !isFileURL) {
290
- reject(new Error(`Request to ${url} ended with ${status} status.`));
291
- return;
292
- }
293
- const properResponse = new Response(response, {
294
- statusText,
295
- status: isFileURL ? 200 : status,
296
- });
297
- Object.defineProperty(properResponse, 'url', { value: finalUrl });
298
- resolve(properResponse);
299
- },
300
- onerror({ status }) {
301
- reject(new Error(`Request to ${url} ended with ${status} status.`));
302
- },
303
- });
304
- });
305
- }
306
-
307
- async function fishBlob(url, options, x) {
308
- const response = await (x ? fishXResponse : fishResponse)(url, options);
309
- return response.blob();
310
- }
311
-
312
- async function fishBuffer(url, options, x) {
313
- const response = await (x ? fishXResponse : fishResponse)(url, options);
314
- return response.arrayBuffer();
315
- }
316
-
317
- async function fishDocument(url, options, x) {
318
- const response = await (x ? fishXResponse : fishResponse)(url, options);
319
- const responseText = await response.text();
320
- const parser = new DOMParser();
321
- return parser.parseFromString(responseText, 'text/html');
322
- }
323
-
324
- async function fishJSON(url, options, x) {
325
- const response = await (x ? fishXResponse : fishResponse)(url, options);
326
- return response.json();
327
- }
328
-
329
- async function fishText(url, options, x) {
330
- const response = await (x ? fishXResponse : fishResponse)(url, options);
331
- return response.text();
332
- }
333
-
334
- // https://httpbin.org/anything
335
- const fish = {
336
- blob: async (url, options) => fishBlob(url, options),
337
- buffer: async (url, options) => fishBuffer(url, options),
338
- document: async (url, options) => fishDocument(url, options),
339
- JSON: async (url, options) => fishJSON(url, options),
340
- text: async (url, options) => fishText(url, options),
341
- };
342
- const fishX = {
343
- blob: async (url, options) => fishBlob(url, options, true),
344
- buffer: async (url, options) => fishBuffer(url, options, true),
345
- document: async (url, options) => fishDocument(url, options, true),
346
- JSON: async (url, options) => fishJSON(url, options, true),
347
- text: async (url, options) => fishText(url, options, true),
348
- };
349
-
350
- function $(selectors, parent) {
351
- const element = (parent ?? document).querySelector(join(selectors));
352
- if (element === null) {
353
- throw new Error(`Could not find the element with the selector ${selectors}`);
354
- }
355
- return element;
356
- }
357
- function $$(selectors, parent) {
358
- const elements = (parent ?? document).querySelectorAll(join(selectors));
359
- if (elements.length === 0) {
360
- throw new Error(`Could not find any element with the selector ${selectors}`);
361
- }
362
- return elements;
363
- }
364
-
365
- /**
366
- * @param key The name of the param in the search query.
367
- * @param fullURL If provided, it will be our target, otherwise it is the current location.
368
- */
369
- function getSearchParam(key, fullURL) {
370
- const { searchParams } = new URL(fullURL ?? window.location.href);
371
- const value = searchParams.get(key);
372
- if (value === null) {
373
- return undefined;
374
- }
375
- return value;
376
- }
377
- /**
378
- *
379
- * @param key The name of the param that is to be in the search query.
380
- * @param value Its value.
381
- * @param fullURL If provided, it will be our target, otherwise it is the current location.
382
- */
383
- function setSearchParam(key, value, fullURL) {
384
- const updatedURL = [];
385
- const { origin, pathname, search, hash } = new URL(fullURL ?? window.location.href);
386
- const searchParams = new URLSearchParams(search);
387
- searchParams.set(key, value);
388
- updatedURL.push(origin, pathname, '?', searchParams.toString());
389
- if (hash !== '') {
390
- updatedURL.push(hash);
391
- }
392
- return updatedURL.join('');
393
- }
394
- /**
395
- * @param keys The names of the param in the search query.
396
- * @param fullURL If provided, it will be our target, otherwise it is the current location.
397
- */
398
- function removeSearchParams(keys, fullURL) {
399
- const updatedURL = [];
400
- const { origin, pathname, search, hash } = new URL(fullURL ?? window.location.href);
401
- const searchParams = new URLSearchParams(search);
402
- for (const key of keys) {
403
- searchParams.delete(key);
404
- }
405
- updatedURL.push(origin, pathname);
406
- if (searchParams.toString() !== '') {
407
- updatedURL.push('?', searchParams.toString());
408
- }
409
- if (hash !== '') {
410
- updatedURL.push(hash);
411
- }
412
- return updatedURL.join('');
413
- }
414
-
415
- /**
416
- * Checks if the VPN is active by accessing the router address.
417
- * Tested on Opera browser build-in VPN only.
418
- */
419
- async function checkVPN() {
420
- let pass = false;
421
- try {
422
- await fishXResponse('http://192.168.1.1/index.html', { method: 'HEAD' });
423
- }
424
- catch (exception) {
425
- if (exception.message.includes('ended with 0 status')) {
426
- pass = true;
427
- }
428
- }
429
- if (pass === false) {
430
- throw new Error('VPN is not enabled!');
431
- }
432
- }
433
- async function checkVPNRandomly() {
434
- // a 1/10 chance to check.
435
- if (Math.floor(Math.random() * 10) === 0) {
436
- await checkVPN();
437
- console.log('%cPass check for VPN!', 'color: #04da79');
438
- }
439
- }
440
-
441
- function addStyle(css, parent = document.documentElement) {
442
- const style = document.createElement('style');
443
- style.setAttribute('type', 'text/css');
444
- style.textContent = css;
445
- parent.append(style);
446
- return style;
447
- }
448
-
449
- /**
450
- * Waits for image to fully load or throws an error if it fails.
451
- */
452
- async function imageLoad(img) {
453
- return new Promise((resolve, reject) => {
454
- if (img.complete) {
455
- resolve();
456
- return;
457
- }
458
- let onLoad;
459
- let onError;
460
- const removeListeners = () => {
461
- img.removeEventListener('load', onLoad);
462
- img.removeEventListener('error', onError);
463
- };
464
- onLoad = () => {
465
- removeListeners();
466
- resolve();
467
- };
468
- onError = () => {
469
- removeListeners();
470
- reject(new Error('Image failed to load'));
471
- };
472
- img.addEventListener('load', onLoad);
473
- img.addEventListener('error', onError);
474
- });
475
- }
476
-
477
- /**
478
- * Waits for the page to load.
479
- * @param completely Whether or not to wait for resources to load as well.
480
- */
481
- async function pageLoad(completely) {
482
- return new Promise((resolve) => {
483
- if (document.readyState === 'complete') {
484
- resolve();
485
- return;
486
- }
487
- if (completely === true) {
488
- window.addEventListener('load', () => resolve());
489
- return;
490
- }
491
- document.addEventListener('DOMContentLoaded', () => resolve());
492
- });
493
- }
494
-
495
- function saveFile(blob, fileName) {
496
- const blobURL = URL.createObjectURL(blob);
497
- const link = document.createElement('a');
498
- link.href = blobURL;
499
- link.download = fileName;
500
- link.dispatchEvent(new MouseEvent('click'));
501
- window.setTimeout(() => window.URL.revokeObjectURL(blobURL), 1000);
502
- }
503
-
504
- function createFlagMessage(flag) {
505
- const { message: messageHeader, values, type } = flag;
506
- let message = messageHeader;
507
- if (values !== undefined) {
508
- message += `\n\n${values.join(' | ')}`;
509
- }
510
- if (type === 'number' && flag.range !== undefined) {
511
- message += '\n';
512
- const [min, max] = flag.range;
513
- if (min !== undefined) {
514
- message += `\nMinimum: ${min}`;
515
- }
516
- if (max !== undefined) {
517
- message += `\nMaximum: ${max}`;
518
- }
519
- }
520
- return message;
521
- }
522
-
523
- function validateFlagValue(flag, promptValue) {
524
- const { type, name, values } = flag;
525
- if (promptValue === null || promptValue === '') {
526
- throw new Error(`You did not provide a value for "${name}" option.`);
527
- }
528
- switch (type) {
529
- case 'boolean': {
530
- if (promptValue !== 'yes' && promptValue !== 'no') {
531
- throw new Error(`Invalid value for "${name}" option!`);
532
- }
533
- return promptValue === 'yes';
534
- }
535
- case 'string': {
536
- if (values !== undefined && !values.includes(promptValue)) {
537
- throw new Error(`Provided value for "${name}" option is not in the list.`);
538
- }
539
- return promptValue;
540
- }
541
- case 'number': {
542
- const promptValueAsNumber = Number(promptValue);
543
- if (Number.isNaN(promptValueAsNumber)) {
544
- throw new TypeError(`You need to provide a number for "${name}" option!`);
545
- }
546
- if (values !== undefined && !values.includes(promptValueAsNumber)) {
547
- throw new Error(`Provided value for "${name}" option is not in the list.`);
548
- }
549
- const [min, max] = flag.range ?? [];
550
- if (min !== undefined && promptValueAsNumber < min) {
551
- throw new Error(`Provided value for "${name}" option is less than the minimum.`);
552
- }
553
- if (max !== undefined && promptValueAsNumber > max) {
554
- throw new Error(`Provided value for "${name}" option is greater than the maximum.`);
555
- }
556
- return promptValueAsNumber;
557
- }
558
- default: {
559
- throw new Error(`Unsupported flag type for "${name}" option.`);
560
- }
561
- }
562
- }
563
-
564
- function processFlag(flag) {
565
- const message = createFlagMessage(flag);
566
- const promptValue = prompt(message, flag.default?.toString());
567
- return [flag.name, validateFlagValue(flag, promptValue)];
568
- }
569
- async function getOptions(flags) {
570
- const options = {};
571
- for (const flag of flags) {
572
- const [name, value] = processFlag(flag);
573
- options[name] = value;
574
- await sleep(300);
575
- }
576
- return options;
577
- }
578
-
579
- function customizeConsole(debug = false) {
580
- const base = window.console;
581
- const logId = `[${scriptName}]:`;
582
- return {
583
- log: base.log.bind(base, logId),
584
- warn: base.warn.bind(base, logId),
585
- error: base.error.bind(base, logId),
586
- debug: debug ? base.log.bind(base, logId) : noop,
587
- };
588
- }
589
-
590
- function createRequest(transport, url, options) {
591
- const getResponse = async () => {
592
- return transport(url, options);
593
- };
594
- return {
595
- response: async () => {
596
- return getResponse();
597
- },
598
- blob: async () => {
599
- const response = await getResponse();
600
- return response.blob();
601
- },
602
- buffer: async () => {
603
- const response = await getResponse();
604
- return response.arrayBuffer();
605
- },
606
- json: async () => {
607
- const response = await getResponse();
608
- return response.json();
609
- },
610
- text: async () => {
611
- const response = await getResponse();
612
- return response.text();
613
- },
614
- document: async () => {
615
- const response = await getResponse();
616
- const responseText = await response.text();
617
- const parser = new DOMParser();
618
- return parser.parseFromString(responseText, 'text/html');
619
- },
620
- multi: async (types) => {
621
- const response = await getResponse();
622
- const responses = { response };
623
- if (types.includes('blob')) {
624
- responses.blob = await response.clone().blob();
625
- }
626
- if (types.includes('buffer')) {
627
- responses.buffer = await response.clone().arrayBuffer();
628
- }
629
- if (types.includes('json')) {
630
- responses.json = (await response.clone().json());
631
- }
632
- if (types.includes('text')) {
633
- responses.text = await response.clone().text();
634
- }
635
- if (types.includes('document')) {
636
- const responseText = await response.clone().text();
637
- const parser = new DOMParser();
638
- responses.document = parser.parseFromString(responseText, 'text/html');
639
- }
640
- return responses;
641
- },
642
- };
643
- }
644
-
645
- function isFileURL(url) {
646
- return url.startsWith('file:///');
647
- }
648
-
649
- async function fetchTransport(url, options) {
650
- const response = await fetch(url, options);
651
- if (!response.ok && !isFileURL(response.url)) {
652
- throw new Error(`Request to ${response.url} ended with ${response.status} status.`);
653
- }
654
- return response;
655
- }
656
-
657
- function rq(url, options) {
658
- return createRequest(fetchTransport, url, options);
659
- }
660
-
661
- async function gmTransport(url, options) {
662
- const { method, anonymous, headers, body, timeOut, onProgress } = options ?? {};
663
- return new Promise((resolve, reject) => {
664
- GM.xmlHttpRequest({
665
- url,
666
- method: method ?? 'GET',
667
- headers,
668
- /** Note: To manually set the 'cookie' header, you have to set 'anonymous' to true. */
669
- anonymous,
670
- data: body,
671
- responseType: 'blob',
672
- timeout: timeOut,
673
- onprogress: onProgress,
674
- onload({ response, statusText, status, finalUrl }) {
675
- const ok = status >= 200 && status < 300;
676
- if (!ok && !isFileURL(finalUrl)) {
677
- reject(new Error(`Request to ${url} ended with ${status} status.`));
678
- return;
679
- }
680
- const properResponse = new Response(response, {
681
- statusText,
682
- status: isFileURL(finalUrl) ? 200 : status,
683
- });
684
- Object.defineProperty(properResponse, 'url', { value: finalUrl });
685
- resolve(properResponse);
686
- },
687
- onerror({ status }) {
688
- reject(new Error(`Request to ${url} ended with ${status} status.`));
689
- },
690
- });
691
- });
692
- }
693
-
694
- function rx(url, options) {
695
- return createRequest(gmTransport, url, options);
696
- }
697
-
698
- exports.$ = $;
699
- exports.$$ = $$;
700
- exports.addStyle = addStyle;
701
- exports.alert = alert;
702
- exports.assert = assert;
703
- exports.assertType = assertType;
704
- exports.asserted = asserted;
705
- exports.assertedType = assertedType;
706
- exports.capitalize = capitalize;
707
- exports.checkVPN = checkVPN;
708
- exports.checkVPNRandomly = checkVPNRandomly;
709
- exports.confirm = confirm;
710
- exports.customizeConsole = customizeConsole;
711
- exports.fish = fish;
712
- exports.fishResponse = fishResponse;
713
- exports.fishX = fishX;
714
- exports.fishXResponse = fishXResponse;
715
- exports.getOptions = getOptions;
716
- exports.getSearchParam = getSearchParam;
717
- exports.imageLoad = imageLoad;
718
- exports.isBoolean = isBoolean;
719
- exports.isFalsy = isFalsy;
720
- exports.isFunction = isFunction;
721
- exports.isNotNullish = isNotNullish;
722
- exports.isNullish = isNullish;
723
- exports.isNumber = isNumber;
724
- exports.isObject = isObject;
725
- exports.isString = isString;
726
- exports.isTruthy = isTruthy;
727
- exports.join = join;
728
- exports.noop = noop;
729
- exports.padZeros = padZeros;
730
- exports.pageLoad = pageLoad;
731
- exports.prompt = prompt;
732
- exports.remove = remove;
733
- exports.removeSearchParams = removeSearchParams;
734
- exports.rq = rq;
735
- exports.rqc = createRequest;
736
- exports.rx = rx;
737
- exports.saveFile = saveFile;
738
- exports.scriptName = scriptName;
739
- exports.setSearchParam = setSearchParam;
740
- exports.sleep = sleep;
741
- exports.tabURL = tabURL;
742
- exports.toString = toString;