Compare commits
751 Commits
add-404-st
...
master
Author | SHA1 | Date |
---|---|---|
Émilien (perso) | 93559cbdd5 | 1 year ago |
Ikko Eltociear Ashimine | 495ccdc221 | 1 year ago |
Samantaz Fox | e6f5fcbc4b | 1 year ago |
Samantaz Fox | df6b51f9c6 | 1 year ago |
lamemakes | 7a569d81ca | 1 year ago |
Émilien (perso) | 875b8ea0c2 | 1 year ago |
Emilien Devos | 8e4833d21a | 1 year ago |
Samantaz Fox | c3a3f98014 | 1 year ago |
Samantaz Fox | b06c87ff8d | 1 year ago |
Samantaz Fox | 69f23d95b8 | 1 year ago |
Samantaz Fox | 3444117818 | 1 year ago |
Samantaz Fox | 39ff94362e | 1 year ago |
Samantaz Fox | 11ab6ffb32 | 1 year ago |
Samantaz Fox | 9dd4195dd0 | 1 year ago |
Samantaz Fox | fcbd5106c3 | 1 year ago |
Samantaz Fox | 836898754e | 1 year ago |
Samantaz Fox | d3b04ac68c | 1 year ago |
Samantaz Fox | b2b61ab0a9 | 1 year ago |
Samantaz Fox | 62bd895562 | 1 year ago |
Samantaz Fox | 8d2ab70cbc | 1 year ago |
Samantaz Fox | 3024424ea2 | 1 year ago |
Samantaz Fox | 5af87f97a3 | 1 year ago |
Samantaz Fox | 96238d719d | 1 year ago |
Daniele Tricoli | 52c317f235 | 1 year ago |
maboroshin | f954483eac | 1 year ago |
Translator | a4ca460651 | 1 year ago |
maboroshin | 37bab74085 | 1 year ago |
Nicolas Dommanget-Muller | 50d6a2afb9 | 1 year ago |
Translator | daccbc2abb | 1 year ago |
04f7rx0n6 | d250b4132b | 1 year ago |
joaooliva | 3690631cdd | 1 year ago |
xrfmkrh | 3b6474d72b | 1 year ago |
maboroshin | fd3e2aa868 | 1 year ago |
gallegonovato | 14a5751a47 | 1 year ago |
Andrey | fda8d2d4d3 | 1 year ago |
Samantaz Fox | 46ea22f75c | 1 year ago |
Samantaz Fox | 68c26563fc | 1 year ago |
TheFrenchGhosty | 9cec83c1ff | 1 year ago |
IceTheDev2 | 281c8ecbf5 | 1 year ago |
Samantaz Fox | 1b942f4f0a | 1 year ago |
Samantaz Fox | e7bed765fe | 1 year ago |
Samantaz Fox | 7556cb69f2 | 1 year ago |
Samantaz Fox | b5e30d66d4 | 1 year ago |
Samantaz Fox | d9521c82cf | 1 year ago |
Samantaz Fox | 505a1566d1 | 1 year ago |
Émilien Devos (perso) | 19192b8be1 | 1 year ago |
Samantaz Fox | 867d488931 | 1 year ago |
Chunky programmer | 45cc835694 | 1 year ago |
Chunky programmer | 233bd3f593 | 1 year ago |
Émilien Devos (perso) | 545a5937d8 | 1 year ago |
Émilien Devos (perso) | 35694cc7e3 | 1 year ago |
Emilien Devos | 372192eabc | 1 year ago |
Émilien Devos (perso) | bc06c2fc27 | 1 year ago |
Émilien Devos (perso) | 7ea6ec1f52 | 1 year ago |
Emilien Devos | 042ad1f266 | 1 year ago |
Samantaz Fox | bbf16279bb | 1 year ago |
Samantaz Fox | 16ac3be85b | 1 year ago |
Samantaz Fox | 21f0b90354 | 1 year ago |
Samantaz Fox | 928ea75dbc | 1 year ago |
Samantaz Fox | 4414c9df70 | 1 year ago |
Samantaz Fox | 898066407d | 1 year ago |
Samantaz Fox | 381a0e326d | 1 year ago |
Samantaz Fox | 193c510c65 | 1 year ago |
Samantaz Fox | f0c8477905 | 1 year ago |
Samantaz Fox | 4379a3d873 | 1 year ago |
Samantaz Fox | df85265453 | 1 year ago |
Samantaz Fox | de78848039 | 1 year ago |
Samantaz Fox | e10f6b6626 | 1 year ago |
Samantaz Fox | 634e913da9 | 1 year ago |
Samantaz Fox | 1b25737b01 | 1 year ago |
Samantaz Fox | 8dd1824869 | 1 year ago |
Samantaz Fox | c7876d564f | 1 year ago |
Samantaz Fox | 5d176ad6de | 1 year ago |
Samantaz Fox | 4b29f8254a | 1 year ago |
Fjuro | c9eafb250f | 1 year ago |
Milo Ivir | fe97b3d761 | 1 year ago |
maboroshin | ed2d16c91d | 1 year ago |
Jeff Huang | a727bb037f | 1 year ago |
Oğuz Ersen | f0f6cb0d83 | 1 year ago |
Eric | e8df08e41e | 1 year ago |
Ihor Hordiichuk | fd06656d86 | 1 year ago |
Jorge Maldonado Ventura | ea6db9c58a | 1 year ago |
Jorge Maldonado Ventura | 184bd3204f | 1 year ago |
atilluF | f0120bece1 | 1 year ago |
Rex_sa | 7e3c685cd6 | 1 year ago |
Matthaiks | 67a79faaeb | 1 year ago |
Ashirg-ch | 11d45adcdc | 1 year ago |
joaooliva | f2cc97b290 | 1 year ago |
Alex | e656714542 | 1 year ago |
maboroshin | a79b7ef170 | 1 year ago |
gallegonovato | ef4ff4e4b2 | 1 year ago |
Samantaz Fox | 9c0c39baed | 1 year ago |
Samantaz Fox | 6440ae0b5c | 2 years ago |
Samantaz Fox | e238c08be5 | 2 years ago |
Samantaz Fox | 8d434ac06a | 2 years ago |
Samantaz Fox | 1333e6db26 | 2 years ago |
ChunkyProgrammer | 8bd2e60abc | 2 years ago |
chunky programmer | c713c32ceb | 2 years ago |
chunky programmer | 12b4dd9191 | 2 years ago |
chunky programmer | b2a0e6f1ff | 2 years ago |
chunky programmer | d728599251 | 2 years ago |
ChunkyProgrammer | d6fb5c03b7 | 2 years ago |
Samantaz Fox | 3a54e9556b | 2 years ago |
Samantaz Fox | 6755e31b72 | 2 years ago |
Samantaz Fox | 10fee9da61 | 2 years ago |
Samantaz Fox | b420de6977 | 2 years ago |
Samantaz Fox | febd14f703 | 2 years ago |
Samantaz Fox | 92f6a4d546 | 2 years ago |
Samantaz Fox | 544fc9f92e | 2 years ago |
Samantaz Fox | c385a944e6 | 2 years ago |
Samantaz Fox | ce1fb8d08c | 2 years ago |
gallegonovato | 56ebb477ca | 2 years ago |
xrfmkrh | cca8bcf2a8 | 2 years ago |
Fjuro | f3d9db10a2 | 2 years ago |
Émilien Devos (perso) | 46a9ce811a | 2 years ago |
Émilien Devos (perso) | 36f7c99cfb | 2 years ago |
Samantaz Fox | 720789b622 | 2 years ago |
Samantaz Fox | ce2649420f | 2 years ago |
Samantaz Fox | 7aac401407 | 2 years ago |
ChunkyProgrammer | 2d5145614b | 2 years ago |
Samantaz Fox | 1eb1bae370 | 2 years ago |
Samantaz Fox | 5017176e39 | 2 years ago |
Samantaz Fox | efda154ec8 | 2 years ago |
Samantaz Fox | c1fb320094 | 2 years ago |
Samantaz Fox | 90914343ec | 2 years ago |
Samantaz Fox | 384a8e200c | 2 years ago |
ChunkyProgrammer | 28584f22c5 | 2 years ago |
chunky programmer | 1b10446e5e | 2 years ago |
chunky programmer | d420741cc1 | 2 years ago |
chunky programmer | f298e225a1 | 2 years ago |
Samantaz Fox | 97e3938f5f | 2 years ago |
Samantaz Fox | deed4d10f2 | 2 years ago |
Артём Котлубай | 70a79f343d | 2 years ago |
Артём Котлубай | e6471feadc | 2 years ago |
John Donne | 49e04192c0 | 2 years ago |
Nicolas Dommanget-Muller | 1f12323ee6 | 2 years ago |
John Donne | 732fb7c499 | 2 years ago |
AHOHNMYC | 14053821ac | 2 years ago |
SC | 346f32855a | 2 years ago |
Ernestas | 7d48b96173 | 2 years ago |
atilluF | b9932b113b | 2 years ago |
Andrey | 72f83d4aa2 | 2 years ago |
Jeff Huang | 919997e41c | 2 years ago |
Damjan Gerl | 6667bdcd92 | 2 years ago |
victor dargallo | cb0e837a5e | 2 years ago |
Fjuro | e6ba3e3dab | 2 years ago |
Milo Ivir | f81bc96da0 | 2 years ago |
abyan akhtar | 4c541489dd | 2 years ago |
Jorge Maldonado Ventura | c60c14851b | 2 years ago |
Rex_sa | d857ee5a7c | 2 years ago |
Oğuz Ersen | 657486c19a | 2 years ago |
Eric | 9d52ddbf8d | 2 years ago |
Ihor Hordiichuk | d8337252a8 | 2 years ago |
gallegonovato | 66e671237f | 2 years ago |
victor dargallo | d5a516d76c | 2 years ago |
maboroshin | 231fb3481e | 2 years ago |
Damjan Gerl | 7b4e3639cf | 2 years ago |
victor dargallo | 778edf63cb | 2 years ago |
SC | fe1648e72e | 2 years ago |
Fjuro | 1825b8edb3 | 2 years ago |
Milo Ivir | a3e587657f | 2 years ago |
Parsa | 4078fc5818 | 2 years ago |
Jeff Huang | a9fcfcf7c9 | 2 years ago |
Oğuz Ersen | 4aa2c406ff | 2 years ago |
Eric | f46cc98654 | 2 years ago |
Ihor Hordiichuk | ec1d6ee851 | 2 years ago |
Jorge Maldonado Ventura | 9eafbbdcbb | 2 years ago |
Jorge Maldonado Ventura | 5c24bf1322 | 2 years ago |
gallegonovato | b97b5b5859 | 2 years ago |
Rex_sa | d139334376 | 2 years ago |
Matthaiks | 155f5fef97 | 2 years ago |
Ashirg-ch | 17ecdbaf7d | 2 years ago |
AHOHNMYC | 67859113fd | 2 years ago |
Samantaz Fox | eefc8bbbdd | 2 years ago |
Samantaz Fox | ff5e42d836 | 2 years ago |
Samantaz Fox | 3cfbc19ccc | 2 years ago |
Samantaz Fox | 7afa03d821 | 2 years ago |
Samantaz Fox | 0107b774f2 | 2 years ago |
Samantaz Fox | 9dfa268204 | 2 years ago |
Samantaz Fox | e24feab1f7 | 2 years ago |
Samantaz Fox | d1b51e57a2 | 2 years ago |
chunky programmer | 5517a4eadb | 2 years ago |
Samantaz Fox | 961cae2b9a | 2 years ago |
Samantaz Fox | adc605024f | 2 years ago |
Samantaz Fox | 9a765418d1 | 2 years ago |
Samantaz Fox | b3c0afef02 | 2 years ago |
Émilien Devos (perso) | 525e4bd67a | 2 years ago |
Gavin | c421f1f205 | 2 years ago |
thtmnisamnstr | fffdaa1410 | 2 years ago |
raphj | 600da635b7 | 2 years ago |
ChunkyProgrammer | e3c1cb3ec9 | 2 years ago |
ChunkyProgrammer | dc929be198 | 2 years ago |
ChunkyProgrammer | 1da00bade3 | 2 years ago |
Samantaz Fox | 8db2a93827 | 2 years ago |
Samantaz Fox | c0eab2b1f6 | 2 years ago |
Samantaz Fox | 9e82e6fc1b | 2 years ago |
Samantaz Fox | ef6eea3a65 | 2 years ago |
Samantaz Fox | d526094380 | 2 years ago |
Samantaz Fox | 562d75a47b | 2 years ago |
Emilien Devos | e0600f4553 | 2 years ago |
Jarek Baran | 0fe1b1ec19 | 2 years ago |
techmetx11 | 73d2ed6f77 | 2 years ago |
Lennart Bernhardt | f83f0d2561 | 2 years ago |
Lennart Bernhardt | 1d187bcf17 | 2 years ago |
ChunkyProgrammer | a3da03bee9 | 2 years ago |
ChunkyProgrammer | f840addd93 | 2 years ago |
techmetx11 | 7755ed4ac8 | 2 years ago |
techmetx11 | 49ddf8b6bd | 2 years ago |
ChunkyProgrammer | 5767344746 | 2 years ago |
ChunkyProgrammer | 3492485789 | 2 years ago |
Samantaz Fox | 8a44bd11d2 | 2 years ago |
Samantaz Fox | c0410602e7 | 2 years ago |
Samantaz Fox | 4ae158ef6d | 2 years ago |
Samantaz Fox | 1f3317e257 | 2 years ago |
victor dargallo | 08cbd44b57 | 2 years ago |
Oğuz Ersen | 224fbcd2b1 | 2 years ago |
victor dargallo | c188dec4fa | 2 years ago |
SC | 3aa6a0c4f0 | 2 years ago |
Fjuro | ce1f61d185 | 2 years ago |
Milo Ivir | c1e45cb84a | 2 years ago |
HamidReza Shareghzade | defec2e8fb | 2 years ago |
maboroshin | ded28b80d3 | 2 years ago |
Jeff Huang | dd6c9dbc65 | 2 years ago |
Eric | 46a7be89a7 | 2 years ago |
Ihor Hordiichuk | 72656e802e | 2 years ago |
gallegonovato | 60e3f8aec0 | 2 years ago |
Rex_sa | aad166c96a | 2 years ago |
Matthaiks | a0bdcc2964 | 2 years ago |
Mateusz Bączek | e1a25a184a | 2 years ago |
Samantaz Fox | 26ea676b8d | 2 years ago |
Samantaz Fox | b66a5c40a9 | 2 years ago |
Émilien Devos (perso) | a6d21cb211 | 2 years ago |
Stéphane | 712aea0831 | 2 years ago |
Samantaz Fox | 6837e42928 | 2 years ago |
ChunkyProgrammer | ffcc837c2a | 2 years ago |
Samantaz Fox | b4806e7ba9 | 2 years ago |
Samantaz Fox | 5c633ad1da | 2 years ago |
Brahim Hadriche | 3848c3f53f | 2 years ago |
thtmnisamnstr | 3341929060 | 2 years ago |
ChunkyProgrammer | a781cf3734 | 2 years ago |
Samantaz Fox | d79d6f38b2 | 2 years ago |
Samantaz Fox | f012d70e47 | 2 years ago |
Samantaz Fox | 01e00a588b | 2 years ago |
Samantaz Fox | 35ac26bd61 | 2 years ago |
ChunkyProgrammer | e3081ef1a9 | 2 years ago |
Brahim Hadriche | 0b17f68eba | 2 years ago |
ChunkyProgrammer | 742c951bc9 | 2 years ago |
ChunkyProgrammer | d8e23d34b6 | 2 years ago |
maboroshin | 548a0f26ef | 2 years ago |
VisualPlugin | 9325fa79ae | 2 years ago |
Felipe Nogueira | 1f607273a8 | 2 years ago |
fresh | 3c3d9ebf84 | 2 years ago |
Émilien Devos (perso) | 6b01629c5d | 2 years ago |
Brahim Hadriche | 025e755542 | 2 years ago |
Paul Fauchon | a3ecd46b01 | 2 years ago |
Paul Fauchon | f6c6c9e5ec | 2 years ago |
Samantaz Fox | bff5c8d9a1 | 2 years ago |
amogusussy | 03542f2f5d | 2 years ago |
Brahim Hadriche | a5cc66e060 | 2 years ago |
Brahim Hadriche | 38f6d08be6 | 2 years ago |
Brahim Hadriche | 8c0efb3ea9 | 2 years ago |
ChunkyProgrammer | 60b7c8015c | 2 years ago |
Samantaz Fox | 406d74d0b6 | 2 years ago |
ChunkyProgrammer | 4a14713462 | 2 years ago |
Brahim Hadriche | 27bf4d02a1 | 2 years ago |
Besnik Bleta | 2974ed348c | 2 years ago |
Milo Ivir | fdf162e318 | 2 years ago |
maboroshin | 24ac873532 | 2 years ago |
Oğuz Ersen | 0efb56238f | 2 years ago |
gallegonovato | eb3af9d4f1 | 2 years ago |
Ashirg-ch | 23f1f8bde3 | 2 years ago |
Ashirg-ch | 3ddcfea8fa | 2 years ago |
techmetx11 | 4ac263f1df | 2 years ago |
techmetx11 | 8eca5b270e | 2 years ago |
thtmnisamnstr | b3eea6ab3e | 2 years ago |
Saurmandal | 7e0210d090 | 2 years ago |
ssantos | 596a16c085 | 2 years ago |
André Marcelo Alvarenga | 57e4312d9f | 2 years ago |
Émilien Devos (perso) | 0995e0447c | 2 years ago |
Brahim Hadriche | 6ee51f460a | 2 years ago |
Brahim Hadriche | 15e9510ab2 | 2 years ago |
Brahim Hadriche | 7b124eec64 | 2 years ago |
Brahim Hadriche | 20289a4d01 | 2 years ago |
Brahim Hadriche | 8445d3ae12 | 2 years ago |
Samantaz Fox | b287ff2126 | 2 years ago |
Andrey | 64780ce1da | 2 years ago |
Raman | 8046316f20 | 2 years ago |
Samantaz Fox | 4bbeb4a4c8 | 2 years ago |
ChunkyProgrammer | b5eb6016bb | 2 years ago |
Wes van der Vleuten | bde21d527f | 2 years ago |
Émilien Devos (perso) | b287ddc52a | 2 years ago |
ChunkyProgrammer | bc5d81fe60 | 2 years ago |
Samantaz Fox | cbbec00e1c | 2 years ago |
Samantaz Fox | ba217c9174 | 2 years ago |
Samantaz Fox | 217b740e01 | 2 years ago |
Émilien Devos (perso) | d6bf9e9bcf | 2 years ago |
Brahim Hadriche | a95f82e44b | 2 years ago |
ChunkyProgrammer | 76ad4e8026 | 2 years ago |
ChunkyProgrammer | d03a62641f | 2 years ago |
ChunkyProgrammer | 4731480821 | 2 years ago |
ChunkyProgrammer | aecbafbc7b | 2 years ago |
ChunkyProgrammer | 8384fa94c2 | 2 years ago |
Samantaz Fox | 7993784701 | 2 years ago |
AHOHNMYC | 9c400fd455 | 2 years ago |
SC | e4d14481c5 | 2 years ago |
Marsel J. Jonker | cb7c4a8220 | 2 years ago |
Damjan Gerl | c5d1344511 | 2 years ago |
Besnik Bleta | 299eb9207b | 2 years ago |
Fjuro | f2390ed052 | 2 years ago |
Milo Ivir | 256b518469 | 2 years ago |
maboroshin | 58688a6311 | 2 years ago |
Jeff Huang | fc5092c399 | 2 years ago |
Oğuz Ersen | 591f816781 | 2 years ago |
Eric | db6d3d2191 | 2 years ago |
Ihor Hordiichuk | 054686e557 | 2 years ago |
Jorge Maldonado Ventura | c1c6f67ad3 | 2 years ago |
Jorge Maldonado Ventura | c82272155e | 2 years ago |
atilluF | 4ca23f2d51 | 2 years ago |
Rex_sa | 45c99190b2 | 2 years ago |
Matthaiks | 7ae9dabe3c | 2 years ago |
Damjan Gerl | 5534cd87f8 | 2 years ago |
Goudarz Jafari | eb7588f1a0 | 2 years ago |
Mateus | 20dc0a9e26 | 2 years ago |
maboroshin | f4de962dc2 | 2 years ago |
eightyy8 | b2f93dc89c | 2 years ago |
SC | bd00b4c730 | 2 years ago |
AHOHNMYC | 4830656484 | 2 years ago |
Samantaz Fox | f9c2412010 | 2 years ago |
Samantaz Fox | 87342e4efd | 2 years ago |
ChunkyProgrammer | 838cbeffcc | 2 years ago |
Samantaz Fox | 27ff8d7c33 | 2 years ago |
Samantaz Fox | feeb872791 | 2 years ago |
thtmnisamnstr | 6f01d6eacf | 2 years ago |
ChunkyProgrammer | e0c70d34cc | 2 years ago |
Brahim Hadriche | d57d278f32 | 2 years ago |
ChunkyProgrammer | b893bdac0d | 2 years ago |
ChunkyProgrammer | 97825be10c | 2 years ago |
ChunkyProgrammer | 28424d0e88 | 2 years ago |
Brahim Hadriche | c37d8e3664 | 2 years ago |
Brahim Hadriche | 47a5b98e25 | 2 years ago |
Brahim Hadriche | 2606decd21 | 2 years ago |
Brahim Hadriche | b2589c74be | 2 years ago |
ChunkyProgrammer | c162c7ff3f | 2 years ago |
Samantaz Fox | d6dd341594 | 2 years ago |
Samantaz Fox | c7f34042a2 | 2 years ago |
Samantaz Fox | 6c687a3cac | 2 years ago |
ChunkyProgrammer | bf5175d1e9 | 2 years ago |
Brahim Hadriche | e7a9aeff95 | 2 years ago |
ChunkyProgrammer | 785fe52674 | 2 years ago |
Gavin Johnson | 72d0c9e409 | 2 years ago |
Gavin Johnson | 5c7bda66ae | 2 years ago |
Gavin Johnson | 96344f28b4 | 2 years ago |
Samantaz Fox | 3b8e6c6040 | 2 years ago |
Macic | 13bf4e9e00 | 2 years ago |
Samantaz Fox | 2a803dc067 | 2 years ago |
ChunkyProgrammer | c2957dbce4 | 2 years ago |
Samantaz Fox | 9a9f8231e8 | 2 years ago |
Hosted Weblate | ad3c721af7 | 2 years ago |
Hosted Weblate | 9b9fde1054 | 2 years ago |
Hosted Weblate | e66e463156 | 2 years ago |
Hosted Weblate | 5c024c677b | 2 years ago |
Hosted Weblate | 68caf355af | 2 years ago |
Hosted Weblate | 32bc44e83b | 2 years ago |
Hosted Weblate | 8cc0f9faf0 | 2 years ago |
Hosted Weblate | 75d136ce77 | 2 years ago |
Hosted Weblate | dd1ffb9283 | 2 years ago |
Hosted Weblate | b3a605c574 | 2 years ago |
Hosted Weblate | f5b3cee263 | 2 years ago |
Hosted Weblate | 24f1d82919 | 2 years ago |
Samantaz Fox | dbee027ed9 | 2 years ago |
Samantaz Fox | 624425cfa8 | 2 years ago |
techmetx11 | caf9520c86 | 2 years ago |
Wes van der Vleuten | 420e12bb8b | 2 years ago |
Wes van der Vleuten | 7fd205179b | 2 years ago |
Wes van der Vleuten | 4aa696fa6e | 2 years ago |
Samantaz Fox | 4e3884cae7 | 2 years ago |
Samantaz Fox | ebc02d0be3 | 2 years ago |
Samantaz Fox | f47d4f88cc | 2 years ago |
Samantaz Fox | cf93c94fc4 | 2 years ago |
Émilien Devos | 030070f1eb | 2 years ago |
hippogriffin | 3509999892 | 2 years ago |
Brahim Hadriche | f6a4d04070 | 2 years ago |
Brahim Hadriche | 0e22a0c21a | 2 years ago |
Samantaz Fox | 0e68756758 | 2 years ago |
Samantaz Fox | a7b2df31f0 | 2 years ago |
Samantaz Fox | ce07f2cd4a | 2 years ago |
DUOLabs333 | ff66cec920 | 2 years ago |
DUO Labs | 67ace4fd9d | 2 years ago |
DUOLabs333 | 86333cd434 | 2 years ago |
Gavin Johnson | 855202e40e | 2 years ago |
DUOLabs333 | 8dcc98b3b9 | 2 years ago |
Brahim Hadriche | 910809f1eb | 2 years ago |
techmetx11 | fe5b81f2c3 | 2 years ago |
Samantaz Fox | ea0d1b6f7b | 2 years ago |
Émilien Devos | c8fecffbbe | 2 years ago |
Samantaz Fox | 215446e638 | 2 years ago |
Samantaz Fox | b779445836 | 2 years ago |
Hosted Weblate | c02ae66bb1 | 2 years ago |
Samantaz Fox | d1bf36bd2b | 2 years ago |
Samantaz Fox | aacf83c06e | 2 years ago |
Samantaz Fox | 1af846e58c | 2 years ago |
Émilien Devos | c012aac997 | 2 years ago |
Émilien Devos | d6087fac47 | 2 years ago |
Samantaz Fox | 4ee483282e | 2 years ago |
Samantaz Fox | 04b97ec261 | 2 years ago |
techmetx11 | 1b5fbfc13e | 2 years ago |
Brahim Hadriche | 01acb9bfbf | 2 years ago |
Brahim Hadriche | 1fb0a49592 | 2 years ago |
DUOLabs333 | 4b2d942024 | 2 years ago |
Samantaz Fox | 05258d56bd | 2 years ago |
marc | 692166bd64 | 2 years ago |
DUOLabs333 | 456e91426a | 2 years ago |
DUOLabs333 | 4fc1b8ae86 | 2 years ago |
DUOLabs333 | 32471382c4 | 2 years ago |
Samantaz Fox | 927c37ce3e | 2 years ago |
Samantaz Fox | a37522a03d | 2 years ago |
Brackets | ed8f02ef01 | 2 years ago |
DUO Labs | 8d08cfe30f | 2 years ago |
Samantaz Fox | 049bfab438 | 2 years ago |
Hosted Weblate | 7f0f40f811 | 2 years ago |
Hosted Weblate | 62b8f8ac80 | 2 years ago |
Hosted Weblate | 16140f8b3f | 2 years ago |
Hosted Weblate | e0275d0908 | 2 years ago |
Hosted Weblate | a57770eb1f | 2 years ago |
Hosted Weblate | 233de2eff9 | 2 years ago |
Hosted Weblate | 9c9d71d41a | 2 years ago |
Hosted Weblate | 6b2fff83b5 | 2 years ago |
Hosted Weblate | 23b229ebb7 | 2 years ago |
Hosted Weblate | 72aa5c94af | 2 years ago |
Hosted Weblate | 4d6ff3a3c6 | 2 years ago |
Hosted Weblate | e2864a5ba1 | 2 years ago |
Hosted Weblate | a36363198c | 2 years ago |
Hosted Weblate | e2ce9c2cee | 2 years ago |
Émilien Devos | 98301a2237 | 2 years ago |
DUOLabs333 | 0d3610f63d | 2 years ago |
DUOLabs333 | 85dd3533bb | 2 years ago |
DUOLabs333 | 76758baab8 | 2 years ago |
DUOLabs333 | 9d83e2da4e | 2 years ago |
DUOLabs333 | 45b8f6d0cd | 2 years ago |
DUOLabs333 | b49ed65a07 | 2 years ago |
DUOLabs333 | 8df1c3bb57 | 2 years ago |
confused_alex | 865704dc7b | 2 years ago |
shironeko | 1aaf290814 | 2 years ago |
brackets0 | 4659e27b56 | 2 years ago |
Samantaz Fox | f9eb839c7a | 2 years ago |
Samantaz Fox | 69b8e0919f | 2 years ago |
Samantaz Fox | 4e3a930626 | 2 years ago |
Samantaz Fox | b6a4de66a5 | 2 years ago |
Samantaz Fox | 40c666cab2 | 2 years ago |
Samantaz Fox | 6c9754e663 | 2 years ago |
Samantaz Fox | 5d6abd5301 | 2 years ago |
Samantaz Fox | 52ef89f02d | 2 years ago |
Samantaz Fox | 2903e896ec | 2 years ago |
Samantaz Fox | c5ee2bfc0f | 2 years ago |
Samantaz Fox | 8e8ca4fcc5 | 2 years ago |
Samantaz Fox | ce7db8d2cb | 2 years ago |
Samantaz Fox | bdc51cd20f | 2 years ago |
Samantaz Fox | 9588fcb5d1 | 2 years ago |
Samantaz Fox | fbcce57ce2 | 2 years ago |
Samantaz Fox | 99bf519781 | 2 years ago |
dev | 1f6c234259 | 2 years ago |
Samantaz Fox | 5160d8bae3 | 2 years ago |
PrivacyDevel | 4fc5d43374 | 2 years ago |
PrivacyDevel | 9656067296 | 2 years ago |
PrivacyDevel | 9eb2ad367e | 2 years ago |
Samantaz Fox | 09b9b758de | 2 years ago |
Samantaz Fox | a46404bf78 | 2 years ago |
Samantaz Fox | c142703453 | 2 years ago |
Samantaz Fox | f44506b7e0 | 2 years ago |
Samantaz Fox | afc0ec3c30 | 2 years ago |
Samantaz Fox | 1bb8f2815d | 2 years ago |
Samantaz Fox | 516efd2df3 | 2 years ago |
Samantaz Fox | 47cc26cb3c | 2 years ago |
Samantaz Fox | cc5c83333f | 2 years ago |
Wes van der Vleuten | d3d9cfdd0d | 2 years ago |
Wes van der Vleuten | c03f92baf7 | 2 years ago |
Wes van der Vleuten | 5bcb5f3175 | 2 years ago |
Wes van der Vleuten | c95ee10d69 | 2 years ago |
Wes van der Vleuten | f604c1c68b | 2 years ago |
Wes van der Vleuten | 7b57381773 | 2 years ago |
Wes van der Vleuten | 437f42250e | 2 years ago |
Samantaz Fox | 09942dee66 | 2 years ago |
Samantaz Fox | 9da1827e95 | 2 years ago |
Samantaz Fox | 758b7df400 | 2 years ago |
Samantaz Fox | 46a63e6150 | 2 years ago |
Samantaz Fox | f267394bbe | 2 years ago |
Samantaz Fox | 2acff70811 | 2 years ago |
Samantaz Fox | db91d3af66 | 2 years ago |
Samantaz Fox | 83795c245a | 2 years ago |
Samantaz Fox | d659a451d6 | 2 years ago |
Samantaz Fox | 87a5d70062 | 2 years ago |
Samantaz Fox | ae03ed7bf7 | 2 years ago |
Samantaz Fox | e23ceb6ae9 | 2 years ago |
Samantaz Fox | 33150f5de3 | 2 years ago |
Samantaz Fox | 7df0cfcbed | 2 years ago |
Samantaz Fox | 907ddfa06a | 2 years ago |
Samantaz Fox | 6aaea7fafa | 2 years ago |
Samantaz Fox | cd03fa06ae | 2 years ago |
Samantaz Fox | 9baaef412f | 2 years ago |
Samantaz Fox | 88141c459c | 2 years ago |
Samantaz Fox | 6250039405 | 2 years ago |
Samantaz Fox | 84cd4d6a5b | 2 years ago |
Samantaz Fox | a1c6159e6f | 2 years ago |
Samantaz Fox | b7555343a0 | 2 years ago |
Samantaz Fox | 4055c3bec8 | 2 years ago |
Samantaz Fox | c5303d55e5 | 2 years ago |
Samantaz Fox | 8096c2d81d | 2 years ago |
Samantaz Fox | 4e1f5c8357 | 2 years ago |
Hosted Weblate | bba693e2af | 2 years ago |
Hosted Weblate | 127bfd5023 | 2 years ago |
Hosted Weblate | 2edfe4a463 | 2 years ago |
Hosted Weblate | 4b1ef90d96 | 2 years ago |
Émilien Devos | 0c7919f3d9 | 2 years ago |
Samantaz Fox | 72cf49eda1 | 2 years ago |
Samantaz Fox | e2ab488e7f | 2 years ago |
thecashewtrader | 1e96206b0b | 2 years ago |
thecashewtrader | 6f301db11c | 2 years ago |
Samantaz Fox | 12db1be87b | 2 years ago |
Samantaz Fox | cdb370f56b | 2 years ago |
Hosted Weblate | ae4f67f39c | 2 years ago |
Hosted Weblate | fcd29a4143 | 2 years ago |
Hosted Weblate | fa544c158a | 2 years ago |
Hosted Weblate | 7f3509aa36 | 2 years ago |
thecashewtrader | a1e0a6b499 | 2 years ago |
thecashewtrader | 6ea3673cf0 | 2 years ago |
Samantaz Fox | 3b39b8c772 | 2 years ago |
thecashewtrader | ffb42a9b23 | 2 years ago |
Samantaz Fox | 6707368f19 | 2 years ago |
Hosted Weblate | 1e186257da | 2 years ago |
Hosted Weblate | d85fcc4e7c | 2 years ago |
Hosted Weblate | 3e13d83ced | 2 years ago |
Hosted Weblate | 14de6a5658 | 2 years ago |
Hosted Weblate | 6100d5f12d | 2 years ago |
Samantaz Fox | dcfa0687f4 | 2 years ago |
Samantaz Fox | a01433960d | 2 years ago |
Benjamin Loison | 18a7ebe3a5 | 2 years ago |
Samantaz Fox | 7069969198 | 2 years ago |
Jakub Filo | 7c45026383 | 2 years ago |
Samantaz Fox | cf12e9dec1 | 2 years ago |
Samantaz Fox | 221d472127 | 2 years ago |
Samantaz Fox | 376ed3f4d3 | 2 years ago |
Samantaz Fox | 7df176d750 | 2 years ago |
Samantaz Fox | 0fa3250f02 | 2 years ago |
Hosted Weblate | 3b439a8fb7 | 2 years ago |
Hosted Weblate | 3a56ed19fe | 2 years ago |
Hosted Weblate | 53662b8400 | 2 years ago |
Hosted Weblate | eac37f1bd4 | 2 years ago |
Hosted Weblate | 1ac5081090 | 2 years ago |
Hosted Weblate | e3de6a4138 | 2 years ago |
Hosted Weblate | fc96ecaa66 | 2 years ago |
Hosted Weblate | 5ca34f3eb5 | 2 years ago |
Hosted Weblate | dcabce50c0 | 2 years ago |
Hosted Weblate | 5b0a4a8db4 | 2 years ago |
Hosted Weblate | b5a2c67d16 | 2 years ago |
Hosted Weblate | f911871990 | 2 years ago |
Chris Helder | c3de622493 | 2 years ago |
Samantaz Fox | 53fb6ad039 | 2 years ago |
Samantaz Fox | 8ab339396a | 2 years ago |
Samantaz Fox | 5048a89b9b | 2 years ago |
Samantaz Fox | feb38f891b | 2 years ago |
Emilien Devos | c658fd27cc | 2 years ago |
Emilien Devos | 260bab598e | 2 years ago |
Emilien Devos | 6f3b4fbaaf | 2 years ago |
Samantaz Fox | 1e7d330350 | 2 years ago |
Émilien Devos | 31244cbcc8 | 2 years ago |
Andrei E | 508a5761a1 | 2 years ago |
Jakub Filo | 4818b89ab1 | 2 years ago |
Hosted Weblate | 4e44123abc | 2 years ago |
Hosted Weblate | 689365d713 | 2 years ago |
Émilien Devos | a7d9df5516 | 2 years ago |
Samantaz Fox | 16b23efb4f | 2 years ago |
Émilien Devos | 389e49183c | 2 years ago |
Emilien Devos | ca4c2115ee | 2 years ago |
Samantaz Fox | 4c1a5f84fa | 2 years ago |
Samantaz Fox | b0bb156918 | 2 years ago |
Samantaz Fox | 5565204273 | 2 years ago |
Samantaz Fox | d950a0ef5d | 2 years ago |
Samantaz Fox | b2c0f7efc3 | 2 years ago |
Samantaz Fox | 9e58bc19c4 | 2 years ago |
Samantaz Fox | 0d7e2afba4 | 2 years ago |
Samantaz Fox | e0d063d306 | 2 years ago |
CalculationPaper | c847d6d370 | 2 years ago |
CalculationPaper | bbf66c9b72 | 2 years ago |
Hosted Weblate | ed0ad587dc | 2 years ago |
Hosted Weblate | 56fe591eee | 2 years ago |
Hosted Weblate | 7b9693bca4 | 2 years ago |
Hosted Weblate | fd0417b14c | 2 years ago |
Hosted Weblate | 89c12f2585 | 2 years ago |
Hosted Weblate | 5c71adb137 | 2 years ago |
Hosted Weblate | 4c23062d1e | 2 years ago |
Hosted Weblate | 190b45086c | 2 years ago |
Hosted Weblate | 008983c8e3 | 2 years ago |
Samantaz Fox | cb8a375c5e | 2 years ago |
Samantaz Fox | 848a60aa9b | 2 years ago |
Samantaz Fox | 88ea794fdb | 2 years ago |
Samantaz Fox | 870350fd61 | 2 years ago |
Samantaz Fox | 1e25894f7e | 2 years ago |
Samantaz Fox | 223e74569a | 2 years ago |
Samantaz Fox | 0a4d793556 | 2 years ago |
Samantaz Fox | 5503914abe | 2 years ago |
Samantaz Fox | 906466d7fb | 2 years ago |
Samantaz Fox | e2532de766 | 2 years ago |
Samantaz Fox | 3ac4390d11 | 2 years ago |
Samantaz Fox | 389ae7a573 | 2 years ago |
Samantaz Fox | 176247091d | 2 years ago |
Samantaz Fox | e22cc73f32 | 2 years ago |
Samantaz Fox | c23ad25899 | 2 years ago |
Émilien Devos | 7f2ec18372 | 2 years ago |
Samantaz Fox | 9cc0418769 | 2 years ago |
Samantaz Fox | 925a2c8e77 | 2 years ago |
Samantaz Fox | 390734d86e | 2 years ago |
Emilien Devos | 218f7be1a7 | 2 years ago |
Emilien Devos | 246955b68a | 2 years ago |
amarakon | d24506baed | 2 years ago |
Samantaz Fox | b0be2237fa | 2 years ago |
Samantaz Fox | 945394fb1a | 2 years ago |
Samantaz Fox | 19886f71f5 | 2 years ago |
Samantaz Fox | 23855c09dc | 2 years ago |
Samantaz Fox | 618ab01cd7 | 2 years ago |
Samantaz Fox | 349d90b60e | 2 years ago |
Samantaz Fox | 9e7c2dcdbb | 2 years ago |
Samantaz Fox | f353589a53 | 2 years ago |
Samantaz Fox | fc97929dee | 2 years ago |
Émilien Devos | 3d77642a1e | 2 years ago |
Emilien Devos | b55c1a35bf | 2 years ago |
Mateusz Jabłoński | 5df700a56e | 2 years ago |
Emilien Devos | 0c64a86ebe | 2 years ago |
Émilien Devos | 644ba46945 | 2 years ago |
Samantaz Fox | 4ab54f284c | 2 years ago |
Émilien Devos | 210c2a8855 | 2 years ago |
Samantaz Fox | 0ed4f1a9a4 | 2 years ago |
Samantaz Fox | 7e648840a1 | 2 years ago |
Émilien Devos | 5e090778ae | 2 years ago |
Samantaz Fox | 049ed114fd | 2 years ago |
Samantaz Fox | 88007a08f2 | 2 years ago |
Émilien Devos | 6c4ed282bb | 2 years ago |
Samantaz Fox | c8765385df | 2 years ago |
Samantaz Fox | 0ed22c0be0 | 2 years ago |
Samantaz Fox | 3ffef4b9fb | 2 years ago |
Samantaz Fox | ceeebceb3a | 2 years ago |
AHOHNMYC | 0338b26d5c | 2 years ago |
PrivateGER | 6577cc0c8c | 2 years ago |
Émilien Devos | 586000ca3d | 2 years ago |
Samantaz Fox | abc81ebd08 | 2 years ago |
Samantaz Fox | cd6c73e487 | 2 years ago |
Samantaz Fox | 69ad57338f | 2 years ago |
138138138 | cbcf31a4f9 | 2 years ago |
138138138 | b19beac5b4 | 2 years ago |
Samantaz Fox | dc6d088e30 | 2 years ago |
Samantaz Fox | b0ad27af23 | 2 years ago |
Hosted Weblate | 5f23c6358a | 2 years ago |
Hosted Weblate | da776c935f | 2 years ago |
Hosted Weblate | 0a315783ef | 2 years ago |
Hosted Weblate | 65061b0514 | 2 years ago |
Hosted Weblate | 063e5e359e | 2 years ago |
Hosted Weblate | f460afca35 | 2 years ago |
Hosted Weblate | 66a08ace1d | 2 years ago |
Hosted Weblate | 68e65e968a | 2 years ago |
Hosted Weblate | 1ba0ab982b | 2 years ago |
Hosted Weblate | 8752b8bb3f | 2 years ago |
Hosted Weblate | 168f86ef89 | 2 years ago |
Hosted Weblate | 85927853f9 | 2 years ago |
Hosted Weblate | 57f60bf173 | 2 years ago |
Hosted Weblate | d16c3ed40a | 2 years ago |
Hosted Weblate | e90f4a2cbf | 2 years ago |
Hosted Weblate | d00839ec68 | 2 years ago |
Samantaz Fox | ea35d92493 | 2 years ago |
Samantaz Fox | beb9894c47 | 2 years ago |
Samantaz Fox | f7b1dcc271 | 2 years ago |
Samantaz Fox | eb226e1dcf | 2 years ago |
Samantaz Fox | 8332ad0f16 | 2 years ago |
Samantaz Fox | 06af5a004e | 2 years ago |
Samantaz Fox | ce32873ef8 | 2 years ago |
Samantaz Fox | 99bc230fe6 | 2 years ago |
Samantaz Fox | 0e3820b634 | 2 years ago |
Samantaz Fox | eba84dcd78 | 2 years ago |
11tuvork28 | 864f27ef72 | 2 years ago |
11tuvork28 | a8b72d8342 | 2 years ago |
11tuvork28 | 15d2cfba90 | 2 years ago |
11tuvork28 | 2851d993ad | 2 years ago |
138138138 | 3f1d88282e | 2 years ago |
138138138 | cc9ce916c6 | 2 years ago |
138138138 | c7d468578f | 2 years ago |
138138138 | e0f6988eb5 | 2 years ago |
138138138 | 09ff370ddc | 2 years ago |
138138138 | 32ecf30c82 | 2 years ago |
138138138 | a62adccd3d | 2 years ago |
138138138 | c75bf35f59 | 2 years ago |
138138138 | 3013782b7b | 2 years ago |
138138138 | 81abebd144 | 2 years ago |
138138138 | 140b6c1227 | 2 years ago |
138138138 | ac685f65e9 | 2 years ago |
138138138 | de74056925 | 2 years ago |
138138138 | f6b1cbd5d0 | 2 years ago |
138138138 | 7db6e43e3f | 2 years ago |
Émilien Devos | 6c73614a47 | 2 years ago |
Samantaz Fox | b5c54b4e41 | 2 years ago |
Samantaz Fox | 8f1c84e6d4 | 2 years ago |
Samantaz Fox | d1df4af734 | 2 years ago |
Samantaz Fox | 23cd04fe88 | 2 years ago |
Samantaz Fox | dbc7c97e0b | 2 years ago |
Samantaz Fox | 2313ca8f72 | 2 years ago |
Hosted Weblate | 9418ba1687 | 2 years ago |
Hosted Weblate | 233491940c | 2 years ago |
Hosted Weblate | 7708e7ab08 | 2 years ago |
Hosted Weblate | 600bd38630 | 2 years ago |
Hosted Weblate | f7290dfcb6 | 2 years ago |
Hosted Weblate | e22f7583eb | 2 years ago |
Hosted Weblate | c0e85f5687 | 2 years ago |
Samantaz Fox | 3593f67eb6 | 2 years ago |
Samantaz Fox | 1b251264a6 | 2 years ago |
Samantaz Fox | 93c1a1d42e | 2 years ago |
Samantaz Fox | d7f6b6b018 | 2 years ago |
Samantaz Fox | fd99f20404 | 2 years ago |
Samantaz Fox | 2b1e1b11a3 | 2 years ago |
Samantaz Fox | 96ac7f9f35 | 2 years ago |
Samantaz Fox | 33da64a669 | 2 years ago |
meow | 38eb4ccbc4 | 2 years ago |
meow | a57414307e | 2 years ago |
Mohammed Anas | 7ad111e2f6 | 2 years ago |
meow | a402128a7d | 2 years ago |
meow | d3ab4a5145 | 2 years ago |
Émilien Devos | 4ae77bcef9 | 2 years ago |
Émilien Devos | e84416e56d | 2 years ago |
TheFrenchGhosty | 59ccc9d73e | 2 years ago |
777 | 1533a28817 | 2 years ago |
meow | 7e4840867e | 2 years ago |
meow | f2f3f045e5 | 2 years ago |
meow | b12149bafd | 2 years ago |
TheFrenchGhosty | 307c1b0b62 | 3 years ago |
TheFrenchGhosty | b201745988 | 3 years ago |
Arkadiusz Fal | 352266481e | 3 years ago |
Gauthier POGAM--LE MONTAGNER | 958867e92b | 3 years ago |
Gauthier POGAM--LE MONTAGNER | b50de2f2ed | 3 years ago |
DoodlesEpic | ad37db4c82 | 3 years ago |
Samantaz Fox | 1f359f5a13 | 3 years ago |
Samantaz Fox | fe53b5503c | 3 years ago |
Samantaz Fox | d66ef8fe22 | 3 years ago |
meow | b729597728 | 3 years ago |
meow | b72b917af2 | 3 years ago |
AHOHNMYC | 319bbd2f81 | 3 years ago |
meow | 1097648f0a | 3 years ago |
meow | 17e6213448 | 3 years ago |
meow | 2ea423032e | 3 years ago |
meow | 2dead1a19b | 3 years ago |
meow | e18b10297b | 3 years ago |
meow | fd66084388 | 3 years ago |
meow | f06d5b973b | 3 years ago |
meow | fd890f9c0a | 3 years ago |
meow | 835237382f | 3 years ago |
meow | 7dd699370f | 3 years ago |
@ -0,0 +1,37 @@
|
||||
name: Close duplicates
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- uses: iv-org/close-potential-duplicates@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Issue title filter work with anymatch https://www.npmjs.com/package/anymatch.
|
||||
# Any matched issue will stop detection immediately.
|
||||
# You can specify multi filters in each line.
|
||||
filter: ''
|
||||
# Exclude keywords in title before detecting.
|
||||
exclude: ''
|
||||
# Label to set, when potential duplicates are detected.
|
||||
label: duplicate
|
||||
# Get issues with state to compare. Supported state: 'all', 'closed', 'open'.
|
||||
state: open
|
||||
# If similarity is higher than this threshold([0,1]), issue will be marked as duplicate.
|
||||
threshold: 0.9
|
||||
# Reactions to be add to comment when potential duplicates are detected.
|
||||
# Available reactions: "-1", "+1", "confused", "laugh", "heart", "hooray", "rocket", "eyes"
|
||||
reactions: ''
|
||||
close: true
|
||||
# Comment to post when potential duplicates are detected.
|
||||
comment: |
|
||||
Hello, your issue is a duplicate of this/these issue(s): {{#issues}}
|
||||
- #{{ number }} [accuracy: {{ accuracy }}%]
|
||||
{{/issues}}
|
||||
|
||||
If this is a mistake please explain why and ping @\unixfox, @\SamantazFox and @\TheFrenchGhosty.
|
||||
|
||||
Please refrain from opening new issues, it won't help in solving your problem.
|
@ -0,0 +1,3 @@
|
||||
[submodule "mocks"]
|
||||
path = mocks
|
||||
url = ../mocks
|
@ -0,0 +1,254 @@
|
||||
'use strict';
|
||||
// Contains only auxiliary methods
|
||||
// May be included and executed unlimited number of times without any consequences
|
||||
|
||||
// Polyfills for IE11
|
||||
Array.prototype.find = Array.prototype.find || function (condition) {
|
||||
return this.filter(condition)[0];
|
||||
};
|
||||
|
||||
Array.from = Array.from || function (source) {
|
||||
return Array.prototype.slice.call(source);
|
||||
};
|
||||
NodeList.prototype.forEach = NodeList.prototype.forEach || function (callback) {
|
||||
Array.from(this).forEach(callback);
|
||||
};
|
||||
String.prototype.includes = String.prototype.includes || function (searchString) {
|
||||
return this.indexOf(searchString) >= 0;
|
||||
};
|
||||
String.prototype.startsWith = String.prototype.startsWith || function (prefix) {
|
||||
return this.substr(0, prefix.length) === prefix;
|
||||
};
|
||||
Math.sign = Math.sign || function(x) {
|
||||
x = +x;
|
||||
if (!x) return x; // 0 and NaN
|
||||
return x > 0 ? 1 : -1;
|
||||
};
|
||||
if (!window.hasOwnProperty('HTMLDetailsElement') && !window.hasOwnProperty('mockHTMLDetailsElement')) {
|
||||
window.mockHTMLDetailsElement = true;
|
||||
const style = 'details:not([open]) > :not(summary) {display: none}';
|
||||
document.head.appendChild(document.createElement('style')).textContent = style;
|
||||
|
||||
addEventListener('click', function (e) {
|
||||
if (e.target.nodeName !== 'SUMMARY') return;
|
||||
const details = e.target.parentElement;
|
||||
if (details.hasAttribute('open'))
|
||||
details.removeAttribute('open');
|
||||
else
|
||||
details.setAttribute('open', '');
|
||||
});
|
||||
}
|
||||
|
||||
// Monstrous global variable for handy code
|
||||
// Includes: clamp, xhr, storage.{get,set,remove}
|
||||
window.helpers = window.helpers || {
|
||||
/**
|
||||
* https://en.wikipedia.org/wiki/Clamping_(graphics)
|
||||
* @param {Number} num Source number
|
||||
* @param {Number} min Low border
|
||||
* @param {Number} max High border
|
||||
* @returns {Number} Clamped value
|
||||
*/
|
||||
clamp: function (num, min, max) {
|
||||
if (max < min) {
|
||||
var t = max; max = min; min = t; // swap max and min
|
||||
}
|
||||
|
||||
if (max < num)
|
||||
return max;
|
||||
if (min > num)
|
||||
return min;
|
||||
return num;
|
||||
},
|
||||
|
||||
/** @private */
|
||||
_xhr: function (method, url, options, callbacks) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url);
|
||||
|
||||
// Default options
|
||||
xhr.responseType = 'json';
|
||||
xhr.timeout = 10000;
|
||||
// Default options redefining
|
||||
if (options.responseType)
|
||||
xhr.responseType = options.responseType;
|
||||
if (options.timeout)
|
||||
xhr.timeout = options.timeout;
|
||||
|
||||
if (method === 'POST')
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
|
||||
// better than onreadystatechange because of 404 codes https://stackoverflow.com/a/36182963
|
||||
xhr.onloadend = function () {
|
||||
if (xhr.status === 200) {
|
||||
if (callbacks.on200) {
|
||||
// fix for IE11. It doesn't convert response to JSON
|
||||
if (xhr.responseType === '' && typeof(xhr.response) === 'string')
|
||||
callbacks.on200(JSON.parse(xhr.response));
|
||||
else
|
||||
callbacks.on200(xhr.response);
|
||||
}
|
||||
} else {
|
||||
// handled by onerror
|
||||
if (xhr.status === 0) return;
|
||||
|
||||
if (callbacks.onNon200)
|
||||
callbacks.onNon200(xhr);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.ontimeout = function () {
|
||||
if (callbacks.onTimeout)
|
||||
callbacks.onTimeout(xhr);
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
if (callbacks.onError)
|
||||
callbacks.onError(xhr);
|
||||
};
|
||||
|
||||
if (options.payload)
|
||||
xhr.send(options.payload);
|
||||
else
|
||||
xhr.send();
|
||||
},
|
||||
/** @private */
|
||||
_xhrRetry: function(method, url, options, callbacks) {
|
||||
if (options.retries <= 0) {
|
||||
console.warn('Failed to pull', options.entity_name);
|
||||
if (callbacks.onTotalFail)
|
||||
callbacks.onTotalFail();
|
||||
return;
|
||||
}
|
||||
helpers._xhr(method, url, options, callbacks);
|
||||
},
|
||||
/**
|
||||
* @callback callbackXhrOn200
|
||||
* @param {Object} response - xhr.response
|
||||
*/
|
||||
/**
|
||||
* @callback callbackXhrError
|
||||
* @param {XMLHttpRequest} xhr
|
||||
*/
|
||||
/**
|
||||
* @param {'GET'|'POST'} method - 'GET' or 'POST'
|
||||
* @param {String} url - URL to send request to
|
||||
* @param {Object} options - other XHR options
|
||||
* @param {XMLHttpRequestBodyInit} [options.payload=null] - payload for POST-requests
|
||||
* @param {'arraybuffer'|'blob'|'document'|'json'|'text'} [options.responseType=json]
|
||||
* @param {Number} [options.timeout=10000]
|
||||
* @param {Number} [options.retries=1]
|
||||
* @param {String} [options.entity_name='unknown'] - string to log
|
||||
* @param {Number} [options.retry_timeout=1000]
|
||||
* @param {Object} callbacks - functions to execute on events fired
|
||||
* @param {callbackXhrOn200} [callbacks.on200]
|
||||
* @param {callbackXhrError} [callbacks.onNon200]
|
||||
* @param {callbackXhrError} [callbacks.onTimeout]
|
||||
* @param {callbackXhrError} [callbacks.onError]
|
||||
* @param {callbackXhrError} [callbacks.onTotalFail] - if failed after all retries
|
||||
*/
|
||||
xhr: function(method, url, options, callbacks) {
|
||||
if (!options.retries || options.retries <= 1) {
|
||||
helpers._xhr(method, url, options, callbacks);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.entity_name) options.entity_name = 'unknown';
|
||||
if (!options.retry_timeout) options.retry_timeout = 1000;
|
||||
const retries_total = options.retries;
|
||||
let currentTry = 1;
|
||||
|
||||
const retry = function () {
|
||||
console.warn('Pulling ' + options.entity_name + ' failed... ' + (currentTry++) + '/' + retries_total);
|
||||
setTimeout(function () {
|
||||
options.retries--;
|
||||
helpers._xhrRetry(method, url, options, callbacks);
|
||||
}, options.retry_timeout);
|
||||
};
|
||||
|
||||
// Pack retry() call into error handlers
|
||||
callbacks._onError = callbacks.onError;
|
||||
callbacks.onError = function (xhr) {
|
||||
if (callbacks._onError)
|
||||
callbacks._onError(xhr);
|
||||
retry();
|
||||
};
|
||||
callbacks._onTimeout = callbacks.onTimeout;
|
||||
callbacks.onTimeout = function (xhr) {
|
||||
if (callbacks._onTimeout)
|
||||
callbacks._onTimeout(xhr);
|
||||
retry();
|
||||
};
|
||||
|
||||
helpers._xhrRetry(method, url, options, callbacks);
|
||||
},
|
||||
|
||||
/**
|
||||
* @typedef {Object} invidiousStorage
|
||||
* @property {(key:String) => Object} get
|
||||
* @property {(key:String, value:Object)} set
|
||||
* @property {(key:String)} remove
|
||||
*/
|
||||
|
||||
/**
|
||||
* Universal storage, stores and returns JS objects. Uses inside localStorage or cookies
|
||||
* @type {invidiousStorage}
|
||||
*/
|
||||
storage: (function () {
|
||||
// access to localStorage throws exception in Tor Browser, so try is needed
|
||||
let localStorageIsUsable = false;
|
||||
try{localStorageIsUsable = !!localStorage.setItem;}catch(e){}
|
||||
|
||||
if (localStorageIsUsable) {
|
||||
return {
|
||||
get: function (key) {
|
||||
let storageItem = localStorage.getItem(key)
|
||||
if (!storageItem) return;
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(storageItem));
|
||||
} catch(e) {
|
||||
// Erase non parsable value
|
||||
helpers.storage.remove(key);
|
||||
}
|
||||
},
|
||||
set: function (key, value) {
|
||||
let encoded_value = encodeURIComponent(JSON.stringify(value))
|
||||
localStorage.setItem(key, encoded_value);
|
||||
},
|
||||
remove: function (key) { localStorage.removeItem(key); }
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: fire 'storage' event for cookies
|
||||
console.info('Storage: localStorage is disabled or unaccessible. Cookies used as fallback');
|
||||
return {
|
||||
get: function (key) {
|
||||
const cookiePrefix = key + '=';
|
||||
function findCallback(cookie) {return cookie.startsWith(cookiePrefix);}
|
||||
const matchedCookie = document.cookie.split('; ').find(findCallback);
|
||||
if (matchedCookie) {
|
||||
const cookieBody = matchedCookie.replace(cookiePrefix, '');
|
||||
if (cookieBody.length === 0) return;
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(cookieBody));
|
||||
} catch(e) {
|
||||
// Erase non parsable value
|
||||
helpers.storage.remove(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
set: function (key, value) {
|
||||
const cookie_data = encodeURIComponent(JSON.stringify(value));
|
||||
|
||||
// Set expiration in 2 year
|
||||
const date = new Date();
|
||||
date.setFullYear(date.getFullYear()+2);
|
||||
|
||||
document.cookie = key + '=' + cookie_data + '; expires=' + date.toGMTString();
|
||||
},
|
||||
remove: function (key) {
|
||||
document.cookie = key + '=; Max-Age=0';
|
||||
}
|
||||
};
|
||||
})()
|
||||
};
|
@ -1,90 +1,46 @@
|
||||
'use strict';
|
||||
var toggle_theme = document.getElementById('toggle_theme');
|
||||
toggle_theme.href = 'javascript:void(0);';
|
||||
toggle_theme.href = 'javascript:void(0)';
|
||||
|
||||
toggle_theme.addEventListener('click', function () {
|
||||
var dark_mode = document.body.classList.contains('light-theme');
|
||||
|
||||
var url = '/toggle_theme?redirect=false';
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = 'json';
|
||||
xhr.timeout = 10000;
|
||||
xhr.open('GET', url, true);
|
||||
|
||||
set_mode(dark_mode);
|
||||
try {
|
||||
window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light');
|
||||
} catch (e) {}
|
||||
|
||||
xhr.send();
|
||||
});
|
||||
|
||||
window.addEventListener('storage', function (e) {
|
||||
if (e.key === 'dark_mode') {
|
||||
update_mode(e.newValue);
|
||||
}
|
||||
});
|
||||
const STORAGE_KEY_THEME = 'dark_mode';
|
||||
const THEME_DARK = 'dark';
|
||||
const THEME_LIGHT = 'light';
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
const dark_mode = document.getElementById('dark_mode_pref').textContent;
|
||||
try {
|
||||
// Update localStorage if dark mode preference changed on preferences page
|
||||
window.localStorage.setItem('dark_mode', dark_mode);
|
||||
} catch (e) {}
|
||||
update_mode(dark_mode);
|
||||
// TODO: theme state controlled by system
|
||||
toggle_theme.addEventListener('click', function () {
|
||||
const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK;
|
||||
const newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK;
|
||||
setTheme(newTheme);
|
||||
helpers.storage.set(STORAGE_KEY_THEME, newTheme);
|
||||
helpers.xhr('GET', '/toggle_theme?redirect=false', {}, {});
|
||||
});
|
||||
|
||||
|
||||
var darkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
var lightScheme = window.matchMedia('(prefers-color-scheme: light)');
|
||||
|
||||
darkScheme.addListener(scheme_switch);
|
||||
lightScheme.addListener(scheme_switch);
|
||||
|
||||
function scheme_switch (e) {
|
||||
// ignore this method if we have a preference set
|
||||
try {
|
||||
if (localStorage.getItem('dark_mode')) {
|
||||
return;
|
||||
}
|
||||
} catch (exception) {}
|
||||
if (e.matches) {
|
||||
if (e.media.includes('dark')) {
|
||||
set_mode(true);
|
||||
} else if (e.media.includes('light')) {
|
||||
set_mode(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function set_mode (bool) {
|
||||
if (bool) {
|
||||
// dark
|
||||
toggle_theme.children[0].setAttribute('class', 'icon ion-ios-sunny');
|
||||
document.body.classList.remove('no-theme');
|
||||
document.body.classList.remove('light-theme');
|
||||
document.body.classList.add('dark-theme');
|
||||
/** @param {THEME_DARK|THEME_LIGHT} theme */
|
||||
function setTheme(theme) {
|
||||
// By default body element has .no-theme class that uses OS theme via CSS @media rules
|
||||
// It rewrites using hard className below
|
||||
if (theme === THEME_DARK) {
|
||||
toggle_theme.children[0].className = 'icon ion-ios-sunny';
|
||||
document.body.className = 'dark-theme';
|
||||
} else if (theme === THEME_LIGHT) {
|
||||
toggle_theme.children[0].className = 'icon ion-ios-moon';
|
||||
document.body.className = 'light-theme';
|
||||
} else {
|
||||
// light
|
||||
toggle_theme.children[0].setAttribute('class', 'icon ion-ios-moon');
|
||||
document.body.classList.remove('no-theme');
|
||||
document.body.classList.remove('dark-theme');
|
||||
document.body.classList.add('light-theme');
|
||||
document.body.className = 'no-theme';
|
||||
}
|
||||
}
|
||||
|
||||
function update_mode (mode) {
|
||||
if (mode === 'true' /* for backwards compatibility */ || mode === 'dark') {
|
||||
// If preference for dark mode indicated
|
||||
set_mode(true);
|
||||
}
|
||||
else if (mode === 'false' /* for backwards compatibility */ || mode === 'light') {
|
||||
// If preference for light mode indicated
|
||||
set_mode(false);
|
||||
}
|
||||
else if (document.getElementById('dark_mode_pref').textContent === '' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
// If no preference indicated here and no preference indicated on the preferences page (backend), but the browser tells us that the operating system has a dark theme
|
||||
set_mode(true);
|
||||
}
|
||||
// else do nothing, falling back to the mode defined by the `dark_mode` preference on the preferences page (backend)
|
||||
// Handles theme change event caused by other tab
|
||||
addEventListener('storage', function (e) {
|
||||
if (e.key === STORAGE_KEY_THEME)
|
||||
setTheme(helpers.storage.get(STORAGE_KEY_THEME));
|
||||
});
|
||||
|
||||
// Set theme from preferences on page load
|
||||
addEventListener('DOMContentLoaded', function () {
|
||||
const prefTheme = document.getElementById('dark_mode_pref').textContent;
|
||||
if (prefTheme) {
|
||||
setTheme(prefTheme);
|
||||
helpers.storage.set(STORAGE_KEY_THEME, prefTheme);
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
var save_player_pos_key = 'save_player_pos';
|
||||
|
||||
function get_all_video_times() {
|
||||
return helpers.storage.get(save_player_pos_key) || {};
|
||||
}
|
||||
|
||||
document.querySelectorAll('.watched-indicator').forEach(function (indicator) {
|
||||
var watched_part = get_all_video_times()[indicator.dataset.id];
|
||||
var total = parseInt(indicator.dataset.length, 10);
|
||||
if (watched_part === undefined) {
|
||||
watched_part = total;
|
||||
}
|
||||
var percentage = Math.round((watched_part / total) * 100);
|
||||
|
||||
if (percentage < 5) {
|
||||
percentage = 5;
|
||||
}
|
||||
if (percentage > 90) {
|
||||
percentage = 100;
|
||||
}
|
||||
|
||||
indicator.style.width = percentage + '%';
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
repository: https://charts.bitnami.com/bitnami/
|
||||
version: 11.1.3
|
||||
digest: sha256:79061645472b6fb342d45e8e5b3aacd018ef5067193e46a060bccdc99fe7f6e1
|
||||
generated: "2022-03-02T05:57:20.081432389+13:00"
|
||||
version: 12.1.9
|
||||
digest: sha256:71ff342a6c0a98bece3d7fe199983afb2113f8db65a3e3819de875af2c45add7
|
||||
generated: "2023-01-20T20:42:32.757707004Z"
|
||||
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,94 @@
|
||||
{
|
||||
"Subscribe": "সাবস্ক্রাইব",
|
||||
"View channel on YouTube": "ইউটিউবে চ্যানেল দেখুন",
|
||||
"View playlist on YouTube": "ইউটিউবে প্লেলিস্ট দেখুন",
|
||||
"newest": "সর্ব-নতুন",
|
||||
"oldest": "পুরানতম",
|
||||
"popular": "জনপ্রিয়",
|
||||
"last": "শেষটা",
|
||||
"Next page": "পরের পৃষ্ঠা",
|
||||
"Previous page": "আগের পৃষ্ঠা",
|
||||
"Clear watch history?": "দেখার ইতিহাস সাফ করবেন?",
|
||||
"New password": "নতুন পাসওয়ার্ড",
|
||||
"New passwords must match": "নতুন পাসওয়ার্ড অবশ্যই মিলতে হবে",
|
||||
"Authorize token?": "টোকেন অনুমোদন করবেন?",
|
||||
"Authorize token for `x`?": "`x` -এর জন্য টোকেন অনুমোদন?",
|
||||
"Yes": "হ্যাঁ",
|
||||
"No": "না",
|
||||
"Import and Export Data": "তথ্য আমদানি ও রপ্তানি",
|
||||
"Import": "আমদানি",
|
||||
"Import Invidious data": "ইনভিডিয়াস তথ্য আমদানি",
|
||||
"Import YouTube subscriptions": "ইউটিউব সাবস্ক্রিপশন আনুন",
|
||||
"Import FreeTube subscriptions (.db)": "ফ্রিটিউব সাবস্ক্রিপশন (.db) আনুন",
|
||||
"Import NewPipe subscriptions (.json)": "নতুন পাইপ সাবস্ক্রিপশন আনুন (.json)",
|
||||
"Import NewPipe data (.zip)": "নিউপাইপ তথ্য আনুন (.zip)",
|
||||
"Export": "তথ্য বের করুন",
|
||||
"Export subscriptions as OPML": "সাবস্ক্রিপশন OPML হিসাবে আনুন",
|
||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML-এ সাবস্ক্রিপশন বের করুন(নিউ পাইপ এবং ফ্রিউটিউব এর জন্য)",
|
||||
"Export data as JSON": "JSON হিসাবে তথ্য বের করুন",
|
||||
"Delete account?": "অ্যাকাউন্ট মুছে ফেলবেন?",
|
||||
"History": "ইতিহাস",
|
||||
"An alternative front-end to YouTube": "ইউটিউবের একটি বিকল্পস্বরূপ সম্মুখ-প্রান্ত",
|
||||
"JavaScript license information": "জাভাস্ক্রিপ্ট লাইসেন্সের তথ্য",
|
||||
"source": "সূত্র",
|
||||
"Log in": "লগ ইন",
|
||||
"Log in/register": "লগ ইন/রেজিস্টার",
|
||||
"User ID": "ইউজার আইডি",
|
||||
"Password": "পাসওয়ার্ড",
|
||||
"Time (h:mm:ss):": "সময় (ঘণ্টা:মিনিট:সেকেন্ড):",
|
||||
"Text CAPTCHA": "টেক্সট ক্যাপচা",
|
||||
"Image CAPTCHA": "চিত্র ক্যাপচা",
|
||||
"Sign In": "সাইন ইন",
|
||||
"Register": "নিবন্ধন",
|
||||
"E-mail": "ই-মেইল",
|
||||
"Preferences": "পছন্দসমূহ",
|
||||
"preferences_category_player": "প্লেয়ারের পছন্দসমূহ",
|
||||
"preferences_video_loop_label": "সর্বদা লুপ: ",
|
||||
"preferences_autoplay_label": "স্বয়ংক্রিয় চালু: ",
|
||||
"preferences_continue_label": "ডিফল্টভাবে পরবর্তী চালাও: ",
|
||||
"preferences_continue_autoplay_label": "পরবর্তী ভিডিও স্বয়ংক্রিয়ভাবে চালাও: ",
|
||||
"preferences_listen_label": "সহজাতভাবে শোনো: ",
|
||||
"preferences_local_label": "ভিডিও প্রক্সি করো: ",
|
||||
"preferences_speed_label": "সহজাত গতি: ",
|
||||
"preferences_quality_label": "পছন্দের ভিডিও মান: ",
|
||||
"preferences_volume_label": "প্লেয়ার শব্দের মাত্রা: ",
|
||||
"LIVE": "লাইভ",
|
||||
"Shared `x` ago": "`x` আগে শেয়ার করা হয়েছে",
|
||||
"Unsubscribe": "আনসাবস্ক্রাইব",
|
||||
"generic_views_count": "{{count}}জন দেখেছে",
|
||||
"generic_views_count_plural": "{{count}}জন দেখেছে",
|
||||
"generic_videos_count": "{{count}}টি ভিডিও",
|
||||
"generic_videos_count_plural": "{{count}}টি ভিডিও",
|
||||
"generic_subscribers_count": "{{count}}জন অনুসরণকারী",
|
||||
"generic_subscribers_count_plural": "{{count}}জন অনুসরণকারী",
|
||||
"preferences_watch_history_label": "দেখার ইতিহাস চালু করো: ",
|
||||
"preferences_quality_option_dash": "ড্যাশ (সময়োপযোগী মান)",
|
||||
"preferences_quality_dash_option_auto": "স্বয়ংক্রিয়",
|
||||
"preferences_quality_dash_option_best": "সেরা",
|
||||
"preferences_quality_dash_option_worst": "মন্দতম",
|
||||
"preferences_quality_dash_option_4320p": "৪৩২০পি",
|
||||
"preferences_quality_dash_option_2160p": "২১৬০পি",
|
||||
"preferences_quality_dash_option_1440p": "১৪৪০পি",
|
||||
"preferences_quality_dash_option_480p": "৪৮০পি",
|
||||
"preferences_quality_dash_option_360p": "৩৬০পি",
|
||||
"preferences_quality_dash_option_240p": "২৪০পি",
|
||||
"preferences_quality_dash_option_144p": "১৪৪পি",
|
||||
"preferences_comments_label": "সহজাত মন্তব্য: ",
|
||||
"youtube": "ইউটিউব",
|
||||
"Fallback captions: ": "বিকল্প উপাখ্যান: ",
|
||||
"preferences_related_videos_label": "সম্পর্কিত ভিডিও দেখাও: ",
|
||||
"preferences_annotations_label": "সহজাতভাবে টীকা দেখাও ",
|
||||
"preferences_quality_option_hd720": "উচ্চ৭২০",
|
||||
"preferences_quality_dash_label": "পছন্দের ড্যাশ ভিডিও মান: ",
|
||||
"preferences_captions_label": "সহজাত উপাখ্যান: ",
|
||||
"generic_playlists_count": "{{count}}টি চালুতালিকা",
|
||||
"generic_playlists_count_plural": "{{count}}টি চালুতালিকা",
|
||||
"reddit": "রেডিট",
|
||||
"invidious": "ইনভিডিয়াস",
|
||||
"generic_subscriptions_count": "{{count}}টি অনুসরণ",
|
||||
"generic_subscriptions_count_plural": "{{count}}টি অনুসরণ",
|
||||
"preferences_quality_option_medium": "মধ্যম",
|
||||
"preferences_quality_option_small": "ছোট",
|
||||
"preferences_quality_dash_option_1080p": "১০৮০পি",
|
||||
"preferences_quality_dash_option_720p": "৭২০পি"
|
||||
}
|
@ -0,0 +1 @@
|
||||
{}
|
@ -0,0 +1,123 @@
|
||||
{
|
||||
"generic_views_count": "බැලීම් {{count}}",
|
||||
"generic_views_count_plural": "බැලීම් {{count}}",
|
||||
"generic_videos_count": "{{count}} වීඩියෝව",
|
||||
"generic_videos_count_plural": "වීඩියෝ {{count}}",
|
||||
"generic_subscribers_count": "ග්රාහකයන් {{count}}",
|
||||
"generic_subscribers_count_plural": "ග්රාහකයන් {{count}}",
|
||||
"generic_subscriptions_count": "දායකත්ව {{count}}",
|
||||
"generic_subscriptions_count_plural": "දායකත්ව {{count}}",
|
||||
"Shared `x` ago": "`x` පෙර බෙදා ගන්නා ලදී",
|
||||
"Unsubscribe": "දායක නොවන්න",
|
||||
"View playlist on YouTube": "YouTube හි ධාවන ලැයිස්තුව බලන්න",
|
||||
"newest": "අලුත්ම",
|
||||
"oldest": "පැරණිතම",
|
||||
"popular": "ජනප්රිය",
|
||||
"last": "අවසන්",
|
||||
"Authorize token?": "ටෝකනය අනුමත කරනවා ද?",
|
||||
"Authorize token for `x`?": "`x` සඳහා ටෝකනය අනුමත කරනවා ද?",
|
||||
"Yes": "ඔව්",
|
||||
"Import and Export Data": "දත්ත ආනයනය සහ අපනයනය කිරීම",
|
||||
"Import": "ආනයන",
|
||||
"Import Invidious data": "Invidious JSON දත්ත ආයාත කරන්න",
|
||||
"Import FreeTube subscriptions (.db)": "FreeTube දායකත්වයන් (.db) ආයාත කරන්න",
|
||||
"Import NewPipe subscriptions (.json)": "NewPipe දායකත්වයන් (.json) ආයාත කරන්න",
|
||||
"Import NewPipe data (.zip)": "NewPipe දත්ත (.zip) ආයාත කරන්න",
|
||||
"Export": "අපනයන",
|
||||
"Export data as JSON": "Invidious දත්ත JSON ලෙස අපනයනය කරන්න",
|
||||
"Delete account?": "ගිණුම මකාදමනවා ද?",
|
||||
"History": "ඉතිහාසය",
|
||||
"An alternative front-end to YouTube": "YouTube සඳහා විකල්ප ඉදිරිපස අන්තයක්",
|
||||
"source": "මූලාශ්රය",
|
||||
"Log in/register": "පුරන්න/ලියාපදිංචිවන්න",
|
||||
"Password": "මුරපදය",
|
||||
"Time (h:mm:ss):": "වේලාව (h:mm:ss):",
|
||||
"Sign In": "පුරන්න",
|
||||
"Preferences": "මනාපයන්",
|
||||
"preferences_category_player": "වීඩියෝ ධාවක මනාපයන්",
|
||||
"preferences_video_loop_label": "නැවත නැවතත්: ",
|
||||
"preferences_autoplay_label": "ස්වයංක්රීය වාදනය: ",
|
||||
"preferences_continue_label": "මීලඟට වාදනය කරන්න: ",
|
||||
"preferences_continue_autoplay_label": "මීළඟ වීඩියෝව ස්වයංක්රීයව ධාවනය කරන්න: ",
|
||||
"preferences_local_label": "Proxy වීඩියෝ: ",
|
||||
"preferences_watch_history_label": "නැරඹුම් ඉතිහාසය සබල කරන්න: ",
|
||||
"preferences_speed_label": "පෙරනිමි වේගය: ",
|
||||
"preferences_quality_option_dash": "DASH (අනුවර්තිත ගුණත්වය)",
|
||||
"preferences_quality_option_medium": "මධ්යස්ථ",
|
||||
"preferences_quality_dash_label": "කැමති DASH වීඩියෝ ගුණත්වය: ",
|
||||
"preferences_quality_dash_option_4320p": "4320p",
|
||||
"preferences_quality_dash_option_1080p": "1080p",
|
||||
"preferences_quality_dash_option_480p": "480p",
|
||||
"preferences_quality_dash_option_360p": "360p",
|
||||
"preferences_quality_dash_option_144p": "144p",
|
||||
"preferences_volume_label": "ධාවකයේ හඬ: ",
|
||||
"preferences_comments_label": "පෙරනිමි අදහස්: ",
|
||||
"youtube": "YouTube",
|
||||
"reddit": "Reddit",
|
||||
"invidious": "Invidious",
|
||||
"preferences_captions_label": "පෙරනිමි උපසිරැසි: ",
|
||||
"preferences_related_videos_label": "අදාළ වීඩියෝ පෙන්වන්න: ",
|
||||
"preferences_annotations_label": "අනුසටහන් පෙන්වන්න: ",
|
||||
"preferences_vr_mode_label": "අන්තර්ක්රියාකාරී අංශක 360 වීඩියෝ (WebGL අවශ්යයි): ",
|
||||
"preferences_region_label": "අන්තර්ගත රට: ",
|
||||
"preferences_player_style_label": "වීඩියෝ ධාවක විලාසය: ",
|
||||
"Dark mode: ": "අඳුරු මාදිලිය: ",
|
||||
"preferences_dark_mode_label": "තේමාව: ",
|
||||
"light": "ආලෝකමත්",
|
||||
"generic_playlists_count": "{{count}} ධාවන ලැයිස්තුව",
|
||||
"generic_playlists_count_plural": "ධාවන ලැයිස්තු {{count}}",
|
||||
"LIVE": "සජීව",
|
||||
"Subscribe": "දායක වන්න",
|
||||
"View channel on YouTube": "YouTube හි නාලිකාව බලන්න",
|
||||
"Next page": "ඊළඟ පිටුව",
|
||||
"Previous page": "පෙර පිටුව",
|
||||
"Clear watch history?": "නැරඹුම් ඉතිහාසය මකාදමනවා ද?",
|
||||
"No": "නැත",
|
||||
"Log in": "පුරන්න",
|
||||
"New password": "නව මුරපදය",
|
||||
"Import YouTube subscriptions": "YouTube/OPML දායකත්වයන් ආයාත කරන්න",
|
||||
"Register": "ලියාපදිංචිවන්න",
|
||||
"New passwords must match": "නව මුරපද ගැලපිය යුතුය",
|
||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML ලෙස දායකත්වයන් අපනයනය කරන්න (NewPipe සහ FreeTube සඳහා)",
|
||||
"Export subscriptions as OPML": "දායකත්වයන් OPML ලෙස අපනයනය කරන්න",
|
||||
"JavaScript license information": "JavaScript බලපත්ර තොරතුරු",
|
||||
"User ID": "පරිශීලක කේතය",
|
||||
"Text CAPTCHA": "CAPTCHA පෙල",
|
||||
"Image CAPTCHA": "CAPTCHA රූපය",
|
||||
"E-mail": "විද්යුත් තැපෑල",
|
||||
"preferences_quality_label": "කැමති වීඩියෝ ගුණත්වය: ",
|
||||
"preferences_quality_option_hd720": "HD720",
|
||||
"preferences_quality_dash_option_auto": "ස්වයංක්රීය",
|
||||
"preferences_quality_option_small": "කුඩා",
|
||||
"preferences_quality_dash_option_best": "උසස්",
|
||||
"preferences_quality_dash_option_2160p": "2160p",
|
||||
"preferences_quality_dash_option_1440p": "1440p",
|
||||
"preferences_quality_dash_option_720p": "720p",
|
||||
"preferences_quality_dash_option_240p": "240p",
|
||||
"preferences_extend_desc_label": "වීඩියෝ විස්තරය ස්වයංක්රීයව දිගහරින්න: ",
|
||||
"preferences_category_visual": "දෘශ්ය මනාපයන්",
|
||||
"dark": "අඳුරු",
|
||||
"preferences_category_misc": "විවිධ මනාප",
|
||||
"preferences_category_subscription": "දායකත්ව මනාප",
|
||||
"Redirect homepage to feed: ": "මුල් පිටුව පෝෂණය වෙත හරවා යවන්න: ",
|
||||
"preferences_max_results_label": "සංග්රහයේ පෙන්වන වීඩියෝ ගණන: ",
|
||||
"preferences_sort_label": "වීඩියෝ වර්ග කරන්න: ",
|
||||
"alphabetically": "අකාරාදී ලෙස",
|
||||
"alphabetically - reverse": "අකාරාදී - ආපසු",
|
||||
"channel name": "නාලිකාවේ නම",
|
||||
"Only show latest video from channel: ": "නාලිකාවේ නවතම වීඩියෝව පමණක් පෙන්වන්න: ",
|
||||
"preferences_unseen_only_label": "නොබැලූ පමණක් පෙන්වන්න: ",
|
||||
"Enable web notifications": "වෙබ් දැනුම්දීම් සබල කරන්න",
|
||||
"Import/export data": "දත්ත ආනයනය / අපනයනය",
|
||||
"Change password": "මුරපදය වෙනස් කරන්න",
|
||||
"Manage subscriptions": "දායකත්ව කළමනාකරණය",
|
||||
"Manage tokens": "ටෝකන කළමනාකරණය",
|
||||
"Watch history": "නැරඹුම් ඉතිහාසය",
|
||||
"Save preferences": "මනාප සුරකින්න",
|
||||
"Token": "ටෝකනය",
|
||||
"View privacy policy.": "රහස්යතා ප්රතිපත්තිය බලන්න.",
|
||||
"Only show latest unwatched video from channel: ": "නාලිකාවේ නවතම නැරඹන නොලද වීඩියෝව පමණක් පෙන්වන්න: ",
|
||||
"preferences_category_data": "දත්ත මනාප",
|
||||
"Clear watch history": "නැරඹුම් ඉතිහාසය මකාදැමීම",
|
||||
"Subscriptions": "දායකත්ව"
|
||||
}
|
@ -0,0 +1 @@
|
||||
Subproject commit 11ec372f72747c09d48ffef04843f72be67d5b54
|
@ -0,0 +1,60 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Parameters
|
||||
#
|
||||
|
||||
interactive=true
|
||||
|
||||
if [ "$1" = "--no-interactive" ]; then
|
||||
interactive=false
|
||||
fi
|
||||
|
||||
#
|
||||
# Enable and start Postgres
|
||||
#
|
||||
|
||||
sudo systemctl start postgresql.service
|
||||
sudo systemctl enable postgresql.service
|
||||
|
||||
#
|
||||
# Create databse and user
|
||||
#
|
||||
|
||||
if [ "$interactive" = "true" ]; then
|
||||
sudo -u postgres -- createuser -P kemal
|
||||
sudo -u postgres -- createdb -O kemal invidious
|
||||
else
|
||||
# Generate a DB password
|
||||
if [ -z "$POSTGRES_PASS" ]; then
|
||||
echo "Generating database password"
|
||||
POSTGRES_PASS=$(tr -dc 'A-Za-z0-9.;!?{[()]}\\/' < /dev/urandom | head -c16)
|
||||
fi
|
||||
|
||||
# hostname:port:database:username:password
|
||||
echo "Writing .pgpass"
|
||||
echo "127.0.0.1:*:invidious:kemal:${POSTGRES_PASS}" > "$HOME/.pgpass"
|
||||
|
||||
sudo -u postgres -- psql -c "CREATE USER kemal WITH PASSWORD '$POSTGRES_PASS';"
|
||||
sudo -u postgres -- psql -c "CREATE DATABASE invidious WITH OWNER kemal;"
|
||||
sudo -u postgres -- psql -c "GRANT ALL ON DATABASE invidious TO kemal;"
|
||||
fi
|
||||
|
||||
|
||||
#
|
||||
# Instructions for modification of pg_hba.conf
|
||||
#
|
||||
|
||||
if [ "$interactive" = "true" ]; then
|
||||
echo
|
||||
echo "-------------"
|
||||
echo " NOTICE "
|
||||
echo "-------------"
|
||||
echo
|
||||
echo "Make sure that your postgreSQL's pg_hba.conf file contains the follwong"
|
||||
echo "lines before previous 'host' configurations:"
|
||||
echo
|
||||
echo "host invidious kemal 127.0.0.1/32 md5"
|
||||
echo "host invidious kemal ::1/128 md5"
|
||||
echo
|
||||
fi
|
@ -0,0 +1,174 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Script that installs the various dependencies of invidious
|
||||
#
|
||||
# Dependencies:
|
||||
# - crystal => Language in which Invidious is developed
|
||||
# - postgres => Database server
|
||||
# - git => required to clone Invidious
|
||||
# - librsvg2-bin => For login captcha (provides 'rsvg-convert')
|
||||
#
|
||||
# - libssl-dev => Used by Crystal's SSL module (standard library)
|
||||
# - libxml2-dev => Used by Crystal's XML module (standard library)
|
||||
# - libyaml-dev => Used by Crystal's YAML module (standard library)
|
||||
# - libgmp-dev => Used by Crystal's BigNumbers module (standard library)
|
||||
# - libevent-dev => Used by crystal's internal scheduler (?)
|
||||
# - libpcre3-dev => Used by Crystal's regex engine (?)
|
||||
#
|
||||
# - libsqlite3-dev => Used to open .db files from NewPipe exports
|
||||
# - zlib1g-dev => TBD
|
||||
# - libreadline-dev => TBD
|
||||
#
|
||||
#
|
||||
# Tested on:
|
||||
# - OpenSUSE Leap 15.3
|
||||
|
||||
#
|
||||
# Load system details
|
||||
#
|
||||
|
||||
if [ -e /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
elif [ -e /usr/lib/os-release ]; then
|
||||
. /usr/lib/os-release
|
||||
else
|
||||
echo "Unsupported Linux system"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
#
|
||||
# Some variables
|
||||
#
|
||||
|
||||
repo_base_url="https://download.opensuse.org/repositories/devel:/languages:/crystal/"
|
||||
repo_end_url="devel:languages:crystal.repo"
|
||||
|
||||
apt_gpg_key="/usr/share/keyrings/crystal.gpg"
|
||||
apt_list_file="/etc/apt/sources.list.d/crystal.list"
|
||||
|
||||
yum_repo_file="/etc/yum.repos.d/crystal.repo"
|
||||
|
||||
#
|
||||
# Major install functions
|
||||
#
|
||||
|
||||
make_repo_url() {
|
||||
echo "${repo_base_url}/${1}/${repo_end_url}"
|
||||
}
|
||||
|
||||
|
||||
install_apt() {
|
||||
repo="$1"
|
||||
|
||||
echo "Adding Crystal repository"
|
||||
|
||||
curl -fsSL "${repo_base_url}/${repo}/Release.key" \
|
||||
| gpg --dearmor \
|
||||
| sudo tee "${apt_gpg_key}" > /dev/null
|
||||
|
||||
echo "deb [signed-by=${apt_gpg_key}] ${repo_base_url}/${repo}/ /" \
|
||||
| sudo tee "$apt_list_file"
|
||||
|
||||
sudo apt-get update
|
||||
|
||||
sudo apt-get install --yes --no-install-recommends \
|
||||
libssl-dev libxml2-dev libyaml-dev libgmp-dev libevent-dev \
|
||||
libpcre3-dev libreadline-dev libsqlite3-dev zlib1g-dev \
|
||||
crystal postgresql-13 git librsvg2-bin make
|
||||
}
|
||||
|
||||
install_yum() {
|
||||
repo=$(make_repo_url "$1")
|
||||
|
||||
echo "Adding Crystal repository"
|
||||
|
||||
cat << END | sudo tee "${yum_repo_file}" > /dev/null
|
||||
[crystal]
|
||||
name=Crystal
|
||||
type=rpm-md
|
||||
baseurl=${repo}/
|
||||
gpgcheck=1
|
||||
gpgkey=${repo}/repodata/repomd.xml.key
|
||||
enabled=1
|
||||
END
|
||||
|
||||
sudo yum -y install \
|
||||
openssl-devel libxml2-devel libyaml-devel gmp-devel \
|
||||
readline-devel sqlite-devel \
|
||||
crystal postgresql postgresql-server git librsvg2-tools make
|
||||
}
|
||||
|
||||
install_pacman() {
|
||||
# TODO: find an alternative to --no-confirm?
|
||||
sudo pacman -S --no-confirm \
|
||||
base-devel librsvg postgresql crystal
|
||||
}
|
||||
|
||||
install_zypper()
|
||||
{
|
||||
repo=$(make_repo_url "$1")
|
||||
|
||||
echo "Adding Crystal repository"
|
||||
sudo zypper --non-interactive addrepo -f "$repo"
|
||||
|
||||
sudo zypper --non-interactive --gpg-auto-import-keys install --no-recommends \
|
||||
libopenssl-devel libxml2-devel libyaml-devel gmp-devel libevent-devel \
|
||||
pcre-devel readline-devel sqlite3-devel zlib-devel \
|
||||
crystal postgresql postgresql-server git rsvg-convert make
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# System-specific logic
|
||||
#
|
||||
|
||||
case "$ID" in
|
||||
archlinux) install_pacman;;
|
||||
|
||||
centos) install_dnf "CentOS_${VERSION_ID}";;
|
||||
|
||||
debian)
|
||||
case "$VERSION_CODENAME" in
|
||||
sid) install_apt "Debian_Unstable";;
|
||||
bookworm) install_apt "Debian_Testing";;
|
||||
*) install_apt "Debian_${VERSION_ID}";;
|
||||
esac
|
||||
;;
|
||||
|
||||
fedora)
|
||||
if [ "$VERSION" == *"Prerelease"* ]; then
|
||||
install_dnf "Fedora_Rawhide"
|
||||
else
|
||||
install_dnf "Fedora_${VERSION}"
|
||||
fi
|
||||
;;
|
||||
|
||||
opensuse-leap) install_zypper "openSUSE_Leap_${VERSION}";;
|
||||
|
||||
opensuse-tumbleweed) install_zypper "openSUSE_Tumbleweed";;
|
||||
|
||||
rhel) install_dnf "RHEL_${VERSION_ID}";;
|
||||
|
||||
ubuntu)
|
||||
# Small workaround for recently released 22.04
|
||||
case "$VERSION_ID" in
|
||||
22.04) install_apt "xUbuntu_21.04";;
|
||||
*) install_apt "xUbuntu_${VERSION_ID}";;
|
||||
esac
|
||||
;;
|
||||
|
||||
*)
|
||||
# Try to match on ID_LIKE instead
|
||||
# Not guaranteed to 100% work
|
||||
case "$ID_LIKE" in
|
||||
archlinux) install_pacman;;
|
||||
centos) install_dnf "CentOS_${VERSION_ID}";;
|
||||
debian) install_apt "Debian_${VERSION_ID}";;
|
||||
*)
|
||||
echo "Error: distribution ${CODENAME} is not supported"
|
||||
echo "Please install dependencies manually"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
@ -0,0 +1,109 @@
|
||||
require "../parsers_helper.cr"
|
||||
|
||||
Spectator.describe Invidious::Hashtag do
|
||||
it "parses richItemRenderer containers (test 1)" do
|
||||
# Enable mock
|
||||
test_content = load_mock("hashtag/martingarrix_page1")
|
||||
videos, _ = extract_items(test_content)
|
||||
|
||||
expect(typeof(videos)).to eq(Array(SearchItem))
|
||||
expect(videos.size).to eq(60)
|
||||
|
||||
#
|
||||
# Random video check 1
|
||||
#
|
||||
expect(typeof(videos[11])).to eq(SearchItem)
|
||||
|
||||
video_11 = videos[11].as(SearchVideo)
|
||||
|
||||
expect(video_11.id).to eq("06eSsOWcKYA")
|
||||
expect(video_11.title).to eq("Martin Garrix - Live @ Tomorrowland 2018")
|
||||
|
||||
expect(video_11.ucid).to eq("UC5H_KXkPbEsGs0tFt8R35mA")
|
||||
expect(video_11.author).to eq("Martin Garrix")
|
||||
expect(video_11.author_verified).to be_true
|
||||
|
||||
expect(video_11.published).to be_close(Time.utc - 3.years, 1.second)
|
||||
expect(video_11.length_seconds).to eq((56.minutes + 41.seconds).total_seconds.to_i32)
|
||||
expect(video_11.views).to eq(40_504_893)
|
||||
|
||||
expect(video_11.live_now).to be_false
|
||||
expect(video_11.premium).to be_false
|
||||
expect(video_11.premiere_timestamp).to be_nil
|
||||
|
||||
#
|
||||
# Random video check 2
|
||||
#
|
||||
expect(typeof(videos[35])).to eq(SearchItem)
|
||||
|
||||
video_35 = videos[35].as(SearchVideo)
|
||||
|
||||
expect(video_35.id).to eq("b9HpOAYjY9I")
|
||||
expect(video_35.title).to eq("Martin Garrix feat. Mike Yung - Dreamer (Official Video)")
|
||||
|
||||
expect(video_35.ucid).to eq("UC5H_KXkPbEsGs0tFt8R35mA")
|
||||
expect(video_35.author).to eq("Martin Garrix")
|
||||
expect(video_35.author_verified).to be_true
|
||||
|
||||
expect(video_35.published).to be_close(Time.utc - 3.years, 1.second)
|
||||
expect(video_35.length_seconds).to eq((3.minutes + 14.seconds).total_seconds.to_i32)
|
||||
expect(video_35.views).to eq(30_790_049)
|
||||
|
||||
expect(video_35.live_now).to be_false
|
||||
expect(video_35.premium).to be_false
|
||||
expect(video_35.premiere_timestamp).to be_nil
|
||||
end
|
||||
|
||||
it "parses richItemRenderer containers (test 2)" do
|
||||
# Enable mock
|
||||
test_content = load_mock("hashtag/martingarrix_page2")
|
||||
videos, _ = extract_items(test_content)
|
||||
|
||||
expect(typeof(videos)).to eq(Array(SearchItem))
|
||||
expect(videos.size).to eq(60)
|
||||
|
||||
#
|
||||
# Random video check 1
|
||||
#
|
||||
expect(typeof(videos[41])).to eq(SearchItem)
|
||||
|
||||
video_41 = videos[41].as(SearchVideo)
|
||||
|
||||
expect(video_41.id).to eq("qhstH17zAjs")
|
||||
expect(video_41.title).to eq("Martin Garrix Radio - Episode 391")
|
||||
|
||||
expect(video_41.ucid).to eq("UC5H_KXkPbEsGs0tFt8R35mA")
|
||||
expect(video_41.author).to eq("Martin Garrix")
|
||||
expect(video_41.author_verified).to be_true
|
||||
|
||||
expect(video_41.published).to be_close(Time.utc - 2.months, 1.second)
|
||||
expect(video_41.length_seconds).to eq((1.hour).total_seconds.to_i32)
|
||||
expect(video_41.views).to eq(63_240)
|
||||
|
||||
expect(video_41.live_now).to be_false
|
||||
expect(video_41.premium).to be_false
|
||||
expect(video_41.premiere_timestamp).to be_nil
|
||||
|
||||
#
|
||||
# Random video check 2
|
||||
#
|
||||
expect(typeof(videos[48])).to eq(SearchItem)
|
||||
|
||||
video_48 = videos[48].as(SearchVideo)
|
||||
|
||||
expect(video_48.id).to eq("lqGvW0NIfdc")
|
||||
expect(video_48.title).to eq("Martin Garrix SENTIO Full Album Mix by Sakul")
|
||||
|
||||
expect(video_48.ucid).to eq("UC3833PXeLTS6yRpwGMQpp4Q")
|
||||
expect(video_48.author).to eq("SAKUL")
|
||||
expect(video_48.author_verified).to be_false
|
||||
|
||||
expect(video_48.published).to be_close(Time.utc - 3.weeks, 1.second)
|
||||
expect(video_48.length_seconds).to eq((35.minutes + 46.seconds).total_seconds.to_i32)
|
||||
expect(video_48.views).to eq(68_704)
|
||||
|
||||
expect(video_48.live_now).to be_false
|
||||
expect(video_48.premium).to be_false
|
||||
expect(video_48.premiere_timestamp).to be_nil
|
||||
end
|
||||
end
|
@ -0,0 +1,46 @@
|
||||
require "../spec_helper"
|
||||
|
||||
Spectator.describe "Utils" do
|
||||
describe "decode_date" do
|
||||
it "parses short dates (en-US)" do
|
||||
expect(decode_date("1s ago")).to be_close(Time.utc - 1.second, 500.milliseconds)
|
||||
expect(decode_date("2min ago")).to be_close(Time.utc - 2.minutes, 500.milliseconds)
|
||||
expect(decode_date("3h ago")).to be_close(Time.utc - 3.hours, 500.milliseconds)
|
||||
expect(decode_date("4d ago")).to be_close(Time.utc - 4.days, 500.milliseconds)
|
||||
expect(decode_date("5w ago")).to be_close(Time.utc - 5.weeks, 500.milliseconds)
|
||||
expect(decode_date("6mo ago")).to be_close(Time.utc - 6.months, 500.milliseconds)
|
||||
expect(decode_date("7y ago")).to be_close(Time.utc - 7.years, 500.milliseconds)
|
||||
end
|
||||
|
||||
it "parses short dates (en-GB)" do
|
||||
expect(decode_date("55s ago")).to be_close(Time.utc - 55.seconds, 500.milliseconds)
|
||||
expect(decode_date("44min ago")).to be_close(Time.utc - 44.minutes, 500.milliseconds)
|
||||
expect(decode_date("22hr ago")).to be_close(Time.utc - 22.hours, 500.milliseconds)
|
||||
expect(decode_date("1day ago")).to be_close(Time.utc - 1.day, 500.milliseconds)
|
||||
expect(decode_date("2days ago")).to be_close(Time.utc - 2.days, 500.milliseconds)
|
||||
expect(decode_date("3wk ago")).to be_close(Time.utc - 3.weeks, 500.milliseconds)
|
||||
expect(decode_date("11mo ago")).to be_close(Time.utc - 11.months, 500.milliseconds)
|
||||
expect(decode_date("11yr ago")).to be_close(Time.utc - 11.years, 500.milliseconds)
|
||||
end
|
||||
|
||||
it "parses long forms (singular)" do
|
||||
expect(decode_date("1 second ago")).to be_close(Time.utc - 1.second, 500.milliseconds)
|
||||
expect(decode_date("1 minute ago")).to be_close(Time.utc - 1.minute, 500.milliseconds)
|
||||
expect(decode_date("1 hour ago")).to be_close(Time.utc - 1.hour, 500.milliseconds)
|
||||
expect(decode_date("1 day ago")).to be_close(Time.utc - 1.day, 500.milliseconds)
|
||||
expect(decode_date("1 week ago")).to be_close(Time.utc - 1.week, 500.milliseconds)
|
||||
expect(decode_date("1 month ago")).to be_close(Time.utc - 1.month, 500.milliseconds)
|
||||
expect(decode_date("1 year ago")).to be_close(Time.utc - 1.year, 500.milliseconds)
|
||||
end
|
||||
|
||||
it "parses long forms (plural)" do
|
||||
expect(decode_date("5 seconds ago")).to be_close(Time.utc - 5.seconds, 500.milliseconds)
|
||||
expect(decode_date("17 minutes ago")).to be_close(Time.utc - 17.minutes, 500.milliseconds)
|
||||
expect(decode_date("23 hours ago")).to be_close(Time.utc - 23.hours, 500.milliseconds)
|
||||
expect(decode_date("3 days ago")).to be_close(Time.utc - 3.days, 500.milliseconds)
|
||||
expect(decode_date("2 weeks ago")).to be_close(Time.utc - 2.weeks, 500.milliseconds)
|
||||
expect(decode_date("9 months ago")).to be_close(Time.utc - 9.months, 500.milliseconds)
|
||||
expect(decode_date("8 years ago")).to be_close(Time.utc - 8.years, 500.milliseconds)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,111 @@
|
||||
require "../../parsers_helper.cr"
|
||||
|
||||
Spectator.describe "parse_video_info" do
|
||||
it "parses scheduled livestreams data" do
|
||||
# Enable mock
|
||||
_player = load_mock("video/scheduled_live_PBD-Podcast.player")
|
||||
_next = load_mock("video/scheduled_live_PBD-Podcast.next")
|
||||
|
||||
raw_data = _player.merge!(_next)
|
||||
info = parse_video_info("N-yVic7BbY0", raw_data)
|
||||
|
||||
# Some basic verifications
|
||||
expect(typeof(info)).to eq(Hash(String, JSON::Any))
|
||||
|
||||
expect(info["videoType"].as_s).to eq("Scheduled")
|
||||
|
||||
# Basic video infos
|
||||
|
||||
expect(info["title"].as_s).to eq("Home Team | PBD Podcast | Ep. 241")
|
||||
expect(info["views"].as_i).to eq(6)
|
||||
expect(info["likes"].as_i).to eq(7)
|
||||
expect(info["lengthSeconds"].as_i).to eq(0_i64)
|
||||
expect(info["published"].as_s).to eq("2023-02-28T14:00:00Z") # Unix 1677592800
|
||||
|
||||
# Extra video infos
|
||||
|
||||
expect(info["allowedRegions"].as_a).to_not be_empty
|
||||
expect(info["allowedRegions"].as_a.size).to eq(249)
|
||||
|
||||
expect(info["allowedRegions"].as_a).to contain(
|
||||
"AD", "AR", "BA", "BT", "CZ", "FO", "GL", "IO", "KE", "KH", "LS",
|
||||
"LT", "MP", "NO", "PR", "RO", "SE", "SK", "SS", "SX", "SZ", "ZW"
|
||||
)
|
||||
|
||||
expect(info["keywords"].as_a).to_not be_empty
|
||||
expect(info["keywords"].as_a.size).to eq(25)
|
||||
|
||||
expect(info["keywords"].as_a).to contain_exactly(
|
||||
"Patrick Bet-David",
|
||||
"Valeutainment",
|
||||
"The BetDavid Podcast",
|
||||
"The BetDavid Show",
|
||||
"Betdavid",
|
||||
"PBD",
|
||||
"BetDavid show",
|
||||
"Betdavid podcast",
|
||||
"podcast betdavid",
|
||||
"podcast patrick",
|
||||
"patrick bet david podcast",
|
||||
"Valuetainment podcast",
|
||||
"Entrepreneurs",
|
||||
"Entrepreneurship",
|
||||
"Entrepreneur Motivation",
|
||||
"Entrepreneur Advice",
|
||||
"Startup Entrepreneurs",
|
||||
"valuetainment",
|
||||
"patrick bet david",
|
||||
"PBD podcast",
|
||||
"Betdavid show",
|
||||
"Betdavid Podcast",
|
||||
"Podcast Betdavid",
|
||||
"Show Betdavid",
|
||||
"PBDPodcast"
|
||||
).in_any_order
|
||||
|
||||
expect(info["allowRatings"].as_bool).to be_true
|
||||
expect(info["isFamilyFriendly"].as_bool).to be_true
|
||||
expect(info["isListed"].as_bool).to be_true
|
||||
expect(info["isUpcoming"].as_bool).to be_true
|
||||
|
||||
# Related videos
|
||||
|
||||
expect(info["relatedVideos"].as_a.size).to eq(20)
|
||||
|
||||
expect(info["relatedVideos"][0]["id"]).to eq("j7jPzzjbVuk")
|
||||
expect(info["relatedVideos"][0]["author"]).to eq("Democracy Now!")
|
||||
expect(info["relatedVideos"][0]["ucid"]).to eq("UCzuqE7-t13O4NIDYJfakrhw")
|
||||
expect(info["relatedVideos"][0]["view_count"]).to eq("7576")
|
||||
expect(info["relatedVideos"][0]["short_view_count"]).to eq("7.5K")
|
||||
expect(info["relatedVideos"][0]["author_verified"]).to eq("true")
|
||||
|
||||
# Description
|
||||
|
||||
description_start_text = "PBD Podcast Episode 241. The home team is ready and at it again with the latest news, interesting topics and trending conversations on topics that matter. Try our sponsor Aura for 14 days free - https://aura.com/pbd"
|
||||
|
||||
expect(info["description"].as_s).to start_with(description_start_text)
|
||||
expect(info["shortDescription"].as_s).to start_with(description_start_text)
|
||||
|
||||
# TODO: Update mocks right before the start of PDB podcast, either on friday or saturday (time unknown)
|
||||
# expect(info["descriptionHtml"].as_s).to start_with(
|
||||
# "PBD Podcast Episode 241. The home team is ready and at it again with the latest news, interesting topics and trending conversations on topics that matter. Try our sponsor Aura for 14 days free - <a href=\"https://aura.com/pbd\">aura.com/pbd</a>"
|
||||
# )
|
||||
|
||||
# Video metadata
|
||||
|
||||
expect(info["genre"].as_s).to eq("Entertainment")
|
||||
expect(info["genreUcid"].as_s).to be_empty
|
||||
expect(info["license"].as_s).to be_empty
|
||||
|
||||
# Author infos
|
||||
|
||||
expect(info["author"].as_s).to eq("PBD Podcast")
|
||||
expect(info["ucid"].as_s).to eq("UCGX7nGXpz-CmO_Arg-cgJ7A")
|
||||
|
||||
expect(info["authorThumbnail"].as_s).to eq(
|
||||
"https://yt3.ggpht.com/61ArDiQshJrvSXcGLhpFfIO3hlMabe2fksitcf6oGob0Mdr5gztdkXxRljICUodL4iuTSrtxW4A=s48-c-k-c0x00ffffff-no-rj"
|
||||
)
|
||||
expect(info["authorVerified"].as_bool).to be_false
|
||||
expect(info["subCountText"].as_s).to eq("594K")
|
||||
end
|
||||
end
|
@ -0,0 +1,35 @@
|
||||
require "db"
|
||||
require "json"
|
||||
require "kemal"
|
||||
|
||||
require "protodec/utils"
|
||||
|
||||
require "spectator"
|
||||
|
||||
require "../src/invidious/exceptions"
|
||||
require "../src/invidious/helpers/macros"
|
||||
require "../src/invidious/helpers/logger"
|
||||
require "../src/invidious/helpers/utils"
|
||||
|
||||
require "../src/invidious/videos"
|
||||
require "../src/invidious/videos/*"
|
||||
require "../src/invidious/comments/content"
|
||||
|
||||
require "../src/invidious/helpers/serialized_yt_data"
|
||||
require "../src/invidious/yt_backend/extractors"
|
||||
require "../src/invidious/yt_backend/extractors_utils"
|
||||
|
||||
OUTPUT = File.open(File::NULL, "w")
|
||||
LOGGER = Invidious::LogHandler.new(OUTPUT, LogLevel::Off)
|
||||
|
||||
def load_mock(file) : Hash(String, JSON::Any)
|
||||
file = File.join(__DIR__, "..", "mocks", file + ".json")
|
||||
content = File.read(file)
|
||||
|
||||
return JSON.parse(content).as_h
|
||||
end
|
||||
|
||||
Spectator.configure do |config|
|
||||
config.fail_blank
|
||||
config.randomize
|
||||
end
|
@ -1,93 +1,28 @@
|
||||
def fetch_channel_playlists(ucid, author, continuation, sort_by)
|
||||
if continuation
|
||||
response_json = YoutubeAPI.browse(continuation)
|
||||
continuation_items = response_json["onResponseReceivedActions"]?
|
||||
.try &.[0]["appendContinuationItemsAction"]["continuationItems"]
|
||||
|
||||
return [] of SearchItem, nil if !continuation_items
|
||||
|
||||
items = [] of SearchItem
|
||||
continuation_items.as_a.select(&.as_h.has_key?("gridPlaylistRenderer")).each { |item|
|
||||
extract_item(item, author, ucid).try { |t| items << t }
|
||||
}
|
||||
|
||||
continuation = continuation_items.as_a.last["continuationItemRenderer"]?
|
||||
.try &.["continuationEndpoint"]["continuationCommand"]["token"].as_s
|
||||
initial_data = YoutubeAPI.browse(continuation)
|
||||
else
|
||||
url = "/channel/#{ucid}/playlists?flow=list&view=1"
|
||||
|
||||
params =
|
||||
case sort_by
|
||||
when "last", "last_added"
|
||||
#
|
||||
# Equivalent to "&sort=lad"
|
||||
# {"2:string": "playlists", "3:varint": 4, "4:varint": 1, "6:varint": 1}
|
||||
"EglwbGF5bGlzdHMYBCABMAE%3D"
|
||||
when "oldest", "oldest_created"
|
||||
url += "&sort=da"
|
||||
# formerly "&sort=da"
|
||||
# Not available anymore :c or maybe ??
|
||||
# {"2:string": "playlists", "3:varint": 2, "4:varint": 1, "6:varint": 1}
|
||||
"EglwbGF5bGlzdHMYAiABMAE%3D"
|
||||
# {"2:string": "playlists", "3:varint": 1, "4:varint": 1, "6:varint": 1}
|
||||
# "EglwbGF5bGlzdHMYASABMAE%3D"
|
||||
when "newest", "newest_created"
|
||||
url += "&sort=dd"
|
||||
else nil # Ignore
|
||||
end
|
||||
|
||||
response = YT_POOL.client &.get(url)
|
||||
initial_data = extract_initial_data(response.body)
|
||||
return [] of SearchItem, nil if !initial_data
|
||||
|
||||
items = extract_items(initial_data, author, ucid)
|
||||
continuation = response.body.match(/"token":"(?<continuation>[^"]+)"/).try &.["continuation"]?
|
||||
# Formerly "&sort=dd"
|
||||
# {"2:string": "playlists", "3:varint": 3, "4:varint": 1, "6:varint": 1}
|
||||
"EglwbGF5bGlzdHMYAyABMAE%3D"
|
||||
end
|
||||
|
||||
return items, continuation
|
||||
end
|
||||
|
||||
# ## NOTE: DEPRECATED
|
||||
# Reason -> Unstable
|
||||
# The Protobuf object must be provided with an id of the last playlist from the current "page"
|
||||
# in order to fetch the next one accurately
|
||||
# (if the id isn't included, entries shift around erratically between pages,
|
||||
# leading to repetitions and skip overs)
|
||||
#
|
||||
# Since it's impossible to produce the appropriate Protobuf without an id being provided by the user,
|
||||
# it's better to stick to continuation tokens provided by the first request and onward
|
||||
def produce_channel_playlists_url(ucid, cursor, sort = "newest", auto_generated = false)
|
||||
object = {
|
||||
"80226972:embedded" => {
|
||||
"2:string" => ucid,
|
||||
"3:base64" => {
|
||||
"2:string" => "playlists",
|
||||
"6:varint" => 2_i64,
|
||||
"7:varint" => 1_i64,
|
||||
"12:varint" => 1_i64,
|
||||
"13:string" => "",
|
||||
"23:varint" => 0_i64,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if cursor
|
||||
cursor = Base64.urlsafe_encode(cursor, false) if !auto_generated
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = cursor
|
||||
initial_data = YoutubeAPI.browse(ucid, params: params || "")
|
||||
end
|
||||
|
||||
if auto_generated
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0x32_i64
|
||||
else
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 1_i64
|
||||
case sort
|
||||
when "oldest", "oldest_created"
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 2_i64
|
||||
when "newest", "newest_created"
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 3_i64
|
||||
when "last", "last_added"
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 4_i64
|
||||
else nil # Ignore
|
||||
end
|
||||
end
|
||||
|
||||
object["80226972:embedded"]["3:string"] = Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json(object["80226972:embedded"]["3:base64"])))
|
||||
object["80226972:embedded"].delete("3:base64")
|
||||
|
||||
continuation = object.try { |i| Protodec::Any.cast_json(i) }
|
||||
.try { |i| Protodec::Any.from_json(i) }
|
||||
.try { |i| Base64.urlsafe_encode(i) }
|
||||
.try { |i| URI.encode_www_form(i) }
|
||||
|
||||
return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en"
|
||||
return extract_items(initial_data, author, ucid)
|
||||
end
|
||||
|
@ -1,89 +1,176 @@
|
||||
def produce_channel_videos_continuation(ucid, page = 1, auto_generated = nil, sort_by = "newest", v2 = false)
|
||||
object_inner_2 = {
|
||||
"2:0:embedded" => {
|
||||
"1:0:varint" => 0_i64,
|
||||
},
|
||||
"5:varint" => 50_i64,
|
||||
"6:varint" => 1_i64,
|
||||
"7:varint" => (page * 30).to_i64,
|
||||
"9:varint" => 1_i64,
|
||||
"10:varint" => 0_i64,
|
||||
}
|
||||
|
||||
object_inner_2_encoded = object_inner_2
|
||||
.try { |i| Protodec::Any.cast_json(i) }
|
||||
.try { |i| Protodec::Any.from_json(i) }
|
||||
.try { |i| Base64.urlsafe_encode(i) }
|
||||
.try { |i| URI.encode_www_form(i) }
|
||||
|
||||
sort_by_numerical =
|
||||
case sort_by
|
||||
when "newest" then 1_i64
|
||||
when "popular" then 2_i64
|
||||
when "oldest" then 3_i64 # Broken as of 10/2022 :c
|
||||
else 1_i64 # Fallback to "newest"
|
||||
end
|
||||
|
||||
object_inner_1 = {
|
||||
"110:embedded" => {
|
||||
"3:embedded" => {
|
||||
"15:embedded" => {
|
||||
"1:embedded" => {
|
||||
"1:string" => object_inner_2_encoded,
|
||||
},
|
||||
"2:embedded" => {
|
||||
"1:string" => "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
"3:varint" => sort_by_numerical,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
object_inner_1_encoded = object_inner_1
|
||||
.try { |i| Protodec::Any.cast_json(i) }
|
||||
.try { |i| Protodec::Any.from_json(i) }
|
||||
.try { |i| Base64.urlsafe_encode(i) }
|
||||
.try { |i| URI.encode_www_form(i) }
|
||||
|
||||
object = {
|
||||
"80226972:embedded" => {
|
||||
"2:string" => ucid,
|
||||
"3:base64" => {
|
||||
"2:string" => "videos",
|
||||
"6:varint" => 2_i64,
|
||||
"7:varint" => 1_i64,
|
||||
"12:varint" => 1_i64,
|
||||
"13:string" => "",
|
||||
"23:varint" => 0_i64,
|
||||
},
|
||||
"3:string" => object_inner_1_encoded,
|
||||
"35:string" => "browse-feed#{ucid}videos102",
|
||||
},
|
||||
}
|
||||
|
||||
if !v2
|
||||
if auto_generated
|
||||
seed = Time.unix(1525757349)
|
||||
until seed >= Time.utc
|
||||
seed += 1.month
|
||||
continuation = object.try { |i| Protodec::Any.cast_json(i) }
|
||||
.try { |i| Protodec::Any.from_json(i) }
|
||||
.try { |i| Base64.urlsafe_encode(i) }
|
||||
.try { |i| URI.encode_www_form(i) }
|
||||
|
||||
return continuation
|
||||
end
|
||||
timestamp = seed - (page - 1).months
|
||||
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0x36_i64
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = "#{timestamp.to_unix}"
|
||||
else
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0_i64
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = "#{page}"
|
||||
# Used in bypass_captcha_job.cr
|
||||
def produce_channel_videos_url(ucid, page = 1, auto_generated = nil, sort_by = "newest", v2 = false)
|
||||
continuation = produce_channel_videos_continuation(ucid, page, auto_generated, sort_by, v2)
|
||||
return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en"
|
||||
end
|
||||
else
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0_i64
|
||||
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["61:string"] = Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json({
|
||||
"1:string" => Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json({
|
||||
"1:varint" => 30_i64 * (page - 1),
|
||||
}))),
|
||||
})))
|
||||
module Invidious::Channel::Tabs
|
||||
extend self
|
||||
|
||||
# -------------------
|
||||
# Regular videos
|
||||
# -------------------
|
||||
|
||||
def make_initial_video_ctoken(ucid, sort_by) : String
|
||||
return produce_channel_videos_continuation(ucid, sort_by: sort_by)
|
||||
end
|
||||
|
||||
case sort_by
|
||||
when "newest"
|
||||
when "popular"
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 0x01_i64
|
||||
when "oldest"
|
||||
object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 0x02_i64
|
||||
else nil # Ignore
|
||||
# Wrapper for AboutChannel, as we still need to call get_videos with
|
||||
# an author name and ucid directly (e.g in RSS feeds).
|
||||
# TODO: figure out how to get rid of that
|
||||
def get_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||
return get_videos(
|
||||
channel.author, channel.ucid,
|
||||
continuation: continuation, sort_by: sort_by
|
||||
)
|
||||
end
|
||||
|
||||
object["80226972:embedded"]["3:string"] = Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json(object["80226972:embedded"]["3:base64"])))
|
||||
object["80226972:embedded"].delete("3:base64")
|
||||
# Wrapper for InvidiousChannel, as we still need to call get_videos with
|
||||
# an author name and ucid directly (e.g in RSS feeds).
|
||||
# TODO: figure out how to get rid of that
|
||||
def get_videos(channel : InvidiousChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||
return get_videos(
|
||||
channel.author, channel.id,
|
||||
continuation: continuation, sort_by: sort_by
|
||||
)
|
||||
end
|
||||
|
||||
continuation = object.try { |i| Protodec::Any.cast_json(i) }
|
||||
.try { |i| Protodec::Any.from_json(i) }
|
||||
.try { |i| Base64.urlsafe_encode(i) }
|
||||
.try { |i| URI.encode_www_form(i) }
|
||||
def get_videos(author : String, ucid : String, *, continuation : String? = nil, sort_by = "newest")
|
||||
continuation ||= make_initial_video_ctoken(ucid, sort_by)
|
||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||
|
||||
return continuation
|
||||
return extract_items(initial_data, author, ucid)
|
||||
end
|
||||
|
||||
def get_60_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
|
||||
if continuation.nil?
|
||||
# Fetch the first "page" of video
|
||||
items, next_continuation = get_videos(channel, sort_by: sort_by)
|
||||
else
|
||||
# Fetch a "page" of videos using the given continuation token
|
||||
items, next_continuation = get_videos(channel, continuation: continuation)
|
||||
end
|
||||
|
||||
def get_channel_videos_response(ucid, page = 1, auto_generated = nil, sort_by = "newest")
|
||||
continuation = produce_channel_videos_continuation(ucid, page,
|
||||
auto_generated: auto_generated, sort_by: sort_by, v2: true)
|
||||
# If there is more to load, then load a second "page"
|
||||
# and replace the previous continuation token
|
||||
if !next_continuation.nil?
|
||||
items_2, next_continuation = get_videos(channel, continuation: next_continuation)
|
||||
items.concat items_2
|
||||
end
|
||||
|
||||
return YoutubeAPI.browse(continuation)
|
||||
return items, next_continuation
|
||||
end
|
||||
|
||||
def get_60_videos(ucid, author, page, auto_generated, sort_by = "newest")
|
||||
videos = [] of SearchVideo
|
||||
# -------------------
|
||||
# Shorts
|
||||
# -------------------
|
||||
|
||||
2.times do |i|
|
||||
initial_data = get_channel_videos_response(ucid, page * 2 + (i - 1), auto_generated: auto_generated, sort_by: sort_by)
|
||||
videos.concat extract_videos(initial_data, author, ucid)
|
||||
def get_shorts(channel : AboutChannel, continuation : String? = nil)
|
||||
if continuation.nil?
|
||||
# EgZzaG9ydHPyBgUKA5oBAA%3D%3D is the protobuf object to load "shorts"
|
||||
# TODO: try to extract the continuation tokens that allows other sorting options
|
||||
initial_data = YoutubeAPI.browse(channel.ucid, params: "EgZzaG9ydHPyBgUKA5oBAA%3D%3D")
|
||||
else
|
||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||
end
|
||||
return extract_items(initial_data, channel.author, channel.ucid)
|
||||
end
|
||||
|
||||
return videos.size, videos
|
||||
# -------------------
|
||||
# Livestreams
|
||||
# -------------------
|
||||
|
||||
def get_livestreams(channel : AboutChannel, continuation : String? = nil)
|
||||
if continuation.nil?
|
||||
# EgdzdHJlYW1z8gYECgJ6AA%3D%3D is the protobuf object to load "streams"
|
||||
initial_data = YoutubeAPI.browse(channel.ucid, params: "EgdzdHJlYW1z8gYECgJ6AA%3D%3D")
|
||||
else
|
||||
initial_data = YoutubeAPI.browse(continuation: continuation)
|
||||
end
|
||||
|
||||
def get_latest_videos(ucid)
|
||||
initial_data = get_channel_videos_response(ucid)
|
||||
author = initial_data["metadata"]?.try &.["channelMetadataRenderer"]?.try &.["title"]?.try &.as_s
|
||||
return extract_items(initial_data, channel.author, channel.ucid)
|
||||
end
|
||||
|
||||
return extract_videos(initial_data, author, ucid)
|
||||
def get_60_livestreams(channel : AboutChannel, continuation : String? = nil)
|
||||
if continuation.nil?
|
||||
# Fetch the first "page" of streams
|
||||
items, next_continuation = get_livestreams(channel)
|
||||
else
|
||||
# Fetch a "page" of streams using the given continuation token
|
||||
items, next_continuation = get_livestreams(channel, continuation: continuation)
|
||||
end
|
||||
|
||||
# Used in bypass_captcha_job.cr
|
||||
def produce_channel_videos_url(ucid, page = 1, auto_generated = nil, sort_by = "newest", v2 = false)
|
||||
continuation = produce_channel_videos_continuation(ucid, page, auto_generated, sort_by, v2)
|
||||
return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en"
|
||||
# If there is more to load, then load a second "page"
|
||||
# and replace the previous continuation token
|
||||
if !next_continuation.nil?
|
||||
items_2, next_continuation = get_livestreams(channel, continuation: next_continuation)
|
||||
items.concat items_2
|
||||
end
|
||||
|
||||
return items, next_continuation
|
||||
end
|
||||
end
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue