@sv443-network/userutils 3.0.0 → 4.0.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @sv443-network/userutils
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - dae5450: Removed `amplifyMedia` function due to massive inconsistencies in sound quality
8
+
9
+ ### Minor Changes
10
+
11
+ - 168c2aa: Added functions `compress` and `decompress` to compress and decompress strings using gzip or deflate
12
+ - 49bc85e: Added utility types `NonEmptyString` and `LooseUnion`
13
+
14
+ ### Patch Changes
15
+
16
+ - 2ae665d: fixed wrong TS type for SelectorObserver options in constructor
17
+
3
18
  ## 3.0.0
4
19
 
5
20
  ### Major Changes
package/README.md CHANGED
@@ -4,13 +4,15 @@
4
4
  ## UserUtils
5
5
  Zero-dependency library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more.
6
6
 
7
- Contains builtin TypeScript declarations. Fully web compatible and supports ESM, CJS and global imports.
7
+ Contains builtin TypeScript declarations. Fully web compatible and supports ESM and CJS imports and global declaration.
8
8
  If you like using this library, please consider [supporting the development ❤️](https://github.com/sponsors/Sv443)
9
9
 
10
10
  <br>
11
+ <sub>
11
12
 
12
- View the documentation of previous major releases: [2.0.1](https://github.com/Sv443-Network/UserUtils/blob/v2.0.1/README.md), [1.2.0](https://github.com/Sv443-Network/UserUtils/blob/v1.2.0/README.md), [0.5.3](https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md)
13
+ View the documentation of previous major releases: [3.0.0](https://github.com/Sv443-Network/UserUtils/blob/v3.0.0/README.md), [2.0.1](https://github.com/Sv443-Network/UserUtils/blob/v2.0.1/README.md), [1.2.0](https://github.com/Sv443-Network/UserUtils/blob/v1.2.0/README.md), [0.5.3](https://github.com/Sv443-Network/UserUtils/blob/v0.5.3/README.md)
13
14
 
15
+ </sub>
14
16
  </div>
15
17
  <br>
16
18
 
@@ -30,7 +32,6 @@ View the documentation of previous major releases: [2.0.1](https://github.com/Sv
30
32
  - [openInNewTab()](#openinnewtab) - open a link in a new tab
31
33
  - [interceptEvent()](#interceptevent) - conditionally intercepts events registered by `addEventListener()` on any given EventTarget object
32
34
  - [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
33
- - [amplifyMedia()](#amplifymedia) - amplify an audio or video element's volume past the maximum of 100%
34
35
  - [isScrollable()](#isscrollable) - check if an element has a horizontal or vertical scroll bar
35
36
  - [**Math:**](#math)
36
37
  - [clamp()](#clamp) - constrain a number between a min and max value
@@ -44,6 +45,8 @@ View the documentation of previous major releases: [2.0.1](https://github.com/Sv
44
45
  - [debounce()](#debounce) - call a function only once, after a given amount of time
45
46
  - [fetchAdvanced()](#fetchadvanced) - wrapper around the fetch API with a timeout option
46
47
  - [insertValues()](#insertvalues) - insert values into a string at specified placeholders
48
+ - [compress()](#compress) - compress a string with Gzip or Deflate
49
+ - [decompress()](#decompress) - decompress a previously compressed string
47
50
  - [**Arrays:**](#arrays)
48
51
  - [randomItem()](#randomitem) - returns a random item from an array
49
52
  - [randomItemIndex()](#randomitemindex) - returns a tuple of a random item and its index from an array
@@ -56,7 +59,9 @@ View the documentation of previous major releases: [2.0.1](https://github.com/Sv
56
59
  - [tr.getLanguage()](#trgetlanguage) - returns the currently active language
57
60
  - [**Utility types for TypeScript:**](#utility-types)
58
61
  - [Stringifiable](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
59
- - [NonEmptyArray](https://github.com/Sv443-Network/UserUtils#nonemptyarray) - any array that should have at least one item
62
+ - [NonEmptyArray](#nonemptyarray) - any array that should have at least one item
63
+ - [NonEmptyString](#nonemptystring) - any string that should have at least one character
64
+ - [LooseUnion](#looseunion) - a union that gives autocomplete in the IDE but also allows any other value of the same type
60
65
 
61
66
  <br><br>
62
67
 
@@ -647,101 +652,6 @@ interceptWindowEvent("beforeunload");
647
652
 
648
653
  <br>
649
654
 
650
- ### amplifyMedia()
651
- Usage:
652
- ```ts
653
- amplifyMedia(mediaElement: HTMLMediaElement, initialGain?: number): AmplifyMediaResult
654
- ```
655
-
656
- Amplifies the volume of a media element (like `<audio>` or `<video>`) by the given gain value.
657
- This is how you can increase the volume of a media element beyond the default maximum volume of 100%.
658
- Make sure to limit the value to a reasonable value ([clamp()](#clamp) is good for this), as it may otherwise cause bleeding eardrums.
659
-
660
- The default gain value passed to the GainNode is `1.0`
661
- It may be read and changed at any point by calling the `getGain()` and `setGain()` methods of the returned object.
662
-
663
- To activate the amplification for the first time, call the `enable()` method of the returned object.
664
-
665
- This is the processing workflow applied to the media element:
666
- `MediaElement (source)` => `GainNode (pre-amplifier)` => 10x `BiquadFilterNode` => `GainNode (post-amplifier)` => `destination`
667
-
668
- ⚠️ This function has to be run in response to a user interaction event, else the browser will reject it because of the strict autoplay policy.
669
- ⚠️ Make sure to call the returned function `enable()` after calling this function to actually enable the amplification.
670
-
671
- The returned object of the type `AmplifyMediaResult` has the following properties:
672
- **Important properties:**
673
- | Property | Description |
674
- | :-- | :-- |
675
- | `enable()` | Call to enable the amplification for the first time or re-enable it if it was disabled before |
676
- | `disable()` | Call to disable amplification |
677
- | `enabled` | Whether the amplification is currently enabled |
678
- | `setGain()` | Used to change the gain value from the default given by the parameter `initialGain` |
679
- | `getGain()` | Returns the current gain value |
680
-
681
- **Other properties:**
682
- | Property | Description |
683
- | :-- | :-- |
684
- | `context` | The AudioContext instance used as the audio destination and context within the nodes are created |
685
- | `sourceNode` | A MediaElementSourceNode instance created from the passed `mediaElement` |
686
- | `gainNode` | The GainNode instance used for volume amplification |
687
-
688
- <br>
689
-
690
- <details><summary><b>Example - click to view</b></summary>
691
-
692
- ```ts
693
- import { amplifyMedia, clamp } from "@sv443-network/userutils";
694
- import type { AmplifyMediaResult } from "@sv443-network/userutils";
695
-
696
- const audioElement = document.querySelector<HTMLAudioElement>("audio");
697
-
698
- let ampResult: AmplifyMediaResult | undefined;
699
-
700
- function updateGainValue(gainValue: number) {
701
- if(!ampResult)
702
- return;
703
- // constrain the value to between 0 and 3 for safety
704
- ampResult.setGain(clamp(gainValue, 0, 3));
705
-
706
- console.log("Gain set to", ampResult.getGain());
707
- }
708
-
709
-
710
- const amplifyButton = document.querySelector<HTMLButtonElement>("button#amplify");
711
-
712
- // amplifyMedia() needs to be called in response to a user interaction event:
713
- amplifyButton.addEventListener("click", () => {
714
- // only needs to be initialized once, afterwards the returned object
715
- // can be used to change settings and enable/disable the amplification
716
- if(!ampResult) {
717
- // initialize amplification and set it to ~2x
718
- ampResult = amplifyMedia(audioElement, 2.0);
719
- }
720
- if(!ampResult.enabled) {
721
- // enable the amplification
722
- ampResult.enable();
723
- }
724
-
725
- updateGainValue(3.5); // try to set gain to ~3.5x
726
-
727
- console.log(ampResult.getGain()); // 3.0 (because of the clamp())
728
- });
729
-
730
-
731
- const disableButton = document.querySelector<HTMLButtonElement>("button#disable");
732
-
733
- disableButton.addEventListener("click", () => {
734
- if(ampResult) {
735
- // disable the amplification
736
- ampResult.disable();
737
- }
738
- });
739
- ```
740
-
741
- </details>
742
-
743
- <br>
744
-
745
655
  ### isScrollable()
746
656
  Usage:
747
657
  ```ts
@@ -1121,6 +1031,106 @@ fetchAdvanced("https://jokeapi.dev/joke/Any?safe-mode", {
1121
1031
 
1122
1032
  </details>
1123
1033
 
1034
+ <br>
1035
+
1036
+ ### insertValues()
1037
+ Usage:
1038
+ ```ts
1039
+ insertValues(input: string, ...values: Stringifiable[]): string
1040
+ ```
1041
+
1042
+ Inserts values into a string in the format `%n`, where `n` is the number of the value, starting at 1.
1043
+ The values will be stringified using `toString()` (see [Stringifiable](#stringifiable)) before being inserted into the input string.
1044
+ If not enough values are passed, the remaining placeholders will be left untouched.
1045
+
1046
+ <details><summary><b>Example - click to view</b></summary>
1047
+
1048
+ ```ts
1049
+ import { insertValues } from "@sv443-network/userutils";
1050
+
1051
+ insertValues("Hello, %1!", "World"); // "Hello, World!"
1052
+ insertValues("Hello, %1! My name is %2.", "World", "John"); // "Hello, World! My name is John."
1053
+ insertValues("Testing %1", { toString: () => "foo" }); // "Testing foo"
1054
+
1055
+ // using an array for the values and not passing enough arguments:
1056
+ const values = ["foo", "bar", "baz"];
1057
+ insertValues("Testing %1, %2, %3 and %4", ...values); // "Testing foo, bar and baz and %4"
1058
+ ```
1059
+
1060
+ </details>
1061
+
1062
+ <br>
1063
+
1064
+ ### compress()
1065
+ Usage:
1066
+ ```ts
1067
+ compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "base64"): Promise<string>
1068
+ compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>
1069
+ ```
1070
+
1071
+ Compresses a string or ArrayBuffer using the specified compression format. Most browsers should support at least `gzip` and `deflate`
1072
+ The `outputType` dictates which format the output will be in. It will default to `base64` if left undefined.
1073
+
1074
+ ⚠️ You need to provide the `@grant unsafeWindow` directive if you are using the `base64` output type or you will get a TypeError.
1075
+ ⚠️ Not all browsers might support compression. Please check [on this page](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream#browser_compatibility) for compatibility and supported compression formats.
1076
+
1077
+ <details><summary><b>Example - click to view</b></summary>
1078
+
1079
+ ```ts
1080
+ import { compress } from "@sv443-network/userutils";
1081
+
1082
+ // using gzip:
1083
+
1084
+ const fooGz = await compress("Hello, World!", "gzip");
1085
+ const barGz = await compress("Hello, World!".repeat(20), "gzip");
1086
+
1087
+ // not as efficient with short strings but can save quite a lot of space with larger strings:
1088
+ console.log(fooGz); // "H4sIAAAAAAAAE/NIzcnJ11EIzy/KSVEEANDDSuwNAAAA"
1089
+ console.log(barGz); // "H4sIAAAAAAAAE/NIzcnJ11EIzy/KSVH0GJkcAKOPcmYEAQAA"
1090
+
1091
+ // depending on the type of data you might want to use a different compression format like deflate:
1092
+
1093
+ const fooDeflate = await compress("Hello, World!", "deflate");
1094
+ const barDeflate = await compress("Hello, World!".repeat(20), "deflate");
1095
+
1096
+ // again, it's not as efficient initially but gets better with longer inputs:
1097
+ console.log(fooDeflate); // "eJzzSM3JyddRCM8vyklRBAAfngRq"
1098
+ console.log(barDeflate); // "eJzzSM3JyddRCM8vyklR9BiZHAAIEVg1"
1099
+ ```
1100
+
1101
+ </details>
1102
+
1103
+ <br>
1104
+
1105
+ ### decompress()
1106
+ Usage:
1107
+ ```ts
1108
+ decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "string"): Promise<string>
1109
+ decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>
1110
+ ```
1111
+
1112
+ Decompresses a string or ArrayBuffer that has been previously [compressed](#compress) using the specified compression format. Most browsers should support at least `gzip` and `deflate`
1113
+ The `outputType` dictates which format the output will be in. It will default to `string` if left undefined.
1114
+
1115
+ ⚠️ You need to provide the `@grant unsafeWindow` directive if you are using the `string` output type or you will get a TypeError.
1116
+ ⚠️ Not all browsers might support decompression. Please check [on this page](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream#browser_compatibility) for compatibility and supported compression formats.
1117
+
1118
+ <details><summary><b>Example - click to view</b></summary>
1119
+
1120
+ ```ts
1121
+ import { compress, decompress } from "@sv443-network/userutils";
1122
+
1123
+ const compressed = await compress("Hello, World!".repeat(20), "gzip");
1124
+
1125
+ console.log(compressed); // "H4sIAAAAAAAAE/NIzcnJ11EIzy/KSVH0GJkcAKOPcmYEAQAA"
1126
+
1127
+ const decompressed = await decompress(compressed, "gzip");
1128
+
1129
+ console.log(decompressed); // "Hello, World!"
1130
+ ```
1131
+
1132
+ </details>
1133
+
1124
1134
  <br><br>
1125
1135
 
1126
1136
  <!-- #SECTION Arrays -->
@@ -1442,6 +1452,11 @@ logSomething(barObject); // Type Error
1442
1452
  <br>
1443
1453
 
1444
1454
  ## NonEmptyArray
1455
+ Usage:
1456
+ ```ts
1457
+ NonEmptyArray<TItem = unknown>
1458
+ ```
1459
+
1445
1460
  This type describes an array that has at least one item.
1446
1461
  Use the generic parameter to specify the type of the items in the array.
1447
1462
 
@@ -1465,6 +1480,59 @@ logFirstItem(["04abc", "69"]); // 4
1465
1480
 
1466
1481
  </details>
1467
1482
 
1483
+ <br>
1484
+
1485
+ ## NonEmptyString
1486
+ Usage:
1487
+ ```ts
1488
+ NonEmptyString<TString extends string>
1489
+ ```
1490
+
1491
+ This type describes a string that has at least one character.
1492
+
1493
+ <details><summary><b>Example - click to view</b></summary>
1494
+
1495
+ ```ts
1496
+ import type { NonEmptyString } from "@sv443-network/userutils";
1497
+
1498
+ function convertToNumber<T extends string>(str: NonEmptyString<T>) {
1499
+ console.log(parseInt(str));
1500
+ }
1501
+
1502
+ convertToNumber("04abc"); // "4"
1503
+ convertToNumber(""); // type error: Argument of type 'string' is not assignable to parameter of type 'never'
1504
+ ```
1505
+
1506
+ </details>
1507
+
1508
+ <br>
1509
+
1510
+ ## LooseUnion
1511
+ Usage:
1512
+ ```ts
1513
+ LooseUnion<TUnion extends string | number | object>
1514
+ ```
1515
+
1516
+ A type that offers autocomplete in the IDE for the passed union but also allows any value of the same type to be passed.
1517
+ Supports unions of strings, numbers and objects.
1518
+
1519
+ <details><summary><b>Example - click to view</b></summary>
1520
+
1521
+ ```ts
1522
+ function foo(bar: LooseUnion<"a" | "b" | "c">) {
1523
+ console.log(bar);
1524
+ }
1525
+
1526
+ // when typing the following, autocomplete suggests "a", "b" and "c"
1527
+ // foo("
1528
+
1529
+ foo("a"); // included in autocomplete, no type error
1530
+ foo(""); // *not* included in autocomplete, still no type error
1531
+ foo(1); // type error: Argument of type '1' is not assignable to parameter of type 'LooseUnion<"a" | "b" | "c">'
1532
+ ```
1533
+
1534
+ </details>
1535
+
1468
1536
  <br><br><br><br>
1469
1537
 
1470
1538
  <!-- #MARKER Footer -->
@@ -9,7 +9,7 @@
9
9
  // ==UserLibrary==
10
10
  // @name UserUtils
11
11
  // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more
12
- // @version 3.0.0
12
+ // @version 4.0.0
13
13
  // @license MIT
14
14
  // @copyright Sv443 (https://github.com/Sv443)
15
15
 
@@ -326,43 +326,6 @@ var UserUtils = (function (exports) {
326
326
  function interceptWindowEvent(eventName, predicate = () => true) {
327
327
  return interceptEvent(getUnsafeWindow(), eventName, predicate);
328
328
  }
329
- function amplifyMedia(mediaElement, initialGain = 1) {
330
- const context = new (window.AudioContext || window.webkitAudioContext)();
331
- const props = {
332
- context,
333
- sourceNode: context.createMediaElementSource(mediaElement),
334
- gainNode: context.createGain(),
335
- /** Sets the gain of the amplifying GainNode */
336
- setGain(gain) {
337
- props.gainNode.gain.value = gain;
338
- },
339
- /** Returns the current gain of the amplifying GainNode */
340
- getGain() {
341
- return props.gainNode.gain.value;
342
- },
343
- /** Whether the amplification is currently enabled */
344
- enabled: false,
345
- /** Enable the amplification for the first time or if it was disabled before */
346
- enable() {
347
- if (props.enabled)
348
- return;
349
- props.enabled = true;
350
- props.sourceNode.connect(props.gainNode);
351
- props.gainNode.connect(props.context.destination);
352
- },
353
- /** Disable the amplification */
354
- disable() {
355
- if (!props.enabled)
356
- return;
357
- props.enabled = false;
358
- props.sourceNode.disconnect(props.gainNode);
359
- props.gainNode.disconnect(props.context.destination);
360
- props.sourceNode.connect(props.context.destination);
361
- }
362
- };
363
- props.setGain(initialGain);
364
- return props;
365
- }
366
329
  function isScrollable(element) {
367
330
  const { overflowX, overflowY } = getComputedStyle(element);
368
331
  return {
@@ -401,13 +364,43 @@ var UserUtils = (function (exports) {
401
364
  return res;
402
365
  });
403
366
  }
404
- function insertValues(str, ...values) {
405
- return str.replace(/%\d/gm, (match) => {
367
+ function insertValues(input, ...values) {
368
+ return input.replace(/%\d/gm, (match) => {
406
369
  var _a, _b;
407
370
  const argIndex = Number(match.substring(1)) - 1;
408
371
  return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
409
372
  });
410
373
  }
374
+ function compress(input, compressionFormat, outputType = "base64") {
375
+ return __async(this, null, function* () {
376
+ const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
377
+ const comp = new CompressionStream(compressionFormat);
378
+ const writer = comp.writable.getWriter();
379
+ writer.write(byteArray);
380
+ writer.close();
381
+ const buf = yield new Response(comp.readable).arrayBuffer();
382
+ return outputType === "arrayBuffer" ? buf : ab2str(buf);
383
+ });
384
+ }
385
+ function decompress(input, compressionFormat, outputType = "string") {
386
+ return __async(this, null, function* () {
387
+ const byteArray = typeof input === "string" ? str2ab(input) : input;
388
+ const decomp = new DecompressionStream(compressionFormat);
389
+ const writer = decomp.writable.getWriter();
390
+ writer.write(byteArray);
391
+ writer.close();
392
+ const buf = yield new Response(decomp.readable).arrayBuffer();
393
+ return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
394
+ });
395
+ }
396
+ function ab2str(buf) {
397
+ return getUnsafeWindow().btoa(
398
+ new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
399
+ );
400
+ }
401
+ function str2ab(str) {
402
+ return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
403
+ }
411
404
 
412
405
  // lib/SelectorObserver.ts
413
406
  var SelectorObserver = class {
@@ -579,10 +572,11 @@ var UserUtils = (function (exports) {
579
572
  exports.SelectorObserver = SelectorObserver;
580
573
  exports.addGlobalStyle = addGlobalStyle;
581
574
  exports.addParent = addParent;
582
- exports.amplifyMedia = amplifyMedia;
583
575
  exports.autoPlural = autoPlural;
584
576
  exports.clamp = clamp;
577
+ exports.compress = compress;
585
578
  exports.debounce = debounce;
579
+ exports.decompress = decompress;
586
580
  exports.fetchAdvanced = fetchAdvanced;
587
581
  exports.getUnsafeWindow = getUnsafeWindow;
588
582
  exports.insertAfter = insertAfter;
package/dist/index.js CHANGED
@@ -305,43 +305,6 @@ function interceptEvent(eventObject, eventName, predicate = () => true) {
305
305
  function interceptWindowEvent(eventName, predicate = () => true) {
306
306
  return interceptEvent(getUnsafeWindow(), eventName, predicate);
307
307
  }
308
- function amplifyMedia(mediaElement, initialGain = 1) {
309
- const context = new (window.AudioContext || window.webkitAudioContext)();
310
- const props = {
311
- context,
312
- sourceNode: context.createMediaElementSource(mediaElement),
313
- gainNode: context.createGain(),
314
- /** Sets the gain of the amplifying GainNode */
315
- setGain(gain) {
316
- props.gainNode.gain.value = gain;
317
- },
318
- /** Returns the current gain of the amplifying GainNode */
319
- getGain() {
320
- return props.gainNode.gain.value;
321
- },
322
- /** Whether the amplification is currently enabled */
323
- enabled: false,
324
- /** Enable the amplification for the first time or if it was disabled before */
325
- enable() {
326
- if (props.enabled)
327
- return;
328
- props.enabled = true;
329
- props.sourceNode.connect(props.gainNode);
330
- props.gainNode.connect(props.context.destination);
331
- },
332
- /** Disable the amplification */
333
- disable() {
334
- if (!props.enabled)
335
- return;
336
- props.enabled = false;
337
- props.sourceNode.disconnect(props.gainNode);
338
- props.gainNode.disconnect(props.context.destination);
339
- props.sourceNode.connect(props.context.destination);
340
- }
341
- };
342
- props.setGain(initialGain);
343
- return props;
344
- }
345
308
  function isScrollable(element) {
346
309
  const { overflowX, overflowY } = getComputedStyle(element);
347
310
  return {
@@ -380,13 +343,43 @@ function fetchAdvanced(_0) {
380
343
  return res;
381
344
  });
382
345
  }
383
- function insertValues(str, ...values) {
384
- return str.replace(/%\d/gm, (match) => {
346
+ function insertValues(input, ...values) {
347
+ return input.replace(/%\d/gm, (match) => {
385
348
  var _a, _b;
386
349
  const argIndex = Number(match.substring(1)) - 1;
387
350
  return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
388
351
  });
389
352
  }
353
+ function compress(input, compressionFormat, outputType = "base64") {
354
+ return __async(this, null, function* () {
355
+ const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
356
+ const comp = new CompressionStream(compressionFormat);
357
+ const writer = comp.writable.getWriter();
358
+ writer.write(byteArray);
359
+ writer.close();
360
+ const buf = yield new Response(comp.readable).arrayBuffer();
361
+ return outputType === "arrayBuffer" ? buf : ab2str(buf);
362
+ });
363
+ }
364
+ function decompress(input, compressionFormat, outputType = "string") {
365
+ return __async(this, null, function* () {
366
+ const byteArray = typeof input === "string" ? str2ab(input) : input;
367
+ const decomp = new DecompressionStream(compressionFormat);
368
+ const writer = decomp.writable.getWriter();
369
+ writer.write(byteArray);
370
+ writer.close();
371
+ const buf = yield new Response(decomp.readable).arrayBuffer();
372
+ return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
373
+ });
374
+ }
375
+ function ab2str(buf) {
376
+ return getUnsafeWindow().btoa(
377
+ new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
378
+ );
379
+ }
380
+ function str2ab(str) {
381
+ return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
382
+ }
390
383
 
391
384
  // lib/SelectorObserver.ts
392
385
  var SelectorObserver = class {
@@ -558,10 +551,11 @@ exports.ConfigManager = ConfigManager;
558
551
  exports.SelectorObserver = SelectorObserver;
559
552
  exports.addGlobalStyle = addGlobalStyle;
560
553
  exports.addParent = addParent;
561
- exports.amplifyMedia = amplifyMedia;
562
554
  exports.autoPlural = autoPlural;
563
555
  exports.clamp = clamp;
556
+ exports.compress = compress;
564
557
  exports.debounce = debounce;
558
+ exports.decompress = decompress;
565
559
  exports.fetchAdvanced = fetchAdvanced;
566
560
  exports.getUnsafeWindow = getUnsafeWindow;
567
561
  exports.insertAfter = insertAfter;
package/dist/index.mjs CHANGED
@@ -303,43 +303,6 @@ function interceptEvent(eventObject, eventName, predicate = () => true) {
303
303
  function interceptWindowEvent(eventName, predicate = () => true) {
304
304
  return interceptEvent(getUnsafeWindow(), eventName, predicate);
305
305
  }
306
- function amplifyMedia(mediaElement, initialGain = 1) {
307
- const context = new (window.AudioContext || window.webkitAudioContext)();
308
- const props = {
309
- context,
310
- sourceNode: context.createMediaElementSource(mediaElement),
311
- gainNode: context.createGain(),
312
- /** Sets the gain of the amplifying GainNode */
313
- setGain(gain) {
314
- props.gainNode.gain.value = gain;
315
- },
316
- /** Returns the current gain of the amplifying GainNode */
317
- getGain() {
318
- return props.gainNode.gain.value;
319
- },
320
- /** Whether the amplification is currently enabled */
321
- enabled: false,
322
- /** Enable the amplification for the first time or if it was disabled before */
323
- enable() {
324
- if (props.enabled)
325
- return;
326
- props.enabled = true;
327
- props.sourceNode.connect(props.gainNode);
328
- props.gainNode.connect(props.context.destination);
329
- },
330
- /** Disable the amplification */
331
- disable() {
332
- if (!props.enabled)
333
- return;
334
- props.enabled = false;
335
- props.sourceNode.disconnect(props.gainNode);
336
- props.gainNode.disconnect(props.context.destination);
337
- props.sourceNode.connect(props.context.destination);
338
- }
339
- };
340
- props.setGain(initialGain);
341
- return props;
342
- }
343
306
  function isScrollable(element) {
344
307
  const { overflowX, overflowY } = getComputedStyle(element);
345
308
  return {
@@ -378,13 +341,43 @@ function fetchAdvanced(_0) {
378
341
  return res;
379
342
  });
380
343
  }
381
- function insertValues(str, ...values) {
382
- return str.replace(/%\d/gm, (match) => {
344
+ function insertValues(input, ...values) {
345
+ return input.replace(/%\d/gm, (match) => {
383
346
  var _a, _b;
384
347
  const argIndex = Number(match.substring(1)) - 1;
385
348
  return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
386
349
  });
387
350
  }
351
+ function compress(input, compressionFormat, outputType = "base64") {
352
+ return __async(this, null, function* () {
353
+ const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
354
+ const comp = new CompressionStream(compressionFormat);
355
+ const writer = comp.writable.getWriter();
356
+ writer.write(byteArray);
357
+ writer.close();
358
+ const buf = yield new Response(comp.readable).arrayBuffer();
359
+ return outputType === "arrayBuffer" ? buf : ab2str(buf);
360
+ });
361
+ }
362
+ function decompress(input, compressionFormat, outputType = "string") {
363
+ return __async(this, null, function* () {
364
+ const byteArray = typeof input === "string" ? str2ab(input) : input;
365
+ const decomp = new DecompressionStream(compressionFormat);
366
+ const writer = decomp.writable.getWriter();
367
+ writer.write(byteArray);
368
+ writer.close();
369
+ const buf = yield new Response(decomp.readable).arrayBuffer();
370
+ return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
371
+ });
372
+ }
373
+ function ab2str(buf) {
374
+ return getUnsafeWindow().btoa(
375
+ new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
376
+ );
377
+ }
378
+ function str2ab(str) {
379
+ return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
380
+ }
388
381
 
389
382
  // lib/SelectorObserver.ts
390
383
  var SelectorObserver = class {
@@ -552,4 +545,4 @@ tr.getLanguage = () => {
552
545
  return curLang;
553
546
  };
554
547
 
555
- export { ConfigManager, SelectorObserver, addGlobalStyle, addParent, amplifyMedia, autoPlural, clamp, debounce, fetchAdvanced, getUnsafeWindow, insertAfter, insertValues, interceptEvent, interceptWindowEvent, isScrollable, mapRange, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, takeRandomItem, tr };
548
+ export { ConfigManager, SelectorObserver, addGlobalStyle, addParent, autoPlural, clamp, compress, debounce, decompress, fetchAdvanced, getUnsafeWindow, insertAfter, insertValues, interceptEvent, interceptWindowEvent, isScrollable, mapRange, openInNewTab, pauseFor, preloadImages, randRange, randomId, randomItem, randomItemIndex, randomizeArray, takeRandomItem, tr };
@@ -34,13 +34,13 @@ export declare class SelectorObserver {
34
34
  * @param baseElementSelector The selector of the element to observe
35
35
  * @param options Fine-tune what triggers the MutationObserver's checking function - `subtree` and `childList` are set to true by default
36
36
  */
37
- constructor(baseElementSelector: string, options: SelectorObserverOptions);
37
+ constructor(baseElementSelector: string, options?: SelectorObserverOptions);
38
38
  /**
39
39
  * Creates a new SelectorObserver that will observe the children of the given base element for changes (only creation and deletion of elements by default)
40
40
  * @param baseElement The element to observe
41
41
  * @param options Fine-tune what triggers the MutationObserver's checking function - `subtree` and `childList` are set to true by default
42
42
  */
43
- constructor(baseElement: Element, options: SelectorObserverOptions);
43
+ constructor(baseElement: Element, options?: SelectorObserverOptions);
44
44
  private checkAllSelectors;
45
45
  private checkSelector;
46
46
  private debounce;
@@ -1,13 +1,13 @@
1
1
  /** Describes an array with at least one item */
2
- export type NonEmptyArray<T = unknown> = [T, ...T[]];
2
+ export type NonEmptyArray<TArray = unknown> = [TArray, ...TArray[]];
3
3
  /** Returns a random item from the passed array */
4
- export declare function randomItem<T = unknown>(array: T[]): T | undefined;
4
+ export declare function randomItem<TItem = unknown>(array: TItem[]): TItem | undefined;
5
5
  /**
6
6
  * Returns a tuple of a random item and its index from the passed array
7
7
  * Returns `[undefined, undefined]` if the passed array is empty
8
8
  */
9
- export declare function randomItemIndex<T = unknown>(array: T[]): [item?: T, index?: number];
9
+ export declare function randomItemIndex<TItem = unknown>(array: TItem[]): [item?: TItem, index?: number];
10
10
  /** Returns a random item from the passed array and mutates the array to remove the item */
11
- export declare function takeRandomItem<T = unknown>(arr: T[]): T | undefined;
11
+ export declare function takeRandomItem<TItem = unknown>(arr: TItem[]): TItem | undefined;
12
12
  /** Returns a copy of the array with its items in a random order */
13
- export declare function randomizeArray<T = unknown>(array: T[]): T[];
13
+ export declare function randomizeArray<TItem = unknown>(array: TItem[]): TItem[];
package/dist/lib/dom.d.ts CHANGED
@@ -45,53 +45,6 @@ export declare function interceptEvent<TEvtObj extends EventTarget, TPredicateEv
45
45
  * Calling this function will set the `Error.stackTraceLimit` to 1000 to ensure the stack trace is preserved.
46
46
  */
47
47
  export declare function interceptWindowEvent<TEvtKey extends keyof WindowEventMap>(eventName: TEvtKey, predicate?: (event: WindowEventMap[TEvtKey]) => boolean): void;
48
- /** An object which contains the results of {@linkcode amplifyMedia()} */
49
- export type AmplifyMediaResult = ReturnType<typeof amplifyMedia>;
50
- /**
51
- * Amplifies the gain of the passed media element's audio by the specified value.
52
- * This function supports any MediaElement instance like `<audio>` or `<video>`
53
- *
54
- * This is the audio processing workflow:
55
- * `MediaElement (source)` => `GainNode (amplification)` => `destination`
56
- *
57
- * ⚠️ This function has to be run in response to a user interaction event, else the browser will reject it because of the strict autoplay policy.
58
- * ⚠️ Make sure to call the returned function `enable()` after calling this function to actually enable the amplification.
59
- * ⚠️ You should implement a safety limit by using the [`clamp()`](https://github.com/Sv443-Network/UserUtils#clamp) function to prevent any accidental bleeding eardrums.
60
- *
61
- * @param mediaElement The media element to amplify (e.g. `<audio>` or `<video>`)
62
- * @param initialGain The initial gain to apply to the GainNode responsible for volume amplification (floating point number, default is `1.0`)
63
- * @returns Returns an object with the following properties:
64
- * **Important properties:**
65
- * | Property | Description |
66
- * | :-- | :-- |
67
- * | `enable()` | Call to enable the amplification for the first time or re-enable it if it was disabled before |
68
- * | `disable()` | Call to disable amplification |
69
- * | `enabled` | Whether the amplification is currently enabled |
70
- * | `setGain()` | Used to change the gain value from the default given by the parameter {@linkcode initialGain} |
71
- * | `getGain()` | Returns the current gain value |
72
- *
73
- * **Other properties:**
74
- * | Property | Description |
75
- * | :-- | :-- |
76
- * | `context` | The AudioContext instance |
77
- * | `sourceNode` | A MediaElementSourceNode instance created from the passed {@linkcode mediaElement} |
78
- * | `gainNode` | The GainNode instance used for volume amplification |
79
- */
80
- export declare function amplifyMedia<TElem extends HTMLMediaElement>(mediaElement: TElem, initialGain?: number): {
81
- context: AudioContext;
82
- sourceNode: MediaElementAudioSourceNode;
83
- gainNode: GainNode;
84
- /** Sets the gain of the amplifying GainNode */
85
- setGain(gain: number): void;
86
- /** Returns the current gain of the amplifying GainNode */
87
- getGain(): number;
88
- /** Whether the amplification is currently enabled */
89
- enabled: boolean;
90
- /** Enable the amplification for the first time or if it was disabled before */
91
- enable(): void;
92
- /** Disable the amplification */
93
- disable(): void;
94
- };
95
48
  /** Checks if an element is scrollable in the horizontal and vertical directions */
96
49
  export declare function isScrollable(element: Element): {
97
50
  vertical: boolean;
@@ -2,6 +2,19 @@
2
2
  export type Stringifiable = string | {
3
3
  toString(): string;
4
4
  };
5
+ /**
6
+ * A type that offers autocomplete for the passed union but also allows any arbitrary value of the same type to be passed.
7
+ * Supports unions of strings, numbers and objects.
8
+ */
9
+ export type LooseUnion<TUnion extends string | number | object> = (TUnion) | (TUnion extends string ? (string & {}) : (TUnion extends number ? (number & {}) : (TUnion extends Record<keyof any, unknown> ? (object & {}) : never)));
10
+ /**
11
+ * A type that allows all strings except for empty ones
12
+ * @example
13
+ * function foo<T extends string>(bar: NonEmptyString<T>) {
14
+ * console.log(bar);
15
+ * }
16
+ */
17
+ export type NonEmptyString<TString extends string> = TString extends "" ? never : TString;
5
18
  /**
6
19
  * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
7
20
  * @param word A word in singular form, to auto-convert to plural
@@ -25,7 +38,15 @@ export declare function fetchAdvanced(url: string, options?: FetchAdvancedOpts):
25
38
  /**
26
39
  * Inserts the passed values into a string at the respective placeholders.
27
40
  * The placeholder format is `%n`, where `n` is the 1-indexed argument number.
28
- * @param str The string to insert the values into
41
+ * @param input The string to insert the values into
29
42
  * @param values The values to insert, in order, starting at `%1`
30
43
  */
31
- export declare function insertValues(str: string, ...values: Stringifiable[]): string;
44
+ export declare function insertValues(input: string, ...values: Stringifiable[]): string;
45
+ /** Compresses a string or an ArrayBuffer using the provided {@linkcode compressionFormat} and returns it as a base64 string */
46
+ export declare function compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "base64"): Promise<string>;
47
+ /** Compresses a string or an ArrayBuffer using the provided {@linkcode compressionFormat} and returns it as an ArrayBuffer */
48
+ export declare function compress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>;
49
+ /** Decompresses a previously compressed base64 string or ArrayBuffer, with the format passed by {@linkcode compressionFormat}, converted to a string */
50
+ export declare function decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType?: "string"): Promise<string>;
51
+ /** Decompresses a previously compressed base64 string or ArrayBuffer, with the format passed by {@linkcode compressionFormat}, converted to an ArrayBuffer */
52
+ export declare function decompress(input: string | ArrayBuffer, compressionFormat: CompressionFormat, outputType: "arrayBuffer"): Promise<ArrayBuffer>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sv443-network/userutils",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",