strc 1.1.0 → 2.0.0-test

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/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MIT License
4
4
 
5
- Copyright (c) 2025 JustDeveloper <https://justdeveloper.is-a.dev/>
5
+ Copyright (c) 2025-2026 JustDeveloper <https://justdeveloper.is-a.dev/>
6
6
 
7
7
  Permission is hereby granted, free of charge, to any person obtaining a copy
8
8
  of this software and associated documentation files (the "Software"), to deal
@@ -37,15 +37,14 @@ SOFTWARE.
37
37
 
38
38
  (function (root, factory) {
39
39
  if (typeof define === 'function' && define.amd) {
40
- define([], factory); /* amd */
40
+ define(['justc'], factory); /* amd */
41
41
  } else if (typeof module === 'object' && module.exports) {
42
- module.exports = factory(); /* node */
42
+ module.exports = factory(require('justc')); /* node */
43
43
  } else {
44
- root.JSSC = factory(); /* browsers */
44
+ root.JSSC = factory(root.JUSTC); /* browsers */
45
45
  Object.freeze(root.JSSC);
46
46
  }
47
- }(typeof self !== 'undefined' ? self : this, function () {
48
-
47
+ }(typeof self !== 'undefined' ? self : this, function (JUSTC) {
49
48
  if ((String.fromCharCode(65536).charCodeAt(0) === 65536) || !(String.fromCharCode(256).charCodeAt(0) === 256)) {
50
49
  throw new Error('Supported UTF-16 only!')
51
50
  }
@@ -135,6 +134,9 @@ SOFTWARE.
135
134
  'JA': 9,
136
135
  'Telu': 10,
137
136
  'MR': 11,
137
+ 'B': 12,
138
+ 'E': 13,
139
+ 'AR': 14
138
140
  };
139
141
  _JSSC.BASE = function() { /* Base */
140
142
  const chrsBase = charsBase();
@@ -397,6 +399,29 @@ SOFTWARE.
397
399
  }
398
400
  return chrsBase;
399
401
  };
402
+ _JSSC.B = function() { /* Baltic */
403
+ const chrsBase = charsLatin();
404
+ for (let i = 0x100; i < 0x17F; i++) {
405
+ chrsBase[i - 128] = _JSSC._char(i);
406
+ }
407
+ chrsBase[255] = _JSSC._char(0x17F); /* U+017F */
408
+ return chrsBase;
409
+ };
410
+ _JSSC.E = function() { /* European */
411
+ const chrsBase = charsLatin();
412
+ for (let i = 0x80; i < 0xFF; i++) {
413
+ chrsBase[i] = _JSSC._char(i);
414
+ }
415
+ chrsBase[255] = _JSSC._char(0x17F); /* U+017F */
416
+ return chrsBase;
417
+ };
418
+ _JSSC.AR = function() { /* Arabic */
419
+ const chrsBase = {};
420
+ for (let i = 0x600; i < 0x6FF; i++) {
421
+ chrsBase[i - 1536] = _JSSC._char(i);
422
+ }
423
+ return chrsBase;
424
+ }
400
425
  _JSSC.use = class {
401
426
  constructor() {
402
427
  let output = {};
@@ -417,7 +442,8 @@ SOFTWARE.
417
442
  function cryptCharCode(
418
443
  code, get = false,
419
444
  repeatBefore = false, repeatAfter = false,
420
- beginId = -1, code2 = 0, sequences = false
445
+ beginId = -1, code2 = 0, sequences = false,
446
+ code3 = -1
421
447
  ) {
422
448
  if (get) {
423
449
  const codeBin = decToBin(code, 16);
@@ -431,23 +457,38 @@ SOFTWARE.
431
457
  beginId: codeSet[2] === '1' ? begid : -1,
432
458
  code2: binToDec(codeBin.slice(0,4)),
433
459
  sequences: codeBin.slice(4,5) === '1',
434
- code3: codeSet[2] === '0' ? begid : -1, /* currently unused */
460
+ code3: codeSet[2] === '0' ? begid : -1,
435
461
  bin: codeBin,
436
462
  }
437
463
  } else {
438
- const sixteenBits = /* 16-bit Data/Header character */
439
-
440
- decToBin(code2, 4) + /* Bits 0-3 : code2 */
441
- (sequences ? '1' : '0') + /* Bit 4 : sequences? */
442
- (beginId >= 0 ? decToBin(beginId, 3) : '000') + /* Bits 5-7 : beginID | code3 */
443
- (repeatBefore ? '1' : '0') + /* Bit 8 : input RLE? */
444
- (repeatAfter ? '1' : '0') + /* Bit 9 : output RLE? */
445
- (beginId >= 0 ? '1' : '0') + /* Bit 10 : beginID? */
446
- decToBin(code, 5); /* Bits 11-15 : code1 */
464
+ const sixteenBits = /* 16-bit Data/Header character */
465
+
466
+ decToBin(code2, 4) + /* Bits 0-3 : code2 */
467
+ (sequences ? '1' : '0') + /* Bit 4 : sequences?|odd? */
468
+ decToBin(beginId >= 0 ? beginId : code3 < 0 ? 0 : code3, 3) + /* Bits 5-7 : beginID | code3 */
469
+ (repeatBefore ? '1' : '0') + /* Bit 8 : inp RLE? | num? */
470
+ (repeatAfter ? '1' : '0') + /* Bit 9 : output RLE? */
471
+ (beginId >= 0 ? '1' : '0') + /* Bit 10 : beginID? */
472
+ decToBin(code, 5); /* Bits 11-15 : code1 */
447
473
 
448
474
  return binToDec(sixteenBits);
449
475
  }
450
476
  }
477
+ /* Code 1 usage table */
478
+ /* ------------------ */
479
+ /* 00: No compression */
480
+ /* 01: CharCode.len=2 */
481
+ /* 02: ASCII in UTF16 */ /* Yeah ik thats not actually matches ASCII */
482
+ /* 03: Integers (Any) */
483
+ /* 04: Build Alphabet */
484
+ /* 05: Char Encodings */
485
+ /* 06: Integers (>15) */
486
+ /* 07: Frequency Map */
487
+ /* 08: URL to binary */
488
+ /* 09: Segmt compress */
489
+ /* 10: Repeating strs */
490
+ /* 11 - 30: Reserved */
491
+ /* 31: Rcrsv compress */
451
492
 
452
493
  const SEQUENCE_MARKER = '\uDBFF'; /* Private Use Area */
453
494
 
@@ -598,95 +639,395 @@ SOFTWARE.
598
639
  return result;
599
640
  }
600
641
 
642
+ const freqMap = {
643
+ ESCAPE_BYTE: 0xFF,
644
+ TOP_COUNT: 254,
645
+ SPLITTER: " \u200B",
646
+
647
+ compress(text, splitter = this.SPLITTER) {
648
+ const freq = {};
649
+ for (let char of text) {
650
+ freq[char] = (freq[char] || 0) + 1;
651
+ }
652
+
653
+ const topChars = Object.entries(freq)
654
+ .sort((a, b) => b[1] - a[1])
655
+ .slice(0, this.TOP_COUNT)
656
+ .map(entry => entry[0]);
657
+
658
+ const charToIndex = new Map(topChars.map((char, i) => [char, i]));
659
+
660
+ let header = String.fromCharCode(topChars.length) + topChars.join('');
661
+
662
+ let bytes = [];
663
+ for (let char of text) {
664
+ if (charToIndex.has(char)) {
665
+ /* frequent */
666
+ bytes.push(charToIndex.get(char));
667
+ } else {
668
+ /* rare */
669
+ bytes.push(this.ESCAPE_BYTE);
670
+ const code = char.charCodeAt(0);
671
+ bytes.push((code >> 8) & 0xFF);
672
+ bytes.push(code & 0xFF);
673
+ }
674
+ }
675
+
676
+ /* to UTF16 */
677
+ let compressedBody = "";
678
+ for (let i = 0; i < bytes.length; i += 2) {
679
+ const b1 = bytes[i];
680
+ const b2 = (i + 1 < bytes.length) ? bytes[i + 1] : 0x00;
681
+ compressedBody += String.fromCharCode((b1 << 8) | b2);
682
+ }
683
+
684
+ return header + splitter + compressedBody;
685
+ },
686
+
687
+ decompress(compressedText, splitter = this.SPLITTER) {
688
+ const parts = compressedText.split(splitter);
689
+
690
+ if (parts.length < 2) {
691
+ throw new Error('Invalid freqMap data: splitter not found');
692
+ }
693
+
694
+ const headerPart = parts[0];
695
+ const bodyPart = parts.slice(1).join(splitter);
696
+
697
+ const topCount = headerPart.charCodeAt(0);
698
+ const topChars = headerPart.substring(1, topCount + 1);
699
+
700
+ let bytes = [];
701
+ for (let i = 0; i < bodyPart.length; i++) {
702
+ const code = bodyPart.charCodeAt(i);
703
+ bytes.push((code >> 8) & 0xFF);
704
+ bytes.push(code & 0xFF);
705
+ }
706
+
707
+ let result = "";
708
+ for (let i = 0; i < bytes.length; i++) {
709
+ const b = bytes[i];
710
+ if (b === this.ESCAPE_BYTE) {
711
+ const charCode = (bytes[i + 1] << 8) | bytes[i + 2];
712
+ result += String.fromCharCode(charCode);
713
+ i += 2;
714
+ } else if (b < topCount) {
715
+ result += topChars[b];
716
+ }
717
+ }
718
+ return result;
719
+ },
720
+
721
+ /**
722
+ * 0 = Fail
723
+ * 1 = Success
724
+ * 2 = Remove last character (Success)
725
+ * @param {string} text
726
+ * @param {string?} splitter
727
+ * @returns {number|[number, number, string, string]}
728
+ */
729
+ test(text, splitter = this.SPLITTER) {
730
+ try {
731
+ if (text.includes(splitter)) return 0;
732
+ const packed = this.compress(text, splitter);
733
+ const unpacked = this.decompress(packed, splitter);
734
+ if (packed.length < text.length) {
735
+ if (unpacked == text) return [1, packed.length, splitter, packed];
736
+ else if (unpacked.slice(0,-1) == text) return [2, packed.length, splitter, packed];
737
+ else return 0;
738
+ }
739
+ return 0;
740
+ } catch (_) {
741
+ return 0;
742
+ }
743
+ }
744
+ };
745
+
746
+ const freqMapSplitters = [
747
+ " \u200B","\u0000",
748
+ "\u001F", "\u0001",
749
+ "\uFFFD", "\u2022",
750
+ "|§|", "\uFEFF"
751
+ ];
752
+
753
+ function segments(str) {
754
+ if (typeof str !== 'string' || str.length === 0) return [];
755
+
756
+ const THRESHOLD = 128;
757
+ const segs = [];
758
+ let currentSeg = str[0];
759
+
760
+ for (let i = 1; i < str.length; i++) {
761
+ const prevCode = str.charCodeAt(i - 1);
762
+ const currCode = str.charCodeAt(i);
763
+
764
+ if (Math.abs(currCode - prevCode) > THRESHOLD) {
765
+ segs.push(currentSeg);
766
+ currentSeg = str[i];
767
+ } else {
768
+ currentSeg += str[i];
769
+ }
770
+ }
771
+
772
+ if (currentSeg) segs.push(currentSeg);
773
+
774
+ return segs;
775
+ }
776
+
777
+ async function tryRecursive(base, opts) {
778
+ if (!opts.recursivecompression) return base;
779
+
780
+ let cur = base;
781
+ let depth = 0;
782
+
783
+ while (depth < 15) {
784
+ depth++;
785
+ const next = await compress(cur, {
786
+ ...opts,
787
+ recursivecompression: false
788
+ });
789
+
790
+ if (next.length >= cur.length) break;
791
+
792
+ const dec = await decompress(next, true);
793
+ if (dec !== cur) break;
794
+
795
+ cur = next;
796
+ }
797
+
798
+ if (depth === 0) return null;
799
+
800
+ return (
801
+ charCode(
802
+ cryptCharCode(
803
+ 31,
804
+ false,
805
+ false,
806
+ false,
807
+ -1,
808
+ depth,
809
+ false,
810
+ -1
811
+ )
812
+ ) + cur
813
+ );
814
+ }
815
+
601
816
  /**
602
817
  * **JavaScript String Compressor - compress function.**
603
- * @param {string} str string
604
- * @returns {string} Compressed string
605
- * @example compress('Hello, World!');
818
+ * @param {string|object|number} input string
819
+ * @param {{segmentation?: boolean, recursiveCompression?: boolean, JUSTC?: boolean}} [options]
820
+ * @returns {Promise<string>} Compressed string
821
+ * @example await compress('Hello, World!');
606
822
  * @since 1.0.0
607
823
  */
608
- function compress(str) {
609
- if (typeof str != 'string') throw new Error('Invalid input.');
824
+ async function compress(input, options) {
825
+ if (typeof input != 'string' && typeof input != 'object' && typeof input != 'number') throw new Error('Invalid input.');
826
+ const opts = {
827
+ segmentation: true,
828
+ recursivecompression: true,
829
+ justc: JUSTC ? true : false,
830
+ };
831
+
832
+ /* Read options */
833
+ if (options) {
834
+ if (typeof options != 'object') throw new Error('Invalid options input.');
835
+ for (const [key, value] of Object.entries(options)) {
836
+ if (typeof value != 'boolean') throw new Error('Invalid options input.');
837
+ if (key.toLowerCase() in opts) {
838
+ opts[key.toLowerCase()] = value;
839
+ continue;
840
+ }
841
+ console.warn(`Unknown option: "${key}".`);
842
+ }
843
+ }
844
+
845
+ const originalInput = input;
846
+ let str = input;
847
+ let isNum = false;
848
+
849
+ if (typeof str === 'number') {
850
+ isNum = true;
851
+ str = str.toString();
852
+ if (str.includes('.')) throw new Error('Invalid input.');
853
+ }
854
+
610
855
  let repeatBefore = false;
611
856
  function repeatChars(txt) {
612
857
  return txt.replace(/(.)\1+/g, ( a , b ) => b + a.length);
613
858
  }
859
+
614
860
  let beginId = -1;
615
- for (const begin of _JSSC._begin) {
861
+ if (typeof str == 'string') for (const begin of _JSSC._begin) {
616
862
  if (str.startsWith(begin)) {
617
863
  beginId = _JSSC._begin.indexOf(begin);
618
864
  str = str.slice(begin.length);
619
865
  break;
620
866
  }
867
+ };
868
+
869
+ let code3 = -1;
870
+ async function toJUSTC(obj) {
871
+ try {
872
+ const result = await JUSTC.stringify(obj);
873
+ if (result && typeof result.then === 'function') {
874
+ return await result;
875
+ }
876
+ return result;
877
+ } catch (_) {
878
+ /* Browsers */
879
+ await JUSTC.initialize();
880
+ return JUSTC.stringify(obj);
881
+ }
621
882
  }
622
- const d = (txt) => !/\d/.test(txt);
623
- if (d(str)) {
883
+ if (beginId == -1) {
884
+ /* JSON Array (as object) */
885
+ if (typeof str == 'object' && Array.isArray(str)) {
886
+ str = JSON.stringify(str).slice(1,-1);
887
+ code3 = 4;
888
+ } else
889
+ /* JSON Object (as object) */
890
+ if (typeof str == 'object') try {
891
+ if (opts.justc) {
892
+ const JUSTCobj = await toJUSTC(str);
893
+ str = JUSTCobj;
894
+ code3 = 2;
895
+ } else {
896
+ str = JSON.stringify(str);
897
+ code3 = 6;
898
+ }
899
+ } catch (error) {
900
+ const msg = new Error('Invalid input.');
901
+ throw new AggregateError([msg, error], msg.message);
902
+ } else
903
+ /* JSON Object (as string) */
904
+ try {
905
+ const obj = JSON.parse(str);
906
+ if (!Array.isArray(obj)) {
907
+
908
+ const JUSTCobj = opts.justc ? await toJUSTC(obj) : false;
909
+
910
+ if (JUSTCobj && JUSTCobj.length < str.length && str == JSON.stringify(obj)) {
911
+ str = JUSTCobj;
912
+ code3 = 1;
913
+ } else {
914
+ str = str.slice(1,-1);
915
+ code3 = 5;
916
+ }
917
+ } else {
918
+ /* JSON Array (as string) */
919
+ str = str.slice(1,-1);
920
+ code3 = 3;
921
+ }} catch (_) {
922
+ }}
923
+
924
+ if (!/\d/.test(str)) {
624
925
  str = repeatChars(str);
625
926
  repeatBefore = true;
626
927
  }
627
- const strdata = stringCodes(str);
628
- const ascii = strdata.maxCharCode < 256;
629
- let repeatAfter = false;
630
- let sequences = false;
631
928
 
632
- function processCompression(output) {
929
+ function processOutput(output, disableSeq = false) {
930
+ let repeatAfter = false;
931
+ let sequences = false;
932
+
633
933
  const hasDigits = /\d/.test(output);
634
934
  if (!hasDigits) {
635
935
  repeatAfter = true;
636
936
  output = repeatChars(output);
637
937
  }
638
938
 
639
- if (true) {
939
+ if (!disableSeq) {
640
940
  const compressed = compressSequences(output);
641
941
  if (compressed.sequences) {
642
942
  sequences = true;
643
- return compressed.compressed;
943
+ return [compressed.compressed, repeatAfter, sequences];
644
944
  }
645
945
  }
646
946
 
647
- return output;
947
+ return [output, repeatAfter, sequences];
648
948
  }
649
949
 
650
- const UniqueCodes = [...new Set(strdata.output)];
651
- if (/^\d+$/.test(str)) { /* Numbers */
652
- /* Up to 8:1 compression ratio */
653
- const convertNums = {
654
- 'A': 10,
655
- 'B': 11,
656
- 'C': 12,
657
- 'D': 13,
658
- 'E': 14
659
- };
660
- const inputt = str
661
- .replaceAll('10', 'A')
662
- .replaceAll('11', 'B')
663
- .replaceAll('12', 'C')
664
- .replaceAll('13', 'D')
665
- .replaceAll('14', 'E');
666
- const binOut = [];
667
- for (const character of inputt.split('')) {
668
- if (/\d/.test(character)) {
669
- binOut.push(decToBin(parseInt(character), 4));
670
- } else {
671
- binOut.push(decToBin(convertNums[character], 4));
672
- }
673
- };
674
- let output = '';
675
- function binPadStart(bin) {
676
- if (bin.length < 16) {
677
- const numm = 4 - stringChunks(bin, 4).length;
678
- return decToBin(15, 4).repeat(numm)+bin;
679
- } else return bin;
950
+ const safeTry = async (fn) => {
951
+ try {
952
+ return await fn();
953
+ } catch {
954
+ return null;
680
955
  }
681
- for (const character of chunkArray(binOut, 4)) {
682
- output += String.fromCharCode(binToDec(binPadStart(character.join(''))));
956
+ };
957
+
958
+ const validate = async (compressed) => {
959
+ try {
960
+ const dec = await decompress(compressed, true);
961
+ return dec === String(originalInput);
962
+ } catch {
963
+ return false;
683
964
  }
684
- output = processCompression(output);
685
- return charCode(cryptCharCode(3, false, false, repeatAfter, -1, 0, sequences)) + output;
686
-
687
- } else if (strdata.max === 2 && strdata.min === 2) {
965
+ };
966
+
967
+ const candidates = [];
968
+
969
+ if (/^\d+$/.test(str)) {
970
+ /* Integers ( < 15 ) */
971
+ candidates.push(async () => {
972
+ const out = await (async () => {
973
+ const num = parseInt(str);
974
+ if (num < 15) {
975
+ return charCode(
976
+ cryptCharCode(isNum ? 6 : 0, false, false, false, -1, num + 1, false, code3)
977
+ );
978
+ }
979
+ return null;
980
+ })();
981
+ if (!out) return null;
982
+ if (!(await validate(out))) return null;
983
+ return out;
984
+ });
985
+ /* Integers (any) */
986
+ candidates.push(async () => {
987
+ const convertNums = {
988
+ 'A': 10,
989
+ 'B': 11,
990
+ 'C': 12,
991
+ 'D': 13,
992
+ 'E': 14
993
+ };
994
+ const inputt = str
995
+ .replaceAll('10', 'A')
996
+ .replaceAll('11', 'B')
997
+ .replaceAll('12', 'C')
998
+ .replaceAll('13', 'D')
999
+ .replaceAll('14', 'E');
1000
+ const binOut = [];
1001
+ for (const character of inputt.split('')) {
1002
+ if (/\d/.test(character)) {
1003
+ binOut.push(decToBin(parseInt(character), 4));
1004
+ } else {
1005
+ binOut.push(decToBin(convertNums[character], 4));
1006
+ }
1007
+ };
1008
+ let [output, RLE, sequences] = ['', false, false];
1009
+ function binPadStart(bin) {
1010
+ if (bin.length < 16) {
1011
+ const numm = 4 - stringChunks(bin, 4).length;
1012
+ return decToBin(15, 4).repeat(numm)+bin;
1013
+ } else return bin;
1014
+ }
1015
+ for (const character of chunkArray(binOut, 4)) {
1016
+ output += String.fromCharCode(binToDec(binPadStart(character.join(''))));
1017
+ }
1018
+ [output, RLE, sequences] = processOutput(output);
1019
+ output = charCode(cryptCharCode(3, false, isNum, RLE, -1, 0, sequences, code3)) + output;
1020
+ if (!(await validate(output))) return null;
1021
+ return output;
1022
+ });
1023
+ }
1024
+
1025
+ candidates.push(async () => {
1026
+ const strdata = stringCodes(str);
1027
+ if (!(strdata.max === 2 && strdata.min === 2)) return null;
1028
+
688
1029
  let chars = strdata.output;
689
- let output = '';
1030
+ let [output, repeatAfter, seq] = ['', false, false];
690
1031
  function addChar(codee) {
691
1032
  output += String.fromCharCode(codee);
692
1033
  }
@@ -717,42 +1058,32 @@ SOFTWARE.
717
1058
  }
718
1059
  }
719
1060
  }
720
- output = processCompression(output);
721
- return charCode(cryptCharCode(1, false, repeatBefore, repeatAfter, beginId, 0, sequences)) + output;
722
-
723
- } else if (ascii) {
724
- /* Up to 2:1 compression ratio */
725
- /*
726
- Bytes
727
-
728
- ---
1061
+ [output, repeatAfter, seq] = processOutput(out);
1062
+ const res = charCode(cryptCharCode(1, false, repeatBefore, repeatAfter, beginId, 0, seq, code3)) + output;
1063
+ if (!(await validate(res))) return null;
1064
+ return res;
1065
+ });
729
1066
 
730
- UTF16 : xxxx xxxx xxxx xxxx
731
- ASCII : xxxx xxxx
1067
+ /* ASCII in UTF-16 */
1068
+ candidates.push(async () => {
1069
+ const strdata = stringCodes(str);
1070
+ if (strdata.maxCharCode >= 256) return null;
732
1071
 
733
- ---
1072
+ let [out, repeatAfter, seq] = ['', false, false];
1073
+ for (const pair of stringChunks(str, 2)) {
1074
+ let bin = '';
1075
+ for (const c of pair) bin += decToBin(c.charCodeAt(0), 8);
1076
+ out += String.fromCharCode(binToDec(bin));
1077
+ }
734
1078
 
735
- input: UTF16 => ASCII
1079
+ [out, repeatAfter, seq] = processOutput(out);
1080
+ const res = charCode(cryptCharCode(2, false, repeatBefore, repeatAfter, beginId, 0, seq, code3)) + out;
1081
+ if (!(await validate(res))) return null;
1082
+ return res;
1083
+ });
736
1084
 
737
- output: ASCII => UTF16
738
- 1 UTF16 char = 2 ASCII chars
739
- */
740
- const twoChars = [];
741
- let output = '';
742
- for (const chars_ of stringChunks(str, 2)) {
743
- let charsCode = '';
744
- for (const char of chars_.split('')) {
745
- charsCode += decToBin(char.charCodeAt(0), 8);
746
- }
747
- twoChars.push(charsCode);
748
- }
749
- for (const char of twoChars) {
750
- output += String.fromCharCode(binToDec(char));
751
- }
752
- output = processCompression(output);
753
- return charCode(cryptCharCode(2, false, repeatBefore, repeatAfter, beginId, 0, sequences)) + output;
754
-
755
- } else {
1085
+ /* Character encodings */
1086
+ candidates.push(async () => {
756
1087
  const characterEncodings = new _JSSC.use();
757
1088
  const stringArray = str.split('');
758
1089
  let useCharacterEncoding;
@@ -789,61 +1120,239 @@ SOFTWARE.
789
1120
  for (const binCharCodes of chunkArray(binaryCharCodes, 2)) {
790
1121
  convertCharCodes.push(binCharCodes.join('').padStart(16, '0'));
791
1122
  }
792
- let outputStr = '';
1123
+ let [outputStr, repeatAfter, seq] = ['', false, false];
793
1124
  for (const characterCode of convertCharCodes) {
794
1125
  outputStr += String.fromCharCode(binToDec(characterCode))
795
1126
  }
796
- /* Up to 2:1 compression ratio */
797
- outputStr = processCompression(outputStr);
798
- return charCode(cryptCharCode(charEncodingID + 5, false, repeatBefore, repeatAfter, beginId, 0, sequences)) + outputStr;
799
-
800
- } else if (UniqueCodes.length < 16) {
801
- /* Up to 4:1 compression ratio */
802
- const chars = UniqueCodes;
803
- let output = '';
804
- for (const char of chars) {
805
- output += String.fromCharCode(char);
1127
+
1128
+ [outputStr, repeatAfter, seq] = processOutput(outputStr);
1129
+ outputStr = charCode(cryptCharCode(5, false, repeatBefore, repeatAfter, beginId, charEncodingID, seq, code3)) + outputStr;
1130
+ if (await validate(outputStr)) return outputStr;
1131
+ }
1132
+ return null;
1133
+ });
1134
+
1135
+ /* Build alphabet */
1136
+ candidates.push(async () => {
1137
+ const uniq = [...new Set(str.split('').map(c => c.charCodeAt(0)))];
1138
+ if (uniq.length >= 16) return null;
1139
+
1140
+ let out = uniq.map(c => String.fromCharCode(c)).join('');
1141
+ let buf = [];
1142
+ let [repeatAfter, seq] = [false, false];
1143
+
1144
+ for (const c of str) {
1145
+ buf.push(uniq.indexOf(c.charCodeAt(0)));
1146
+ if (buf.length === 4) {
1147
+ out += String.fromCharCode(binToDec(buf.map(n => decToBin(n, 4)).join('')));
1148
+ buf = [];
806
1149
  }
807
-
808
- let char_ = [];
809
- for (const charCode of strdata.output) {
810
- const index = UniqueCodes.indexOf(charCode);
811
-
812
- if (char_.length === 4) {
813
- let outchar = '';
814
- for (const index of char_) {
815
- outchar += decToBin(index, 4);
816
- }
817
- output += String.fromCharCode(binToDec(outchar));
818
- char_ = [];
1150
+ }
1151
+
1152
+ if (buf.length) {
1153
+ out += String.fromCharCode(
1154
+ binToDec(buf.map(n => decToBin(n, 4)).join('').padStart(16, '1'))
1155
+ );
1156
+ }
1157
+
1158
+ [out, repeatAfter, seq] = processOutput(out);
1159
+ const res = charCode(cryptCharCode(4, false, repeatBefore, repeatAfter, beginId, uniq.length, seq, code3)) + out;
1160
+ if (!(await validate(res))) return null;
1161
+ return res;
1162
+ });
1163
+
1164
+ /* Frequency map */
1165
+ candidates.push(async () => {
1166
+ for (const splitter of freqMapSplitters) {
1167
+ const test = freqMap.test(str, splitter);
1168
+ if (!Array.isArray(test)) continue;
1169
+
1170
+ const [, , sp, packed] = test;
1171
+ const code2 = binToDec((test[0] - 1).toString() + decToBin(freqMapSplitters.indexOf(sp), 3));
1172
+ const res = charCode(cryptCharCode(7, false, false, false, -1, code2)) + packed;
1173
+
1174
+ if (await validate(res)) return res;
1175
+ }
1176
+ return null;
1177
+ });
1178
+
1179
+ /* URL */
1180
+ candidates.push(async () => {
1181
+ if (typeof str !== 'string') return null;
1182
+
1183
+ let url;
1184
+ try {
1185
+ url = new URL(_JSSC._begin[beginId] + str);
1186
+ } catch {
1187
+ return null;
1188
+ }
1189
+
1190
+ const originalHref = url.href;
1191
+
1192
+ let hasPercent = /%[0-9A-Fa-f]{2}/.test(originalHref);
1193
+ let hasPunycode = url.hostname.includes('xn--');
1194
+ let hasQuery = !!url.search;
1195
+ let hasFragment = !!url.hash;
1196
+
1197
+ /* normalize */
1198
+ let normalized = originalHref.slice(_JSSC._begin[beginId].length);
1199
+
1200
+ /* punycode to unicode */
1201
+ if (hasPunycode && typeof punycode !== 'undefined') {
1202
+ url.hostname = punycode.toUnicode(url.hostname);
1203
+ normalized = url.href.slice(_JSSC._begin[beginId].length);
1204
+ }
1205
+
1206
+ /* percent to bytes */
1207
+ let bytes = [];
1208
+ for (let i = 0; i < normalized.length; i++) {
1209
+ const ch = normalized[i];
1210
+ if (ch === '%' && i + 2 < normalized.length) {
1211
+ const hex = normalized.slice(i + 1, i + 3);
1212
+ if (/^[0-9A-Fa-f]{2}$/.test(hex)) {
1213
+ bytes.push(parseInt(hex, 16));
1214
+ i += 2;
1215
+ continue;
819
1216
  }
820
- char_.push(index);
821
1217
  }
822
-
823
- if (char_.length > 0) {
824
- let outchar = '';
825
- for (const index of char_) {
826
- outchar += decToBin(index, 4);
827
- }
828
- output += String.fromCharCode(binToDec(outchar.padStart(16, '1')));
1218
+ bytes.push(normalized.charCodeAt(i));
1219
+ }
1220
+
1221
+ let odd = bytes.length & 1;
1222
+ if (odd) bytes.push(0);
1223
+
1224
+ /* bytes to UTF16 */
1225
+ let out = '';
1226
+ for (let i = 0; i < bytes.length; i += 2) {
1227
+ out += String.fromCharCode(
1228
+ (bytes[i] << 8) | (bytes[i + 1] ?? 0)
1229
+ );
1230
+ }
1231
+
1232
+ let code2 =
1233
+ (hasPercent ? 1 : 0) |
1234
+ (hasPunycode ? 2 : 0) |
1235
+ (hasQuery ? 4 : 0) |
1236
+ (hasFragment ? 8 : 0);
1237
+
1238
+ let repeatAfter = false;
1239
+ [out, repeatAfter,] = processOutput(out, true);
1240
+
1241
+ const res =
1242
+ charCode(
1243
+ cryptCharCode(
1244
+ 8,
1245
+ false,
1246
+ repeatBefore,
1247
+ repeatAfter,
1248
+ beginId,
1249
+ code2,
1250
+ odd,
1251
+ code3
1252
+ )
1253
+ ) + out;
1254
+
1255
+ if (!(await validate(res))) return null;
1256
+ return res;
1257
+ });
1258
+
1259
+ /* Segmentation */
1260
+ if (opts.segmentation) candidates.push(async () => {
1261
+ const segs = segments(str);
1262
+
1263
+ if (segs.length < 2) return null;
1264
+
1265
+ let out = segs.length - 2 < 15 ? '' : String.fromCharCode(segs.length - 2);
1266
+
1267
+ for (const seg of segs) {
1268
+ const segOpts = {
1269
+ ...opts,
1270
+ segmentation: false
829
1271
  }
830
-
831
- output = processCompression(output);
832
- return charCode(cryptCharCode(4, false, repeatBefore, repeatAfter, beginId, UniqueCodes.length, sequences)) + output;
1272
+ const compressed = await compress(seg, segOpts);
1273
+
1274
+ out += String.fromCharCode(seg.length);
1275
+ out += compressed;
833
1276
  }
834
1277
 
835
- /* 1:1 compression ratio (no compression) */
836
- let output = processCompression(str);
837
- return charCode(cryptCharCode(0, false, repeatBefore, repeatAfter, beginId, 0, sequences)) + output;
838
- }
1278
+ const res =
1279
+ charCode(
1280
+ cryptCharCode(
1281
+ 9,
1282
+ false,
1283
+ repeatBefore,
1284
+ opts.justc,
1285
+ beginId,
1286
+ Math.min(segs.length - 2, 15),
1287
+ opts.recursivecompression,
1288
+ code3
1289
+ )
1290
+ ) + out;
1291
+
1292
+ if (!(await validate(res))) return null;
1293
+ return res;
1294
+ });
1295
+
1296
+ /* Repeating string */
1297
+ const rcheck = str.match(/^(.{1,7}?)(?:\1)+$/);
1298
+ if (rcheck) candidates.push(async () => {
1299
+ const main = rcheck[1];
1300
+ const count = str.length / main.length;
1301
+ if (Math.floor(count) != count || count < 1 || count > 65535 + 15) return null;
1302
+ let [out, repeatAfter, seq] = ['', false, false];
1303
+ [out, repeatAfter, seq] = processOutput(main);
1304
+
1305
+ const res =
1306
+ charCode(
1307
+ cryptCharCode(
1308
+ 10,
1309
+ false,
1310
+ repeatBefore,
1311
+ repeatAfter,
1312
+ beginId,
1313
+ Math.min(count - 1, 15),
1314
+ seq,
1315
+ code3
1316
+ )
1317
+ ) + (
1318
+ (count - 1) > 14 ? String.fromCharCode(count - 15) : ''
1319
+ ) + out;
1320
+
1321
+ if (!(await validate(res))) return null;
1322
+ return res;
1323
+ });
1324
+
1325
+ /* run all */
1326
+ const results = (await Promise.all(candidates.map(fn => safeTry(fn))))
1327
+ .filter(r => typeof r === 'string' && r.length <= String(str).length);
1328
+
1329
+ let best;
1330
+ if (!results.length) {
1331
+ let [repeatAfter, sequences] = [false, false];
1332
+ const savedStr = str;
1333
+ [str, repeatAfter, sequences] = processOutput(str);
1334
+ if (await validate(str)) best = charCode(cryptCharCode(0, false, repeatBefore, repeatAfter, beginId, 0, sequences, code3)) + str;
1335
+ else best = charCode(cryptCharCode(0, false, repeatBefore, false, beginId, 0, false, code3)) + savedStr;
1336
+ } else best = results.reduce((a, b) => (b.length < a.length ? b : a));
1337
+
1338
+ if (opts.recursivecompression) try {
1339
+ for (const r of results) {
1340
+ const rc = await tryRecursive(r, opts);
1341
+ if (rc && rc.length <= best.length && await validate(rc)) {
1342
+ best = rc;
1343
+ }
1344
+ }
1345
+ } catch (_){};
1346
+
1347
+ return best;
839
1348
  }
840
1349
 
841
- function characterEncodings(strcode, realstr) {
1350
+ function characterEncodings(id, realstr) {
842
1351
  const strcode2charencoding = {};
843
1352
  for (const [name, code] of Object.entries(_JSSC._IDs)) {
844
1353
  strcode2charencoding[code] = name
845
1354
  }
846
- const possibleCharEncoding = strcode2charencoding[strcode - 5];
1355
+ const possibleCharEncoding = strcode2charencoding[id];
847
1356
  if (possibleCharEncoding) {
848
1357
  const characterEncodings_ = new _JSSC.use();
849
1358
  const characterEncoding = characterEncodings_['JSSC'+possibleCharEncoding]();
@@ -866,13 +1375,43 @@ SOFTWARE.
866
1375
  }
867
1376
  }
868
1377
 
1378
+ async function parseJUSTC(str) {
1379
+ try {
1380
+ const result = JUSTC.parse(str);
1381
+
1382
+ if (result && typeof result.then === 'function') {
1383
+ return await result;
1384
+ }
1385
+
1386
+ return result;
1387
+ } catch (err) {
1388
+ if (typeof window !== 'undefined') { /* Browsers */
1389
+ try {
1390
+ await JUSTC.initialize();
1391
+
1392
+ const retry = JUSTC.parse(str);
1393
+ if (retry && typeof retry.then === 'function') {
1394
+ return await retry;
1395
+ }
1396
+
1397
+ return retry;
1398
+ } catch {
1399
+ return null;
1400
+ }
1401
+ }
1402
+
1403
+ return null;
1404
+ }
1405
+ }
1406
+
869
1407
  /**
870
1408
  * **JavaScript String Compressor - decompress function.**
871
1409
  * @param {string} str Compressed string
872
- * @returns {string} Decompressed string
1410
+ * @param {boolean?} [stringify] Return only string in any way
1411
+ * @returns {Promise<string|object|number>} Decompressed string/object/integer
873
1412
  * @since 1.0.0
874
1413
  */
875
- function decompress(str) {
1414
+ async function decompress(str, stringify = false) {
876
1415
  if (typeof str != 'string') throw new Error('Invalid input.');
877
1416
  const strcodes = cryptCharCode(str.charCodeAt(0) - 32, true);
878
1417
  const strcode = strcodes.code;
@@ -883,31 +1422,51 @@ SOFTWARE.
883
1422
 
884
1423
  /* sequences */
885
1424
  let realstr = str.slice(1);
886
- if (strcodes.sequences) {
1425
+ if (strcodes.sequences && strcode != 8 && strcode != 9) {
887
1426
  realstr = decompressSequences(realstr);
888
1427
  }
889
1428
 
890
1429
  /* RLE */
891
- if (strcodes.repeatAfter) {
1430
+ if (strcodes.repeatAfter && strcode != 9) {
892
1431
  realstr = repeatChars(realstr);
893
1432
  }
894
1433
 
895
- function begin(out) {
1434
+ async function begin(out) {
896
1435
  if (strcodes.beginId >= 0) {
897
1436
  return _JSSC._begin[strcodes.beginId] + out;
1437
+ } else if (strcodes.code3 == 1 || strcodes.code3 == 2) {
1438
+ /* JSON Object */
1439
+ const result = await parseJUSTC(out);
1440
+ if (result && typeof result.then === 'function') {
1441
+ return JSON.stringify(await result);
1442
+ } else return JSON.stringify(result);
898
1443
  } else return out;
899
1444
  }
900
1445
 
901
- function processOutput(out) {
902
- if (strcodes.repeatBefore) {
903
- return repeatChars(begin(out));
904
- } else return begin(out);
1446
+ async function processOutput(out) {
1447
+ let output = out;
1448
+ if (strcodes.repeatBefore && strcode != 3) {
1449
+ output = repeatChars(await begin(out));
1450
+ } else output = await begin(out);
1451
+
1452
+ if ((strcodes.repeatBefore && strcode == 3) || strcode == 30) output = parseInt(output); else { /* Integer */
1453
+ if (strcodes.code3 == 3 || strcodes.code3 == 4) output = '[' + output + ']'; /* JSON Array */
1454
+ else if (strcodes.code3 == 5) output = '{' + output + '}'; /* JSON Object (as string) */
1455
+ if (strcodes.code3 == 2 || strcodes.code3 == 4 || strcodes.code3 == 6) output = JSON.parse(output);} /* JSON Object/Array (as object) */
1456
+
1457
+ if (stringify) {
1458
+ if (typeof output == 'object') output = JSON.stringify(output);
1459
+ else if (typeof output == 'number') output = output.toString();
1460
+ }
1461
+
1462
+ return output;
905
1463
  }
906
1464
 
907
1465
  let output = '';
908
1466
  switch (strcode) {
909
- case 0:
910
- return processOutput(realstr);
1467
+ case 0: case 6:
1468
+ if (strcodes.code2 > 0) return await processOutput(String(strcodes.code2 - 1));
1469
+ return await processOutput(realstr);
911
1470
  case 1:
912
1471
  function addChar(cde) {
913
1472
  output += String.fromCharCode(cde);
@@ -923,7 +1482,7 @@ SOFTWARE.
923
1482
  addChar(char.charCodeAt(0));
924
1483
  }
925
1484
  }
926
- return processOutput(output);
1485
+ return await processOutput(output);
927
1486
  case 2:
928
1487
  function toChar(binCode) {
929
1488
  return String.fromCharCode(binToDec(binCode));
@@ -939,7 +1498,7 @@ SOFTWARE.
939
1498
  output += toChar(binCode8);
940
1499
  }
941
1500
  }
942
- return processOutput(output);
1501
+ return await processOutput(output);
943
1502
  case 3:
944
1503
  for (const char of realstr.split('')) {
945
1504
  const binCodes = stringChunks(decToBin(char.charCodeAt(0), 16), 4);
@@ -950,7 +1509,7 @@ SOFTWARE.
950
1509
  }
951
1510
  }
952
1511
  }
953
- return processOutput(output);
1512
+ return await processOutput(output);
954
1513
  case 4:
955
1514
  const chars = [];
956
1515
  for (const char of realstr.slice(0, strcodes.code2).split('')) {
@@ -965,12 +1524,87 @@ SOFTWARE.
965
1524
  }
966
1525
  }
967
1526
  }
968
- return processOutput(output);
969
- default:
970
- const decoded = characterEncodings(strcode, realstr);
1527
+ return await processOutput(output);
1528
+ case 5:
1529
+ const decoded = characterEncodings(strcodes.code2, realstr);
971
1530
  if (decoded) {
972
- return processOutput(decoded);
1531
+ return await processOutput(decoded);
1532
+ } else throw new Error('Invalid compressed string');
1533
+ case 7:
1534
+ const splitter = freqMapSplitters[binToDec(decToBin(strcodes.code2).slice(1))];
1535
+ output = freqMap.decompress(realstr, splitter);
1536
+ if (parseInt(decToBin(strcodes.code2).slice(0,1)) == 1) output = output.slice(0,-1);
1537
+ return await processOutput(output);
1538
+ case 8: {
1539
+ let bytes = [];
1540
+ for (const ch of realstr) {
1541
+ const c = ch.charCodeAt(0);
1542
+ bytes.push((c >> 8) & 0xFF, c & 0xFF);
1543
+ }
1544
+ if (strcodes.sequences) bytes.pop();
1545
+
1546
+ let out = '';
1547
+ for (const b of bytes) {
1548
+ out += String.fromCharCode(b);
1549
+ }
1550
+
1551
+ /* percent restore if needed */
1552
+ if (strcodes.code2 & 1) {
1553
+ out = out.replace(
1554
+ /[\x00-\x20\x7F-\xFF]/g,
1555
+ c => '%' + c.charCodeAt(0).toString(16).padStart(2, '0').toUpperCase()
1556
+ );
1557
+ }
1558
+
1559
+ /* punycode restore */
1560
+ if (strcodes.code2 & 2 && typeof punycode !== 'undefined') {
1561
+ const u = new URL(out);
1562
+ u.hostname = punycode.toASCII(u.hostname);
1563
+ out = u.href;
1564
+ }
1565
+
1566
+ return await processOutput(out);}
1567
+ case 9: {
1568
+ let idx = 0;
1569
+ const segCount = strcodes.code2 < 15 ? strcodes.code2 + 2 : realstr.charCodeAt(idx++) + 2;
1570
+ let out = '';
1571
+
1572
+ for (let i = 0; i < segCount; i++) {
1573
+ const len = realstr.charCodeAt(idx++);
1574
+ const segmentCompressed = realstr.slice(idx);
1575
+
1576
+ const seg = (await decompress(
1577
+ segmentCompressed, true
1578
+ )).slice(0, len);
1579
+
1580
+ out += seg;
1581
+ idx += (await compress(seg, {segmentation: false, justc: strcodes.repeatAfter, recursivecompression: strcodes.sequences})).length;
973
1582
  }
1583
+
1584
+ return await processOutput(out);}
1585
+ case 10:
1586
+ const sliceChar = strcodes.code2 == 15;
1587
+ const repeatCount = sliceChar ? realstr.charCodeAt(0) + 15 : strcodes.code2;
1588
+ if (sliceChar) realstr = realstr.slice(1);
1589
+ return await processOutput(realstr.repeat(repeatCount));
1590
+ case 31: {
1591
+ let out = realstr;
1592
+ const depth = strcodes.code2;
1593
+
1594
+ for (let i = 0; i < depth; i++) {
1595
+ const first = out.charCodeAt(0) - 32;
1596
+ const meta = cryptCharCode(first, true);
1597
+
1598
+ if (meta.code === 31) {
1599
+ throw new Error('Attempt to nested recursive compression');
1600
+ }
1601
+
1602
+ out = await decompress(out, true);
1603
+ }
1604
+
1605
+ return out;
1606
+ }
1607
+ default:
974
1608
  throw new Error('Invalid compressed string');
975
1609
  }
976
1610
  }