Compare commits
2531 Commits
0.13.0
...
disable-no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72f03cd9de | ||
|
|
7f445f6167 | ||
|
|
bf599284ed | ||
|
|
a21c10e209 | ||
|
|
56ee44ee09 | ||
|
|
c6df54a103 | ||
|
|
575b039170 | ||
|
|
7a32269d7f | ||
|
|
004e371051 | ||
|
|
d5f43bae92 | ||
|
|
e215a20a0a | ||
|
|
919413e2b9 | ||
|
|
45839f8d59 | ||
|
|
55b62f9fde | ||
|
|
4c3529f3d5 | ||
|
|
ab7580c0da | ||
|
|
25c079fded | ||
|
|
d8bb75be63 | ||
|
|
a8021e09a7 | ||
|
|
16530ac6de | ||
|
|
5d1a1fef0f | ||
|
|
6082887070 | ||
|
|
8da336b7aa | ||
|
|
897f871f99 | ||
|
|
2f335b3d2c | ||
|
|
3b1837a99b | ||
|
|
cc59de0c93 | ||
|
|
997d936e9c | ||
|
|
253256bf37 | ||
|
|
e60a1836fe | ||
|
|
b417ea8e3a | ||
|
|
b58b0440d6 | ||
|
|
a9d2d2211a | ||
|
|
fe057c7873 | ||
|
|
dfcaed93ea | ||
|
|
5e3fcad1fb | ||
|
|
555bb711c9 | ||
|
|
afa3eff313 | ||
|
|
9e91fd5241 | ||
|
|
d90d347006 | ||
|
|
385fe4be22 | ||
|
|
7ec88741a6 | ||
|
|
86cb68413d | ||
|
|
05c5d293eb | ||
|
|
fd0ac3a671 | ||
|
|
f109d812a1 | ||
|
|
5d2f2690e2 | ||
|
|
505a81d087 | ||
|
|
dfab62ce48 | ||
|
|
31699468fc | ||
|
|
857096ba5f | ||
|
|
36c7d4270b | ||
|
|
d77586d02d | ||
|
|
c8fbb74835 | ||
|
|
dfee375b98 | ||
|
|
9268258773 | ||
|
|
9031ca5c3f | ||
|
|
18d68184a9 | ||
|
|
3816f92095 | ||
|
|
fd55b08a1d | ||
|
|
d0d8ba0068 | ||
|
|
18197e7e3e | ||
|
|
f75a81c9ee | ||
|
|
9979a1b910 | ||
|
|
dbba9d7687 | ||
|
|
d199b7264e | ||
|
|
b4c532f062 | ||
|
|
b24a89f820 | ||
|
|
7112f35793 | ||
|
|
85ba04b715 | ||
|
|
8af202e86b | ||
|
|
57353fe0c6 | ||
|
|
6c116e34c4 | ||
|
|
9ff26ea4d4 | ||
|
|
2194bd2812 | ||
|
|
1e6cd0b18f | ||
|
|
85d178ff8b | ||
|
|
e0d09c3cda | ||
|
|
47f529dc09 | ||
|
|
dde850a2e9 | ||
|
|
34e1a465be | ||
|
|
7d756209b1 | ||
|
|
60e870b277 | ||
|
|
ddf1e84f7c | ||
|
|
ec183e3c9a | ||
|
|
01135db80a | ||
|
|
955e3de56d | ||
|
|
cdd473e195 | ||
|
|
34fe7f04fa | ||
|
|
55c280af5d | ||
|
|
ea5a4f6d6e | ||
|
|
d169f91d64 | ||
|
|
8b29895ef3 | ||
|
|
3cfc5c164d | ||
|
|
4e55d9937c | ||
|
|
03fe8d1716 | ||
|
|
d6d7d209c1 | ||
|
|
fa044a2971 | ||
|
|
72fce387db | ||
|
|
da10cdd5ab | ||
|
|
83de3a51ea | ||
|
|
8faf8b8e57 | ||
|
|
5a349ae88b | ||
|
|
e2fc64296d | ||
|
|
ec55b905cb | ||
|
|
492d1144e0 | ||
|
|
4f4b19a962 | ||
|
|
febd785428 | ||
|
|
8642c66710 | ||
|
|
76cc8ac66b | ||
|
|
53d349a648 | ||
|
|
b344e1aadb | ||
|
|
3c882cff6e | ||
|
|
f73aef33f0 | ||
|
|
d12dff9dcf | ||
|
|
99d770be64 | ||
|
|
71a8867a4a | ||
|
|
2bbd424fce | ||
|
|
ef8dc7272b | ||
|
|
ad4a06fca5 | ||
|
|
c04f45d5e3 | ||
|
|
fb36155022 | ||
|
|
7ace3fc989 | ||
|
|
170e754998 | ||
|
|
6a75fa08dc | ||
|
|
1c5db46a4d | ||
|
|
7e4690e43c | ||
|
|
698a6f3886 | ||
|
|
1668e4187e | ||
|
|
df599c0fc3 | ||
|
|
e98aeaf293 | ||
|
|
5026698ebf | ||
|
|
bd221b7b2c | ||
|
|
ba37259258 | ||
|
|
eca8d2e7d7 | ||
|
|
cb0f7bf6b0 | ||
|
|
1ec15dc073 | ||
|
|
f124e8cf93 | ||
|
|
9621175dc9 | ||
|
|
e6ddd6d6c1 | ||
|
|
d7ebd763f5 | ||
|
|
99091e919c | ||
|
|
6ddbccbc95 | ||
|
|
0ca333715b | ||
|
|
4e44a91d08 | ||
|
|
fc5f84a0cd | ||
|
|
2289f98c1e | ||
|
|
49edf0ee33 | ||
|
|
8532727d54 | ||
|
|
336653b02c | ||
|
|
d546f1870e | ||
|
|
15c66e2b01 | ||
|
|
be92bfd4af | ||
|
|
f5cb7ba96c | ||
|
|
f013a6b1d7 | ||
|
|
519c227c4f | ||
|
|
6f4665588f | ||
|
|
eba311baa9 | ||
|
|
e92b3779ad | ||
|
|
a82d21ff78 | ||
|
|
63e1165936 | ||
|
|
84cc732281 | ||
|
|
971b6ec96f | ||
|
|
46f7ca9ffa | ||
|
|
e85bc3e0f6 | ||
|
|
dee20f92a7 | ||
|
|
12b818a83c | ||
|
|
1c91110464 | ||
|
|
4cd7a3e83f | ||
|
|
fa99c9aa85 | ||
|
|
2d949834e9 | ||
|
|
c7b74aa8b4 | ||
|
|
5e3c9cf290 | ||
|
|
508f137b30 | ||
|
|
67dd2b419a | ||
|
|
0749baae4b | ||
|
|
df9f897ebe | ||
|
|
8ef1e81294 | ||
|
|
d755d05f88 | ||
|
|
ce4a52325b | ||
|
|
714a001332 | ||
|
|
c78f84d5c6 | ||
|
|
a6c9b263da | ||
|
|
5ad2fc64b4 | ||
|
|
2ae074a9a4 | ||
|
|
c5967ad572 | ||
|
|
5ece07a807 | ||
|
|
e00f9a869d | ||
|
|
662cf2f633 | ||
|
|
16f1a23c54 | ||
|
|
fb364b532c | ||
|
|
2bebf578c7 | ||
|
|
a5b9c83256 | ||
|
|
dc0e242a63 | ||
|
|
178e317d11 | ||
|
|
3aa740d877 | ||
|
|
951d7d8339 | ||
|
|
b5bd1f507e | ||
|
|
7b9368ae75 | ||
|
|
b2c3d9fb0a | ||
|
|
6bd48ca645 | ||
|
|
cdc007c83c | ||
|
|
7dbbe27cdd | ||
|
|
b91874355c | ||
|
|
8b0ed8b110 | ||
|
|
574e35a720 | ||
|
|
32ae38b933 | ||
|
|
56e505164d | ||
|
|
d4f3139b73 | ||
|
|
97dceb3a5a | ||
|
|
212f6d6bf5 | ||
|
|
36904fa72e | ||
|
|
8c2495a399 | ||
|
|
2d1f6128be | ||
|
|
9233f71549 | ||
|
|
b2a738cf13 | ||
|
|
34a79c5f1e | ||
|
|
fd30f5775a | ||
|
|
a2600acfa9 | ||
|
|
fa2c8f42b3 | ||
|
|
db4e36ad9b | ||
|
|
38697ade31 | ||
|
|
2cf3c7eb41 | ||
|
|
4ea932dc1f | ||
|
|
7b587d8e00 | ||
|
|
9612a4d0de | ||
|
|
7141a716e7 | ||
|
|
0d33566bbc | ||
|
|
2d341c53f2 | ||
|
|
b20443241d | ||
|
|
fb28431cdc | ||
|
|
2602df8ef7 | ||
|
|
9e7ff8f8f5 | ||
|
|
5b5473e794 | ||
|
|
73c000fd44 | ||
|
|
ef51661f5d | ||
|
|
6bb9e8442a | ||
|
|
9b1721f8ad | ||
|
|
15532fdb11 | ||
|
|
39e0b3f6c5 | ||
|
|
329bf7895e | ||
|
|
61feb5a851 | ||
|
|
da06560fa9 | ||
|
|
fd120ab275 | ||
|
|
14a57fd229 | ||
|
|
0be2ab582d | ||
|
|
d00037d1d4 | ||
|
|
e10827bf1c | ||
|
|
98a5a7cb4f | ||
|
|
748282cff3 | ||
|
|
f3df1e8d3c | ||
|
|
a93058d385 | ||
|
|
89135a9cf5 | ||
|
|
47a02e3423 | ||
|
|
c9fd8167f5 | ||
|
|
c9b9ded518 | ||
|
|
250a9191cb | ||
|
|
6fab5d0554 | ||
|
|
461fae4f77 | ||
|
|
8aed1e11c3 | ||
|
|
6cf6c56dd1 | ||
|
|
042ff8da64 | ||
|
|
8b72481b8c | ||
|
|
aa0724f204 | ||
|
|
eff8b23f57 | ||
|
|
81a2300af8 | ||
|
|
2a541cb4d5 | ||
|
|
0120f44fd6 | ||
|
|
b65dced646 | ||
|
|
e7394fe7a1 | ||
|
|
fb673639f9 | ||
|
|
dc62b6ffdb | ||
|
|
1f2ccac8a0 | ||
|
|
46e4d35ccd | ||
|
|
eb7e79fdf7 | ||
|
|
37c953e633 | ||
|
|
5bb2cb7d71 | ||
|
|
692f4e5be2 | ||
|
|
7bb1471207 | ||
|
|
4752e16ad2 | ||
|
|
2a156e7313 | ||
|
|
0314dd44ea | ||
|
|
b80a3d5401 | ||
|
|
8c4ef9f955 | ||
|
|
67d2635e41 | ||
|
|
71a1ad307c | ||
|
|
9a48fd81a3 | ||
|
|
d928b9eaad | ||
|
|
790b7afcca | ||
|
|
7b9f5d74dc | ||
|
|
6eadba784d | ||
|
|
3a39045617 | ||
|
|
67cb8b7107 | ||
|
|
4962c00ba8 | ||
|
|
6764185543 | ||
|
|
0a66a68db8 | ||
|
|
8d47ec714e | ||
|
|
9607fe03af | ||
|
|
62057e676a | ||
|
|
43ff3be751 | ||
|
|
7cbd79fee5 | ||
|
|
3fba342ed2 | ||
|
|
2419c4d8ae | ||
|
|
e37fb09d41 | ||
|
|
86b1357a68 | ||
|
|
b782ab5787 | ||
|
|
6c0486646f | ||
|
|
dffe8fc49e | ||
|
|
1336ecbef2 | ||
|
|
d00e430f50 | ||
|
|
5a728243a2 | ||
|
|
37f38dbf6c | ||
|
|
2eb7c5c037 | ||
|
|
ba0bc72d0b | ||
|
|
e1219cbdef | ||
|
|
bf0a48847c | ||
|
|
3bb7fbb2f1 | ||
|
|
326a362eb8 | ||
|
|
fdc380e7f7 | ||
|
|
db04f1556e | ||
|
|
444b1c99d0 | ||
|
|
3b1a286290 | ||
|
|
6c8a5a1e7f | ||
|
|
1769b0fdce | ||
|
|
68cbc11810 | ||
|
|
edcc155482 | ||
|
|
8231216371 | ||
|
|
e1215584b4 | ||
|
|
a905175e8c | ||
|
|
e98ed41656 | ||
|
|
fd139841f4 | ||
|
|
dac0f42777 | ||
|
|
f0788162a4 | ||
|
|
c963e50cde | ||
|
|
b4a4767fb3 | ||
|
|
d6918b2976 | ||
|
|
47e273c402 | ||
|
|
ba25e8e720 | ||
|
|
058ae5cc29 | ||
|
|
f8be5d2559 | ||
|
|
792d25594c | ||
|
|
9f9020e422 | ||
|
|
302fecbdcb | ||
|
|
d74873fed1 | ||
|
|
40ed4a0506 | ||
|
|
c25d664edc | ||
|
|
9bad7e2940 | ||
|
|
6704ce3214 | ||
|
|
914cfbd953 | ||
|
|
85cf27119c | ||
|
|
7691f53520 | ||
|
|
094f835642 | ||
|
|
92eea3b18b | ||
|
|
c021b93b5c | ||
|
|
d94d4c2045 | ||
|
|
46d08237c6 | ||
|
|
3deafe9f8d | ||
|
|
998edba6f0 | ||
|
|
4f219362fe | ||
|
|
c427444946 | ||
|
|
d59323a601 | ||
|
|
a779cdd463 | ||
|
|
73a142fd9b | ||
|
|
0453d08eed | ||
|
|
a18068aedf | ||
|
|
12fba3bb7a | ||
|
|
fc2b9031d4 | ||
|
|
28a6589a1e | ||
|
|
cf9c790499 | ||
|
|
3d6dceae10 | ||
|
|
130a14d0d9 | ||
|
|
f07b4f8fc4 | ||
|
|
75f655aafc | ||
|
|
a3fd9ccea7 | ||
|
|
4136ac633a | ||
|
|
2724456882 | ||
|
|
b396a2cbfd | ||
|
|
ddb06b0cac | ||
|
|
f54e247eb4 | ||
|
|
2ac19eb8fc | ||
|
|
6cdaafdc37 | ||
|
|
ed6476b5ea | ||
|
|
b90bceb2dc | ||
|
|
f31bd5ffb9 | ||
|
|
5abe7fe123 | ||
|
|
b6792cf02e | ||
|
|
1261323c66 | ||
|
|
2a45b4eba0 | ||
|
|
a6a0bbf398 | ||
|
|
7f3ef12297 | ||
|
|
da2f592de6 | ||
|
|
ee91effb7a | ||
|
|
b13f9c25b3 | ||
|
|
f85563eb66 | ||
|
|
7b689a186d | ||
|
|
092d7df761 | ||
|
|
6c444707d7 | ||
|
|
81c006cc04 | ||
|
|
7cbd1e413f | ||
|
|
f34f8ef188 | ||
|
|
c3eb385cd3 | ||
|
|
be34f03157 | ||
|
|
4964785b13 | ||
|
|
1b7757c14f | ||
|
|
58c9f20226 | ||
|
|
f19be0c3ce | ||
|
|
f236a6872b | ||
|
|
3e0096f360 | ||
|
|
438b334320 | ||
|
|
4aa96ecab9 | ||
|
|
e5557b515e | ||
|
|
7b9d26d688 | ||
|
|
8d4b4cd14c | ||
|
|
342fc202a7 | ||
|
|
4436359d07 | ||
|
|
91f8395222 | ||
|
|
de00e86cd5 | ||
|
|
c6e086c6ff | ||
|
|
82f3eda82b | ||
|
|
05f9613e14 | ||
|
|
50bb591826 | ||
|
|
2ca23c714d | ||
|
|
b030d822f1 | ||
|
|
65b5183f01 | ||
|
|
33431844b7 | ||
|
|
325a67155d | ||
|
|
6876f88f43 | ||
|
|
ad9d3f4425 | ||
|
|
5eca7a8a30 | ||
|
|
de153ece4e | ||
|
|
2d8964d37d | ||
|
|
ceb1feb350 | ||
|
|
2ea0590b03 | ||
|
|
80a513baa5 | ||
|
|
ba48f68fc3 | ||
|
|
319587e2f1 | ||
|
|
bf7952d9c7 | ||
|
|
f29ab53aff | ||
|
|
b5b0c58de7 | ||
|
|
a1bb421eec | ||
|
|
139786b9ef | ||
|
|
301444563b | ||
|
|
9966c21c6b | ||
|
|
12c219ee6c | ||
|
|
008f79df61 | ||
|
|
fd54cf2d05 | ||
|
|
e0d26f4055 | ||
|
|
6c2170960b | ||
|
|
950deaca40 | ||
|
|
2bd286f195 | ||
|
|
75792bfa7b | ||
|
|
737c49b689 | ||
|
|
2c447a42f2 | ||
|
|
dad8f9a0ce | ||
|
|
2eac23a0b3 | ||
|
|
6a204753c0 | ||
|
|
19bb26a789 | ||
|
|
00904ae3f2 | ||
|
|
d214a0b333 | ||
|
|
4940759627 | ||
|
|
be1a390b50 | ||
|
|
f707f990e6 | ||
|
|
a120f143d7 | ||
|
|
65fbdbff6a | ||
|
|
6ec4dcfafd | ||
|
|
48191aca6e | ||
|
|
83556bace2 | ||
|
|
814c9e6c3a | ||
|
|
547abe17d9 | ||
|
|
6b8450558d | ||
|
|
c3747c2d49 | ||
|
|
245122104a | ||
|
|
b0f127d4d8 | ||
|
|
d379a36c0e | ||
|
|
a12571e748 | ||
|
|
21879da80d | ||
|
|
6cabc96f75 | ||
|
|
f4f9e75c27 | ||
|
|
2f235adadf | ||
|
|
afb284fa9b | ||
|
|
93252f33ff | ||
|
|
6cf0ff6b49 | ||
|
|
86f75758a7 | ||
|
|
0ec94405ce | ||
|
|
33780f1995 | ||
|
|
1cb715ac9f | ||
|
|
f65b628bf3 | ||
|
|
60fa552469 | ||
|
|
437bdedb07 | ||
|
|
a531f4f057 | ||
|
|
bb396a310e | ||
|
|
66e7285108 | ||
|
|
2b81a82620 | ||
|
|
b555df8377 | ||
|
|
232730e909 | ||
|
|
2c1b4b0dd8 | ||
|
|
1e0712625a | ||
|
|
88752f32bd | ||
|
|
7a93d4c7e4 | ||
|
|
c3c767eed8 | ||
|
|
6f19a3a21e | ||
|
|
06be0fb351 | ||
|
|
e2bbc9a6fa | ||
|
|
17e481c107 | ||
|
|
b8f27a42a7 | ||
|
|
575c66efd3 | ||
|
|
88ad7c8d8d | ||
|
|
1adcac175e | ||
|
|
dd8c412abc | ||
|
|
e969c1490a | ||
|
|
e91421253e | ||
|
|
35d15c7c2b | ||
|
|
20cb751ff6 | ||
|
|
0614b52f03 | ||
|
|
a629521c37 | ||
|
|
5eb83bb39c | ||
|
|
0e17d026f2 | ||
|
|
ea0b1fdf44 | ||
|
|
1d78d67af3 | ||
|
|
d9c58c4837 | ||
|
|
129779a757 | ||
|
|
c8a985cc77 | ||
|
|
db7e7aae46 | ||
|
|
cb9b84f940 | ||
|
|
33d9be0ffb | ||
|
|
2571e420f3 | ||
|
|
e17c8b1f4d | ||
|
|
8805ee7c8c | ||
|
|
7df2fd0bc8 | ||
|
|
f7f0910953 | ||
|
|
d300797e22 | ||
|
|
56dbe15943 | ||
|
|
736d63c08f | ||
|
|
5f65e92500 | ||
|
|
a65b9b2b53 | ||
|
|
cc10e8ca5d | ||
|
|
dfe0c738d6 | ||
|
|
21b96a3159 | ||
|
|
888c7e2c29 | ||
|
|
d6eaeef8a6 | ||
|
|
961c5740b7 | ||
|
|
22e8f7e287 | ||
|
|
c26b383a1b | ||
|
|
2cec1fe4bb | ||
|
|
9da1c6c6de | ||
|
|
806ddf1aca | ||
|
|
0ad2793b68 | ||
|
|
585e4617e8 | ||
|
|
76c6886791 | ||
|
|
91bc6599c6 | ||
|
|
4d44b2c3a4 | ||
|
|
b49b5fbda9 | ||
|
|
17e6093abb | ||
|
|
3dc980e800 | ||
|
|
c44c1003af | ||
|
|
39c27f0c66 | ||
|
|
678b10dbcf | ||
|
|
a1d6411f1f | ||
|
|
c4c8a10507 | ||
|
|
ee94ccdeb0 | ||
|
|
e3df9f9ead | ||
|
|
62dc629337 | ||
|
|
dccdf38ce7 | ||
|
|
84124b837d | ||
|
|
6176da3cbb | ||
|
|
24bc3e2704 | ||
|
|
f13fb80b42 | ||
|
|
0a9e19646a | ||
|
|
65e45c4079 | ||
|
|
440105976f | ||
|
|
7eba7fbcc7 | ||
|
|
d9bfb3d305 | ||
|
|
4246c7a523 | ||
|
|
e3054ccdd2 | ||
|
|
df968d1919 | ||
|
|
7661c3c061 | ||
|
|
57ed047025 | ||
|
|
bda3a26423 | ||
|
|
080d9a8dc7 | ||
|
|
984a4acc7b | ||
|
|
b10f37bea9 | ||
|
|
0947c26612 | ||
|
|
f5e7fe34a5 | ||
|
|
c6f088d6ca | ||
|
|
21e29411af | ||
|
|
f85930700b | ||
|
|
707ef1d0f7 | ||
|
|
adc12addfa | ||
|
|
bfef9d4b6e | ||
|
|
fd29cb9023 | ||
|
|
d806310665 | ||
|
|
4982bff74d | ||
|
|
347c189f3f | ||
|
|
81b12b8001 | ||
|
|
3e781b7d13 | ||
|
|
9c44e41a4c | ||
|
|
402bb01151 | ||
|
|
82a1d9dffa | ||
|
|
570541ba49 | ||
|
|
73eabb6ca2 | ||
|
|
5a52b4fe45 | ||
|
|
caa08a6379 | ||
|
|
cd02078e26 | ||
|
|
0341faeb13 | ||
|
|
77131cff91 | ||
|
|
0c7726d4e1 | ||
|
|
1f1e14fba5 | ||
|
|
9be8263f26 | ||
|
|
7b2aa5f98e | ||
|
|
26b28cea49 | ||
|
|
23049e026f | ||
|
|
9ab242ca2e | ||
|
|
aa59925374 | ||
|
|
43ea8fa706 | ||
|
|
6df85718e6 | ||
|
|
82e6f6e095 | ||
|
|
9ba3e1cdb4 | ||
|
|
b20f72b963 | ||
|
|
092b8a4e52 | ||
|
|
e5f07dedbf | ||
|
|
ca9eb0d539 | ||
|
|
142317c2be | ||
|
|
3dea670091 | ||
|
|
8435e79913 | ||
|
|
30e85b40f9 | ||
|
|
be1a43a337 | ||
|
|
abca8f7a7c | ||
|
|
7b60dac526 | ||
|
|
ea6434662d | ||
|
|
0b7a108a59 | ||
|
|
57c63f3598 | ||
|
|
ae30f32c36 | ||
|
|
a50f64f6e9 | ||
|
|
1323b94b7a | ||
|
|
6d68fbc31d | ||
|
|
d97e128dc0 | ||
|
|
86ca568d6d | ||
|
|
92b04a30f3 | ||
|
|
2b0bb69a4f | ||
|
|
67b01506c9 | ||
|
|
b20b802a8d | ||
|
|
87f46a7532 | ||
|
|
45a1407144 | ||
|
|
4a0359c04e | ||
|
|
4e629ca858 | ||
|
|
c32cae00d5 | ||
|
|
02431b3f98 | ||
|
|
0323202a03 | ||
|
|
b6670a7e3d | ||
|
|
62c1991b88 | ||
|
|
3a6085ad31 | ||
|
|
6e1b62aedf | ||
|
|
2451497b31 | ||
|
|
6699c80357 | ||
|
|
1af1474d04 | ||
|
|
06a1d2ac41 | ||
|
|
f47b588b91 | ||
|
|
262131f68e | ||
|
|
ceae48088e | ||
|
|
e655af251c | ||
|
|
3351ad8197 | ||
|
|
5054510d15 | ||
|
|
947fe4fbb3 | ||
|
|
50c8afb525 | ||
|
|
a539de4f97 | ||
|
|
8d5a867271 | ||
|
|
6a1954f8f7 | ||
|
|
0044178d49 | ||
|
|
387bddb51b | ||
|
|
41ba19b615 | ||
|
|
0e37e42abd | ||
|
|
a28945273d | ||
|
|
fd6f03655e | ||
|
|
8b62c05fe2 | ||
|
|
0e92a63d14 | ||
|
|
a4ff0b62ce | ||
|
|
dd141daefd | ||
|
|
a1001ada47 | ||
|
|
f938aa530e | ||
|
|
8d419da277 | ||
|
|
8e3ff79f22 | ||
|
|
d0c12caae9 | ||
|
|
17a99a524b | ||
|
|
41a757c3be | ||
|
|
61fc6539c2 | ||
|
|
99f04012a7 | ||
|
|
891116f13b | ||
|
|
4fcd0964cd | ||
|
|
5005212bec | ||
|
|
a279d6f433 | ||
|
|
5c74196f60 | ||
|
|
33724d40a8 | ||
|
|
d78e5281fe | ||
|
|
fceb8093f1 | ||
|
|
d984a898d4 | ||
|
|
52688106e4 | ||
|
|
66b45a8fe2 | ||
|
|
0db23f9252 | ||
|
|
23b2abc273 | ||
|
|
0350d0784a | ||
|
|
83219a499c | ||
|
|
3f4d5bc85c | ||
|
|
83b790950c | ||
|
|
16b10ec3d7 | ||
|
|
1d4dd4484d | ||
|
|
8bd2669d3d | ||
|
|
b5d2eb5c70 | ||
|
|
25362f16a0 | ||
|
|
39b34eece8 | ||
|
|
b3426fdc94 | ||
|
|
6aa65593ef | ||
|
|
66becbf46f | ||
|
|
cbf3d75087 | ||
|
|
0b0036813f | ||
|
|
c06a20e085 | ||
|
|
c60fccd7f6 | ||
|
|
46f8b04e40 | ||
|
|
12b46bbd41 | ||
|
|
f2b69fd812 | ||
|
|
0aae728e33 | ||
|
|
cea38de4ad | ||
|
|
3e4fab7070 | ||
|
|
6745ca7775 | ||
|
|
88c5e3b6fa | ||
|
|
2fdb2c7c9a | ||
|
|
7afa027b95 | ||
|
|
637a5cc14f | ||
|
|
81e77693b2 | ||
|
|
d49f884f69 | ||
|
|
34eeb29436 | ||
|
|
0495347c64 | ||
|
|
023f9b44d3 | ||
|
|
5248814053 | ||
|
|
7ddab5b8cd | ||
|
|
a26adb162c | ||
|
|
ef5df36a8a | ||
|
|
9b74cf3225 | ||
|
|
1321c90920 | ||
|
|
4b46313e19 | ||
|
|
e9add69e26 | ||
|
|
5b020e81ca | ||
|
|
c76bd7b45b | ||
|
|
21b7ae3ac3 | ||
|
|
e45da7161f | ||
|
|
7a3c000955 | ||
|
|
f75c5db372 | ||
|
|
7cd1d82d26 | ||
|
|
0085c3b0d8 | ||
|
|
4251c6cb99 | ||
|
|
ac973df8e7 | ||
|
|
a76a955a1b | ||
|
|
c89b98a1e0 | ||
|
|
dda4596c0c | ||
|
|
4ac863203d | ||
|
|
be5831d9bd | ||
|
|
1e2fa27af7 | ||
|
|
fcbaf2a978 | ||
|
|
b5c172974e | ||
|
|
7bb70924c1 | ||
|
|
a62fb6dcd1 | ||
|
|
e9c06237f2 | ||
|
|
bdb1eca741 | ||
|
|
29000a5209 | ||
|
|
88f0867442 | ||
|
|
3de06174bf | ||
|
|
5187894add | ||
|
|
d58f7bb935 | ||
|
|
84594b0e1e | ||
|
|
578bbcd181 | ||
|
|
0cdf13742a | ||
|
|
b00d24579d | ||
|
|
5c76cdaad9 | ||
|
|
5d994d179e | ||
|
|
8930020776 | ||
|
|
0637bf0dcb | ||
|
|
2abec5b62f | ||
|
|
bc937a6434 | ||
|
|
3a84e3abcf | ||
|
|
e4c223ca59 | ||
|
|
31de2d304c | ||
|
|
c83113d49b | ||
|
|
d95980b3ba | ||
|
|
08a88af965 | ||
|
|
4d4f61c922 | ||
|
|
110b77f453 | ||
|
|
ba0d205457 | ||
|
|
1ee4cae802 | ||
|
|
095c894548 | ||
|
|
9f5f5da894 | ||
|
|
aa3608f0af | ||
|
|
56ebef4352 | ||
|
|
3e5c353298 | ||
|
|
0d57a887ea | ||
|
|
b5fdd29cd5 | ||
|
|
ae61662f61 | ||
|
|
b633f8d207 | ||
|
|
c4c813fd0a | ||
|
|
f9c4cc274a | ||
|
|
cc45a0ca28 | ||
|
|
9ed1d28f76 | ||
|
|
7ca2acd24e | ||
|
|
fdca468049 | ||
|
|
c0e8feb66e | ||
|
|
0dd710c195 | ||
|
|
39110ad21c | ||
|
|
57bb8c610a | ||
|
|
cf619f24a9 | ||
|
|
fd313f0d66 | ||
|
|
67a18dcff6 | ||
|
|
54b19a04bb | ||
|
|
ca4df29670 | ||
|
|
7da0b2fd7f | ||
|
|
cfcb64c516 | ||
|
|
135ae11c20 | ||
|
|
ce68d09d26 | ||
|
|
aa55e67389 | ||
|
|
f7992d2d09 | ||
|
|
eecfc155b8 | ||
|
|
9cef7945c0 | ||
|
|
1b1932f787 | ||
|
|
9e4fd193c6 | ||
|
|
d16a748f37 | ||
|
|
f616b8e518 | ||
|
|
97b469f59c | ||
|
|
7ec93825b6 | ||
|
|
4a4867deeb | ||
|
|
965a8ea9fd | ||
|
|
3e8ddabcc1 | ||
|
|
50267a6dd6 | ||
|
|
5a8825d016 | ||
|
|
e6bdcff0dd | ||
|
|
4803285e50 | ||
|
|
bab263f426 | ||
|
|
03d5509b44 | ||
|
|
4a5f193e94 | ||
|
|
fdd66f12f0 | ||
|
|
349f073b8e | ||
|
|
84e271be4b | ||
|
|
f215fa936b | ||
|
|
75d04a92c8 | ||
|
|
90c907710c | ||
|
|
00425670d7 | ||
|
|
cb525af0a2 | ||
|
|
09f7e38eed | ||
|
|
7c49a0ba7a | ||
|
|
45e57f1ad3 | ||
|
|
eb911de928 | ||
|
|
b393e31b76 | ||
|
|
4a095eb98e | ||
|
|
d793d4ba78 | ||
|
|
2f54ec4e18 | ||
|
|
b9cd40fe1e | ||
|
|
173cd7c15e | ||
|
|
950c8f7104 | ||
|
|
b63bebb519 | ||
|
|
cf72b34866 | ||
|
|
d06cf2a07b | ||
|
|
10804927bb | ||
|
|
f178297452 | ||
|
|
311e7684b2 | ||
|
|
37ff2ac2b9 | ||
|
|
4786d586cf | ||
|
|
b7aaae4a19 | ||
|
|
008598b56f | ||
|
|
f3bc55e37e | ||
|
|
31fa3dfd59 | ||
|
|
5b47438b71 | ||
|
|
c33ee83d87 | ||
|
|
835dd1abcc | ||
|
|
576f5496c0 | ||
|
|
ff460c7736 | ||
|
|
22adbe2073 | ||
|
|
b8f0b4b583 | ||
|
|
f88e5f09b4 | ||
|
|
2b9c6c95b1 | ||
|
|
3f34db549f | ||
|
|
f4c0ee49a7 | ||
|
|
2fc0a6df93 | ||
|
|
0a9c804940 | ||
|
|
96faa9d12b | ||
|
|
42d9fd9c88 | ||
|
|
908bc7561b | ||
|
|
3de92b337d | ||
|
|
31466785ad | ||
|
|
e38b602b7d | ||
|
|
a2f5342a83 | ||
|
|
d432732959 | ||
|
|
4eb3de7b4e | ||
|
|
2e6adfb44a | ||
|
|
c85c6d0ac5 | ||
|
|
6c9dd8deb6 | ||
|
|
fe64e6dbf2 | ||
|
|
4fd51ec880 | ||
|
|
0323c521be | ||
|
|
992125febc | ||
|
|
f0bd0abc0f | ||
|
|
5c0d4c4408 | ||
|
|
5f8e47a14d | ||
|
|
9407b91060 | ||
|
|
a6e38e2ad2 | ||
|
|
8701de64ce | ||
|
|
114cac99e7 | ||
|
|
78194599e2 | ||
|
|
49d9491fda | ||
|
|
392a363d02 | ||
|
|
0a87ba6930 | ||
|
|
065c104f27 | ||
|
|
7f272f1293 | ||
|
|
e67f50f157 | ||
|
|
dd13392e36 | ||
|
|
2638c66638 | ||
|
|
1d40225159 | ||
|
|
08f11d2f22 | ||
|
|
ee20ab3041 | ||
|
|
e0402919e1 | ||
|
|
086e156a02 | ||
|
|
d0b9292aee | ||
|
|
7b0cb69938 | ||
|
|
479dd684f8 | ||
|
|
573738443c | ||
|
|
0268629c3b | ||
|
|
cf9e6c5d06 | ||
|
|
ad44c6eeee | ||
|
|
2b79d76541 | ||
|
|
cfaf3180f9 | ||
|
|
7881efa269 | ||
|
|
1ac52ce18f | ||
|
|
bb123ae0df | ||
|
|
4859b67a7b | ||
|
|
586e8b1479 | ||
|
|
5a526e2077 | ||
|
|
d3452775b3 | ||
|
|
d82734641b | ||
|
|
abbc2bad35 | ||
|
|
b491d69eec | ||
|
|
d93514d327 | ||
|
|
49ed485145 | ||
|
|
1a2ca8634d | ||
|
|
60cceabaa6 | ||
|
|
56df6b5e08 | ||
|
|
ca5d5668d9 | ||
|
|
4a45d10a8b | ||
|
|
82e1e9f0e5 | ||
|
|
2e1b93b857 | ||
|
|
37e9cefc52 | ||
|
|
1807b1492e | ||
|
|
460a6b4e99 | ||
|
|
96d87a288f | ||
|
|
a0d00410ca | ||
|
|
445ff856fe | ||
|
|
b7fe212a18 | ||
|
|
cbabf0ae7e | ||
|
|
344ccf3b03 | ||
|
|
26a7e1b049 | ||
|
|
43bd331e48 | ||
|
|
8bbb016fa4 | ||
|
|
ea43ba7124 | ||
|
|
5a59bd9998 | ||
|
|
993d731c92 | ||
|
|
f529948d81 | ||
|
|
4f3f51c583 | ||
|
|
5ba9a1f87d | ||
|
|
a0fb75efcb | ||
|
|
6e6f4d5a37 | ||
|
|
f06053b1cf | ||
|
|
df0cd30236 | ||
|
|
8806c57c63 | ||
|
|
f66cfa1299 | ||
|
|
9e84a4dbab | ||
|
|
59ab066518 | ||
|
|
960bd2a6be | ||
|
|
2cdaef1371 | ||
|
|
9b6c5c08d8 | ||
|
|
268ce4fefc | ||
|
|
bca8dace80 | ||
|
|
75e5b49c3a | ||
|
|
3cf08dc451 | ||
|
|
76c9cf62ff | ||
|
|
7a93af1786 | ||
|
|
6f899d998f | ||
|
|
73da8463ca | ||
|
|
b38fab9738 | ||
|
|
45e4d64b20 | ||
|
|
e6fba5d802 | ||
|
|
50ba3c5d5c | ||
|
|
65663fb857 | ||
|
|
8df2f38d34 | ||
|
|
c5fae84263 | ||
|
|
533d0a1fd4 | ||
|
|
a0fe229c7a | ||
|
|
f876cd5a6a | ||
|
|
62e9e9a662 | ||
|
|
a51c5c3ae7 | ||
|
|
26636eda0d | ||
|
|
074df7637b | ||
|
|
4fc8905ebf | ||
|
|
8cbb2278a7 | ||
|
|
fe7f8c3c3a | ||
|
|
91430738e7 | ||
|
|
d6585d7583 | ||
|
|
1924d75c2b | ||
|
|
06b27d932d | ||
|
|
ac1ac2cfed | ||
|
|
53cdb04be7 | ||
|
|
c106de02e9 | ||
|
|
8bc91ced4f | ||
|
|
2d0b9597cc | ||
|
|
b92fbb893b | ||
|
|
957a40df1f | ||
|
|
1eea5b350d | ||
|
|
c8e7080d1d | ||
|
|
629d0e441f | ||
|
|
5cfb878131 | ||
|
|
bfd02ac13d | ||
|
|
bd50bb1aa4 | ||
|
|
1045062147 | ||
|
|
492def3b76 | ||
|
|
afde9ac8a3 | ||
|
|
d977258c6c | ||
|
|
62943f8803 | ||
|
|
22e0859538 | ||
|
|
2c47ef31c0 | ||
|
|
d69b9f9cf8 | ||
|
|
a52e9be3e8 | ||
|
|
21f9141500 | ||
|
|
0e000e44aa | ||
|
|
ad9fe3be09 | ||
|
|
3829bdf198 | ||
|
|
6933a049b1 | ||
|
|
e3544bf4bc | ||
|
|
d5d0cb6a0c | ||
|
|
cac5f20a28 | ||
|
|
beea32661a | ||
|
|
b9e57d0283 | ||
|
|
5c0458a887 | ||
|
|
65942cd46c | ||
|
|
9a8f1a0b0a | ||
|
|
edb37c95e1 | ||
|
|
896882958d | ||
|
|
45d687c4e1 | ||
|
|
42be1321b2 | ||
|
|
f2cc16009f | ||
|
|
4336a55c2e | ||
|
|
bc5c0f65b8 | ||
|
|
b7eba3bc17 | ||
|
|
661d715446 | ||
|
|
d2c166243b | ||
|
|
5ed5eabbfe | ||
|
|
18b4b141d9 | ||
|
|
cd1e478b95 | ||
|
|
cf0ecaaf41 | ||
|
|
5f466eed79 | ||
|
|
93198438b8 | ||
|
|
1057d0eaa6 | ||
|
|
73b1284234 | ||
|
|
5f2e87f0e3 | ||
|
|
d9528f5cc3 | ||
|
|
877bb5c821 | ||
|
|
ae353cef2e | ||
|
|
909606826d | ||
|
|
e6e2d51b58 | ||
|
|
ea16c01fa2 | ||
|
|
f20d420aa7 | ||
|
|
a8649f286c | ||
|
|
fe879e6d94 | ||
|
|
94f285695e | ||
|
|
428747ab69 | ||
|
|
049e8e07ef | ||
|
|
716a845e92 | ||
|
|
47dfd4e681 | ||
|
|
555108c7fd | ||
|
|
e1af3d9bf3 | ||
|
|
e864c7541c | ||
|
|
b4a6cbbd09 | ||
|
|
fe4eef5855 | ||
|
|
c0997f951c | ||
|
|
cb378c1754 | ||
|
|
20b961c1c8 | ||
|
|
c4c2e01213 | ||
|
|
4b6d0fb517 | ||
|
|
62e46b7a36 | ||
|
|
052c5c67b8 | ||
|
|
b794c5cfcf | ||
|
|
be8a4013a7 | ||
|
|
87c25f83a4 | ||
|
|
e08bea5f51 | ||
|
|
739f610507 | ||
|
|
608313c1d1 | ||
|
|
2c1cf42994 | ||
|
|
196db1e1d0 | ||
|
|
a2f5435c48 | ||
|
|
75ec0b4fcf | ||
|
|
b4bfe27786 | ||
|
|
7617382114 | ||
|
|
b2f67cb154 | ||
|
|
c5107ddd3d | ||
|
|
8b75590d3e | ||
|
|
a7624d4724 | ||
|
|
b3099001be | ||
|
|
533c6cefee | ||
|
|
acfa9e8a55 | ||
|
|
8823753b46 | ||
|
|
c5ccefe6f7 | ||
|
|
148071a744 | ||
|
|
56fab9d178 | ||
|
|
f422a77014 | ||
|
|
e248e7ebaf | ||
|
|
aa4c623a06 | ||
|
|
cbdba66ef3 | ||
|
|
d652ab9920 | ||
|
|
e49aaa0216 | ||
|
|
61d49a1215 | ||
|
|
c481ca924b | ||
|
|
23e5b6ba72 | ||
|
|
3e88b72316 | ||
|
|
7566243151 | ||
|
|
aaefa38602 | ||
|
|
de6db4141f | ||
|
|
a61735e29a | ||
|
|
9bdfb0a32b | ||
|
|
94ecd29e35 | ||
|
|
980f5f1299 | ||
|
|
f99d62a2bc | ||
|
|
89be1975ea | ||
|
|
fec82df451 | ||
|
|
89fd35e02d | ||
|
|
3286328de4 | ||
|
|
977c34c0d7 | ||
|
|
5ec23df460 | ||
|
|
7f307e3bea | ||
|
|
fa050fb8a8 | ||
|
|
d741cbf6fd | ||
|
|
fa09327403 | ||
|
|
e27aaf9ba1 | ||
|
|
c0063ea09b | ||
|
|
e3e07aa3e6 | ||
|
|
502e8daedd | ||
|
|
3035f0119f | ||
|
|
48a3c3a0c1 | ||
|
|
fab9ae64a0 | ||
|
|
b56ebd13b6 | ||
|
|
3de39698dd | ||
|
|
b86476410f | ||
|
|
57ea45ff51 | ||
|
|
ec30f7c5d4 | ||
|
|
3cc9114f81 | ||
|
|
790167e914 | ||
|
|
9687d432fd | ||
|
|
378c0d049e | ||
|
|
abe283b38e | ||
|
|
3af42079e0 | ||
|
|
eb4843649c | ||
|
|
9432efeba5 | ||
|
|
4a3ed75ae5 | ||
|
|
fc8c555519 | ||
|
|
44f30c0e05 | ||
|
|
83b5fd252e | ||
|
|
9ae18b0b3b | ||
|
|
84487b2e52 | ||
|
|
9689376de6 | ||
|
|
a2f79a163f | ||
|
|
489d0151ad | ||
|
|
bcb44ab600 | ||
|
|
1a78bb4b58 | ||
|
|
2600695927 | ||
|
|
705e4fca06 | ||
|
|
a4ba4cf6ce | ||
|
|
0b2109576a | ||
|
|
c8cdc50d29 | ||
|
|
b29e60a97a | ||
|
|
209a986fe4 | ||
|
|
295e5c9731 | ||
|
|
08c93e94e4 | ||
|
|
ec28e9fb27 | ||
|
|
7081e8a226 | ||
|
|
01a2383d7b | ||
|
|
903b569f6c | ||
|
|
9b79e35d52 | ||
|
|
237100da18 | ||
|
|
1e33c0c288 | ||
|
|
cbc7603248 | ||
|
|
8d53f3abb8 | ||
|
|
2dcfec2639 | ||
|
|
dacb1a70ee | ||
|
|
93200c00f2 | ||
|
|
3b484c362e | ||
|
|
fbee9fe51e | ||
|
|
c451aab150 | ||
|
|
48bff9a5d2 | ||
|
|
db36d80669 | ||
|
|
6c8d3232a0 | ||
|
|
e6418fe79b | ||
|
|
34a0bb04f7 | ||
|
|
28302c82a3 | ||
|
|
5025c40ab2 | ||
|
|
6fdd30d97f | ||
|
|
b7c000e435 | ||
|
|
5ef2bbe5d6 | ||
|
|
e1d37c3b98 | ||
|
|
ec475e5783 | ||
|
|
c4b67b4cae | ||
|
|
b2cfebcce2 | ||
|
|
2bf445e165 | ||
|
|
121224b1b0 | ||
|
|
55ce1ce3ec | ||
|
|
2b14633047 | ||
|
|
88042d27ec | ||
|
|
148f3002ec | ||
|
|
ec82c1f957 | ||
|
|
9919520c28 | ||
|
|
689795e8bc | ||
|
|
5c8ab35f55 | ||
|
|
ddd7f3f384 | ||
|
|
c2f74879da | ||
|
|
5311683d43 | ||
|
|
5f03a583d1 | ||
|
|
a6970e02f9 | ||
|
|
1f7f013e0a | ||
|
|
991a04dc2a | ||
|
|
685902adab | ||
|
|
26ce0eb4b9 | ||
|
|
b30db728cc | ||
|
|
5eb098b5aa | ||
|
|
a2995ea03a | ||
|
|
82da5cfd01 | ||
|
|
909285ea46 | ||
|
|
a0bc0641c7 | ||
|
|
e7ada61881 | ||
|
|
79e99908de | ||
|
|
f1d7644184 | ||
|
|
5f365927b3 | ||
|
|
bd893cb24c | ||
|
|
e3352c3658 | ||
|
|
63d3d6049e | ||
|
|
668810858d | ||
|
|
b7131a5cd5 | ||
|
|
c9a316ad35 | ||
|
|
ec8fe21485 | ||
|
|
4a0b10984a | ||
|
|
69406b00d1 | ||
|
|
a1f3ae730a | ||
|
|
fedaef5d17 | ||
|
|
e35345f135 | ||
|
|
d0dbbd1cb1 | ||
|
|
70e14f92a4 | ||
|
|
b45f371911 | ||
|
|
f1a7ee997b | ||
|
|
51ebec7c13 | ||
|
|
c3ed1ad040 | ||
|
|
85ebf2e484 | ||
|
|
1996e6afaa | ||
|
|
799f97e847 | ||
|
|
dd2a7f91cc | ||
|
|
c86e9dfc8a | ||
|
|
40a2579821 | ||
|
|
380b64071e | ||
|
|
5ce0b1c18e | ||
|
|
a00453e151 | ||
|
|
a1aa40f500 | ||
|
|
1ad4685bb7 | ||
|
|
d8629e61d6 | ||
|
|
d4ddd7204d | ||
|
|
5f18799d8f | ||
|
|
15ba3325d9 | ||
|
|
3046350cb2 | ||
|
|
ffa9e5dfab | ||
|
|
8584654f11 | ||
|
|
c7e65ce795 | ||
|
|
cc3f019b28 | ||
|
|
920785631b | ||
|
|
b7fe1db89a | ||
|
|
5a08dfa72f | ||
|
|
c28b1f6fb9 | ||
|
|
c81ca187f8 | ||
|
|
ba148d749f | ||
|
|
606dd11b4f | ||
|
|
e254d2de8e | ||
|
|
b0b8ba7000 | ||
|
|
4d512d908d | ||
|
|
168376b046 | ||
|
|
df9e7f284c | ||
|
|
6365ee7487 | ||
|
|
7a8620a570 | ||
|
|
2c24bf3222 | ||
|
|
e036d89a86 | ||
|
|
36e9fb9d68 | ||
|
|
8b56a038b7 | ||
|
|
d5f0e23e29 | ||
|
|
3a2bd4e928 | ||
|
|
c5136ca4d6 | ||
|
|
28dac81a90 | ||
|
|
c89632d2a8 | ||
|
|
82c8f3b556 | ||
|
|
29278ff916 | ||
|
|
608b9e66f4 | ||
|
|
ac0ed14eae | ||
|
|
a06dfaf82a | ||
|
|
dfd5e30015 | ||
|
|
b19524d56a | ||
|
|
e0d25ff887 | ||
|
|
198dfffaeb | ||
|
|
c4ef055248 | ||
|
|
420ceffbb0 | ||
|
|
eeeecf9763 | ||
|
|
8df591e8d4 | ||
|
|
702ed5bfa8 | ||
|
|
75c6844b66 | ||
|
|
b39f01dcdf | ||
|
|
dba7dc4e96 | ||
|
|
ef96a50cea | ||
|
|
eed78c960d | ||
|
|
eea7ca9b72 | ||
|
|
c7c732ebc0 | ||
|
|
fb06482916 | ||
|
|
1bb24efbbc | ||
|
|
13800589a9 | ||
|
|
848add1b1b | ||
|
|
fc67dd18d0 | ||
|
|
b3e9b92344 | ||
|
|
a291b29c6f | ||
|
|
b566c4ba1a | ||
|
|
2349a6ab0c | ||
|
|
70eaf41acb | ||
|
|
912091981e | ||
|
|
aca42ff6a5 | ||
|
|
3fd885a188 | ||
|
|
2dacdf0210 | ||
|
|
5e8856e65b | ||
|
|
d42539949e | ||
|
|
28ca5b2b57 | ||
|
|
eea6f5cbfa | ||
|
|
b55dcc16ae | ||
|
|
0cb442d40e | ||
|
|
680dc1f962 | ||
|
|
30c92ce1b7 | ||
|
|
e55a09241e | ||
|
|
5f02237119 | ||
|
|
68552b6d65 | ||
|
|
44a4cea3a2 | ||
|
|
77c13e10ec | ||
|
|
0f66ff50a4 | ||
|
|
af515856c2 | ||
|
|
62e8c09183 | ||
|
|
cb40a74aaf | ||
|
|
a2c3ef94ec | ||
|
|
4184fb3ae7 | ||
|
|
20d2d141e4 | ||
|
|
0cfc3bfb79 | ||
|
|
2de206cb81 | ||
|
|
64c6dfd307 | ||
|
|
03d0b296e1 | ||
|
|
ca4408b343 | ||
|
|
527f408f6a | ||
|
|
adccca366e | ||
|
|
76cad41382 | ||
|
|
501db8f276 | ||
|
|
cc684ff0b1 | ||
|
|
7823c07f1a | ||
|
|
eb6d3d68a9 | ||
|
|
c98c7d47db | ||
|
|
194694a59b | ||
|
|
555cee3500 | ||
|
|
f4bb854372 | ||
|
|
d04b8e2fc6 | ||
|
|
21c1450e4a | ||
|
|
2b6b0e62f4 | ||
|
|
4fb360b19f | ||
|
|
0fa1567793 | ||
|
|
d7377015a2 | ||
|
|
139017ba7c | ||
|
|
ff3b53e34a | ||
|
|
b409cdece5 | ||
|
|
2478e67d16 | ||
|
|
dd35877eda | ||
|
|
4acfd2c342 | ||
|
|
df3f9a2ae8 | ||
|
|
78ae9ff093 | ||
|
|
ca781651cb | ||
|
|
417fa3cf3c | ||
|
|
92bb477f68 | ||
|
|
0f08cc5aa9 | ||
|
|
3dac33ffba | ||
|
|
fe73eccb90 | ||
|
|
b9544d9534 | ||
|
|
99ba9874b1 | ||
|
|
b41ca72d2b | ||
|
|
1ba17a0e14 | ||
|
|
480d31eb5e | ||
|
|
1973f93d4a | ||
|
|
3a2f4d4def | ||
|
|
192d2b86b6 | ||
|
|
6ca948ac3c | ||
|
|
5f932e9749 | ||
|
|
ff46c18164 | ||
|
|
de777907f2 | ||
|
|
72c4fd868a | ||
|
|
4bc2cd003d | ||
|
|
c9c6c1f769 | ||
|
|
bb7d8735cb | ||
|
|
557b0d76ab | ||
|
|
13073411cc | ||
|
|
0530045536 | ||
|
|
6d29e9c1b7 | ||
|
|
d3e4739745 | ||
|
|
3379f3ef61 | ||
|
|
9da7919062 | ||
|
|
aeed7deb2d | ||
|
|
38b617d848 | ||
|
|
dda02f783f | ||
|
|
cda322e311 | ||
|
|
934d3178a2 | ||
|
|
2cded8dcac | ||
|
|
babe0c31ad | ||
|
|
aa2ae37365 | ||
|
|
9a8b81fcbe | ||
|
|
138f38aade | ||
|
|
42914bff6f | ||
|
|
59acf95bf5 | ||
|
|
31c1046771 | ||
|
|
ec4a22687f | ||
|
|
22d9d16a7a | ||
|
|
0520e67303 | ||
|
|
d571573e52 | ||
|
|
0f643daac6 | ||
|
|
989317e5d3 | ||
|
|
cce6db4aeb | ||
|
|
46109abde5 | ||
|
|
2e16774190 | ||
|
|
812a21bce6 | ||
|
|
17e0207120 | ||
|
|
e02608edf8 | ||
|
|
1a7e160956 | ||
|
|
59d966356e | ||
|
|
5937e0289e | ||
|
|
85c212aee3 | ||
|
|
1978c3d3bd | ||
|
|
30a2c28d74 | ||
|
|
94a330532d | ||
|
|
f65e57ee82 | ||
|
|
750064c06f | ||
|
|
651fd79325 | ||
|
|
b53f988fca | ||
|
|
1595eaeb74 | ||
|
|
a54cbe896d | ||
|
|
9995a8d53f | ||
|
|
b88872d54b | ||
|
|
f02ac01f7e | ||
|
|
3ab3a5a236 | ||
|
|
fa3eb185ab | ||
|
|
4a56ba34d4 | ||
|
|
b860c69fd7 | ||
|
|
8408ceffe8 | ||
|
|
da9133c3c3 | ||
|
|
4a6e920d0e | ||
|
|
13f58d602f | ||
|
|
45fda595c3 | ||
|
|
1f15ea0bd8 | ||
|
|
99eebfa10f | ||
|
|
ccbbe81141 | ||
|
|
ad2da5969e | ||
|
|
2ce82f3da5 | ||
|
|
6bc9479e00 | ||
|
|
740e52229f | ||
|
|
1efd2390e3 | ||
|
|
564328abf9 | ||
|
|
926f819e31 | ||
|
|
45f0b9ac45 | ||
|
|
9a82e86ce9 | ||
|
|
0ad974c57b | ||
|
|
62f015fc34 | ||
|
|
b508787037 | ||
|
|
176ef19cca | ||
|
|
db83ede73c | ||
|
|
452d1e8307 | ||
|
|
92f337c67e | ||
|
|
1e4022680a | ||
|
|
e16d951da0 | ||
|
|
7b2b19b4ba | ||
|
|
8ebfaed546 | ||
|
|
0b52d52f4a | ||
|
|
6901d10d54 | ||
|
|
7caa2ae9bc | ||
|
|
cc50859912 | ||
|
|
6c16780144 | ||
|
|
b5f79444f9 | ||
|
|
b73c6e94c0 | ||
|
|
07616a9d34 | ||
|
|
e88ee26e3b | ||
|
|
7422c38e66 | ||
|
|
8ed1c77e47 | ||
|
|
8c65b8c16f | ||
|
|
1f435522b4 | ||
|
|
eb880e8de0 | ||
|
|
83b1d80a5b | ||
|
|
529aff3126 | ||
|
|
bf2bd519eb | ||
|
|
38c7dda00f | ||
|
|
056e7432bd | ||
|
|
074497b0f6 | ||
|
|
1eca969cf6 | ||
|
|
c1cbdae5ee | ||
|
|
4d4b6a2fa0 | ||
|
|
d30a972a90 | ||
|
|
24013af3bb | ||
|
|
338dc3223c | ||
|
|
c422a6dd4f | ||
|
|
8305af8f10 | ||
|
|
6435c7b921 | ||
|
|
af7c57b082 | ||
|
|
80941eacbd | ||
|
|
ceb252986e | ||
|
|
5d8de5fde2 | ||
|
|
750ef296c6 | ||
|
|
454ae8656a | ||
|
|
75450dcdbc | ||
|
|
bd2c7e3bb9 | ||
|
|
9d23cf33fd | ||
|
|
97eb01a28d | ||
|
|
9a2a636aed | ||
|
|
61c8256ef0 | ||
|
|
8e1791570e | ||
|
|
aa30d1f359 | ||
|
|
326f4bd681 | ||
|
|
7690c6c33d | ||
|
|
fece1077f2 | ||
|
|
75fc7db50d | ||
|
|
96da04576e | ||
|
|
001ec3663e | ||
|
|
21a00b77bd | ||
|
|
408f3852ec | ||
|
|
61150c74d2 | ||
|
|
7bb7003c9d | ||
|
|
920463f2ff | ||
|
|
ca1185d0be | ||
|
|
be655ee328 | ||
|
|
02d4186b11 | ||
|
|
3f97bebd69 | ||
|
|
2e378da922 | ||
|
|
b37f51bd7f | ||
|
|
eb8b0f72cc | ||
|
|
d8fe9a4d29 | ||
|
|
c97cdf551e | ||
|
|
80fc60b5e2 | ||
|
|
3b2e142542 | ||
|
|
0e58d99f4e | ||
|
|
92798abb5d | ||
|
|
bd7950b757 | ||
|
|
59a15ceef6 | ||
|
|
4011a113cc | ||
|
|
70cbe91776 | ||
|
|
f92027c44b | ||
|
|
1443335315 | ||
|
|
6ff2229a09 | ||
|
|
bb72672dd9 | ||
|
|
d96dee3aa6 | ||
|
|
bd0aaa343b | ||
|
|
3126e1ac94 | ||
|
|
a117d87f33 | ||
|
|
9dc4f8a1aa | ||
|
|
0d536d11e3 | ||
|
|
72a4962fd0 | ||
|
|
a3045a3953 | ||
|
|
c620a22017 | ||
|
|
e3593fe197 | ||
|
|
856ec03cc7 | ||
|
|
c80c5631f0 | ||
|
|
ef70668a77 | ||
|
|
ebd4691462 | ||
|
|
28554235be | ||
|
|
efbbb6fd20 | ||
|
|
9de57021a3 | ||
|
|
e21f770485 | ||
|
|
697c00dccf | ||
|
|
1caf6a3298 | ||
|
|
02fd02d482 | ||
|
|
239fb0db94 | ||
|
|
fe1d73c3e5 | ||
|
|
43da06a354 | ||
|
|
fea6b67067 | ||
|
|
f065ae54d5 | ||
|
|
3cf417766d | ||
|
|
0fb41b10e9 | ||
|
|
bc9dc3bf1e | ||
|
|
3cde5e28a8 | ||
|
|
cb8e7181c4 | ||
|
|
9a3becdecc | ||
|
|
e3c10d779d | ||
|
|
dd9f1024f4 | ||
|
|
9841f74adc | ||
|
|
b56e493d92 | ||
|
|
a2c5211b20 | ||
|
|
b7a7abed48 | ||
|
|
72bfdfd925 | ||
|
|
b80d34612a | ||
|
|
648cc0f006 | ||
|
|
1fc9506442 | ||
|
|
830692dd60 | ||
|
|
95a6759381 | ||
|
|
960b37b1c2 | ||
|
|
b1d17dea4f | ||
|
|
6b06471953 | ||
|
|
4ca957d3eb | ||
|
|
eb9b63477c | ||
|
|
80c01b055c | ||
|
|
50aec67069 | ||
|
|
7baced75e5 | ||
|
|
99743a94fb | ||
|
|
9bdfd6025b | ||
|
|
91400d2ce0 | ||
|
|
7b88d0efe3 | ||
|
|
4aada65dae | ||
|
|
0560d2cfb7 | ||
|
|
58c1a68ad9 | ||
|
|
588fc6df85 | ||
|
|
2c9e4ded40 | ||
|
|
88a538e71b | ||
|
|
513363504f | ||
|
|
0e844edacb | ||
|
|
5751bb2481 | ||
|
|
28669d940a | ||
|
|
3d87bdb6b4 | ||
|
|
1499ce43bf | ||
|
|
4d22b43d65 | ||
|
|
823603650f | ||
|
|
062867a38d | ||
|
|
f3e0c5d653 | ||
|
|
fc7f48b7db | ||
|
|
04d56420d1 | ||
|
|
a017574f74 | ||
|
|
ae24360c02 | ||
|
|
3fea1976c8 | ||
|
|
cf97dd9fcd | ||
|
|
0e3a48ff76 | ||
|
|
48cbe45a9d | ||
|
|
276bf09238 | ||
|
|
05988c1c49 | ||
|
|
d46b26e3bc | ||
|
|
236c172c6f | ||
|
|
59fcb56972 | ||
|
|
c07cd3a856 | ||
|
|
37766347a5 | ||
|
|
79da61782b | ||
|
|
8af87f1a8b | ||
|
|
494c954cbb | ||
|
|
71bc9eea28 | ||
|
|
e3b2bcfd06 | ||
|
|
142d974641 | ||
|
|
e56129111a | ||
|
|
0e1d6aa85c | ||
|
|
bcdb8cd770 | ||
|
|
7b2ca55089 | ||
|
|
f6ef0b684a | ||
|
|
02e1cdf210 | ||
|
|
b58950c574 | ||
|
|
833a60f29c | ||
|
|
f776d67c03 | ||
|
|
13e7cca1a4 | ||
|
|
0f3c477ff3 | ||
|
|
039cc30c07 | ||
|
|
25c8cd9246 | ||
|
|
c58841100a | ||
|
|
03e24cccd0 | ||
|
|
35f011758d | ||
|
|
2ebfaf76f2 | ||
|
|
0cf187dee7 | ||
|
|
bdeb325bad | ||
|
|
a1225b6d0d | ||
|
|
f0368b02c4 | ||
|
|
202de1436d | ||
|
|
7f8746fcd4 | ||
|
|
e05a25d701 | ||
|
|
6930570fa2 | ||
|
|
aba2c5b938 | ||
|
|
d82f86dcd9 | ||
|
|
159b4f9734 | ||
|
|
46a737c7a1 | ||
|
|
a731486ab7 | ||
|
|
c3e57f1fdd | ||
|
|
a9af484412 | ||
|
|
007646774e | ||
|
|
2d78e35e16 | ||
|
|
7524b5e349 | ||
|
|
2a04a48b89 | ||
|
|
3cbdaab81e | ||
|
|
8c858a5953 | ||
|
|
1812958106 | ||
|
|
4e5324916c | ||
|
|
1a77becc6a | ||
|
|
23ccaea2ff | ||
|
|
2a4b252a9d | ||
|
|
9ae4edfee5 | ||
|
|
bf48809b61 | ||
|
|
57a80a3c10 | ||
|
|
3f3e52d7ae | ||
|
|
5c69110658 | ||
|
|
be055d9dcb | ||
|
|
1e34a61911 | ||
|
|
97bd1da2a2 | ||
|
|
330ffb803f | ||
|
|
7b77f200be | ||
|
|
15a3c8408f | ||
|
|
bc1784ed2b | ||
|
|
55f0a82249 | ||
|
|
7aada3f328 | ||
|
|
dad885c051 | ||
|
|
f5c7bbfda8 | ||
|
|
f832743009 | ||
|
|
7551de6439 | ||
|
|
e03b4b7505 | ||
|
|
2d59fdd178 | ||
|
|
e61c8046f4 | ||
|
|
c0796ac3d6 | ||
|
|
68be24ffc6 | ||
|
|
9dcc87c705 | ||
|
|
d36c536107 | ||
|
|
affeeb39de | ||
|
|
f5d8a952f2 | ||
|
|
da07f99d3d | ||
|
|
eef66de68c | ||
|
|
4aa1180fce | ||
|
|
553d52a45e | ||
|
|
347b153884 | ||
|
|
1e7c176481 | ||
|
|
e390405d0c | ||
|
|
7378a84c96 | ||
|
|
b25013c4a2 | ||
|
|
6942916f13 | ||
|
|
f69f0b97f5 | ||
|
|
4361ea9686 | ||
|
|
be2ee33273 | ||
|
|
8c2ddb0255 | ||
|
|
466a5a932b | ||
|
|
8a3c6382e9 | ||
|
|
a2b45120c5 | ||
|
|
546ad52e11 | ||
|
|
1aefc5b540 | ||
|
|
1085ca4a2d | ||
|
|
9766322e99 | ||
|
|
cfb68e3bff | ||
|
|
a006963fb8 | ||
|
|
24c95c27c3 | ||
|
|
3c40c0be6b | ||
|
|
b1fc80b79a | ||
|
|
50d793e49b | ||
|
|
34c43b8349 | ||
|
|
7002a316fd | ||
|
|
1f37faad42 | ||
|
|
68cf24d100 | ||
|
|
86491da253 | ||
|
|
90249cdafa | ||
|
|
7c75111c41 | ||
|
|
7b53b6bfef | ||
|
|
fded5fd900 | ||
|
|
950965bd4a | ||
|
|
3a359319fa | ||
|
|
d3dd82c699 | ||
|
|
81f192bccb | ||
|
|
60a23febed | ||
|
|
d0e280cbac | ||
|
|
ecb62c8659 | ||
|
|
12669df92b | ||
|
|
44b2afeffa | ||
|
|
70f435e909 | ||
|
|
512d82071e | ||
|
|
3896230199 | ||
|
|
b902880a05 | ||
|
|
418526af16 | ||
|
|
45ad212459 | ||
|
|
0f49d424d3 | ||
|
|
01e42c8d6f | ||
|
|
26107bd6c3 | ||
|
|
7d3ecd2297 | ||
|
|
16056661dd | ||
|
|
059f50dad4 | ||
|
|
4c9975a7d9 | ||
|
|
9f9cc1ffb5 | ||
|
|
e768e1e277 | ||
|
|
acaf7b969a | ||
|
|
2b94975345 | ||
|
|
e6b4e12689 | ||
|
|
7eaac995bd | ||
|
|
a19cdb5e72 | ||
|
|
f54fbd057e | ||
|
|
19eceb4ecc | ||
|
|
dcff1ec25f | ||
|
|
567cda4cd3 | ||
|
|
900d8790b3 | ||
|
|
cad284519f | ||
|
|
0727acf458 | ||
|
|
d8813179be | ||
|
|
10d690c8fb | ||
|
|
52f71cdda0 | ||
|
|
2a9a348164 | ||
|
|
00346781bb | ||
|
|
4c6e92eea1 | ||
|
|
b63f469110 | ||
|
|
f6f176afc1 | ||
|
|
3de37a61c5 | ||
|
|
2d955dae48 | ||
|
|
46577fb128 | ||
|
|
37dba6ebfd | ||
|
|
66b949bed1 | ||
|
|
c9a05187fb | ||
|
|
cc956583fb | ||
|
|
14206efb09 | ||
|
|
5e6d7f5d16 | ||
|
|
7a33831d14 | ||
|
|
4f120e19fd | ||
|
|
37d064d836 | ||
|
|
824150f89b | ||
|
|
f7dc4cca2c | ||
|
|
ea39bb4334 | ||
|
|
5680d5a7be | ||
|
|
004246124b | ||
|
|
c41beae99a | ||
|
|
fe2cffb25b | ||
|
|
f71d5c429d | ||
|
|
dce5816b18 | ||
|
|
f99a7b2a8c | ||
|
|
ec36c69984 | ||
|
|
2458db03de | ||
|
|
7528b7bc1a | ||
|
|
8af33084ed | ||
|
|
f643175156 | ||
|
|
0321dda1d7 | ||
|
|
ff5d79e3ee | ||
|
|
4ee3ec09df | ||
|
|
cfe9d47fa0 | ||
|
|
607d6125fc | ||
|
|
6215259565 | ||
|
|
d034fecc89 | ||
|
|
f18d8229c0 | ||
|
|
e736626953 | ||
|
|
c2c438637a | ||
|
|
94638fe42c | ||
|
|
55ecfda39a | ||
|
|
d97a272aa5 | ||
|
|
80a1944b9d | ||
|
|
138cf943a9 | ||
|
|
c7e672e533 | ||
|
|
1b74a04efd | ||
|
|
290c7e6009 | ||
|
|
e8a56e0fea | ||
|
|
1ae7b646b3 | ||
|
|
42e2d73ce2 | ||
|
|
9e2a65a5ce | ||
|
|
fea20ea913 | ||
|
|
5b2480fff2 | ||
|
|
b0dca2a363 | ||
|
|
59bbe72798 | ||
|
|
f99a30a57e | ||
|
|
aa4cb29621 | ||
|
|
91ad4e396b | ||
|
|
351e17aacf | ||
|
|
6c8e09acdb | ||
|
|
1a7b341745 | ||
|
|
af592ea8c1 | ||
|
|
bb096a0357 | ||
|
|
3c226892c6 | ||
|
|
47f6fe069a | ||
|
|
aa3c1d930b | ||
|
|
99b0b4f5b8 | ||
|
|
bcd239ac2b | ||
|
|
2cc25b1e6e | ||
|
|
5fd3ed782f | ||
|
|
c34a24b633 | ||
|
|
775612ec5a | ||
|
|
fd43b16213 | ||
|
|
5a455ec4f7 | ||
|
|
1277c3d156 | ||
|
|
8033d1ca6d | ||
|
|
28df6881a7 | ||
|
|
e5fa5df7be | ||
|
|
f7dbf2bdd4 | ||
|
|
857c57daba | ||
|
|
5515da3c2d | ||
|
|
cfc111f855 | ||
|
|
3dd4043827 | ||
|
|
351ecfae0f | ||
|
|
b22393092b | ||
|
|
1485ee8027 | ||
|
|
60826c2d0c | ||
|
|
fb383458d7 | ||
|
|
196ee1aa8b | ||
|
|
2df97cd2f5 | ||
|
|
501b523680 | ||
|
|
6efa6691b1 | ||
|
|
c47f1ae236 | ||
|
|
aac240fe41 | ||
|
|
041debcd93 | ||
|
|
0632a2d3c8 | ||
|
|
9f40b3a873 | ||
|
|
8fad0af935 | ||
|
|
48ad744ebf | ||
|
|
556d5b0ca5 | ||
|
|
e30d70b6d4 | ||
|
|
a58f5a925a | ||
|
|
a3cc3c57fd | ||
|
|
0d0d3edeae | ||
|
|
dd0be7c522 | ||
|
|
9d2982fcd7 | ||
|
|
ebfd7d2153 | ||
|
|
818cd2454d | ||
|
|
b31d1c06f5 | ||
|
|
6cd884555c | ||
|
|
47ef74a1bb | ||
|
|
cc6d6ddd66 | ||
|
|
6a6cf015a6 | ||
|
|
ca79e81b39 | ||
|
|
a9e86cecf5 | ||
|
|
5773b1c3e5 | ||
|
|
b562b3410b | ||
|
|
f6440e9830 | ||
|
|
e43636e1e9 | ||
|
|
6783bf9903 | ||
|
|
807723c5b2 | ||
|
|
d3c4936116 | ||
|
|
bbb40aef51 | ||
|
|
485a3e29e7 | ||
|
|
1477f99c2c | ||
|
|
2e1f9d5fa9 | ||
|
|
9dea251862 | ||
|
|
17edfd6573 | ||
|
|
458e9d6cc7 | ||
|
|
485459b8b2 | ||
|
|
fcf377d26b | ||
|
|
3be1c9261f | ||
|
|
38600b3347 | ||
|
|
62f7f7a689 | ||
|
|
552f616305 | ||
|
|
a3164177f8 | ||
|
|
fa6bf21cd1 | ||
|
|
eecf76c1fb | ||
|
|
d1635cf24e | ||
|
|
b43e9ed7e7 | ||
|
|
12b2ab5da8 | ||
|
|
1c9085556c | ||
|
|
9122f8acee | ||
|
|
ef8c9f093c | ||
|
|
801dffd571 | ||
|
|
0b1c57b39f | ||
|
|
2febc268f7 | ||
|
|
58995bb3a2 | ||
|
|
8c944815bc | ||
|
|
f065a21542 | ||
|
|
27e032d10d | ||
|
|
ab3980cd38 | ||
|
|
1db648a525 | ||
|
|
ce3b5b683d | ||
|
|
9d23f1298d | ||
|
|
3f791b65b5 | ||
|
|
317d8703ca | ||
|
|
fda619f704 | ||
|
|
e4a0669da8 | ||
|
|
89725df3dc | ||
|
|
51799844c9 | ||
|
|
48de136e9d | ||
|
|
cb6f97a831 | ||
|
|
7e0cd0ab60 | ||
|
|
8521f04087 | ||
|
|
8ba45808be | ||
|
|
d876fd7f5b | ||
|
|
352e409a6e | ||
|
|
d6ec441c8e | ||
|
|
d197497349 | ||
|
|
d892ba6aa5 | ||
|
|
84b2583973 | ||
|
|
108648b427 | ||
|
|
71bf8b6b4d | ||
|
|
576067c1e5 | ||
|
|
e23bab0103 | ||
|
|
4e111c84f3 | ||
|
|
8cecce7570 | ||
|
|
0338fd42e1 | ||
|
|
b3788bc143 | ||
|
|
18d66ddded | ||
|
|
701b5ea561 | ||
|
|
86d0de4b0e | ||
|
|
a95958f9f6 | ||
|
|
69ab236f3f | ||
|
|
4cf3c6a616 | ||
|
|
da48bbf312 | ||
|
|
ac957db6d1 | ||
|
|
64464f23ae | ||
|
|
52cb239194 | ||
|
|
efd54b7523 | ||
|
|
2aca57cb82 | ||
|
|
d68baf08cb | ||
|
|
a7578aa709 | ||
|
|
a8261d376a | ||
|
|
fc346b4efd | ||
|
|
ad09e734da | ||
|
|
a674fea1c2 | ||
|
|
9e22b34fac | ||
|
|
fe24408620 | ||
|
|
c07ad0941c | ||
|
|
2f02b38b62 | ||
|
|
3ac766530d | ||
|
|
de77c71042 | ||
|
|
9c854a1757 | ||
|
|
f66fa1150e | ||
|
|
f820706e4f | ||
|
|
29e9e0f2cc | ||
|
|
2933093e17 | ||
|
|
71cd8918be | ||
|
|
c049ba59ff | ||
|
|
51c5f28443 | ||
|
|
bb1ed902a9 | ||
|
|
b016a60a75 | ||
|
|
890d485bb5 | ||
|
|
208bb2d72f | ||
|
|
267bf289c4 | ||
|
|
b3e083d866 | ||
|
|
a675c64c2d | ||
|
|
8b50c8515f | ||
|
|
1eaa377583 | ||
|
|
4345b1d930 | ||
|
|
06bf0c2622 | ||
|
|
3ac8de0a64 | ||
|
|
f237fd9847 | ||
|
|
5730280325 | ||
|
|
ab4df7e078 | ||
|
|
b52e6c99ab | ||
|
|
7dab548522 | ||
|
|
785c341822 | ||
|
|
7d2e1f63b5 | ||
|
|
e119459411 | ||
|
|
97ef2191fd | ||
|
|
e833ccf309 | ||
|
|
a4134d30fa | ||
|
|
6069fd02d3 | ||
|
|
bb15dc57a4 | ||
|
|
bdfe170c3b | ||
|
|
0fa2ba53ab | ||
|
|
4bb657debf | ||
|
|
dd12840e34 | ||
|
|
b027dcfec9 | ||
|
|
9e9b6f1542 | ||
|
|
7cd66e20d0 | ||
|
|
d93df15eff | ||
|
|
ddfd20d997 | ||
|
|
fd8af88493 | ||
|
|
bfa488f77d | ||
|
|
03be793930 | ||
|
|
37d88d5ff7 | ||
|
|
4616f889fd | ||
|
|
59cbf95c4f | ||
|
|
058711d3a8 | ||
|
|
2ddc61fa5c | ||
|
|
e04b7d0f01 | ||
|
|
2faa2ed1f4 | ||
|
|
5e2889e776 | ||
|
|
5bda36fb28 | ||
|
|
53fbb257b9 | ||
|
|
65a32d6e20 | ||
|
|
92450920d4 | ||
|
|
0099a9822e | ||
|
|
0cf86974dd | ||
|
|
716705aa15 | ||
|
|
757993064e | ||
|
|
3f738cf905 | ||
|
|
570715100b | ||
|
|
ad8750b40d | ||
|
|
757ea93393 | ||
|
|
dbd5a222d5 | ||
|
|
bba80bc80f | ||
|
|
094143bc28 | ||
|
|
24a335d304 | ||
|
|
c62b318b9e | ||
|
|
ea5c7c321a | ||
|
|
6d92775ab5 | ||
|
|
1a9360ca75 | ||
|
|
22b9bbe702 | ||
|
|
6fb44083ec | ||
|
|
ba02be08bb | ||
|
|
56fe3ede5b | ||
|
|
e48a000784 | ||
|
|
6d1c150ff5 | ||
|
|
21190a240f | ||
|
|
8a525bc131 | ||
|
|
734905d1f7 | ||
|
|
90edf2fc60 | ||
|
|
e3f37c14db | ||
|
|
c6c92184d9 | ||
|
|
c4fbc65354 | ||
|
|
54d250bde4 | ||
|
|
ef309bd8d0 | ||
|
|
6cdb6ec711 | ||
|
|
03891b66b6 | ||
|
|
42dd6326d5 | ||
|
|
5c4defdb8e | ||
|
|
f08d53b0c6 | ||
|
|
6859b85266 | ||
|
|
075adb4f03 | ||
|
|
5ce72a3461 | ||
|
|
8c2958b86d | ||
|
|
f15b7cebac | ||
|
|
f6d8df1e83 | ||
|
|
19ed5bf993 | ||
|
|
5567e2843d | ||
|
|
0a8e20fd60 | ||
|
|
558c4341e4 | ||
|
|
250860d92c | ||
|
|
64aecba7a0 | ||
|
|
3689b08237 | ||
|
|
30e567e8b6 | ||
|
|
ddd74549fe | ||
|
|
14620c32aa | ||
|
|
fb7068d415 | ||
|
|
8614ff40df | ||
|
|
aa10a9d899 | ||
|
|
a5b8feca93 | ||
|
|
486e47f985 | ||
|
|
bb5a1ad513 | ||
|
|
eac0a52f10 | ||
|
|
7ac00258cc | ||
|
|
e3a0ae8a4b | ||
|
|
2953159f8b | ||
|
|
9693363c76 | ||
|
|
a2533af116 | ||
|
|
b4aecb5b74 | ||
|
|
15aa2498b5 | ||
|
|
0372ff0c2c | ||
|
|
7a8d5a391a | ||
|
|
2a6c81a89d | ||
|
|
301871aec6 | ||
|
|
25359e5320 | ||
|
|
b6fff53b21 | ||
|
|
ae7b5fac74 | ||
|
|
26168a9520 | ||
|
|
698dfca319 | ||
|
|
3bcb98e644 | ||
|
|
2deb436ccd | ||
|
|
2b3405c4a9 | ||
|
|
677a465630 | ||
|
|
8ecb76fc0b | ||
|
|
0178013fc1 | ||
|
|
c273a8ee69 | ||
|
|
0ed56b706b | ||
|
|
4582b6cf76 | ||
|
|
05513bcd1e | ||
|
|
f5dd135ed8 | ||
|
|
9c8f85741c | ||
|
|
ca515f2eae | ||
|
|
80c1ebd768 | ||
|
|
b51fd7fc13 | ||
|
|
efe86c37b2 | ||
|
|
d20a4a8bfc | ||
|
|
9da2d11e80 | ||
|
|
5ef554aecf | ||
|
|
9a7fea0447 | ||
|
|
ae52ff93b2 | ||
|
|
80a567bf1e | ||
|
|
ce2a3361eb | ||
|
|
ca9ea109c6 | ||
|
|
2a33a746f0 | ||
|
|
e8c5246645 | ||
|
|
98295b85ab | ||
|
|
af1823db8c | ||
|
|
a2ab6b89f1 | ||
|
|
5de300fb35 | ||
|
|
62a4c82e95 | ||
|
|
d522c864d4 | ||
|
|
aa8ff7ace3 | ||
|
|
4e6a931de3 | ||
|
|
5e141e869d | ||
|
|
611555514c | ||
|
|
e1c78fcbd3 | ||
|
|
8640d6bb1e | ||
|
|
28d5bedcc7 | ||
|
|
373b890e1d | ||
|
|
aad0f90a9d | ||
|
|
5dc45c35e6 | ||
|
|
b8c87632e6 | ||
|
|
c85903383a | ||
|
|
4aededf038 | ||
|
|
4bc6501b8d | ||
|
|
a1b3b47573 | ||
|
|
c8cf4fe09c | ||
|
|
ca07d75405 | ||
|
|
c5001f3620 | ||
|
|
8d5f941829 | ||
|
|
c3bfaa1c33 | ||
|
|
ea0d52c0b8 | ||
|
|
fcb37f40f6 | ||
|
|
7f30d07f4c | ||
|
|
59744a96fa | ||
|
|
b82fb58dc4 | ||
|
|
c728214af7 | ||
|
|
305d636217 | ||
|
|
31312747e9 | ||
|
|
5ef288b840 | ||
|
|
f6615a490d | ||
|
|
bd4f5ebcdf | ||
|
|
1fd7ff5655 | ||
|
|
ab7e1b42bd | ||
|
|
a7723e6ded | ||
|
|
1b78001201 | ||
|
|
36c0eae7ed | ||
|
|
0ae43e242f | ||
|
|
bafd4f1860 | ||
|
|
388e58bf1e | ||
|
|
eee973fe86 | ||
|
|
61769c6f9c | ||
|
|
665ef9424e | ||
|
|
7a0f0ca5ce | ||
|
|
63be05146d | ||
|
|
9239cfb3c1 | ||
|
|
6fd24ad54f | ||
|
|
d70933c9f2 | ||
|
|
9ac2ddcb4d | ||
|
|
8d9569e06b | ||
|
|
02f8e657f3 | ||
|
|
3dc711ab9d | ||
|
|
702922dd88 | ||
|
|
2583c809ca | ||
|
|
b6071ce6dc | ||
|
|
186132bb98 | ||
|
|
c15790f230 | ||
|
|
13924a8353 | ||
|
|
fd84b57ac8 | ||
|
|
591a6b330a | ||
|
|
a3b767bb13 | ||
|
|
847ee61bf4 | ||
|
|
0c6cede287 | ||
|
|
ce4b07d7d7 | ||
|
|
a1f49b279f | ||
|
|
1c8075ca40 | ||
|
|
56b0952cd1 | ||
|
|
1c152f6cad | ||
|
|
57c05354c2 | ||
|
|
90b5479735 | ||
|
|
1079c4516c | ||
|
|
7381985c79 | ||
|
|
fd26f9f34e | ||
|
|
88b70973cc | ||
|
|
f0658bbd09 | ||
|
|
661e07c8db | ||
|
|
6e51189d4d | ||
|
|
dfdb7c835b | ||
|
|
f1d7aa09e4 | ||
|
|
88e6b865d9 | ||
|
|
d5c6d74f14 | ||
|
|
202f3d36c4 | ||
|
|
7a54b1d36a | ||
|
|
9091b36249 | ||
|
|
21285d9f6d | ||
|
|
2ebc773863 | ||
|
|
44f4057876 | ||
|
|
d85020079f | ||
|
|
956dc382ea | ||
|
|
99aa214859 | ||
|
|
405e98f429 | ||
|
|
a8c375fc95 | ||
|
|
4a56a2cad6 | ||
|
|
438945907d | ||
|
|
db245add0f | ||
|
|
986699bce5 | ||
|
|
d1803320f1 | ||
|
|
d4609519f0 | ||
|
|
2b4a6284e4 | ||
|
|
3c6be7e04c | ||
|
|
e738e57e26 | ||
|
|
21ebc398fa | ||
|
|
1ac611239e | ||
|
|
97e6047725 | ||
|
|
cf3f0fcc39 | ||
|
|
19c32bf993 | ||
|
|
e86eb16d91 | ||
|
|
1fcd1ff3e8 | ||
|
|
58f4212aa8 | ||
|
|
f01152eda1 | ||
|
|
11ff40bcd6 | ||
|
|
46e985b306 | ||
|
|
fdc014af67 | ||
|
|
bf11a46abe | ||
|
|
8f41130a14 | ||
|
|
e96c4732d6 | ||
|
|
a1d38a6940 | ||
|
|
9b8703cf49 | ||
|
|
c4d77bc18a | ||
|
|
c69fbb72d3 | ||
|
|
64e4791dca | ||
|
|
bc1e62ce51 | ||
|
|
79c1040796 | ||
|
|
eaf55bf12c | ||
|
|
ce528c9783 | ||
|
|
b9c7501012 | ||
|
|
ae10052aaf | ||
|
|
10abcd519f | ||
|
|
1d6c763e92 | ||
|
|
3fa0ce99f0 | ||
|
|
7380585f00 | ||
|
|
7557ffcda1 | ||
|
|
bc9d70109c | ||
|
|
7448159d6b | ||
|
|
a65998274f | ||
|
|
b2f4a0276a | ||
|
|
99d9c3a900 | ||
|
|
e4dc430c74 | ||
|
|
1435516a9c | ||
|
|
2a1befb41a | ||
|
|
2840d98fd4 | ||
|
|
32b9c0c840 | ||
|
|
f16273772e | ||
|
|
6375a62465 | ||
|
|
aa63c3f70e | ||
|
|
004fb96b2f | ||
|
|
5895604282 | ||
|
|
a1af75a87f | ||
|
|
732bd28c92 | ||
|
|
90715467a2 | ||
|
|
7425700009 | ||
|
|
8e884fe115 | ||
|
|
96c09450b8 | ||
|
|
64cfd2296c | ||
|
|
17cf0772fb | ||
|
|
66605196ad | ||
|
|
2c9b148627 | ||
|
|
07ef48a07a | ||
|
|
03f94db5e2 | ||
|
|
9b202adebd | ||
|
|
daf8e5b8b6 | ||
|
|
25bd27ef95 | ||
|
|
dd41e4906c | ||
|
|
20660b92f8 | ||
|
|
f0cc7a925c | ||
|
|
057e69fe70 | ||
|
|
4be82c5ca6 | ||
|
|
0eaf8f38a1 | ||
|
|
f31af18aa9 | ||
|
|
5859cd290c | ||
|
|
a39b1583da | ||
|
|
ac0eb9acaf | ||
|
|
a0d9e46c33 | ||
|
|
573404d3ac | ||
|
|
2fe545e19a | ||
|
|
ea52c05f05 | ||
|
|
2a643e86bc | ||
|
|
cc76428cd2 | ||
|
|
7ffc3a0652 | ||
|
|
51df0860cc | ||
|
|
e4f397d049 | ||
|
|
0c8dff162d | ||
|
|
4865529fed | ||
|
|
0a404cc9a6 | ||
|
|
17b84f32df | ||
|
|
a03958d937 | ||
|
|
27cd1e73f3 | ||
|
|
d6bd893573 | ||
|
|
7a7049b25b | ||
|
|
62ff9605ce | ||
|
|
2847c34f58 | ||
|
|
b5a00f3c47 | ||
|
|
09d0972ab4 | ||
|
|
6b12449be4 | ||
|
|
955b36913f | ||
|
|
7e6cf7b979 | ||
|
|
b82ae5e84a | ||
|
|
c5a17cd043 | ||
|
|
1692f7640c | ||
|
|
ebcb21dbfe | ||
|
|
b6d12cfb11 | ||
|
|
7f75a7ca0b | ||
|
|
bdc9196b4a | ||
|
|
a283c3143d | ||
|
|
57635c0d24 | ||
|
|
7ed4485717 | ||
|
|
394952a86a | ||
|
|
85854cac77 | ||
|
|
5bf3c28436 | ||
|
|
e25249ce4d | ||
|
|
40073e7089 | ||
|
|
0e141f21e8 | ||
|
|
9a1f4de323 | ||
|
|
83493237a5 | ||
|
|
fb14d9c134 | ||
|
|
63fca853d0 | ||
|
|
f647f7bdea | ||
|
|
06076c683f | ||
|
|
6b61eefca7 | ||
|
|
985dd65b83 | ||
|
|
f26ad00155 | ||
|
|
a210327318 | ||
|
|
5ae76bfe6c | ||
|
|
58fb74179b | ||
|
|
92223dbee5 | ||
|
|
1ceb827a82 | ||
|
|
f85472c0ce | ||
|
|
4933cd46d7 | ||
|
|
421ad21b40 | ||
|
|
6cea83991c | ||
|
|
b04a2d4f61 | ||
|
|
f8467fcda6 | ||
|
|
9f00dba0cb | ||
|
|
6a8a49d8ef | ||
|
|
7e2954c325 | ||
|
|
da21d33d96 | ||
|
|
27663b10a2 | ||
|
|
c099a5ad2e | ||
|
|
a4c05deb21 | ||
|
|
9df77707d3 | ||
|
|
ceea6e4597 | ||
|
|
b5b0599222 | ||
|
|
94152c4d17 | ||
|
|
f02b5e8c4d | ||
|
|
f1820ffaf7 | ||
|
|
52cad8d6da | ||
|
|
1590393fcc | ||
|
|
64f13df99b | ||
|
|
45cdb81861 | ||
|
|
ff563a70a5 | ||
|
|
84a5edf0eb | ||
|
|
5528a130b6 | ||
|
|
a384f6e5fd | ||
|
|
3646395f1d | ||
|
|
8bbf351d04 | ||
|
|
dde0292e1c | ||
|
|
ff1212a188 | ||
|
|
27934dad37 | ||
|
|
0d509c82ee | ||
|
|
30e6d29106 | ||
|
|
7a9ef0d664 | ||
|
|
3cce74d364 | ||
|
|
9698988be3 | ||
|
|
29af5fc4a6 | ||
|
|
a7b79824de | ||
|
|
d625d0ffbd | ||
|
|
1dcfa90c8e | ||
|
|
8170dad9bd | ||
|
|
699f85e773 | ||
|
|
f225d38680 | ||
|
|
2630dc8dcd | ||
|
|
276662a147 | ||
|
|
ed8a9af355 | ||
|
|
e6e3d826b9 | ||
|
|
5b3606ad1d | ||
|
|
072cc13f14 | ||
|
|
c1ed660ca0 | ||
|
|
2c44051318 | ||
|
|
d0a690c303 | ||
|
|
87e1fa0a28 | ||
|
|
a1af27b125 | ||
|
|
ceaddbc821 | ||
|
|
9989c8100a | ||
|
|
c0e73e71c5 | ||
|
|
b0ba670c91 | ||
|
|
d5c9b7dfe8 | ||
|
|
095b5fcea0 | ||
|
|
aeee40c894 | ||
|
|
a7fbcd0aa8 | ||
|
|
c9bc081f8c | ||
|
|
fbb5df0849 | ||
|
|
cef061d6fb | ||
|
|
def58ff11f | ||
|
|
9e73e3b153 | ||
|
|
e9ea365f2f | ||
|
|
55118a6768 | ||
|
|
1e214aae7c | ||
|
|
ff09a7255a | ||
|
|
26b7200360 | ||
|
|
b38a2bbd12 | ||
|
|
097cbcdae3 | ||
|
|
c0fdc28a84 | ||
|
|
6218078c51 | ||
|
|
a9aae6b36c | ||
|
|
96fb2118d5 | ||
|
|
48fc0949cc | ||
|
|
7d270211ae | ||
|
|
a9f5b84c7f | ||
|
|
2d20f12335 | ||
|
|
45b53b8902 | ||
|
|
898b768b30 | ||
|
|
06aa1bb90f | ||
|
|
1f6078cf25 | ||
|
|
ba36ab9559 | ||
|
|
586c0a0579 | ||
|
|
209d7117fb | ||
|
|
3751d11a0b | ||
|
|
1af86f6afb | ||
|
|
4c77908bb4 | ||
|
|
952b208a01 | ||
|
|
40fb29ea2b | ||
|
|
c1081e3df0 | ||
|
|
4b60f7ddff | ||
|
|
75d8c4f5c0 | ||
|
|
16a7fcb79b | ||
|
|
8cd0137aed | ||
|
|
f455b12085 | ||
|
|
1a9057a175 | ||
|
|
0fcfb7b82b | ||
|
|
30f08ae48c | ||
|
|
8f1b65de59 | ||
|
|
d88f9f3b3e | ||
|
|
08e8d0f56f | ||
|
|
fb535ad6bb | ||
|
|
15efac520e | ||
|
|
dd5623ffbf | ||
|
|
7a6a0f364c | ||
|
|
93297b63b1 | ||
|
|
e1540390a8 | ||
|
|
71ba071160 | ||
|
|
af449161ff | ||
|
|
03aa11b412 | ||
|
|
5e272db8f5 | ||
|
|
827e68acf5 | ||
|
|
987ea1cb98 | ||
|
|
633ecb524e | ||
|
|
f19d8f7095 | ||
|
|
a20e3cd77e | ||
|
|
a7b6a67615 | ||
|
|
e7f05d76fa | ||
|
|
5cb57fb176 | ||
|
|
95bde7bb8a | ||
|
|
daa2329f8b | ||
|
|
b23710f89f | ||
|
|
277dda0dcb | ||
|
|
cf9134416c | ||
|
|
2425368c3a | ||
|
|
20c4d213d9 | ||
|
|
af9134ffb4 | ||
|
|
f65ddaa0f1 | ||
|
|
9580a21786 | ||
|
|
dfd17bdd88 | ||
|
|
0f48d221b4 | ||
|
|
8f57388cd3 | ||
|
|
0992587da5 | ||
|
|
138a6b1136 | ||
|
|
c6ec8317ac | ||
|
|
81c2ecc788 | ||
|
|
7abe5dc845 |
92
.ameba.yml
Normal file
92
.ameba.yml
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#
|
||||||
|
# Lint
|
||||||
|
#
|
||||||
|
|
||||||
|
# Exclude assigns for ECR files
|
||||||
|
Lint/UselessAssign:
|
||||||
|
Excluded:
|
||||||
|
- src/invidious.cr
|
||||||
|
- src/invidious/helpers/errors.cr
|
||||||
|
- src/invidious/routes/**/*.cr
|
||||||
|
|
||||||
|
# Ignore false negative (if !db.query_one?...)
|
||||||
|
Lint/UnreachableCode:
|
||||||
|
Excluded:
|
||||||
|
- src/invidious/database/base.cr
|
||||||
|
|
||||||
|
# Ignore shadowed variable `key` (it works for now, and that's
|
||||||
|
# a sensitive part of the code)
|
||||||
|
Lint/ShadowingOuterLocalVar:
|
||||||
|
Excluded:
|
||||||
|
- src/invidious/helpers/tokens.cr
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Style
|
||||||
|
#
|
||||||
|
|
||||||
|
Style/RedundantBegin:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Style/RedundantReturn:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Metrics
|
||||||
|
#
|
||||||
|
|
||||||
|
# Ignore function complexity (number of if/else & case/when branches)
|
||||||
|
# For some functions that can hardly be simplified for now
|
||||||
|
Metrics/CyclomaticComplexity:
|
||||||
|
Excluded:
|
||||||
|
# get_about_info(ucid, locale) => [17/10]
|
||||||
|
- src/invidious/channels/about.cr
|
||||||
|
|
||||||
|
# fetch_channel_community(ucid, continuation, ...) => [34/10]
|
||||||
|
- src/invidious/channels/community.cr
|
||||||
|
|
||||||
|
# create_notification_stream(env, topics, connection_channel) => [14/10]
|
||||||
|
- src/invidious/helpers/helpers.cr:84:5
|
||||||
|
|
||||||
|
# get_index(plural_form, count) => [25/10]
|
||||||
|
- src/invidious/helpers/i18next.cr
|
||||||
|
|
||||||
|
# call(context) => [18/10]
|
||||||
|
- src/invidious/helpers/static_file_handler.cr
|
||||||
|
|
||||||
|
# show(env) => [38/10]
|
||||||
|
- src/invidious/routes/embed.cr
|
||||||
|
|
||||||
|
# get_video_playback(env) => [45/10]
|
||||||
|
- src/invidious/routes/video_playback.cr
|
||||||
|
|
||||||
|
# handle(env) => [40/10]
|
||||||
|
- src/invidious/routes/watch.cr
|
||||||
|
|
||||||
|
# playlist_ajax(env) => [24/10]
|
||||||
|
- src/invidious/routes/playlists.cr
|
||||||
|
|
||||||
|
# fetch_youtube_comments(id, cursor, ....) => [40/10]
|
||||||
|
# template_youtube_comments(comments, locale, ...) => [16/10]
|
||||||
|
# content_to_comment_html(content) => [14/10]
|
||||||
|
- src/invidious/comments.cr
|
||||||
|
|
||||||
|
# to_json(locale, json) => [21/10]
|
||||||
|
# extract_video_info(video_id, ...) => [44/10]
|
||||||
|
# process_video_params(query, preferences) => [20/10]
|
||||||
|
- src/invidious/videos.cr
|
||||||
|
|
||||||
|
# produce_search_params(page, sort, ...) => [29/10]
|
||||||
|
# process_search_query(query, page, ...) => [14/10]
|
||||||
|
- src/invidious/search.cr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#src/invidious/playlists.cr:327:5
|
||||||
|
#[C] Metrics/CyclomaticComplexity: Cyclomatic complexity too high [19/10]
|
||||||
|
# fetch_playlist(plid : String)
|
||||||
|
|
||||||
|
#src/invidious/playlists.cr:436:5
|
||||||
|
#[C] Metrics/CyclomaticComplexity: Cyclomatic complexity too high [11/10]
|
||||||
|
# extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
||||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# https://github.community/t/how-to-change-the-category/2261/3
|
||||||
|
videojs-*.js linguist-detectable=false
|
||||||
|
video.min.js linguist-detectable=false
|
||||||
18
.github/CODEOWNERS
vendored
Normal file
18
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Default and lowest precedence. If none of the below matches, @iv-org/developers would be requested for review.
|
||||||
|
* @iv-org/developers
|
||||||
|
|
||||||
|
docker-compose.yml @unixfox
|
||||||
|
docker/ @unixfox
|
||||||
|
kubernetes/ @unixfox
|
||||||
|
|
||||||
|
README.md @thefrenchghosty
|
||||||
|
config/config.example.yml @thefrenchghosty @SamantazFox @unixfox
|
||||||
|
|
||||||
|
scripts/ @syeopite
|
||||||
|
shards.lock @syeopite
|
||||||
|
shards.yml @syeopite
|
||||||
|
|
||||||
|
locales/ @SamantazFox
|
||||||
|
src/invidious/helpers/i18n.cr @SamantazFox
|
||||||
|
|
||||||
|
src/invidious/helpers/youtube_api.cr @SamantazFox
|
||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: https://invidious.io/donate/
|
||||||
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a bug report to help us improve Invidious
|
||||||
|
title: '[Bug] '
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
BEFORE TRYING TO REPORT A BUG:
|
||||||
|
|
||||||
|
* Read the FAQ!
|
||||||
|
* Use the search function to check if there is already an issue open for your problem!
|
||||||
|
|
||||||
|
If you want to suggest a new feature please use "Feature request" instead
|
||||||
|
If you want to suggest an enhancement to an existing feature please use "Enhancement" instead
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**Steps to Reproduce**
|
||||||
|
<!-- Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
<!-- If applicable, copy the log that appear in the browser page where the error is reported. -->
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context about the problem here.
|
||||||
|
- Browser (if applicable):
|
||||||
|
- OS (if applicable):
|
||||||
|
-->
|
||||||
24
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement
|
||||||
|
about: Suggest an enhancement for an existing feature
|
||||||
|
title: '[Enhancement] '
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please use the search function to check if the desired function has already been requested by someone else -->
|
||||||
|
<!-- If you want to suggest a new feature please use "Feature request" instead -->
|
||||||
|
<!-- If you want to report a bug, please use "Bug report" instead -->
|
||||||
|
|
||||||
|
**Is your enhancement request related to a problem? Please describe.**
|
||||||
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the enhancement here. -->
|
||||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: '[Feature request] '
|
||||||
|
labels: feature-request
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please use the search function to check if the desired function has already been requested by someone else -->
|
||||||
|
<!-- If you want to suggest an enhancement to an existing feature please use "Enhancement" instead -->
|
||||||
|
<!-- If you want to report a bug, please use "Bug report" instead -->
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
124
.github/workflows/ci.yml
vendored
Normal file
124
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
name: Invidious CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *" # Every day at 00:00
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
- "api-only"
|
||||||
|
pull_request:
|
||||||
|
branches: "*"
|
||||||
|
paths-ignore:
|
||||||
|
- "*.md"
|
||||||
|
- LICENCE
|
||||||
|
- TRANSLATION
|
||||||
|
- invidious.service
|
||||||
|
- .git*
|
||||||
|
- .editorconfig
|
||||||
|
|
||||||
|
- screenshots/*
|
||||||
|
- assets/**
|
||||||
|
- locales/*
|
||||||
|
- config/**
|
||||||
|
- .github/ISSUE_TEMPLATE/*
|
||||||
|
- kubernetes/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: "build - crystal: ${{ matrix.crystal }}, stable: ${{ matrix.stable }}"
|
||||||
|
|
||||||
|
continue-on-error: ${{ !matrix.stable }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
stable: [true]
|
||||||
|
crystal:
|
||||||
|
- 1.0.0
|
||||||
|
- 1.1.1
|
||||||
|
- 1.2.2
|
||||||
|
include:
|
||||||
|
- crystal: nightly
|
||||||
|
stable: false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Crystal
|
||||||
|
uses: crystal-lang/install-crystal@v1.5.3
|
||||||
|
with:
|
||||||
|
crystal: ${{ matrix.crystal }}
|
||||||
|
|
||||||
|
- name: Cache Shards
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./lib
|
||||||
|
key: shards-${{ hashFiles('shard.lock') }}
|
||||||
|
|
||||||
|
- name: Install Shards
|
||||||
|
run: |
|
||||||
|
if ! shards check; then
|
||||||
|
shards install
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: crystal spec
|
||||||
|
|
||||||
|
- name: Run lint
|
||||||
|
run: |
|
||||||
|
if ! crystal tool format --check; then
|
||||||
|
crystal tool format
|
||||||
|
git diff
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: crystal build --warnings all --error-on-warnings --error-trace src/invidious.cr
|
||||||
|
|
||||||
|
build-docker:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Build Docker
|
||||||
|
run: docker-compose build --build-arg release=0
|
||||||
|
|
||||||
|
- name: Run Docker
|
||||||
|
run: docker-compose up -d
|
||||||
|
|
||||||
|
- name: Test Docker
|
||||||
|
run: while curl -Isf http://localhost:3000; do sleep 1; done
|
||||||
|
|
||||||
|
build-docker-arm64:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Build Docker ARM64 image
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile.arm64
|
||||||
|
platforms: linux/arm64/v8
|
||||||
|
build-args: release=0
|
||||||
|
|
||||||
|
- name: Test Docker
|
||||||
|
run: while curl -Isf http://localhost:3000; do sleep 1; done
|
||||||
|
|
||||||
|
|
||||||
77
.github/workflows/container-release.yml
vendored
Normal file
77
.github/workflows/container-release.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
name: Build and release container
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
paths-ignore:
|
||||||
|
- "*.md"
|
||||||
|
- LICENCE
|
||||||
|
- TRANSLATION
|
||||||
|
- invidious.service
|
||||||
|
- .git*
|
||||||
|
- .editorconfig
|
||||||
|
|
||||||
|
- screenshots/*
|
||||||
|
- .github/ISSUE_TEMPLATE/*
|
||||||
|
- kubernetes/**
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Crystal
|
||||||
|
uses: oprypin/install-crystal@v1.2.4
|
||||||
|
with:
|
||||||
|
crystal: 1.2.2
|
||||||
|
|
||||||
|
- name: Run lint
|
||||||
|
run: |
|
||||||
|
if ! crystal tool format --check; then
|
||||||
|
crystal tool format
|
||||||
|
git diff
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: arm64
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: quay.io
|
||||||
|
username: ${{ secrets.QUAY_USERNAME }}
|
||||||
|
password: ${{ secrets.QUAY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push Docker AMD64 image for Push Event
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
labels: quay.expires-after=12w
|
||||||
|
push: true
|
||||||
|
tags: quay.io/invidious/invidious:${{ github.sha }},quay.io/invidious/invidious:latest
|
||||||
|
build-args: release=1
|
||||||
|
|
||||||
|
- name: Build and push Docker ARM64 image for Push Event
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile.arm64
|
||||||
|
platforms: linux/arm64/v8
|
||||||
|
labels: quay.expires-after=12w
|
||||||
|
push: true
|
||||||
|
tags: quay.io/invidious/invidious:${{ github.sha }}-arm64,quay.io/invidious/invidious:latest-arm64
|
||||||
|
build-args: release=1
|
||||||
24
.github/workflows/stale.yml
vendored
Normal file
24
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Documentation: https://github.com/marketplace/actions/close-stale-issues
|
||||||
|
|
||||||
|
name: "Stale issue handler"
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 */12 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v3
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
days-before-stale: 365
|
||||||
|
days-before-pr-stale: 45 # PRs should be active. Anything that hasn't had activity in more than 45 days should be considered abandoned.
|
||||||
|
days-before-close: 30
|
||||||
|
exempt-pr-labels: blocked
|
||||||
|
stale-issue-message: 'This issue has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely outdated. If you think this issue is still relevant and applicable, you just have to post a comment and it will be unmarked.'
|
||||||
|
stale-pr-message: 'This pull request has been automatically marked as stale and will be closed in 30 days because it has not had recent activity and is much likely abandoned or outdated. If you think this pull request is still relevant and applicable, you just have to post a comment and it will be unmarked.'
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-pr-label: "stale"
|
||||||
|
ascending: true
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,4 +6,4 @@
|
|||||||
/.vscode/
|
/.vscode/
|
||||||
/invidious
|
/invidious
|
||||||
/sentry
|
/sentry
|
||||||
shard.lock
|
/config/config.yml
|
||||||
|
|||||||
591
CHANGELOG.md
591
CHANGELOG.md
@@ -1,3 +1,594 @@
|
|||||||
|
# Note: This is no longer updated and links to omarroths repo, which doesn't exist anymore.
|
||||||
|
|
||||||
|
# 0.20.0 (2019-011-06)
|
||||||
|
|
||||||
|
# Version 0.20.0: Custom Playlists
|
||||||
|
|
||||||
|
It's been quite a while since the last release! There've been [198 commits](https://github.com/omarroth/invidious/compare/0.19.0..0.20.0) from 27 contributors.
|
||||||
|
|
||||||
|
A couple smaller features have since been added. Channel pages and playlists in particular have received a bit of a face-lift, with both now displaying their descriptions as expected, and playlists providing video count and published information. Channels will also now provide video descriptions in their RSS feed.
|
||||||
|
|
||||||
|
Turkish (tr), Chinese (zh-TW, in addition to zh-CN), and Japanese (jp) are all now supported languages. Thank you as always to the hard work done by translators that makes this possible.
|
||||||
|
|
||||||
|
The feed menu and default home page are both now configurable for registered and unregistered users, and is quite a bit of an improvement for users looking to reduce distractions for their daily use.
|
||||||
|
|
||||||
|
## For Administrators
|
||||||
|
|
||||||
|
`feed_menu` and `default_home` are now configurable by the user, and have therefore been moved into `default_user_preferences`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
feed_menu: ["Popular", "Top"]
|
||||||
|
default_home: Top
|
||||||
|
|
||||||
|
# becomes:
|
||||||
|
|
||||||
|
default_user_preferences:
|
||||||
|
feed_menu: ["Popular", "Top"]
|
||||||
|
default_home: Top
|
||||||
|
```
|
||||||
|
|
||||||
|
Several new options have also been added, including the ability to set a support email for the instance using `admin_email: EMAIL`, and forcing the use of a specific connection in the case of rate-limiting using `force_resolve` (see below).
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
Authenticated endpoints are now [properly documented](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints), as well how to generate and use API tokens. My hope is that this makes some of the more [interesting](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authnotifications) endpoints more accessible for developers to use in their own applications.
|
||||||
|
|
||||||
|
API endpoints for interacting with custom playlists have also been added with documentation available [here](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authplaylists).
|
||||||
|
|
||||||
|
## Custom playlists
|
||||||
|
|
||||||
|
This is probably the feature that has been the longest in the pipe and that I'm quite pleased is now implemented. It is now possible to create custom playlists, which can be played and edited through Invidious. API endpoints have also been added (documentation [here](https://github.com/omarroth/invidious/wiki/Authenticated-Endpoints#get-apiv1authplaylists)).
|
||||||
|
|
||||||
|
Overall I'm quite pleased with how smoothly it has been rolled out and with the experience so far, and I'm exctited for how it can be extended and improved in future.
|
||||||
|
|
||||||
|
## [instances.invidio.us](https://instances.invidio.us)
|
||||||
|
|
||||||
|
It is now possible to view a list of public instances (as provided in the [wiki](https://github.com/omarroth/invidious/wiki/Invidious-Instances)) through an API or a pretty new interface [here](https://instances.invidio.us). It combines uptime information, statistics from each instance and basic information already provided in the wiki. I expect it should be much more user-friendly than compiling the information yourself, and is already used by [Invidition](https://codeberg.org/Booteille/Invidition) to provide a list of instances for users to choose from.
|
||||||
|
|
||||||
|
The site itself is licensed under the AGPLv3 and the source is available [here](https://github.com/omarroth/instances.invidio.us).
|
||||||
|
|
||||||
|
## Video unavailable [#811](https://github.com/omarroth/invidious/issues/811)
|
||||||
|
|
||||||
|
Many users have likely noticed this error message if using Invidious directly or through another service, such as FreeTube. This issue is caused by rate-limiting by Google, and is not a new issuee for projects like Invidious (notably [youtube-dl](https://github.com/ytdl-org/youtube-dl#http-error-429-too-many-requests-or-402-payment-required)) and appears to be affecting smaller, private instances as well.
|
||||||
|
|
||||||
|
There is not a permanent fix for administrators currently, however there is some information available [here](https://github.com/omarroth/invidious/issues/811#issuecomment-540017772) that may provide a temporary solution. Unfortanately, in most cases the best option is to wait for the instance to be unbanned or to move the instance to a different IP. A more informative error message is also now provided, which should help an administrator more quickly diagnose the problem.
|
||||||
|
|
||||||
|
For those interested, I would recommend following [#811](https://github.com/omarroth/invidious/issues/811) for any future progress on the issue.
|
||||||
|
|
||||||
|
## BAT verified publisher
|
||||||
|
|
||||||
|
I'm quite late to this announcement, however I'm pleased to mention that Invidious is now a BAT verified publisher! I would recommend looking [here](https://basicattentiontoken.org/about/) or [here](https://www.reddit.com/r/BATProject/comments/7cr7yc/new_to_bat_read_this_introduction_to_basic/) for learning more about what it is and how it works. Overall I think it makes an interesting substitute for services like Liberapay, and a (hopefully) much less-intrusive alternative to direct advertising.
|
||||||
|
|
||||||
|
BAT is combined under other cryptocurrencies below. Currently there's a fairly significant delay in payout, which is the reason for the large fluctuation in crypto donations between September and October (and also the reason for the late announcement).
|
||||||
|
|
||||||
|
## Release schedule
|
||||||
|
|
||||||
|
Currently I'm quite pleased with the current state of the project. There's plenty of things I'd still like to add, however at this point I expect the rate of most new additions will slow down a bit, with more focus on stabililty and any long-standing bugs.
|
||||||
|
|
||||||
|
Because of this, I'm planning on releasing a new version quarterly, with any necessary hotfixes being pushed as a new patch release as necessary. As always it will be possible to run Invidious directly from [master](https://github.com/omarroth/invidious/wiki/Updating) if you'd still like to have the lastest version.
|
||||||
|
|
||||||
|
I'll plan on providing finances each release, with a similar monthly breakdown as below.
|
||||||
|
|
||||||
|
## Finances for September 2019
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth) : \$64.37
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$76.04
|
||||||
|
- Crypto : ~\$99.89 (converted from BAT, BCH, BTC)
|
||||||
|
- Total : \$240.30
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-lb1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node11 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node12 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node13 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node14 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node15 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node16 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$135.00
|
||||||
|
|
||||||
|
## Finances for October 2019
|
||||||
|
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$134.40
|
||||||
|
- Crypto : ~\$8.29 (converted from BAT, BCH, BTC)
|
||||||
|
- Total : \$142.69
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-lb1 (nyc1) : \$5.00 (load balancer)
|
||||||
|
- invidious-lb2 (nyc1) : \$5.00 (load balancer)
|
||||||
|
- invidious-lb3 (nyc1) : \$5.00 (load balancer)
|
||||||
|
- invidious-lb4 (nyc1) : \$5.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node11 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node12 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node13 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node14 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node15 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node16 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node17 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node18 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$155.00
|
||||||
|
|
||||||
|
# 0.19.0 (2019-07-13)
|
||||||
|
|
||||||
|
# Version 0.19.0: Communities
|
||||||
|
|
||||||
|
Hello again everyone! Focus this month has mainly been on improving playback performance, along with a couple new features I'd like to announce. There have been [109 commits](https://github.com/omarroth/invidious/compare/0.18.0...0.19.0) this past month from 10 contributors.
|
||||||
|
|
||||||
|
This past month has seen the addition of Chinese (`zh-CN`) and Icelandic (`is`) translations. I would like to give a huge thanks to their respective translators, and again an enormous thanks to everyone who helps translate the site.
|
||||||
|
|
||||||
|
I'm delighted to mention that [FreeTube 0.6.0](https://github.com/FreeTubeApp/FreeTube) now supports 1080p thanks to the Invidious API. I would very much recommend reading the [relevant post](https://freetube.writeas.com/freetube-release-0-6-0-beta-1080p-and-a-lot-of-qol) for some more information on how it works, along with several other major improvements. Folks that are interested in adding similar functionality for their own projects should feel free to get in touch.
|
||||||
|
|
||||||
|
This past month there has been quite a bit of work on improving memory usage and improving download and playback speeds. As mentioned in the previous release, some extra hardware has been allocated which should also help with this. I'm still looking for ways to improve performance and feedback is always appreciated.
|
||||||
|
|
||||||
|
Along with performance, a couple quality of life improvements have been added, including author thumbnails and banners, clickable titles for embedded videos, and better styling for captions, among some other enhancements.
|
||||||
|
|
||||||
|
## Communities
|
||||||
|
|
||||||
|
Support for YouTube's [communities tab](https://creatoracademy.youtube.com/page/lesson/community-tab) has been added. It's a very interesting but surprisingly unknown feature. Essentially, providing comments for a channel, rather than a video, where an author can post updates for their subscribers.
|
||||||
|
|
||||||
|
It's commonly used to promote interesting links and foster discussion. I hope this feature helps people find more interesting content that otherwise would have been overlooked.
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
For accessing channel communities, an `/api/v1/channels/comments/:ucid` endpoint has been added, with similar behavior and schema to `/api/v1/comments/:id`, with an extra `attachment` field for top-level comments. More info on usage and available data can be found in the [wiki](https://github.com/omarroth/invidious/wiki/API#get-apiv1channelscommentsucid-apiv1channelsucidcomments).
|
||||||
|
|
||||||
|
An `/api/v1/auth/feeds` endpoint has been added for programmatically accessing a user's subscription feed, with options for displaying notifications and filtering an existing feed.
|
||||||
|
|
||||||
|
An `/api/v1/search/suggestions` endpoint has been added for retrieving suggestions for a given query.
|
||||||
|
|
||||||
|
## For Administrators
|
||||||
|
|
||||||
|
It is now possible to disable more resource intensive features, such as downloads and DASH functionality by adding `disable_proxy` to your config. See [#453](https://github.com/omarroth/invidious/issues/453) and the [Wiki](https://github.com/omarroth/invidious/wiki/Configuration) for more information and example usage. I expect this to be a big help for folks with limited bandwidth when hosting their own instances.
|
||||||
|
|
||||||
|
## Finances
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth) : \$38.39
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$84.85
|
||||||
|
- Crypto : ~\$0.00 (converted from BCH, BTC)
|
||||||
|
- Total : \$123.24
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-load1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node7 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node8 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node9 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node10 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$105.00
|
||||||
|
|
||||||
|
The goal on Patreon has been updated to reflect the above expenses. As mentioned above, the main reason for more hardware is to improve playback and download speeds, although I'm still looking into improving performance without allocating more hardware.
|
||||||
|
|
||||||
|
As always I'm grateful for everyone's support and feedback. I'll see you all next month.
|
||||||
|
|
||||||
|
# 0.18.0 (2019-06-06)
|
||||||
|
|
||||||
|
# Version 0.18.0: Native Notifications and Optimizations
|
||||||
|
|
||||||
|
Hope everyone has been doing well. This past month there have been [97 commits](https://github.com/omarroth/invidious/compare/0.17.0...0.18.0) from 10 contributors. For the most part changes this month have been on optimizing various parts of the site, mainly subscription feeds and support for serving images and other assets.
|
||||||
|
|
||||||
|
I'm quite happy to mention that support for Greek (`el`) has been added, which I hope will continue to make the site accessible for more users.
|
||||||
|
|
||||||
|
Subscription feeds will now only update when necessary, rather than periodically. This greatly lightens the load on DB as well as making the feeds generally more responsive when changing subscriptions, importing data, and when receiving new uploads.
|
||||||
|
|
||||||
|
Caching for images and other assets should be greatly improved with [#456](https://github.com/omarroth/invidious/issues/456). JavaScript has been pulled out into separate files where possible to take advantage of this, which should result in lighter pages and faster load times.
|
||||||
|
|
||||||
|
This past month several people have encountered issues with downloads and watching high quality video through the site, see [#532](https://github.com/omarroth/invidious/issues/532) and [#562](https://github.com/omarroth/invidious/issues/562). For this coming month I've allocated some more hardware which should help with this, and I'm also looking into optimizing how videos are currently served.
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
`viewCount` is now available for `/api/v1/popular` and all videos returned from `/api/v1/auth/notifications`. Both also now provide `"type"` for indicating available information for each object.
|
||||||
|
|
||||||
|
An `/authorize_token` page is now available for more easily creating new tokens for use in applications, see [this comment](https://github.com/omarroth/invidious/issues/473#issuecomment-496230812) in [#473](https://github.com/omarroth/invidious/issues/473) for more details.
|
||||||
|
|
||||||
|
A POST `/api/v1/auth/notifications` endpoint is also now available for correctly returning notifications for 150+ channels.
|
||||||
|
|
||||||
|
## For Administrators
|
||||||
|
|
||||||
|
There are two new schema changes for administrators: `views` for adding view count to the popular page, and `feed_needs_update` for tracking feed changes.
|
||||||
|
|
||||||
|
As always the relevant migration scripts are provided which should run when following instructions for [updating](https://github.com/omarroth/invidious/wiki/Updating). Otherwise, adding `check_tables: true` to your config will automatically make the required changes.
|
||||||
|
|
||||||
|
## Native Notifications
|
||||||
|
|
||||||
|
[<img src="https://omar.yt/81c3ae1839831bd9300d75e273b6552a86dc2352/native_notification.png" height="160" width="472">](https://omar.yt/81c3ae1839831bd9300d75e273b6552a86dc2352/native_notification.png "Example of native notification, available in repository under screnshots/native_notification.png")
|
||||||
|
|
||||||
|
It is now possible to receive [Web notifications](https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API) from subscribed channels.
|
||||||
|
|
||||||
|
You can enable notifications by clicking "Enable web notifications" in your preferences. Generally they appear within 20-60 seconds of a new video being uploaded, and I've found them to be an enormous quality of life improvement.
|
||||||
|
|
||||||
|
Although it has been fairly stable, please feel free to report any issues you find [here](https://github.com/omarroth/invidious/issues) or emailing me directly at omarroth@protonmail.com.
|
||||||
|
|
||||||
|
Important to note for administrators is that instances require [`use_pubsub_feeds`](https://github.com/omarroth/invidious/wiki/Configuration) and must be served over HTTPS in order to correctly send web notifications.
|
||||||
|
|
||||||
|
## Finances
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth) : \$49.73
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$100.57
|
||||||
|
- Crypto : ~\$11.12 (converted from BCH, BTC)
|
||||||
|
- Total : \$161.42
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-load1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node6 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$85.00
|
||||||
|
|
||||||
|
See you all next month!
|
||||||
|
|
||||||
|
# 0.17.0 (2019-05-06)
|
||||||
|
|
||||||
|
# Version 0.17.0: Player and Authentication API
|
||||||
|
|
||||||
|
Hello everyone! This past month there have been [130 commits](https://github.com/omarroth/invidious/compare/0.16.0..0.17.0) from 11 contributors. Large focus has been on improving the player as well as adding API access for other projects to make use of Invidious.
|
||||||
|
|
||||||
|
There have also been significant changes in preparation of native notifications (see [#195](https://github.com/omarroth/invidious/issues/195), [#469](https://github.com/omarroth/invidious/issues/469), [#473](https://github.com/omarroth/invidious/issues/473), and [#502](https://github.com/omarroth/invidious/issues/502)), and playlists. I expect to see both of these to be added in the next release.
|
||||||
|
|
||||||
|
I'm quite happy to mention that new translations have been added for Esperanto (`eo`) and Ukranian (`uk`). Support for pluralization has also been added, so it should now be possible to make a more native experience for speakers in other languages. The system currently in place is a bit cumbersome, so for any help using this feature please get in touch!
|
||||||
|
|
||||||
|
## For Administrators
|
||||||
|
|
||||||
|
A `check_tables` option has been added to automatically migrate without the use of custom scripts. This method will likely prove to be much more robust, and is currently enabled for the official instance. To prevent any unintended changes to the DB, `check_tables` is disabled by default and will print commands before executing. Having this makes features that require schema changes much easier to implement, and also makes it easier to upgrade from older instances.
|
||||||
|
|
||||||
|
As part of [#303](https://github.com/omarroth/invidious/issues/303), a `cache_annotations` option has been added to speed up access from `/api/v1/annotations/:id`. This vastly improves the experience for videos with annotations. Currently, only videos that contain legacy annotations will be cached, which should help keep down the size of the cache. `cache_annotations` is disabled by default.
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
An authorization API has been added which allows other applications to read and modify user subscriptions and preferences (see [#473](https://github.com/omarroth/invidious/issues/473)). Support for accessing user feeds and notifications is also planned. I believe this feature is a large step forward in supporting syncing subscriptions and preferences with other services, and I'm excited to see what other developers do with this functionality.
|
||||||
|
|
||||||
|
Support for server-to-client push notifications is currently underway. This allows Invidious users, as well as applications using the Invidious API, to receive notifications about uploads in near real-time (see #469). An `/api/v1/auth/notifications` endpoint is currently available. I'm very excited for this to be integrated into the site, and to see how other developers use it in their own projects.
|
||||||
|
|
||||||
|
An `/api/v1/storyboards/:id` endpoint has been added for accessing storyboard URLs, which allows developers to add video previews to their players (see below).
|
||||||
|
|
||||||
|
## Player
|
||||||
|
|
||||||
|
Support for annotations has been merged into master with [#303](https://github.com/omarroth/invidious/issues/303), thanks @glmdgrielson! Annotations can be enabled by default or only for subscribed channels, and can also be toggled per video. I'm extremely proud of the progress made here, and I'm so thankful to everyone that has made this possible. I expect this to be the last update with regards to supporting annotations, but I do plan on continuing to improve the experience as much as possible.
|
||||||
|
|
||||||
|
The Invidious player now supports video previews and a corresponding API endpoint `/api/v1/storyboards/:id` has been added for developers looking to add similar functionality to their own players. Not much else to say here. Overall it's a very nice quality of life improvement and an attractive addition to the site.
|
||||||
|
|
||||||
|
It is now possible to select specific sources for videos provided using DASH (see [#34](https://github.com/omarroth/invidious/issues/34)). I would consider support largely feature complete, although there are still several issues to be fixed before I would consider it ready for larger rollout. You can watch videos in 1080p by setting `Default quality` to `dash` in your preferences, or by adding `&quality=dash` to the end of video URLs.
|
||||||
|
|
||||||
|
## Finances
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth) : \$49.73
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$63.03
|
||||||
|
- Crypto : ~\$0.00 (converted from BCH, BTC)
|
||||||
|
- Total : \$112.76
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-load1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$80.00
|
||||||
|
|
||||||
|
That's all for now. Thanks!
|
||||||
|
|
||||||
|
# 0.16.0 (2019-04-06)
|
||||||
|
|
||||||
|
# Version 0.16.0: API Improvements and Annotations
|
||||||
|
|
||||||
|
Hello again! This past month has seen [116 commits](https://github.com/omarroth/invidious/compare/0.15.0..0.16.0) from 13 contributors and a couple important changes I'd like to announce.
|
||||||
|
|
||||||
|
A privacy policy is now available [here](https://invidio.us/privacy). I've done my best to explain things as clearly as possible without oversimplifying, and would very much recommend reading it if you're concerned about your privacy and want to learn more about how Invidious uses your data. Please let me know if there is anything that needs clarification.
|
||||||
|
|
||||||
|
I'm also very happy to announce that a Spanish translation has been added to the site. You can use it with `?hl=es` or by setting `es` as your default locale. As always I'm extremely grateful to translators for making the site accessible to more people.
|
||||||
|
|
||||||
|
## For Administrators
|
||||||
|
|
||||||
|
Invidious now supports server-to-server [push notifications](https://developers.google.com/youtube/v3/guides/push_notifications). This uses [PubSubHubbub](https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html) to automatically handle new videos sent to an instance, which is less resource intensive and generally faster. Note that it will not pull all videos from a subscribed channel, so recommended usage is in addition to `channel_threads`. Using PubSub requires a valid `domain` that updates can be sent to, and a random string that can be used to sign updates sent to the instance. You can enable it by adding `use_pubsub_feeds: true` to your `config.yml`. See [Configuration](https://github.com/omarroth/invidious/wiki/Configuration) for more info.
|
||||||
|
|
||||||
|
Unfortunately there are a couple necessary changes to the DB to support `liveNow` and `premiereTimestamp` in subscription feeds. Migration scripts have been provided that should be used automatically if following the instructions [here](https://github.com/omarroth/invidious/wiki/Updating).
|
||||||
|
|
||||||
|
You can now configure default user preferences for your instance. This allows you to set default locale, player preferences, and more. See [#415](https://github.com/omarroth/invidious/issues/415) for more details and example usage.
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
The [fields](https://developers.google.com/youtube/v3/getting-started#fields) API has been added with [#429](https://github.com/omarroth/invidious/pull/429) and is now supported on all JSON endpoints, thanks [**@afrmtbl**](https://github.com/afrmtbl)! Synax is straight-forward and can be used to reduce data transfer and create a simpler response for debugging. You can see an example [here](https://invidio.us/api/v1/videos/CvFH_6DNRCY?pretty=1&fields=title,recommendedVideos/title). I've been quite happy using it and hope it is similarly useful for others.
|
||||||
|
|
||||||
|
An `/api/v1/annotations/:id` endpoint has been added for pulling legacy annotation data from [this](https://archive.org/details/youtubeannotations) archive, see below for more details. You can also access annotation data available on YouTube using `?source=youtube`, although this will only return card data as legacy annotations were deleted on January 15th.
|
||||||
|
|
||||||
|
A couple minor changes to existing endpoints:
|
||||||
|
|
||||||
|
- A `premiereTimestamp` field has been added to `/api/v1/videos/:id`
|
||||||
|
- A `sort_by` param has been added to `/api/v1/comments/:id`, supports `new`, `top`.
|
||||||
|
|
||||||
|
More info is available in the [documentation](https://github.com/omarroth/invidious/wiki/API).
|
||||||
|
|
||||||
|
## Annotations
|
||||||
|
|
||||||
|
I'm pleased to announce that annotation data is finally available from the roughly 1.4 billion videos archived as part of [this](https://www.reddit.com/r/DataHoarder/comments/aa6czg/youtube_annotation_archive/) project. They are accessible from the Internet Archive [here](https://archive.org/details/youtubeannotations) or as a 355GB torrent, see [here](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. A corresponding `/api/v1/annotations/:id` endpoint has been added to Invidious which uses the collection from IA to provide legacy annotations.
|
||||||
|
|
||||||
|
Support for them in the player is possible thanks to [this](https://github.com/afrmtbl/videojs-youtube-annotations) plugin developed by [**@afrmtbl**](https://github.com/afrmtbl). A PR for adding support to the site is available as [#303](https://github.com/omarroth/invidious/pull/303). There's also an [extension](https://github.com/afrmtbl/AnnotationsRestored) for overlaying them on top of the YouTube player (again thanks to [**@afrmtbl**](https://github.com/afrmtbl)), and an [extension](https://tech234a.bitbucket.io/AnnotationsReloaded?src=invidious) for hooking into code still present in the YouTube player itself, developed by [**@tech234a**](https://github.com/tech234a).
|
||||||
|
|
||||||
|
I would recommend reading the [official announcement](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. I would like to again thank everyone that helped contribute to this project.
|
||||||
|
|
||||||
|
## Finances
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth) : \$42.42
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$70.11
|
||||||
|
- Crypto : ~\$1.76 (converted from BCH, BTC, BSV)
|
||||||
|
- Total : \$114.29
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-load1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$80.00
|
||||||
|
|
||||||
|
This past month the site saw a couple abnormal peaks in traffic, so an additional webserver has been added to match the increased load. The goal on Patreon has been updated to match the above expenses.
|
||||||
|
|
||||||
|
Thanks everyone!
|
||||||
|
|
||||||
|
# 0.15.0 (2019-03-06)
|
||||||
|
|
||||||
|
## Version 0.15.0: Preferences and Channel Playlists
|
||||||
|
|
||||||
|
The project has seen quite a bit of activity this past month. Large focus has been on fixing bugs, but there's still quite a few new features I'm happy to announce. There have been [133 commits](https://github.com/omarroth/invidious/compare/0.14.0...0.15.0) from 15 contributors this past month.
|
||||||
|
|
||||||
|
As a couple miscellaneous changes, a couple [nice screenshots](https://github.com/omarroth/invidious#screenshots) have been added to the README, so folks can see more of what the site has to offer without creating an account.
|
||||||
|
|
||||||
|
The footer has also been cleaned up quite a bit, and now displays the current version, so it's easier to know what features are available from the current instance.
|
||||||
|
|
||||||
|
## For Administrators
|
||||||
|
|
||||||
|
This past month there has been a minor release - `0.14.1` - which fixes a breaking change made by YouTube for their polymer redesign.
|
||||||
|
|
||||||
|
There have been several new features that unfortunately require a database migration. There are migration scripts provided in `config/migrate-scripts`, and the [wiki](https://github.com/omarroth/invidious/wiki/Updating) has instructions for automatically applying them. I'll do my best to keep those changes to a minimum, and expect to see a corresponding script to automatically apply any new changes.
|
||||||
|
|
||||||
|
Administrator preferences have been added with [#312](https://github.com/omarroth/invidious/issues/312), which allows administrators to customize their instance. Administrators can change the order of feed menus, change the default homepage, disable open registration, and several other options. There's a short 'how-to' [here](https://github.com/omarroth/invidious/issues/312#issuecomment-468831842), and the new options are documented [here](https://github.com/omarroth/invidious/wiki/Configuration).
|
||||||
|
|
||||||
|
An `/api/v1/stats` endpoint has been added with [#356](https://github.com/omarroth/invidious/issues/356), which reports the instance version and number of active users. Statistics are disabled by default, and can be enabled in administator preferences. Statistics for the official instance are available [here](https://invidio.us/api/v1/stats?pretty=1).
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
`/api/v1/channels/:ucid` now provides an `autoGenerated` tag, which returns true for topic channels, and larger genre channels generated by YouTube. These channels don't have any videos of their own, so `latestVideos` will be empty. It is recommended instead to display a list of playlists generated by YouTube.
|
||||||
|
|
||||||
|
You can now pull a list of playlists from a channel with `/api/v1/channels/playlists/:ucid`. Supported options are documented in the [wiki](https://github.com/omarroth/invidious/wiki/API#get-apiv1channelsplaylistsucid-apiv1channelsucidplaylists). Pagination is handled with a `continuation` token, which is generated on each call. Of note is that auto-generated channels currently have one page of results, and subsequent calls will be empty.
|
||||||
|
|
||||||
|
For quickly pulling the latest 30 videos from a channel, there is now `/api/v1/channels/latest/:ucid`. It is much faster than a call to `/api/v1/channels/:ucid`. It will not convert an author name to a valid ucid automatically, and will not return any extra data about a channel.
|
||||||
|
|
||||||
|
## Preferences
|
||||||
|
|
||||||
|
In addition to administrator preferences mentioned above, you can now change your preferences without an account (see [#42](https://github.com/omarroth/invidious/pull/42)). I think this is quite an improvement to the usability of the site, and is much friendlier to privacy-conscious folks that don't want to make an account. Preferences will be automatically imported to a newly created account.
|
||||||
|
|
||||||
|
Several issues with sorting subscriptions have been fixed, and `/manage_subscriptions` has been sped up significantly. The subscription feed has also seen a bump in performance. Delayed notifications have unfortunately started becoming a problem now that there are more users on the site. Some new changes are currently being tested which should mostly resolve the issue, so expect to see more in the next release.
|
||||||
|
|
||||||
|
## Channel Playlists
|
||||||
|
|
||||||
|
You can now view available playlists from a channel, and [auto-generated channels](https://invidio.us/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ) are no longer empty. You can sort as you would on YouTube, and all the same functionality should be available. I'm quite pleased to finally have it implemented, since it's currently the only data available from the above mentioned auto-generated channels, and makes it much easier to consume music on the site.
|
||||||
|
|
||||||
|
There's also more discussion on improving Invidious for streaming music in [#304](https://github.com/omarroth/invidious/issues/304), and adding support for music.youtube.com. I would appreciate any thoughts on how to improve that experience, since it's a very large and useful part of YouTube.
|
||||||
|
|
||||||
|
## Finances
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth) : \$42.42
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$30.97
|
||||||
|
- Crypto : ~\$0.00 (converted from BCH, BTC)
|
||||||
|
- Total : \$73.39
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-load1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$75.00
|
||||||
|
|
||||||
|
It's been very humbling to see how fast the project has grown, and I look forward to making the site even better. Thank you everyone.
|
||||||
|
|
||||||
|
# 0.14.0 (2019-02-06)
|
||||||
|
|
||||||
|
## Version 0.14.0: Community
|
||||||
|
|
||||||
|
This last month several contributors have made improvements specifically for the people using this project. New pages have been added to the wiki, and there is now a [Matrix Server](https://riot.im/app/#/room/#invidious:matrix.org) and IRC channel so it's easier and faster for people to ask questions or chat. There have been [101 commits](https://github.com/omarroth/invidious/compare/0.13.0...0.14.0) since the last major release from 8 contributors.
|
||||||
|
|
||||||
|
It has come to my attention in the past month how many people are self-hosting, and I would like to make it easier for them to do so.
|
||||||
|
|
||||||
|
With that in mind, expect future releases to have a section for For Administrators (if any relevant changes) and For Developers (if any relevant changes).
|
||||||
|
|
||||||
|
## For Administrators
|
||||||
|
|
||||||
|
This month the most notable change for administrators is releases. As always, there will be a major release each month. However, a new minor release will be made whenever there are any critical bugs that need to be fixed.
|
||||||
|
|
||||||
|
This past month is the first time there has been a minor release - `0.13.1` - which fixes a breaking change made by YouTube. Administrators using versioning for their instances will be able to rely on the latest version, and should have a system in place to upgrade their instance as soon as a new release is available.
|
||||||
|
|
||||||
|
Several new pages have been added to the [wiki](https://github.com/omarroth/invidious/wiki#for-administrators) (as mentioned below) that will help administrators better setup their own instances. Configuration, maintenance, and instructions for updating are of note, as well as several common issues that are encountered when first setting up.
|
||||||
|
|
||||||
|
## For Developers
|
||||||
|
|
||||||
|
There's now a `pretty=1` parameter for most endpoints so you can view data easily from the browser, which is convenient for debugging and casual use. You can see an example [here](https://invidio.us/api/v1/videos/CvFH_6DNRCY?pretty=1).
|
||||||
|
|
||||||
|
Unfortunately the `/api/v1/insights/:id` endpoint is no longer functional, as YouTube removed all publicly available analytics around a month ago. The YouTube endpoint now returns a 404, so it's unlikely it will be functional again.
|
||||||
|
|
||||||
|
## Wiki
|
||||||
|
|
||||||
|
There have been a sizable number of changes to the Wiki, including a [list of public Invidious instances](https://github.com/omarroth/invidious/wiki/Invidious-Instances), the [list of extensions](https://github.com/omarroth/invidious/wiki/Extensions), and documentation for administrators (as mentioned above) and developers.
|
||||||
|
|
||||||
|
The wiki is editable by anyone so feel free to add anything you think is useful.
|
||||||
|
|
||||||
|
## Matrix & IRC
|
||||||
|
|
||||||
|
Thee is now a [Matrix Server](https://riot.im/app/#/room/#invidious:matrix.org) for Invidious, so please feel free to hop on if you have any questions or want to chat. There is also a registered IRC channel: #invidious on Freenode which is bridged to Matrix.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Several new features have been added, including a download button, creator hearts and comment colors, and a French translation.
|
||||||
|
|
||||||
|
There have been fixes for Google logins, missing text in locales, invalid links to genre channels, and better error handling in the player, among others.
|
||||||
|
|
||||||
|
Several fixes and features are omitted for space, so I'd recommend taking a look at the [compare tab](https://github.com/omarroth/invidious/compare/0.13.0...0.14.0) for more information.
|
||||||
|
|
||||||
|
## Annotations Update
|
||||||
|
|
||||||
|
Annotations were removed January 15th, 2019 around15:00 UTC. Before they were deleted we were able to archive annotations from around 1.4 billion videos. I'd very much recommend taking a look [here](https://www.reddit.com/r/DataHoarder/comments/al7exa/youtube_annotation_archive_update_and_preview/) for more information and a list of acknowledgements. I'm extremely thankful to everyone who was able to contribute and I'm glad we were able to save such a large part of internet history.
|
||||||
|
|
||||||
|
There's been large strides in supporting them in the player as well, which you can follow in [#303](https://github.com/omarroth/invidious/pull/303). You can preview the functionality at https://dev.invidio.us . Before they are added to the main site expect to see an option to disable them, both site-wide and per video.
|
||||||
|
|
||||||
|
Organizing this project has unfortunately taken up quite a bit of my time, and I've been very grateful for everyone's patience.
|
||||||
|
|
||||||
|
## Finances
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth) : \$49.42
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$27.89
|
||||||
|
- Crypto : ~\$0.00 (converted from BCH, BTC)
|
||||||
|
- Total : \$77.31
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-load1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$75.00
|
||||||
|
|
||||||
|
As always I'm grateful for everyone's contributions and support. I'll see you all in March.
|
||||||
|
|
||||||
|
# 0.13.1 (2019-01-19)
|
||||||
|
|
||||||
|
##
|
||||||
|
|
||||||
|
# 0.13.0 (2019-01-06)
|
||||||
|
|
||||||
|
## Version 0.13.0: Translations, Annotations, and Tor
|
||||||
|
|
||||||
|
I hope everyone had a happy New Year! There's been a couple new additions since last release, with [44 commits](https://github.com/omarroth/invidious/compare/0.12.0...0.13.0) from 9 contributors. It's been quite a year for the project, and I hope to continue improving the project into 2019! Starting off the new year:
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
I'm happy to announce support for translations has been added with [`a160c64`](https://github.com/omarroth/invidious/a160c64). Currently, there is support for:
|
||||||
|
|
||||||
|
- Arabic (`ar`)
|
||||||
|
- Dutch (`nl`)
|
||||||
|
- English (`en-US`)
|
||||||
|
- German (`de`)
|
||||||
|
- Norwegian Bokmål (`nb_NO`)
|
||||||
|
- Polish (`pl`)
|
||||||
|
- Russian (`ru`)
|
||||||
|
|
||||||
|
Which you can change in your preferences under `Language`. You can also add `&hl=LANGUAGE` to the end of any request to translate it to your preferred language, for example https://invidio.us/?hl=ru. I'd like to say thank you again to everyone who has helped translate the site! I've mentioned this before, but I'm delighted that so many people find the project useful.
|
||||||
|
|
||||||
|
## Annotations
|
||||||
|
|
||||||
|
Recently, [YouTube announced that all annotations will be deleted on January 15th, 2019](https://support.google.com/youtube/answer/7342737). I believe that annotations have a very important place in YouTube's history, and [announced a project to archive them](https://www.reddit.com/r/DataHoarder/comments/aa6czg/youtube_annotation_archive/).
|
||||||
|
|
||||||
|
I expect annotations to be supported in the Invidious player once archiving is complete (see [#110](https://github.com/omarroth/invidious/issues/110) for details), and would also like to host them for other developers to use in their projects.
|
||||||
|
|
||||||
|
The code is available [here](https://github.com/omarroth/archive), and contains instructions for running a worker if you would like to contribute. There's much more information available in the announcement as well for anyone who is interested.
|
||||||
|
|
||||||
|
## Tor
|
||||||
|
|
||||||
|
I unfortunately missed the chance to mention this in the previous release, but I'm now happy to announce that you can now view Invidious through Tor at the following links:
|
||||||
|
|
||||||
|
kgg2m7yk5aybusll.onion
|
||||||
|
axqzx4s6s54s32yentfqojs3x5i7faxza6xo3ehd4bzzsg2ii4fv2iid.onion
|
||||||
|
|
||||||
|
Invidious is well suited to use through Tor, as it does not require any JS and is fairly lightweight. I'd recommend looking [here](https://diasp.org/posts/10965196) and [here](https://www.reddit.com/r/TOR/comments/a3c1ak/you_can_now_watch_youtube_videos_anonymously_with/) for more details on how to use the onion links, and would like to say thank you to [/u/whonix-os](https://www.reddit.com/user/whonix-os) for suggesting it and providing support setting setting them up.
|
||||||
|
|
||||||
|
## Popular and Trending
|
||||||
|
|
||||||
|
You can now easily view videos trending on YouTube with [`a16f967`](https://github.com/omarroth/invidious/a16f967). It also provides support for viewing YouTube's various categories categories, such as `News`, `Gaming`, and `Music`. You can also change the `region` parameter to view trending in different countries, which should be made easier to use in the coming weeks.
|
||||||
|
|
||||||
|
A link to `/feed/popular` has also been added, which provides a list of videos sorted using the algorithm described [here](https://github.com/omarroth/invidious/issues/217#issuecomment-436503761). I think it better reflects what users watch on the site, but I'd like to hear peoples' thoughts on this and on how it could be improved.
|
||||||
|
|
||||||
|
## Finances
|
||||||
|
|
||||||
|
### Donations
|
||||||
|
|
||||||
|
- [Patreon](https://www.patreon.com/omarroth): \$64.63
|
||||||
|
- [Liberapay](https://liberapay.com/omarroth) : \$30.05
|
||||||
|
- Crypto : ~\$28.74 (converted from BCH, BTC)
|
||||||
|
- Total : \$123.42
|
||||||
|
|
||||||
|
### Expenses
|
||||||
|
|
||||||
|
- invidious-load1 (nyc1) : \$10.00 (load balancer)
|
||||||
|
- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds)
|
||||||
|
- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server)
|
||||||
|
- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database)
|
||||||
|
- Total : \$75.00
|
||||||
|
|
||||||
|
### What will happen with what's left over?
|
||||||
|
|
||||||
|
I believe this is the first month that all expenses have been fully paid for by donations. Thank you! I expect to allocate the current amount for hardware to improve performance and for hosting annotation data, as mentioned above.
|
||||||
|
|
||||||
|
Anything that is left over is kept to continue hosting the project for as long as possible. Thank you again everyone!
|
||||||
|
|
||||||
|
I think that's everything for 2018. There's lots still planned, and I'm very excited for the future of this project!
|
||||||
|
|
||||||
# 0.12.0 (2018-12-06)
|
# 0.12.0 (2018-12-06)
|
||||||
|
|
||||||
## Version 0.12.0: Accessibility, Privacy, Transparency
|
## Version 0.12.0: Accessibility, Privacy, Transparency
|
||||||
|
|||||||
119
Makefile
Normal file
119
Makefile
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# -----------------------
|
||||||
|
# Compilation options
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
RELEASE := 1
|
||||||
|
STATIC := 0
|
||||||
|
|
||||||
|
DISABLE_QUIC := 0
|
||||||
|
NO_DBG_SYMBOLS := 0
|
||||||
|
|
||||||
|
|
||||||
|
FLAGS ?=
|
||||||
|
|
||||||
|
|
||||||
|
ifeq ($(RELEASE), 1)
|
||||||
|
FLAGS += --release
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(STATIC), 1)
|
||||||
|
FLAGS += --static
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
ifeq ($(NO_DBG_SYMBOLS), 1)
|
||||||
|
FLAGS += --no-debug
|
||||||
|
else
|
||||||
|
FLAGS += --debug
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(DISABLE_QUIC), 1)
|
||||||
|
FLAGS += -Ddisable_quic
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Main
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
all: invidious
|
||||||
|
|
||||||
|
get-libs:
|
||||||
|
shards install --production
|
||||||
|
|
||||||
|
# TODO: add support for ARM64 via cross-compilation
|
||||||
|
invidious: get-libs
|
||||||
|
crystal build src/invidious.cr $(FLAGS) --progress --stats --error-trace
|
||||||
|
|
||||||
|
|
||||||
|
run: invidious
|
||||||
|
./invidious
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Development
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
|
||||||
|
format:
|
||||||
|
crystal tool format
|
||||||
|
|
||||||
|
test:
|
||||||
|
crystal spec
|
||||||
|
|
||||||
|
verify:
|
||||||
|
crystal build src/invidious.cr -Dskip_videojs_download \
|
||||||
|
--no-codegen --progress --stats --error-trace
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# (Un)Install
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Cleaning
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm invidious
|
||||||
|
|
||||||
|
distclean: clean
|
||||||
|
rm -rf libs
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Help page
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Targets available in this Makefile:"
|
||||||
|
@echo ""
|
||||||
|
@echo " get-libs Fetch Crystal libraries"
|
||||||
|
@echo " invidious Build Invidious"
|
||||||
|
@echo " run Launch Invidious"
|
||||||
|
@echo ""
|
||||||
|
@echo " format Run the Crystal formatter"
|
||||||
|
@echo " test Run tests"
|
||||||
|
@echo " verify Just make sure that the code compiles, but without"
|
||||||
|
@echo " generating any binaries. Useful to search for errors"
|
||||||
|
@echo ""
|
||||||
|
@echo " clean Remove build artifacts"
|
||||||
|
@echo " distclean Remove build artifacts and libraries"
|
||||||
|
@echo ""
|
||||||
|
@echo ""
|
||||||
|
@echo "Build options available for this Makefile:"
|
||||||
|
@echo ""
|
||||||
|
@echo " RELEASE Make a release build (Default: 1)"
|
||||||
|
@echo " STATIC Link libraries statically (Default: 0)"
|
||||||
|
@echo ""
|
||||||
|
@echo " DISABLE_QUIC Disable support for QUIC (Default: 0)"
|
||||||
|
@echo " NO_DBG_SYMBOLS Strip debug symbols (Default: 0)"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# No targets generates an output named after themselves
|
||||||
|
.PHONY: all get-libs build amd64 run
|
||||||
|
.PHONY: format test verify clean distclean help
|
||||||
274
README.md
274
README.md
@@ -1,169 +1,175 @@
|
|||||||
# Invidious
|
<div align="center">
|
||||||
|
<img src="assets/invidious-colored-vector.svg" width="192" height="192" alt="Invidious logo">
|
||||||
|
<h1>Invidious</h1>
|
||||||
|
|
||||||
## Invidious is an alternative front-end to YouTube
|
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html">
|
||||||
|
<img alt="License: AGPLv3" src="https://shields.io/badge/License-AGPL%20v3-blue.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/iv-org/invidious/actions">
|
||||||
|
<img alt="Build Status" src="https://github.com/iv-org/invidious/workflows/Invidious%20CI/badge.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/iv-org/invidious/commits/master">
|
||||||
|
<img alt="GitHub commits" src="https://img.shields.io/github/commit-activity/y/iv-org/invidious?color=red&label=commits">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/iv-org/invidious/issues">
|
||||||
|
<img alt="GitHub issues" src="https://img.shields.io/github/issues/iv-org/invidious?color=important">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/iv-org/invidious/pulls">
|
||||||
|
<img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/iv-org/invidious?color=blueviolet">
|
||||||
|
</a>
|
||||||
|
<a href="https://hosted.weblate.org/engage/invidious/">
|
||||||
|
<img alt="Translation Status" src="https://hosted.weblate.org/widgets/invidious/-/translations/svg-badge.svg">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://github.com/humanetech-community/awesome-humane-tech">
|
||||||
|
<img alt="Awesome Humane Tech" src="https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true">
|
||||||
|
</a>
|
||||||
|
|
||||||
- Audio-only mode (and no need to keep window open on mobile)
|
<h3>An open source alternative front-end to YouTube</h3>
|
||||||
- [Open-source](https://github.com/omarroth/invidious) (AGPLv3 licensed)
|
|
||||||
|
<a href="https://invidious.io/">Website</a>
|
||||||
|
•
|
||||||
|
<a href="https://instances.invidious.io/">Instances list</a>
|
||||||
|
•
|
||||||
|
<a href="https://docs.invidious.io/FAQ.md">FAQ</a>
|
||||||
|
•
|
||||||
|
<a href="https://docs.invidious.io/">Documentation</a>
|
||||||
|
•
|
||||||
|
<a href="#contribute">Contribute</a>
|
||||||
|
•
|
||||||
|
<a href="https://invidious.io/donate/">Donate</a>
|
||||||
|
|
||||||
|
<h5>Chat with us:</h5>
|
||||||
|
<a href="https://matrix.to/#/#invidious:matrix.org">
|
||||||
|
<img alt="Matrix" src="https://img.shields.io/matrix/invidious:matrix.org?label=Matrix&color=darkgreen">
|
||||||
|
</a>
|
||||||
|
<a href="https://web.libera.chat/?channel=#invidious">
|
||||||
|
<img alt="Libera.chat (IRC)" src="https://img.shields.io/badge/IRC%20%28Libera.chat%29-%23invidious-darkgreen">
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<a rel="me" href="https://social.tchncs.de/@invidious">
|
||||||
|
<img alt="Mastodon: @invidious@social.tchncs.de" src="https://img.shields.io/badge/Mastodon-%40invidious%40social.tchncs.de-darkgreen">
|
||||||
|
</a>
|
||||||
|
<br>
|
||||||
|
<a href="https://invidious.io/contact/">
|
||||||
|
<img alt="E-mail" src="https://img.shields.io/badge/E%2d%2dmail-darkgreen">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
| Player | Preferences | Subscriptions |
|
||||||
|
|-------------------------------------|-------------------------------------|---------------------------------------|
|
||||||
|
|  |  |  |
|
||||||
|
|  |  |  |
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
**User features**
|
||||||
|
- Lightweight
|
||||||
- No ads
|
- No ads
|
||||||
- No need to create a Google account to save subscriptions
|
- No tracking
|
||||||
- Lightweight (homepage is ~4 KB compressed)
|
- No JavaScript required
|
||||||
- Tools for managing subscriptions:
|
- Light/Dark themes
|
||||||
- Only show unseen videos
|
- Customizable homepage
|
||||||
- Only show latest (or latest unseen) video from each channel
|
- Subscriptions independent from Google
|
||||||
- Delivers notifications from all subscribed channels
|
- Notifications for all subscribed channels
|
||||||
- Automatically redirect homepage to feed
|
- Audio-only mode (with background play on mobile)
|
||||||
- Import subscriptions from YouTube
|
- Support for Reddit comments
|
||||||
- Dark mode
|
- [Available in many languages](locales/), thanks to [our translators](#contribute)
|
||||||
- Embed support
|
|
||||||
- Set default player options (speed, quality, autoplay, loop)
|
|
||||||
- Does not require JS to play videos
|
|
||||||
- Support for Reddit comments in place of YT comments
|
|
||||||
- Import/Export subscriptions, watch history, preferences
|
|
||||||
- Does not use any of the official YouTube APIs
|
|
||||||
- Developer [API](https://github.com/omarroth/invidious/wiki/API)
|
|
||||||
|
|
||||||
Liberapay: https://liberapay.com/omarroth
|
**Data import/export**
|
||||||
Patreon: https://patreon.com/omarroth
|
- Import subscriptions from YouTube, NewPipe and Freetube
|
||||||
BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY
|
- Import watch history from NewPipe
|
||||||
BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk
|
- Export subscriptions to NewPipe and Freetube
|
||||||
|
- Import/Export Invidious user data
|
||||||
|
|
||||||
## Installation
|
**Technical features**
|
||||||
|
- Embedded video support
|
||||||
|
- [Developer API](https://docs.invidious.io/API.md)
|
||||||
|
- Does not use official YouTube APIs
|
||||||
|
- No Contributor License Agreement (CLA)
|
||||||
|
|
||||||
### Docker:
|
|
||||||
|
|
||||||
#### Build and start cluster:
|
## Quick start
|
||||||
|
|
||||||
```bash
|
**Using invidious:**
|
||||||
$ docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
And visit `localhost:3000` in your browser.
|
- [Select a public instance from the list](https://instances.invidious.io) and start watching videos right now!
|
||||||
|
|
||||||
#### Rebuild cluster:
|
**Hosting invidious:**
|
||||||
|
|
||||||
```bash
|
- [Follow the installation instructions](https://docs.invidious.io/Installation.md)
|
||||||
$ docker-compose build
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Delete data and rebuild:
|
|
||||||
|
|
||||||
```bash
|
## Documentation
|
||||||
$ docker volume rm invidious_postgresdata
|
|
||||||
$ docker-compose build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arch Linux:
|
The full documentation can be accessed online at https://docs.invidious.io/
|
||||||
|
|
||||||
```bash
|
The documentation's source code is available in this repository:
|
||||||
# Install dependencies
|
https://github.com/iv-org/documentation
|
||||||
$ sudo pacman -S shards crystal imagemagick librsvg
|
|
||||||
|
|
||||||
# Setup PostgresSQL
|
### Extensions
|
||||||
$ sudo systemctl enable postgresql
|
|
||||||
$ sudo systemctl start postgresql
|
|
||||||
$ sudo -i -u postgres
|
|
||||||
$ createuser -s YOUR_USER_NAME
|
|
||||||
$ createdb YOUR_USER_NAME
|
|
||||||
$ exit
|
|
||||||
|
|
||||||
# Setup Invidious
|
We highly recommend the use of [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect#get),
|
||||||
$ git clone https://github.com/omarroth/invidious
|
a browser extension that automatically redirects Youtube URLs to any Invidious instance and replaces
|
||||||
$ cd invidious
|
embedded youtube videos on other websites with invidious.
|
||||||
$ ./setup.sh
|
|
||||||
$ shards
|
|
||||||
$ crystal build src/invidious.cr --release
|
|
||||||
```
|
|
||||||
|
|
||||||
### On Ubuntu:
|
The documentation contains a list of browser extensions that we recommended to use along with Invidious.
|
||||||
|
|
||||||
```bash
|
You can read more here: https://docs.invidious.io/Extensions.md
|
||||||
# Install dependencies
|
|
||||||
$ curl -sSL https://dist.crystal-lang.org/apt/setup.sh | sudo bash
|
|
||||||
$ sudo apt update
|
|
||||||
$ sudo apt install crystal libssl-dev libxml2-dev libyaml-dev libgmp-dev libreadline-dev librsvg2-dev postgresql imagemagick libsqlite3-dev
|
|
||||||
|
|
||||||
# Setup PostgreSQL
|
|
||||||
$ sudo systemctl enable postgresql
|
|
||||||
$ sudo systemctl start postgresql
|
|
||||||
$ sudo -i -u postgres
|
|
||||||
$ createuser -s YOUR_USER_NAME_HERE
|
|
||||||
$ createdb YOUR_USER_NAME_HERE
|
|
||||||
$ exit
|
|
||||||
|
|
||||||
# Setup Invidious
|
## Contribute
|
||||||
$ git clone https://github.com/omarroth/invidious
|
|
||||||
$ cd invidious
|
|
||||||
$ ./setup.sh
|
|
||||||
$ shards
|
|
||||||
$ crystal build src/invidious.cr --release
|
|
||||||
```
|
|
||||||
|
|
||||||
### On OSX:
|
### Code
|
||||||
|
|
||||||
```bash
|
1. Fork it ( https://github.com/iv-org/invidious/fork ).
|
||||||
# Install dependencies
|
1. Create your feature branch (`git checkout -b my-new-feature`).
|
||||||
$ brew update
|
1. Stage your files (`git add .`).
|
||||||
$ brew install shards crystal-lang postgres imagemagick librsvg
|
1. Commit your changes (`git commit -am 'Add some feature'`).
|
||||||
|
1. Push to the branch (`git push origin my-new-feature`).
|
||||||
|
1. Create a new pull request ( https://github.com/iv-org/invidious/compare ).
|
||||||
|
|
||||||
# Setup Invidious
|
### Translations
|
||||||
$ git clone https://github.com/omarroth/invidious
|
|
||||||
$ cd invidious
|
|
||||||
$ ./setup.sh
|
|
||||||
$ shards
|
|
||||||
$ crystal build src/invidious.cr --release
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage:
|
We use [Weblate](https://weblate.org) to manage Invidious translations.
|
||||||
|
|
||||||
```bash
|
You can suggest new translations and/or correction here: https://hosted.weblate.org/engage/invidious/.
|
||||||
$ crystal build src/invidious.cr --release
|
|
||||||
$ ./invidious -h
|
|
||||||
Usage: invidious [arguments]
|
|
||||||
-b HOST, --bind HOST Host to bind (defaults to 0.0.0.0)
|
|
||||||
-p PORT, --port PORT Port to listen for connections (defaults to 3000)
|
|
||||||
-s, --ssl Enables SSL
|
|
||||||
--ssl-key-file FILE SSL key file
|
|
||||||
--ssl-cert-file FILE SSL certificate file
|
|
||||||
-h, --help Shows this help
|
|
||||||
-t THREADS, --crawl-threads=THREADS
|
|
||||||
Number of threads for crawling (default: 1)
|
|
||||||
-c THREADS, --channel-threads=THREADS
|
|
||||||
Number of threads for refreshing channels (default: 1)
|
|
||||||
-f THREADS, --feed-threads=THREADS
|
|
||||||
Number of threads for refreshing feeds (default: 1)
|
|
||||||
-v THREADS, --video-threads=THREADS
|
|
||||||
Number of threads for refreshing videos (default: 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
Or for development:
|
Creating an account is not required, but recommended, especially if you want to contribute regularly.
|
||||||
|
Weblate also allows you to log-in with major SSO providers like Github, Gitlab, BitBucket, Google, ...
|
||||||
|
|
||||||
```bash
|
|
||||||
$ curl -fsSLo- https://raw.githubusercontent.com/samueleaton/sentry/master/install.cr | crystal eval
|
|
||||||
$ ./sentry
|
|
||||||
```
|
|
||||||
|
|
||||||
## Extensions
|
## Projects using Invidious
|
||||||
|
|
||||||
- [Alternate Tube Redirector](https://addons.mozilla.org/en-US/firefox/addon/alternate-tube-redirector/): Automatically open Youtube Videos on alternate sites like Invidious or Hooktube.
|
- [FreeTube](https://github.com/FreeTubeApp/FreeTube): A libre software YouTube app for privacy.
|
||||||
- [Invidious Redirect](https://greasyfork.org/en/scripts/370461-invidious-redirect): Redirects Youtube URLs to Invidio.us (userscript)
|
- [CloudTube](https://sr.ht/~cadence/tube/): A JavaScript-rich alternate YouTube player.
|
||||||
- [iPhone Redirector Shortcut](https://www.icloud.com/shortcuts/6bbf26d989cf4d07a5fe1626efbc0950): Automatically open YouTube videos in Invidious (iPhone shortcut)
|
- [PeerTubeify](https://gitlab.com/Cha_deL/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists.
|
||||||
- [Youtube to Invidious](https://greasyfork.org/en/scripts/375264-youtube-to-invidious): Scan page for youtube embeds and urls and replace with Invidious (userscript)
|
- [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A material design music player that streams music from YouTube.
|
||||||
- [Invidious Downloader](https://github.com/erupete/InvidiousDownloader): Tampermonkey userscript for downloading videos or audio on Invidious (userscript)
|
- [HoloPlay](https://github.com/stephane-r/HoloPlay): Funny Android application connecting on Invidious API's with search, playlists and favorites.
|
||||||
|
|
||||||
## Made with Invidious
|
|
||||||
|
|
||||||
- [FreeTube](https://github.com/FreeTubeApp/FreeTube): An Open Source YouTube app for privacy.
|
## Liability
|
||||||
- [CloudTube](https://github.com/cloudrac3r/cadencegq): Website featuring pastebin, image host, and YouTube player
|
|
||||||
- [PeerTubeify](https://gitlab.com/Ealhad/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists.
|
|
||||||
|
|
||||||
## Contributing
|
We take no responsibility for the use of our tool, or external instances
|
||||||
|
provided by third parties. We strongly recommend you abide by the valid
|
||||||
|
official regulations in your country. Furthermore, we refuse liability
|
||||||
|
for any inappropriate use of Invidious, such as illegal downloading.
|
||||||
|
This tool is provided to you in the spirit of free, open software.
|
||||||
|
|
||||||
1. Fork it ( https://github.com/omarroth/invidious/fork )
|
You may view the LICENSE in which this software is provided to you [here](./LICENSE).
|
||||||
2. Create your feature branch (git checkout -b my-new-feature)
|
|
||||||
3. Commit your changes (git commit -am 'Add some feature')
|
|
||||||
4. Push to the branch (git push origin my-new-feature)
|
|
||||||
5. Create a new Pull Request
|
|
||||||
|
|
||||||
## Contributors
|
> 16. Limitation of Liability.
|
||||||
|
>
|
||||||
- [omarroth](https://github.com/omarroth) - creator, maintainer
|
> IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|||||||
1
TRANSLATION
Normal file
1
TRANSLATION
Normal file
@@ -0,0 +1 @@
|
|||||||
|
https://hosted.weblate.org/projects/invidious/
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
a:hover,
|
|
||||||
a:active {
|
|
||||||
color: rgb(0, 182, 240);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #a0a0a0;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: rgba(35, 35, 35, 1);
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-form legend {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-menu-heading {
|
|
||||||
color: #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-form > fieldset > input,
|
|
||||||
.pure-control-group > input,
|
|
||||||
.pure-form > fieldset > select,
|
|
||||||
.pure-control-group > select {
|
|
||||||
color: rgba(35, 35, 35, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar > .searchbar input {
|
|
||||||
background-color: inherit;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,88 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen,
|
||||||
|
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica,
|
||||||
|
Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#contents {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted {
|
||||||
|
background-color: rgb(255, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-profile > * {
|
||||||
|
font-size: 1.17em;
|
||||||
|
font-weight: bold;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-profile > img {
|
||||||
|
width: 48px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body a.channel-owner {
|
||||||
|
background-color: #008bec;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 9px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-heart-container {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0px 7px 6px 0px;
|
||||||
|
margin: 0px -7px -4px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-heart {
|
||||||
|
position: relative;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-heart-background-hearted {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-heart-small-hearted {
|
||||||
|
position: absolute;
|
||||||
|
right: -7px;
|
||||||
|
bottom: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-heart-small-container {
|
||||||
|
position: relative;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
color: rgb(255, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feed-menu {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feed-menu-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
.feed-menu-item {
|
||||||
|
flex: 0 0 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.h-box {
|
.h-box {
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
@@ -14,6 +99,7 @@ div {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,24 +108,36 @@ div {
|
|||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.pure-button-primary {
|
body a.pure-button {
|
||||||
|
color: rgba(0,0,0,.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.pure-button-primary,
|
||||||
|
body a.pure-button-primary,
|
||||||
|
.channel-owner:hover {
|
||||||
background-color: #a0a0a0;
|
background-color: #a0a0a0;
|
||||||
color: rgba(35, 35, 35, 1);
|
color: rgba(35, 35, 35, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.pure-button-primary:hover {
|
button.pure-button-primary:hover,
|
||||||
|
body a.pure-button-primary:hover {
|
||||||
background-color: rgba(0, 182, 240, 1);
|
background-color: rgba(0, 182, 240, 1);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.thumbnail {
|
div.thumbnail {
|
||||||
|
padding: 28.125%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.thumbnail {
|
img.thumbnail {
|
||||||
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.length {
|
.length {
|
||||||
@@ -50,9 +148,8 @@ img.thumbnail {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: sans-serif;
|
right: 0.25em;
|
||||||
right: 0.5em;
|
bottom: -0.75em;
|
||||||
bottom: -0.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.watched {
|
.watched {
|
||||||
@@ -63,7 +160,6 @@ img.thumbnail {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 4px 8px 4px 8px;
|
padding: 4px 8px 4px 8px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: sans-serif;
|
|
||||||
left: 0.2em;
|
left: 0.2em;
|
||||||
top: -0.7em;
|
top: -0.7em;
|
||||||
}
|
}
|
||||||
@@ -83,7 +179,7 @@ img.thumbnail {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar > .searchbar {
|
.searchbar {
|
||||||
flex-grow: 2; /* take double the space of the other items */
|
flex-grow: 2; /* take double the space of the other items */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,29 +189,36 @@ img.thumbnail {
|
|||||||
|
|
||||||
.navbar .index-link {
|
.navbar .index-link {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar > .searchbar .pure-form input[type="search"] {
|
.searchbar .pure-form fieldset { padding: 0; }
|
||||||
border-top: 0;
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
padding: initial 0;
|
.searchbar input[type="search"] {
|
||||||
|
width: 100%;
|
||||||
|
margin: 1px;
|
||||||
|
|
||||||
box-shadow: none;
|
border: 1px solid;
|
||||||
|
border-color: #0000 #0000 #CCC #0000;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
transition: 0.1s border-bottom;
|
box-shadow: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar > .searchbar .pure-form fieldset {
|
.searchbar input[type="search"]:focus {
|
||||||
padding: 0;
|
margin: 0 0 0.5px 0;
|
||||||
|
border: 2px solid;
|
||||||
|
border-color: #0000 #0000 #FED #0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* attract focus to the searchbar by adding a subtle transition */
|
/* https://stackoverflow.com/a/55170420 */
|
||||||
.navbar > .searchbar .pure-form input[type="search"]:focus {
|
input[type="search"]::-webkit-search-cancel-button {
|
||||||
border-bottom: 2px solid #aaa;
|
-webkit-appearance: none;
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAn0lEQVR42u3UMQrDMBBEUZ9WfQqDmm22EaTyjRMHAlM5K+Y7lb0wnUZPIKHlnutOa+25Z4D++MRBX98MD1V/trSppLKHqj9TTBWKcoUqffbUcbBBEhTjBOV4ja4l4OIAZThEOV6jHO8ARXD+gPPvKMABinGOrnu6gTNUawrcQKNCAQ7QeTxORzle3+sDfjJpPCqhJh7GixZq4rHcc9l5A9qZ+WeBhgEuAAAAAElFTkSuQmCC);
|
||||||
|
background-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-field {
|
.user-field {
|
||||||
@@ -133,6 +236,16 @@ img.thumbnail {
|
|||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-aspect-ratio: 16/9) {
|
||||||
|
.player-dimensions.vjs-fluid {
|
||||||
|
padding-top: 46.86% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player-container {
|
||||||
|
padding-bottom: 46.86% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 767px) {
|
@media screen and (max-width: 767px) {
|
||||||
.navbar {
|
.navbar {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -150,6 +263,11 @@ img.thumbnail {
|
|||||||
.navbar > .searchbar > form {
|
.navbar > .searchbar > form {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
margin: 0.42em 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 320px) {
|
@media screen and (max-width: 320px) {
|
||||||
@@ -159,21 +277,43 @@ img.thumbnail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
/*
|
||||||
|
* Video "cards" (results/playlist/channel videos)
|
||||||
|
*/
|
||||||
|
|
||||||
|
.video-card-row { margin: 15px 0; }
|
||||||
|
|
||||||
|
.flexible { display: flex; }
|
||||||
|
.flex-left { flex: 1 1 100%; flex-wrap: wrap; }
|
||||||
|
.flex-right { flex: 1 0 max-content; flex-wrap: nowrap; }
|
||||||
|
|
||||||
|
p.channel-name { margin: 0; }
|
||||||
|
p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
* Footer
|
* Footer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.footer {
|
footer {
|
||||||
color: #666666;
|
color: #919191;
|
||||||
margin: 2em 0;
|
margin-top: auto;
|
||||||
|
padding: 1.5em 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
max-height: 30vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer a {
|
footer a {
|
||||||
color: inherit;
|
color: #919191 !important;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer span {
|
||||||
|
margin: 4px 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
/* keyframes */
|
/* keyframes */
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
@@ -185,85 +325,224 @@ img.thumbnail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Control Bar */
|
fieldset > select,
|
||||||
.video-js .vjs-control-bar,
|
span > select {
|
||||||
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
|
color: rgba(49, 49, 51, 1);
|
||||||
background-color: rgba(35, 35, 35, 0.75);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-menu li.vjs-menu-item:focus,
|
.pure-control-group label {
|
||||||
.vjs-menu li.vjs-menu-item:hover {
|
word-wrap: normal;
|
||||||
background-color: rgba(255, 255, 255, 0.75);
|
|
||||||
color: rgba(49, 49, 51, 0.75);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-menu li.vjs-selected,
|
/*
|
||||||
.vjs-menu li.vjs-selected:focus,
|
* Light theme
|
||||||
.vjs-menu li.vjs-selected:hover {
|
*/
|
||||||
background-color: rgba(0, 182, 240, 0.75);
|
|
||||||
|
.light-theme a:hover,
|
||||||
|
.light-theme a:active,
|
||||||
|
.light-theme summary:hover {
|
||||||
|
color: #075A9E !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Progress Bar */
|
.light-theme a.pure-button-primary:hover {
|
||||||
.video-js .vjs-slider {
|
color: #fff !important;
|
||||||
background-color: rgba(15, 15, 15, 0.5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-load-progress,
|
.light-theme a {
|
||||||
.video-js .vjs-load-progress div {
|
color: #335d7a;
|
||||||
background: rgba(87, 87, 88, 1);
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-slider:hover,
|
/* All links that do not fit with the default color goes here */
|
||||||
.video-js button:hover {
|
.light-theme a:not([data-id]) > .icon,
|
||||||
color: rgba(0, 182, 240, 1);
|
.light-theme .pure-u-lg-1-5 > .h-box > a[href^="/watch?"],
|
||||||
|
.light-theme .playlist-restricted > ol > li > a {
|
||||||
|
color: #303030;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-play-progress {
|
.light-theme .pure-menu-heading {
|
||||||
background-color: rgba(0, 182, 240, 1);
|
color: #565d64;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ProgressBar marker */
|
@media (prefers-color-scheme: light) {
|
||||||
.vjs-marker {
|
.no-theme a:hover,
|
||||||
background-color: rgba(255, 255, 255, 1);
|
.no-theme a:active,
|
||||||
|
.no-theme summary:hover {
|
||||||
|
color: #075A9E !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme a.pure-button-primary:hover {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme a {
|
||||||
|
color: #335d7a;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All links that do not fit with the default color goes here */
|
||||||
|
.no-theme a:not([data-id]) > .icon,
|
||||||
|
.no-theme .pure-u-lg-1-5 > .h-box > a[href^="/watch?"],
|
||||||
|
.no-theme .playlist-restricted > ol > li > a {
|
||||||
|
color: #303030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-theme .pure-menu-heading {
|
||||||
|
color: #565d64;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Big "Play" Button */
|
/*
|
||||||
.video-js .vjs-big-play-button {
|
* Dark theme
|
||||||
background-color: rgba(35, 35, 35, 0.5);
|
*/
|
||||||
|
|
||||||
|
.dark-theme a:hover,
|
||||||
|
.dark-theme a:active,
|
||||||
|
.dark-theme summary:hover {
|
||||||
|
color: rgb(0, 182, 240);
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js:hover .vjs-big-play-button {
|
.dark-theme a {
|
||||||
background-color: rgba(35, 35, 35, 0.75);
|
color: #a0a0a0;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-current-time,
|
body.dark-theme {
|
||||||
.video-js .vjs-time-divider,
|
background-color: rgba(35, 35, 35, 1);
|
||||||
.video-js .vjs-duration {
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme .pure-form legend {
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme .pure-menu-heading {
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme input,
|
||||||
|
.dark-theme select,
|
||||||
|
.dark-theme textarea {
|
||||||
|
color: rgba(35, 35, 35, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme .pure-form input[type="file"] {
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme .searchbar input {
|
||||||
|
background-color: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.no-theme a:hover,
|
||||||
|
.no-theme a:active {
|
||||||
|
color: rgb(0, 182, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme a {
|
||||||
|
color: #a0a0a0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.no-theme {
|
||||||
|
background-color: rgba(35, 35, 35, 1);
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme .pure-form legend {
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme .pure-menu-heading {
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme input,
|
||||||
|
.no-theme select,
|
||||||
|
.no-theme textarea {
|
||||||
|
color: rgba(35, 35, 35, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme .pure-form input[type="file"] {
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-theme .searchbar input {
|
||||||
|
background-color: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#filters {
|
||||||
|
display: inline;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#filters > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#filters > summary {
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-time-divider {
|
#filters > summary::before {
|
||||||
min-width: 0px;
|
content: "[ + ]";
|
||||||
padding-left: 0px;
|
font-size: 1.5em;
|
||||||
padding-right: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-poster {
|
#filters[open] > summary::before {
|
||||||
background-size: cover;
|
content: "[ - ]";
|
||||||
object-fit: cover;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player {
|
/*With commit d9528f5 all contents of the page is now within a flexbox. However,
|
||||||
position: absolute;
|
the hr element is rendered improperly within one.
|
||||||
left: 0;
|
See https://stackoverflow.com/a/34372979 for more info */
|
||||||
top: 0;
|
hr {
|
||||||
|
margin: 10px 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Description Expansion Styling*/
|
||||||
|
#descexpansionbutton {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
#descexpansionbutton ~ div {
|
||||||
|
overflow: hidden;
|
||||||
|
height: 8.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#descexpansionbutton:checked ~ div {
|
||||||
|
overflow: unset;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#player-container {
|
#descexpansionbutton ~ label {
|
||||||
position: relative;
|
order: 1;
|
||||||
padding-bottom: 56.25%;
|
margin-top: 20px;
|
||||||
margin-left: 1em;
|
|
||||||
margin-right: 1em;
|
|
||||||
height: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bidi (bidirectional text) support */
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
p,
|
||||||
|
#descriptionWrapper,
|
||||||
|
#description-box {
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#descriptionWrapper {
|
||||||
|
max-width: 600px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center the "invidious" logo on the search page */
|
||||||
|
#logo > h1 { text-align: center; }
|
||||||
|
|||||||
26
assets/css/embed.css
Normal file
26
assets/css/embed.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#player {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
z-index: -100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watch-on-invidious {
|
||||||
|
font-size: 1.3em !important;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0 1em 0 1em !important;
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watch-on-invidious > a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watch-on-invidious > a:hover {
|
||||||
|
color: rgba(0, 182, 240, 1);;
|
||||||
|
}
|
||||||
16
assets/css/empty.css
Normal file
16
assets/css/empty.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#search-widget {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20vh 0 50px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo > h1 {
|
||||||
|
font-size: 3.5em;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1500px) and (max-height: 1000px) {
|
||||||
|
#logo > h1 {
|
||||||
|
font-size: 10vmin;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
assets/css/grids-responsive-min.css
vendored
4
assets/css/grids-responsive-min.css
vendored
File diff suppressed because one or more lines are too long
4
assets/css/ionicons.min.css
vendored
4
assets/css/ionicons.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
|||||||
a:hover,
|
|
||||||
a:active {
|
|
||||||
color: #167ac6;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #303030;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
254
assets/css/player.css
Normal file
254
assets/css/player.css
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
/* Youtube player style */
|
||||||
|
.video-js.player-style-youtube .vjs-progress-control {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-control-bar {
|
||||||
|
background: linear-gradient(rgba(0,0,0,0.1), rgba(0, 0, 0,0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-slider {
|
||||||
|
background-color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-load-progress > div {
|
||||||
|
background-color: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-play-progress {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-progress-control:hover .vjs-progress-holder {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-control-bar > .vjs-spacer {
|
||||||
|
flex: 1;
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-play-progress .vjs-time-tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-play-progress::before {
|
||||||
|
color: red;
|
||||||
|
font-size: 0.85em;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-progress-holder:hover .vjs-play-progress::before {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-control-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-big-play-button {
|
||||||
|
/*
|
||||||
|
Styles copied from video-js.min.css, definition of
|
||||||
|
.vjs-big-play-centered .vjs-big-play-button
|
||||||
|
*/
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -0.81666em;
|
||||||
|
margin-left: -1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-youtube .vjs-menu-button-popup .vjs-menu {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.vjs-menu-content::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-user-inactive {
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-text-track-display > div > div > div {
|
||||||
|
background-color: rgba(0, 0, 0, 0.75) !important;
|
||||||
|
border-radius: 9px !important;
|
||||||
|
padding: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-play-control,
|
||||||
|
.vjs-volume-panel,
|
||||||
|
.vjs-current-time,
|
||||||
|
.vjs-time-control,
|
||||||
|
.vjs-duration,
|
||||||
|
.vjs-progress-control,
|
||||||
|
.vjs-remaining-time {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-captions-button {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-quality-selector,
|
||||||
|
.video-js .vjs-http-source-selector {
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-playback-rate {
|
||||||
|
order: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-share-control {
|
||||||
|
order: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-fullscreen-control {
|
||||||
|
order: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-playback-rate > .vjs-menu {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-control-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-control-bar::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-icon-cog {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-control-bar,
|
||||||
|
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
|
||||||
|
background-color: rgba(35, 35, 35, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-menu li.vjs-menu-item:focus,
|
||||||
|
.vjs-menu li.vjs-menu-item:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.75);
|
||||||
|
color: rgba(49, 49, 51, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-menu li.vjs-selected,
|
||||||
|
.vjs-menu li.vjs-selected:focus,
|
||||||
|
.vjs-menu li.vjs-selected:hover {
|
||||||
|
background-color: rgba(0, 182, 240, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bar */
|
||||||
|
.video-js .vjs-slider {
|
||||||
|
background-color: rgba(15, 15, 15, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-load-progress,
|
||||||
|
.video-js .vjs-load-progress div {
|
||||||
|
background: rgba(87, 87, 88, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-slider:hover,
|
||||||
|
.video-js button:hover {
|
||||||
|
color: rgba(0, 182, 240, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.player-style-invidious .vjs-play-progress {
|
||||||
|
background-color: rgba(0, 182, 240, 1);
|
||||||
|
}
|
||||||
|
vjs-menu-content
|
||||||
|
/* Overlay */
|
||||||
|
.video-js .vjs-overlay {
|
||||||
|
background-color: rgba(35, 35, 35, 0.75);
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ProgressBar marker */
|
||||||
|
.vjs-marker {
|
||||||
|
background-color: rgba(255, 255, 255, 1);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Big "Play" Button */
|
||||||
|
.video-js .vjs-big-play-button {
|
||||||
|
background-color: rgba(35, 35, 35, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js:hover .vjs-big-play-button {
|
||||||
|
background-color: rgba(35, 35, 35, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-current-time,
|
||||||
|
.video-js .vjs-time-divider,
|
||||||
|
.video-js .vjs-duration {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-time-divider {
|
||||||
|
min-width: 0px;
|
||||||
|
padding-left: 0px;
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-poster {
|
||||||
|
background-size: cover;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-dimensions.vjs-fluid {
|
||||||
|
padding-top: 82vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
video.video-js {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player-container {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
padding-bottom: 82vh;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-operations-bar {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 1px !important;
|
||||||
|
left: initial !important;
|
||||||
|
width: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-operations-bar ul {
|
||||||
|
position: absolute !important;
|
||||||
|
bottom: unset !important;
|
||||||
|
top: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.video-js .vjs-share {
|
||||||
|
justify-content: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 650px) {
|
||||||
|
.vjs-modal-dialog-content {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
assets/css/pure-min.css
vendored
6
assets/css/pure-min.css
vendored
File diff suppressed because one or more lines are too long
1
assets/css/video-js.min.css
vendored
1
assets/css/video-js.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* videojs-share
|
|
||||||
* @version 2.0.1
|
|
||||||
* @copyright 2018 Mikhail Khazov <mkhazov.work@gmail.com>
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-modal-dialog-content{display:flex;align-items:center;padding:0;background-image:linear-gradient(to bottom, rgba(0,0,0,0.77), rgba(0,0,0,0.75))}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{position:absolute;right:0;top:5px;width:30px;height:30px;color:#fff;cursor:pointer;opacity:0.9;transition:opacity 0.25s ease-out}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button:before{content:'×';font-size:20px;line-height:15px}.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button:hover{opacity:1}.video-js .vjs-share{display:flex;flex-direction:column;justify-content:space-around;align-items:center;width:100%;height:100%;max-height:400px}.video-js .vjs-share__top,.video-js .vjs-share__middle,.video-js .vjs-share__bottom{display:flex}.video-js .vjs-share__top,.video-js .vjs-share__middle{flex-direction:column;justify-content:space-between}.video-js .vjs-share__middle{padding:0 25px}.video-js .vjs-share__title{align-self:center;font-size:22px;color:#fff}.video-js .vjs-share__subtitle{width:100%;margin:0 auto 12px;font-size:16px;color:#fff;opacity:0.7}.video-js .vjs-share__short-link-wrapper{position:relative;display:block;width:100%;height:40px;margin:0 auto;margin-bottom:15px;border:0;color:rgba(255,255,255,0.65);background-color:#363636;outline:none;overflow:hidden;flex-shrink:0}.video-js .vjs-share__short-link{display:block;width:100%;height:100%;padding:0 40px 0 15px;border:0;color:rgba(255,255,255,0.65);background-color:#363636;outline:none}.video-js .vjs-share__btn{position:absolute;right:0;bottom:0;height:40px;width:40px;display:flex;align-items:center;padding:0 11px;border:0;color:#fff;background-color:#2e2e2e;background-size:18px 19px;background-position:center;background-repeat:no-repeat;cursor:pointer;outline:none;transition:width 0.3s ease-out, padding 0.3s ease-out}.video-js .vjs-share__btn svg{flex-shrink:0}.video-js .vjs-share__btn span{position:relative;padding-left:10px;opacity:0;transition:opacity 0.3s ease-out}.video-js .vjs-share__btn:hover{justify-content:center;width:100%;padding:0 40px;background-image:none}.video-js .vjs-share__btn:hover span{opacity:1}.video-js .vjs-share__socials{display:flex;flex-wrap:wrap;justify-content:center;align-content:flex-start;transition:width 0.3s ease-out, height 0.3s ease-out}.video-js .vjs-share__social{display:flex;justify-content:center;align-items:center;flex-shrink:0;width:32px;height:32px;margin-right:6px;margin-bottom:6px;cursor:pointer;font-size:8px;transition:transform 0.3s ease-out, filter 0.2s ease-out;border:none;outline:none}.video-js .vjs-share__social:hover{filter:brightness(115%)}.video-js .vjs-share__social svg{width:100%;max-height:24px}.video-js .vjs-share__social_vk{background-color:#5d7294}.video-js .vjs-share__social_ok{background-color:#ed7c20}.video-js .vjs-share__social_mail{background-color:#134785}.video-js .vjs-share__social_tw{background-color:#76aaeb}.video-js .vjs-share__social_reddit{background-color:#ff4500}.video-js .vjs-share__social_fbFeed{background-color:#475995}.video-js .vjs-share__social_messenger{background-color:#0084ff}.video-js .vjs-share__social_gp{background-color:#d53f35}.video-js .vjs-share__social_linkedin{background-color:#0077b5}.video-js .vjs-share__social_viber{background-color:#766db5}.video-js .vjs-share__social_telegram{background-color:#4bb0e2}.video-js .vjs-share__social_whatsapp{background-color:#78c870}.video-js .vjs-share__bottom{justify-content:center}@media (max-height: 220px){.video-js .vjs-share .hidden-xs{display:none}}@media (max-height: 350px){.video-js .vjs-share .hidden-sm{display:none}}@media (min-height: 400px){.video-js .vjs-share__title{margin-bottom:15px}.video-js .vjs-share__short-link-wrapper{margin-bottom:30px}}@media (min-width: 320px){.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{right:5px;top:10px}}@media (min-width: 660px){.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button{right:20px;top:20px}.video-js .vjs-share__social{width:40px;height:40px}}
|
|
||||||
1
assets/css/videojs-youtube-annotations.min.css
vendored
Normal file
1
assets/css/videojs-youtube-annotations.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.__cxt-ar-annotations-container__{--annotation-close-size: 20px;position:absolute;width:100%;height:100%;top:0;left:0;pointer-events:none;overflow:hidden}.__cxt-ar-annotation__{position:absolute;box-sizing:border-box;font-family:Arial,sans-serif;color:#fff;z-index:20;pointer-events:auto}.__cxt-ar-annotation__ span{position:absolute;left:0;top:0;overflow:hidden;word-wrap:break-word;white-space:pre-wrap;pointer-events:none;box-sizing:border-box;padding:2%;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.__cxt-ar-annotation-close__{display:none;position:absolute;width:var(--annotation-close-size);height:var(--annotation-close-size);cursor:pointer;right:calc(var(--annotation-close-size)/-1.8);top:calc(var(--annotation-close-size)/-1.8);z-index:1}.__cxt-ar-annotation__:hover:not([hidden]):not([data-ar-closed]) .__cxt-ar-annotation-close__{display:block}.__cxt-ar-annotation__[hidden]{display:none!important}.__cxt-ar-annotation__[data-ar-type=highlight]{border:1px solid rgba(255,255,255,.1);background-color:transparent}.__cxt-ar-annotation__[data-ar-type=highlight]:hover{border:1px solid rgba(255,255,255,.5);background-color:transparent}.__cxt-ar-annotation__ svg{pointer-events:all}
|
||||||
1
assets/css/videojs.markers.min.css
vendored
1
assets/css/videojs.markers.min.css
vendored
@@ -1 +0,0 @@
|
|||||||
.vjs-marker{position:absolute;left:0;bottom:0;opacity:1;height:100%;transition:opacity .2s ease;-webkit-transition:opacity .2s ease;-moz-transition:opacity .2s ease;z-index:100}.vjs-marker:hover{cursor:pointer;-webkit-transform:scale(1.3,1.3);-moz-transform:scale(1.3,1.3);-o-transform:scale(1.3,1.3);-ms-transform:scale(1.3,1.3);transform:scale(1.3,1.3)}.vjs-tip{visibility:hidden;display:block;opacity:.8;padding:5px;font-size:10px;position:absolute;bottom:14px;z-index:100000}.vjs-tip .vjs-tip-arrow{background:url(data:image/gif;base64,R0lGODlhCQAJAIABAAAAAAAAACH5BAEAAAEALAAAAAAJAAkAAAIRjAOnwIrcDJxvwkplPtchVQAAOw==) no-repeat top left;bottom:0;left:50%;margin-left:-4px;background-position:bottom left;position:absolute;width:9px;height:5px}.vjs-tip .vjs-tip-inner{border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px;padding:5px 8px 4px 8px;background-color:#000;color:#fff;max-width:200px;text-align:center}.vjs-break-overlay{visibility:hidden;position:absolute;z-index:100000;top:0}.vjs-break-overlay .vjs-break-overlay-text{padding:9px;text-align:center}
|
|
||||||
Binary file not shown.
@@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" standalone="no"?>
|
<?xml version="1.0" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||||
<!--
|
<!--
|
||||||
2018-6-14: Created with FontForge (http://fontforge.org)
|
2019-5-24: Created with FontForge (http://fontforge.org)
|
||||||
-->
|
-->
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||||
<metadata>
|
<metadata>
|
||||||
Created by FontForge 20160407 at Thu Jun 14 08:50:34 2018
|
Created by FontForge 20160407 at Fri May 24 15:45:40 2019
|
||||||
By Adam Bradley
|
By Adam Bradley
|
||||||
Copyright (c) 2018, Adam Bradley
|
Copyright (c) 2019, Adam Bradley
|
||||||
</metadata>
|
</metadata>
|
||||||
<defs>
|
<defs>
|
||||||
<font id="Ionicons" horiz-adv-x="416" >
|
<font id="Ionicons" horiz-adv-x="416" >
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 305 KiB After Width: | Height: | Size: 305 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
assets/invidious-colored-vector.svg
Normal file
2
assets/invidious-colored-vector.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="512pt" height="512pt" version="1.0" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><g><rect x="-.0072516" y=".00056299" width="512.01" height="512.02" fill="#575757" stroke-width=".063019"/><path d="m247.17 455.95c-19.792-0.78921-38.719-4.2564-57.154-10.47-60.968-20.55-108.68-68.579-127-127.86-7.8955-25.538-10.062-53.943-6.2586-82.067 3.7105-27.439 13.603-53.515 29.342-77.344 12.069-18.273 29.138-36.277 47.228-49.816 36.891-27.61 85.944-42.49 132.38-40.157 25.88 1.3001 49.939 6.765 73.106 16.606 8.1948 3.481 20.024 9.6845 27.696 14.525 14.15 8.9272 22.367 15.498 34.482 27.573 13.254 13.211 22.128 24.276 30.398 37.906 7.2081 11.879 14.099 27.15 18.229 40.397 1.5996 5.1305 4.442 16.456 5.6852 22.653 2.3908 11.917 2.6998 15.722 2.7049 33.312 6e-3 18.515-0.46256 24.413-2.9166 36.758-9.3274 46.92-35.58 88.167-74.872 117.64-22.814 17.112-50.027 29.535-78.547 35.858-16.714 3.7059-35.421 5.2453-54.498 4.4846zm-35.1-78.786c-5.3e-4 -0.52647-0.0741-2.0564-0.16311-3.3999l-0.16178-2.4427-4.7018-0.26271c-4.0477-0.22614-4.7968-0.33363-5.3847-0.77253-2.0235-1.5108-1.4679-6.0695 2.2494-18.457 0.8637-2.8781 3.3371-11.321 5.4966-18.762 2.1594-7.4409 5.2002-17.836 6.7573-23.101 1.5571-5.2648 4.1948-14.282 5.8615-20.038 1.6667-5.7562 3.6145-12.4 4.3284-14.764 0.71391-2.3641 3.2583-11.037 5.6542-19.272 4.9475-17.007 8.1626-27.723 8.9438-29.811 0.51852-1.3858 0.54785-1.4139 0.99761-0.95317 0.25486 0.26106 3.8462 7.3667 7.9807 15.79 4.1345 8.4236 13.089 26.573 19.898 40.331 17.188 34.73 37.849 76.578 43.261 87.622l4.5356 9.257 11.359-0.0895c6.2475-0.0492 11.615-0.19623 11.929-0.32672 0.5614-0.23385 0.54167-0.2959-1.3723-4.3176-1.068-2.2442-8.1436-16.601-15.724-31.904-48.687-98.293-61.22-123.86-67.889-138.48-4.7022-10.309-6.9031-14.807-7.7139-15.762-0.82931-0.97742-1.6319-1.0638-2.3704-0.25525-1.1993 1.313-4.1046 10.063-9.3869 28.27-2.0569 7.0899-6.5372 22.425-9.9562 34.077-6.6396 22.629-8.5182 29.037-14.33 48.883-2.0354 6.9495-4.7977 16.369-6.1385 20.931-1.3408 4.5628-4.033 13.81-5.9826 20.549-4.304 14.877-6.136 20.889-7.3886 24.25-2.1371 5.7334-2.5723 6.3292-4.9216 6.7384-0.88855 0.15472-2.4102 0.28196-3.3815 0.28275-2.1993 3e-3 -3.5494 0.36339-4.0558 1.0863-0.42176 0.60215-0.56421 4.8802-0.18251 5.4812 0.20573 0.32388 2.4672 0.37414 23.34 0.51873l8.6151 0.0597-7e-4 -0.95723zm36.751-205.59c4.3282-0.92335 8.4607-4.943 9.4374-9.1796 0.36569-1.5862 0.32543-4.9758-0.077-6.4799-0.85108-3.1813-3.2688-6.291-6.039-7.7675-3.8111-2.0313-9.456-2.0295-13.272 5e-3 -5.9828 3.1888-8.1556 11.089-4.7878 17.408 2.6995 5.0648 8.3611 7.3754 14.738 6.015z" fill="#f0f0f0" stroke-width=".025526"/></g><g transform="matrix(.069892 0 0 -.069892 44.236 474.48)"><path d="m2787 4669c-124-65-123-255 3-319 86-44 196-16 247 62 58 87 26 211-67 258-51 26-132 26-183-1z" fill="#00b6f0" stroke="#00b6f0" stroke-width="4.25"/><path d="m2882 4108c-12-16-63-166-102-303-30-104-101-350-165-565-20-69-58-199-85-290-26-91-64-221-85-290-20-69-58-199-85-290-26-91-64-221-85-290-20-69-57-195-81-280-59-207-93-299-115-310-10-6-35-10-56-10-73 0-84-8-81-54l3-41 228-3 228-2-3 47-3 48-73 3c-66 3-74 5-84 27-13 28 0 104 37 225 13 41 47 156 75 255s66 230 85 290c18 61 56 191 85 290 28 99 66 230 85 290 18 61 56 191 85 290 85 297 123 419 131 429 5 5 17-11 28-35 10-24 192-393 403-819s447-902 523-1058l139-282h168c92 0 168 4 168 8s-75 158-166 342c-588 1183-969 1958-1033 2100-29 63-69 151-89 195-44 95-58 110-80 83z" fill="#575757"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
103
assets/js/community.js
Normal file
103
assets/js/community.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
var community_data = JSON.parse(document.getElementById('community_data').innerHTML);
|
||||||
|
|
||||||
|
String.prototype.supplant = function (o) {
|
||||||
|
return this.replace(/{([^{}]*)}/g, function (a, b) {
|
||||||
|
var r = o[b];
|
||||||
|
return typeof r === 'string' || typeof r === 'number' ? r : a;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide_youtube_replies(event) {
|
||||||
|
var target = event.target;
|
||||||
|
|
||||||
|
sub_text = target.getAttribute('data-inner-text');
|
||||||
|
inner_text = target.getAttribute('data-sub-text');
|
||||||
|
|
||||||
|
body = target.parentNode.parentNode.children[1];
|
||||||
|
body.style.display = 'none';
|
||||||
|
|
||||||
|
target.innerHTML = sub_text;
|
||||||
|
target.onclick = show_youtube_replies;
|
||||||
|
target.setAttribute('data-inner-text', inner_text);
|
||||||
|
target.setAttribute('data-sub-text', sub_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function show_youtube_replies(event) {
|
||||||
|
var target = event.target;
|
||||||
|
|
||||||
|
sub_text = target.getAttribute('data-inner-text');
|
||||||
|
inner_text = target.getAttribute('data-sub-text');
|
||||||
|
|
||||||
|
body = target.parentNode.parentNode.children[1];
|
||||||
|
body.style.display = '';
|
||||||
|
|
||||||
|
target.innerHTML = sub_text;
|
||||||
|
target.onclick = hide_youtube_replies;
|
||||||
|
target.setAttribute('data-inner-text', inner_text);
|
||||||
|
target.setAttribute('data-sub-text', sub_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function number_with_separator(val) {
|
||||||
|
while (/(\d+)(\d{3})/.test(val.toString())) {
|
||||||
|
val = val.toString().replace(/(\d+)(\d{3})/, '$1' + ',' + '$2');
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_replies(target, load_more) {
|
||||||
|
var continuation = target.getAttribute('data-continuation');
|
||||||
|
|
||||||
|
var body = target.parentNode.parentNode;
|
||||||
|
var fallback = body.innerHTML;
|
||||||
|
body.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
|
var url = '/api/v1/channels/comments/' + community_data.ucid +
|
||||||
|
'?format=html' +
|
||||||
|
'&hl=' + community_data.preferences.locale +
|
||||||
|
'&thin_mode=' + community_data.preferences.thin_mode +
|
||||||
|
'&continuation=' + continuation;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
if (load_more) {
|
||||||
|
body = body.parentNode.parentNode;
|
||||||
|
body.removeChild(body.lastElementChild);
|
||||||
|
body.innerHTML += xhr.response.contentHtml;
|
||||||
|
} else {
|
||||||
|
body.removeChild(body.lastElementChild);
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
var a = document.createElement('a');
|
||||||
|
p.appendChild(a);
|
||||||
|
|
||||||
|
a.href = 'javascript:void(0)';
|
||||||
|
a.onclick = hide_youtube_replies;
|
||||||
|
a.setAttribute('data-sub-text', community_data.hide_replies_text);
|
||||||
|
a.setAttribute('data-inner-text', community_data.show_replies_text);
|
||||||
|
a.innerText = community_data.hide_replies_text;
|
||||||
|
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = xhr.response.contentHtml;
|
||||||
|
|
||||||
|
body.appendChild(p);
|
||||||
|
body.appendChild(div);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Pulling comments failed.');
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
29
assets/js/dash.mediaplayer.min.js
vendored
29
assets/js/dash.mediaplayer.min.js
vendored
File diff suppressed because one or more lines are too long
105
assets/js/embed.js
Normal file
105
assets/js/embed.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
var video_data = JSON.parse(document.getElementById('video_data').innerHTML);
|
||||||
|
|
||||||
|
function get_playlist(plid, retries) {
|
||||||
|
if (retries == undefined) retries = 5;
|
||||||
|
|
||||||
|
if (retries <= 0) {
|
||||||
|
console.log('Failed to pull playlist');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plid.startsWith('RD')) {
|
||||||
|
var plid_url = '/api/v1/mixes/' + plid +
|
||||||
|
'?continuation=' + video_data.id +
|
||||||
|
'&format=html&hl=' + video_data.preferences.locale;
|
||||||
|
} else {
|
||||||
|
var plid_url = '/api/v1/playlists/' + plid +
|
||||||
|
'?index=' + video_data.index +
|
||||||
|
'&continuation' + video_data.id +
|
||||||
|
'&format=html&hl=' + video_data.preferences.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', plid_url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState === 4) {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
if (xhr.response.nextVideo) {
|
||||||
|
player.on('ended', function () {
|
||||||
|
var url = new URL('https://example.com/embed/' + xhr.response.nextVideo);
|
||||||
|
|
||||||
|
url.searchParams.set('list', plid);
|
||||||
|
if (!plid.startsWith('RD')) {
|
||||||
|
url.searchParams.set('index', xhr.response.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.autoplay || video_data.params.continue_autoplay) {
|
||||||
|
url.searchParams.set('autoplay', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.listen !== video_data.preferences.listen) {
|
||||||
|
url.searchParams.set('listen', video_data.params.listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.speed !== video_data.preferences.speed) {
|
||||||
|
url.searchParams.set('speed', video_data.params.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.local !== video_data.preferences.local) {
|
||||||
|
url.searchParams.set('local', video_data.params.local);
|
||||||
|
}
|
||||||
|
|
||||||
|
location.assign(url.pathname + url.search);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.log('Pulling playlist failed... ' + retries + '/5');
|
||||||
|
setTimeout(function () { get_playlist(plid, retries - 1) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Pulling playlist failed... ' + retries + '/5');
|
||||||
|
get_playlist(plid, retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', function (e) {
|
||||||
|
if (video_data.plid) {
|
||||||
|
get_playlist(video_data.plid);
|
||||||
|
} else if (video_data.video_series) {
|
||||||
|
player.on('ended', function () {
|
||||||
|
var url = new URL('https://example.com/embed/' + video_data.video_series.shift());
|
||||||
|
|
||||||
|
if (video_data.params.autoplay || video_data.params.continue_autoplay) {
|
||||||
|
url.searchParams.set('autoplay', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.listen !== video_data.preferences.listen) {
|
||||||
|
url.searchParams.set('listen', video_data.params.listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.speed !== video_data.preferences.speed) {
|
||||||
|
url.searchParams.set('speed', video_data.params.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.local !== video_data.preferences.local) {
|
||||||
|
url.searchParams.set('local', video_data.params.local);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.video_series.length !== 0) {
|
||||||
|
url.searchParams.set('playlist', video_data.video_series.join(','))
|
||||||
|
}
|
||||||
|
|
||||||
|
location.assign(url.pathname + url.search);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
167
assets/js/handlers.js
Normal file
167
assets/js/handlers.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var n2a = function (n) { return Array.prototype.slice.call(n); };
|
||||||
|
|
||||||
|
var video_player = document.getElementById('player_html5_api');
|
||||||
|
if (video_player) {
|
||||||
|
video_player.onmouseenter = function () { video_player['data-title'] = video_player['title']; video_player['title'] = ''; };
|
||||||
|
video_player.onmouseleave = function () { video_player['title'] = video_player['data-title']; video_player['data-title'] = ''; };
|
||||||
|
video_player.oncontextmenu = function () { video_player['title'] = video_player['data-title']; };
|
||||||
|
}
|
||||||
|
|
||||||
|
// For dynamically inserted elements
|
||||||
|
document.addEventListener('click', function (e) {
|
||||||
|
if (!e || !e.target) { return; }
|
||||||
|
e = e.target;
|
||||||
|
var handler_name = e.getAttribute('data-onclick');
|
||||||
|
switch (handler_name) {
|
||||||
|
case 'jump_to_time':
|
||||||
|
var time = e.getAttribute('data-jump-time');
|
||||||
|
player.currentTime(time);
|
||||||
|
break;
|
||||||
|
case 'get_youtube_replies':
|
||||||
|
var load_more = e.getAttribute('data-load-more') !== null;
|
||||||
|
var load_replies = e.getAttribute('data-load-replies') !== null;
|
||||||
|
get_youtube_replies(e, load_more, load_replies);
|
||||||
|
break;
|
||||||
|
case 'toggle_parent':
|
||||||
|
toggle_parent(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
n2a(document.querySelectorAll('[data-mouse="switch_classes"]')).forEach(function (e) {
|
||||||
|
var classes = e.getAttribute('data-switch-classes').split(',');
|
||||||
|
var ec = classes[0];
|
||||||
|
var lc = classes[1];
|
||||||
|
var onoff = function (on, off) {
|
||||||
|
var cs = e.getAttribute('class');
|
||||||
|
cs = cs.split(off).join(on);
|
||||||
|
e.setAttribute('class', cs);
|
||||||
|
};
|
||||||
|
e.onmouseenter = function () { onoff(ec, lc); };
|
||||||
|
e.onmouseleave = function () { onoff(lc, ec); };
|
||||||
|
});
|
||||||
|
|
||||||
|
n2a(document.querySelectorAll('[data-onsubmit="return_false"]')).forEach(function (e) {
|
||||||
|
e.onsubmit = function () { return false; };
|
||||||
|
});
|
||||||
|
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="mark_watched"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { mark_watched(e); };
|
||||||
|
});
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="mark_unwatched"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { mark_unwatched(e); };
|
||||||
|
});
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="add_playlist_video"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { add_playlist_video(e); };
|
||||||
|
});
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="add_playlist_item"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { add_playlist_item(e); };
|
||||||
|
});
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="remove_playlist_item"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { remove_playlist_item(e); };
|
||||||
|
});
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="revoke_token"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { revoke_token(e); };
|
||||||
|
});
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="remove_subscription"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { remove_subscription(e); };
|
||||||
|
});
|
||||||
|
n2a(document.querySelectorAll('[data-onclick="notification_requestPermission"]')).forEach(function (e) {
|
||||||
|
e.onclick = function () { Notification.requestPermission(); };
|
||||||
|
});
|
||||||
|
|
||||||
|
n2a(document.querySelectorAll('[data-onrange="update_volume_value"]')).forEach(function (e) {
|
||||||
|
var cb = function () { update_volume_value(e); }
|
||||||
|
e.oninput = cb;
|
||||||
|
e.onchange = cb;
|
||||||
|
});
|
||||||
|
|
||||||
|
function update_volume_value(element) {
|
||||||
|
document.getElementById('volume-value').innerText = element.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function revoke_token(target) {
|
||||||
|
var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
|
row.style.display = 'none';
|
||||||
|
var count = document.getElementById('count');
|
||||||
|
count.innerText = count.innerText - 1;
|
||||||
|
|
||||||
|
var referer = window.encodeURIComponent(document.location.href);
|
||||||
|
var url = '/token_ajax?action_revoke_token=1&redirect=false' +
|
||||||
|
'&referer=' + referer +
|
||||||
|
'&session=' + target.getAttribute('data-session');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
count.innerText = parseInt(count.innerText) + 1;
|
||||||
|
row.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value;
|
||||||
|
xhr.send('csrf_token=' + csrf_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_subscription(target) {
|
||||||
|
var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
|
row.style.display = 'none';
|
||||||
|
var count = document.getElementById('count');
|
||||||
|
count.innerText = count.innerText - 1;
|
||||||
|
|
||||||
|
var referer = window.encodeURIComponent(document.location.href);
|
||||||
|
var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
|
||||||
|
'&referer=' + referer +
|
||||||
|
'&c=' + target.getAttribute('data-ucid');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
count.innerText = parseInt(count.innerText) + 1;
|
||||||
|
row.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var csrf_token = target.parentNode.querySelector('input[name="csrf_token"]').value;
|
||||||
|
xhr.send('csrf_token=' + csrf_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle keypresses
|
||||||
|
window.addEventListener('keydown', (event) => {
|
||||||
|
// Ignore modifier keys
|
||||||
|
if (event.ctrlKey || event.metaKey) return;
|
||||||
|
|
||||||
|
// Ignore shortcuts if any text input is focused
|
||||||
|
let focused_tag = document.activeElement.tagName.toLowerCase();
|
||||||
|
const allowed = /^(button|checkbox|file|radio|submit)$/;
|
||||||
|
|
||||||
|
if (focused_tag === "textarea") return;
|
||||||
|
if (focused_tag === "input") {
|
||||||
|
let focused_type = document.activeElement.type.toLowerCase();
|
||||||
|
if (!focused_type.match(allowed)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus search bar on '/'
|
||||||
|
if (event.key == "/") {
|
||||||
|
document.getElementById('searchbox').focus();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
143
assets/js/notifications.js
Normal file
143
assets/js/notifications.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
var notification_data = JSON.parse(document.getElementById('notification_data').innerHTML);
|
||||||
|
|
||||||
|
var notifications, delivered;
|
||||||
|
|
||||||
|
function get_subscriptions(callback, retries) {
|
||||||
|
if (retries == undefined) retries = 5;
|
||||||
|
|
||||||
|
if (retries <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', '/api/v1/auth/subscriptions?fields=authorId', true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState === 4) {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
subscriptions = xhr.response;
|
||||||
|
callback(subscriptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.log('Pulling subscriptions failed... ' + retries + '/5');
|
||||||
|
setTimeout(function () { get_subscriptions(callback, retries - 1) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Pulling subscriptions failed... ' + retries + '/5');
|
||||||
|
get_subscriptions(callback, retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_notification_stream(subscriptions) {
|
||||||
|
notifications = new SSE(
|
||||||
|
'/api/v1/auth/notifications?fields=videoId,title,author,authorId,publishedText,published,authorThumbnails,liveNow', {
|
||||||
|
withCredentials: true,
|
||||||
|
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId }).join(','),
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||||
|
});
|
||||||
|
delivered = [];
|
||||||
|
|
||||||
|
var start_time = Math.round(new Date() / 1000);
|
||||||
|
|
||||||
|
notifications.onmessage = function (event) {
|
||||||
|
if (!event.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var notification = JSON.parse(event.data);
|
||||||
|
console.log('Got notification:', notification);
|
||||||
|
|
||||||
|
if (start_time < notification.published && !delivered.includes(notification.videoId)) {
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
var system_notification =
|
||||||
|
new Notification((notification.liveNow ? notification_data.live_now_text : notification_data.upload_text).replace('`x`', notification.author), {
|
||||||
|
body: notification.title,
|
||||||
|
icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname,
|
||||||
|
img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname,
|
||||||
|
tag: notification.videoId
|
||||||
|
});
|
||||||
|
|
||||||
|
system_notification.onclick = function (event) {
|
||||||
|
window.open('/watch?v=' + event.currentTarget.tag, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delivered.push(notification.videoId);
|
||||||
|
localStorage.setItem('notification_count', parseInt(localStorage.getItem('notification_count') || '0') + 1);
|
||||||
|
var notification_ticker = document.getElementById('notification_ticker');
|
||||||
|
|
||||||
|
if (parseInt(localStorage.getItem('notification_count')) > 0) {
|
||||||
|
notification_ticker.innerHTML =
|
||||||
|
'<span id="notification_count">' + localStorage.getItem('notification_count') + '</span> <i class="icon ion-ios-notifications"></i>';
|
||||||
|
} else {
|
||||||
|
notification_ticker.innerHTML =
|
||||||
|
'<i class="icon ion-ios-notifications-outline"></i>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notifications.addEventListener('error', handle_notification_error);
|
||||||
|
notifications.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_notification_error(event) {
|
||||||
|
console.log('Something went wrong with notifications, trying to reconnect...');
|
||||||
|
notifications = { close: function () { } };
|
||||||
|
setTimeout(function () { get_subscriptions(create_notification_stream) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', function (e) {
|
||||||
|
localStorage.setItem('notification_count', document.getElementById('notification_count') ? document.getElementById('notification_count').innerText : '0');
|
||||||
|
|
||||||
|
if (localStorage.getItem('stream')) {
|
||||||
|
localStorage.removeItem('stream');
|
||||||
|
} else {
|
||||||
|
setTimeout(function () {
|
||||||
|
if (!localStorage.getItem('stream')) {
|
||||||
|
notifications = { close: function () { } };
|
||||||
|
localStorage.setItem('stream', true);
|
||||||
|
get_subscriptions(create_notification_stream);
|
||||||
|
}
|
||||||
|
}, Math.random() * 1000 + 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('storage', function (e) {
|
||||||
|
if (e.key === 'stream' && !e.newValue) {
|
||||||
|
if (notifications) {
|
||||||
|
localStorage.setItem('stream', true);
|
||||||
|
} else {
|
||||||
|
setTimeout(function () {
|
||||||
|
if (!localStorage.getItem('stream')) {
|
||||||
|
notifications = { close: function () { } };
|
||||||
|
localStorage.setItem('stream', true);
|
||||||
|
get_subscriptions(create_notification_stream);
|
||||||
|
}
|
||||||
|
}, Math.random() * 1000 + 50);
|
||||||
|
}
|
||||||
|
} else if (e.key === 'notification_count') {
|
||||||
|
var notification_ticker = document.getElementById('notification_ticker');
|
||||||
|
|
||||||
|
if (parseInt(e.newValue) > 0) {
|
||||||
|
notification_ticker.innerHTML =
|
||||||
|
'<span id="notification_count">' + e.newValue + '</span> <i class="icon ion-ios-notifications"></i>';
|
||||||
|
} else {
|
||||||
|
notification_ticker.innerHTML =
|
||||||
|
'<i class="icon ion-ios-notifications-outline"></i>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('unload', function (e) {
|
||||||
|
if (notifications) {
|
||||||
|
localStorage.removeItem('stream');
|
||||||
|
}
|
||||||
|
});
|
||||||
700
assets/js/player.js
Normal file
700
assets/js/player.js
Normal file
@@ -0,0 +1,700 @@
|
|||||||
|
var player_data = JSON.parse(document.getElementById('player_data').innerHTML);
|
||||||
|
var video_data = JSON.parse(document.getElementById('video_data').innerHTML);
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
preload: 'auto',
|
||||||
|
liveui: true,
|
||||||
|
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
|
||||||
|
controlBar: {
|
||||||
|
children: [
|
||||||
|
'playToggle',
|
||||||
|
'volumePanel',
|
||||||
|
'currentTimeDisplay',
|
||||||
|
'timeDivider',
|
||||||
|
'durationDisplay',
|
||||||
|
'progressControl',
|
||||||
|
'remainingTimeDisplay',
|
||||||
|
'Spacer',
|
||||||
|
'captionsButton',
|
||||||
|
'qualitySelector',
|
||||||
|
'playbackRateMenuButton',
|
||||||
|
'fullscreenToggle'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
html5: {
|
||||||
|
preloadTextTracks: false,
|
||||||
|
vhs: {
|
||||||
|
overrideNative: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player_data.aspect_ratio) {
|
||||||
|
options.aspectRatio = player_data.aspect_ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
var embed_url = new URL(location);
|
||||||
|
embed_url.searchParams.delete('v');
|
||||||
|
var short_url = location.origin + '/' + video_data.id + embed_url.search;
|
||||||
|
embed_url = location.origin + '/embed/' + video_data.id + embed_url.search;
|
||||||
|
|
||||||
|
var save_player_pos_key = "save_player_pos";
|
||||||
|
|
||||||
|
videojs.Vhs.xhr.beforeRequest = function(options) {
|
||||||
|
if (options.uri.indexOf('videoplayback') === -1 && options.uri.indexOf('local=true') === -1) {
|
||||||
|
options.uri = options.uri + '?local=true';
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
var player = videojs('player', options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for add time argument to url
|
||||||
|
* @param {String} url
|
||||||
|
* @returns urlWithTimeArg
|
||||||
|
*/
|
||||||
|
function addCurrentTimeToURL(url) {
|
||||||
|
var urlUsed = new URL(url);
|
||||||
|
urlUsed.searchParams.delete('start');
|
||||||
|
var currentTime = Math.ceil(player.currentTime());
|
||||||
|
if (currentTime > 0)
|
||||||
|
urlUsed.searchParams.set('t', currentTime);
|
||||||
|
else if (urlUsed.searchParams.has('t'))
|
||||||
|
urlUsed.searchParams.delete('t');
|
||||||
|
return urlUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var shareOptions = {
|
||||||
|
socials: ['fbFeed', 'tw', 'reddit', 'email'],
|
||||||
|
|
||||||
|
get url() {
|
||||||
|
return addCurrentTimeToURL(short_url);
|
||||||
|
},
|
||||||
|
title: player_data.title,
|
||||||
|
description: player_data.description,
|
||||||
|
image: player_data.thumbnail,
|
||||||
|
get embedCode() {
|
||||||
|
return "<iframe id='ivplayer' width='640' height='360' src='" +
|
||||||
|
addCurrentTimeToURL(embed_url) + "' style='border:none;'></iframe>";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const storage = (() => {
|
||||||
|
try { if (localStorage.length !== -1) return localStorage; }
|
||||||
|
catch (e) { console.info('No storage available: ' + e); }
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (location.pathname.startsWith('/embed/')) {
|
||||||
|
var overlay_content = '<h1><a rel="noopener" target="_blank" href="' + location.origin + '/watch?v=' + video_data.id + '">' + player_data.title + '</a></h1>';
|
||||||
|
player.overlay({
|
||||||
|
overlays: [
|
||||||
|
{ start: 'loadstart', content: overlay_content, end: 'playing', align: 'top'},
|
||||||
|
{ start: 'pause', content: overlay_content, end: 'playing', align: 'top'}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect mobile users and initialize mobileUi for better UX
|
||||||
|
// Detection code taken from https://stackoverflow.com/a/20293441
|
||||||
|
|
||||||
|
function isMobile() {
|
||||||
|
try{ document.createEvent("TouchEvent"); return true; }
|
||||||
|
catch(e){ return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMobile()) {
|
||||||
|
player.mobileUi();
|
||||||
|
|
||||||
|
buttons = ["playToggle", "volumePanel", "captionsButton"];
|
||||||
|
|
||||||
|
if (video_data.params.quality !== 'dash') buttons.push("qualitySelector")
|
||||||
|
|
||||||
|
// Create new control bar object for operation buttons
|
||||||
|
const ControlBar = videojs.getComponent("controlBar");
|
||||||
|
let operations_bar = new ControlBar(player, {
|
||||||
|
children: [],
|
||||||
|
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
|
||||||
|
});
|
||||||
|
buttons.slice(1).forEach(child => operations_bar.addChild(child))
|
||||||
|
|
||||||
|
// Remove operation buttons from primary control bar
|
||||||
|
primary_control_bar = player.getChild("controlBar");
|
||||||
|
buttons.forEach(child => primary_control_bar.removeChild(child));
|
||||||
|
|
||||||
|
operations_bar_element = operations_bar.el();
|
||||||
|
operations_bar_element.className += " mobile-operations-bar"
|
||||||
|
player.addChild(operations_bar)
|
||||||
|
|
||||||
|
// Playback menu doesn't work when it's initialized outside of the primary control bar
|
||||||
|
playback_element = document.getElementsByClassName("vjs-playback-rate")[0]
|
||||||
|
operations_bar_element.append(playback_element)
|
||||||
|
|
||||||
|
// The share and http source selector element can't be fetched till the players ready.
|
||||||
|
player.one("playing", () => {
|
||||||
|
share_element = document.getElementsByClassName("vjs-share-control")[0]
|
||||||
|
operations_bar_element.append(share_element)
|
||||||
|
|
||||||
|
if (video_data.params.quality === 'dash') {
|
||||||
|
http_source_selector = document.getElementsByClassName("vjs-http-source-selector vjs-menu-button")[0]
|
||||||
|
operations_bar_element.append(http_source_selector)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
player.on('error', function (event) {
|
||||||
|
if (player.error().code === 2 || player.error().code === 4) {
|
||||||
|
setTimeout(function (event) {
|
||||||
|
console.log('An error occurred in the player, reloading...');
|
||||||
|
|
||||||
|
var currentTime = player.currentTime();
|
||||||
|
var playbackRate = player.playbackRate();
|
||||||
|
var paused = player.paused();
|
||||||
|
|
||||||
|
player.load();
|
||||||
|
|
||||||
|
if (currentTime > 0.5) currentTime -= 0.5;
|
||||||
|
|
||||||
|
player.currentTime(currentTime);
|
||||||
|
player.playbackRate(playbackRate);
|
||||||
|
|
||||||
|
if (!paused) player.play();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable VR video support
|
||||||
|
if (!video_data.params.listen && video_data.vr && video_data.params.vr_mode) {
|
||||||
|
player.crossOrigin("anonymous")
|
||||||
|
switch (video_data.projection_type) {
|
||||||
|
case "EQUIRECTANGULAR":
|
||||||
|
player.vr({projection: "equirectangular"});
|
||||||
|
default: // Should only be "MESH" but we'll use this as a fallback.
|
||||||
|
player.vr({projection: "EAC"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add markers
|
||||||
|
if (video_data.params.video_start > 0 || video_data.params.video_end > 0) {
|
||||||
|
var markers = [{ time: video_data.params.video_start, text: 'Start' }];
|
||||||
|
|
||||||
|
if (video_data.params.video_end < 0) {
|
||||||
|
markers.push({ time: video_data.length_seconds - 0.5, text: 'End' });
|
||||||
|
} else {
|
||||||
|
markers.push({ time: video_data.params.video_end, text: 'End' });
|
||||||
|
}
|
||||||
|
|
||||||
|
player.markers({
|
||||||
|
onMarkerReached: function (marker) {
|
||||||
|
if (marker.text === 'End')
|
||||||
|
player.loop() ? player.markers.prev('Start') : player.pause();
|
||||||
|
},
|
||||||
|
markers: markers
|
||||||
|
});
|
||||||
|
|
||||||
|
player.currentTime(video_data.params.video_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.volume(video_data.params.volume / 100);
|
||||||
|
player.playbackRate(video_data.params.speed);
|
||||||
|
|
||||||
|
player.on('waiting', function () {
|
||||||
|
if (player.playbackRate() > 1 && player.liveTracker.isLive() && player.liveTracker.atLiveEdge()) {
|
||||||
|
console.log('Player has caught up to source, resetting playbackRate.')
|
||||||
|
player.playbackRate(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (video_data.premiere_timestamp && Math.round(new Date() / 1000) < video_data.premiere_timestamp) {
|
||||||
|
player.getChild('bigPlayButton').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.save_player_pos) {
|
||||||
|
const url = new URL(location);
|
||||||
|
const hasTimeParam = url.searchParams.has("t");
|
||||||
|
const remeberedTime = get_video_time();
|
||||||
|
let lastUpdated = 0;
|
||||||
|
|
||||||
|
if(!hasTimeParam) set_seconds_after_start(remeberedTime);
|
||||||
|
|
||||||
|
const updateTime = () => {
|
||||||
|
const raw = player.currentTime();
|
||||||
|
const time = Math.floor(raw);
|
||||||
|
|
||||||
|
if(lastUpdated !== time && raw <= video_data.length_seconds - 15) {
|
||||||
|
save_video_time(time);
|
||||||
|
lastUpdated = time;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
player.on("timeupdate", updateTime);
|
||||||
|
}
|
||||||
|
else remove_all_video_times();
|
||||||
|
|
||||||
|
if (video_data.params.autoplay) {
|
||||||
|
var bpb = player.getChild('bigPlayButton');
|
||||||
|
bpb.hide();
|
||||||
|
|
||||||
|
player.ready(function () {
|
||||||
|
new Promise(function (resolve, reject) {
|
||||||
|
setTimeout(() => resolve(1), 1);
|
||||||
|
}).then(function (result) {
|
||||||
|
var promise = player.play();
|
||||||
|
|
||||||
|
if (promise !== undefined) {
|
||||||
|
promise.then(_ => {
|
||||||
|
}).catch(error => {
|
||||||
|
bpb.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!video_data.params.listen && video_data.params.quality === 'dash') {
|
||||||
|
player.httpSourceSelector();
|
||||||
|
|
||||||
|
if (video_data.params.quality_dash != "auto") {
|
||||||
|
player.ready(() => {
|
||||||
|
player.on("loadedmetadata", () => {
|
||||||
|
const qualityLevels = Array.from(player.qualityLevels()).sort((a, b) => a.height - b.height);
|
||||||
|
let targetQualityLevel;
|
||||||
|
switch (video_data.params.quality_dash) {
|
||||||
|
case "best":
|
||||||
|
targetQualityLevel = qualityLevels.length - 1;
|
||||||
|
break;
|
||||||
|
case "worst":
|
||||||
|
targetQualityLevel = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
const targetHeight = Number.parseInt(video_data.params.quality_dash, 10);
|
||||||
|
for (let i = 0; i < qualityLevels.length; i++) {
|
||||||
|
if (qualityLevels[i].height <= targetHeight) {
|
||||||
|
targetQualityLevel = i;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < qualityLevels.length; i++) {
|
||||||
|
qualityLevels[i].enabled = (i == targetQualityLevel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
player.vttThumbnails({
|
||||||
|
src: location.origin + '/api/v1/storyboards/' + video_data.id + '?height=90',
|
||||||
|
showTimestamp: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable annotations
|
||||||
|
if (!video_data.params.listen && video_data.params.annotations) {
|
||||||
|
window.addEventListener('load', function (e) {
|
||||||
|
var video_container = document.getElementById('player');
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'text';
|
||||||
|
xhr.timeout = 60000;
|
||||||
|
xhr.open('GET', '/api/v1/annotations/' + video_data.id, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState === 4) {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
videojs.registerPlugin('youtubeAnnotationsPlugin', youtubeAnnotationsPlugin);
|
||||||
|
if (!player.paused()) {
|
||||||
|
player.youtubeAnnotationsPlugin({ annotationXml: xhr.response, videoContainer: video_container });
|
||||||
|
} else {
|
||||||
|
player.one('play', function (event) {
|
||||||
|
player.youtubeAnnotationsPlugin({ annotationXml: xhr.response, videoContainer: video_container });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('__ar_annotation_click', e => {
|
||||||
|
const { url, target, seconds } = e.detail;
|
||||||
|
var path = new URL(url);
|
||||||
|
|
||||||
|
if (path.href.startsWith('https://www.youtube.com/watch?') && seconds) {
|
||||||
|
path.search += '&t=' + seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path.pathname + path.search;
|
||||||
|
|
||||||
|
if (target === 'current') {
|
||||||
|
window.location.href = path;
|
||||||
|
} else if (target === 'new') {
|
||||||
|
window.open(path, '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function increase_volume(delta) {
|
||||||
|
const curVolume = player.volume();
|
||||||
|
let newVolume = curVolume + delta;
|
||||||
|
if (newVolume > 1) {
|
||||||
|
newVolume = 1;
|
||||||
|
} else if (newVolume < 0) {
|
||||||
|
newVolume = 0;
|
||||||
|
}
|
||||||
|
player.volume(newVolume);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_muted() {
|
||||||
|
const isMuted = player.muted();
|
||||||
|
player.muted(!isMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
function skip_seconds(delta) {
|
||||||
|
const duration = player.duration();
|
||||||
|
const curTime = player.currentTime();
|
||||||
|
let newTime = curTime + delta;
|
||||||
|
if (newTime > duration) {
|
||||||
|
newTime = duration;
|
||||||
|
} else if (newTime < 0) {
|
||||||
|
newTime = 0;
|
||||||
|
}
|
||||||
|
player.currentTime(newTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_seconds_after_start(delta) {
|
||||||
|
const start = video_data.params.video_start;
|
||||||
|
player.currentTime(start + delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save_video_time(seconds) {
|
||||||
|
const videoId = video_data.id;
|
||||||
|
const all_video_times = get_all_video_times();
|
||||||
|
|
||||||
|
all_video_times[videoId] = seconds;
|
||||||
|
|
||||||
|
set_all_video_times(all_video_times);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_video_time() {
|
||||||
|
try {
|
||||||
|
const videoId = video_data.id;
|
||||||
|
const all_video_times = get_all_video_times();
|
||||||
|
const timestamp = all_video_times[videoId];
|
||||||
|
|
||||||
|
return timestamp || 0;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_all_video_times(times) {
|
||||||
|
if (storage) {
|
||||||
|
if (times) {
|
||||||
|
try {
|
||||||
|
storage.setItem(save_player_pos_key, JSON.stringify(times));
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('set_all_video_times: ' + e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
storage.removeItem(save_player_pos_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_all_video_times() {
|
||||||
|
if (storage) {
|
||||||
|
const raw = storage.getItem(save_player_pos_key);
|
||||||
|
if (raw !== null) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw);
|
||||||
|
} catch (e) {
|
||||||
|
console.debug('get_all_video_times: ' + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_all_video_times() {
|
||||||
|
set_all_video_times(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_time_percent(percent) {
|
||||||
|
const duration = player.duration();
|
||||||
|
const newTime = duration * (percent / 100);
|
||||||
|
player.currentTime(newTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
function play() { player.play(); }
|
||||||
|
function pause() { player.pause(); }
|
||||||
|
function stop() { player.pause(); player.currentTime(0); }
|
||||||
|
function toggle_play() { player.paused() ? play() : pause(); }
|
||||||
|
|
||||||
|
const toggle_captions = (function () {
|
||||||
|
let toggledTrack = null;
|
||||||
|
const onChange = function (e) {
|
||||||
|
toggledTrack = null;
|
||||||
|
};
|
||||||
|
const bindChange = function (onOrOff) {
|
||||||
|
player.textTracks()[onOrOff]('change', onChange);
|
||||||
|
};
|
||||||
|
// Wrapper function to ignore our own emitted events and only listen
|
||||||
|
// to events emitted by Video.js on click on the captions menu items.
|
||||||
|
const setMode = function (track, mode) {
|
||||||
|
bindChange('off');
|
||||||
|
track.mode = mode;
|
||||||
|
window.setTimeout(function () {
|
||||||
|
bindChange('on');
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
bindChange('on');
|
||||||
|
return function () {
|
||||||
|
if (toggledTrack !== null) {
|
||||||
|
if (toggledTrack.mode !== 'showing') {
|
||||||
|
setMode(toggledTrack, 'showing');
|
||||||
|
} else {
|
||||||
|
setMode(toggledTrack, 'disabled');
|
||||||
|
}
|
||||||
|
toggledTrack = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used as a fallback if no captions are currently active.
|
||||||
|
// TODO: Make this more intelligent by e.g. relying on browser language.
|
||||||
|
let fallbackCaptionsTrack = null;
|
||||||
|
|
||||||
|
const tracks = player.textTracks();
|
||||||
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
|
const track = tracks[i];
|
||||||
|
if (track.kind !== 'captions') continue;
|
||||||
|
|
||||||
|
if (fallbackCaptionsTrack === null) {
|
||||||
|
fallbackCaptionsTrack = track;
|
||||||
|
}
|
||||||
|
if (track.mode === 'showing') {
|
||||||
|
setMode(track, 'disabled');
|
||||||
|
toggledTrack = track;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback if no captions are currently active.
|
||||||
|
if (fallbackCaptionsTrack !== null) {
|
||||||
|
setMode(fallbackCaptionsTrack, 'showing');
|
||||||
|
toggledTrack = fallbackCaptionsTrack;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
function toggle_fullscreen() {
|
||||||
|
player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function increase_playback_rate(steps) {
|
||||||
|
const maxIndex = options.playbackRates.length - 1;
|
||||||
|
const curIndex = options.playbackRates.indexOf(player.playbackRate());
|
||||||
|
let newIndex = curIndex + steps;
|
||||||
|
if (newIndex > maxIndex) {
|
||||||
|
newIndex = maxIndex;
|
||||||
|
} else if (newIndex < 0) {
|
||||||
|
newIndex = 0;
|
||||||
|
}
|
||||||
|
player.playbackRate(options.playbackRates[newIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', e => {
|
||||||
|
if (e.target.tagName.toLowerCase() === 'input') {
|
||||||
|
// Ignore input when focus is on certain elements, e.g. form fields.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// See https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L310-L313
|
||||||
|
const isPlayerFocused = false
|
||||||
|
|| e.target === document.querySelector('.video-js')
|
||||||
|
|| e.target === document.querySelector('.vjs-tech')
|
||||||
|
|| e.target === document.querySelector('.iframeblocker')
|
||||||
|
|| e.target === document.querySelector('.vjs-control-bar')
|
||||||
|
;
|
||||||
|
let action = null;
|
||||||
|
|
||||||
|
const code = e.keyCode;
|
||||||
|
const decoratedKey =
|
||||||
|
e.key
|
||||||
|
+ (e.altKey ? '+alt' : '')
|
||||||
|
+ (e.ctrlKey ? '+ctrl' : '')
|
||||||
|
+ (e.metaKey ? '+meta' : '')
|
||||||
|
;
|
||||||
|
switch (decoratedKey) {
|
||||||
|
case ' ':
|
||||||
|
case 'k':
|
||||||
|
case 'MediaPlayPause':
|
||||||
|
action = toggle_play;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MediaPlay': action = play; break;
|
||||||
|
case 'MediaPause': action = pause; break;
|
||||||
|
case 'MediaStop': action = stop; break;
|
||||||
|
|
||||||
|
case 'ArrowUp':
|
||||||
|
if (isPlayerFocused) action = increase_volume.bind(this, 0.1);
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
if (isPlayerFocused) action = increase_volume.bind(this, -0.1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
action = toggle_muted;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'MediaFastForward':
|
||||||
|
action = skip_seconds.bind(this, 5 * player.playbackRate());
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'MediaTrackPrevious':
|
||||||
|
action = skip_seconds.bind(this, -5 * player.playbackRate());
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
action = skip_seconds.bind(this, 10 * player.playbackRate());
|
||||||
|
break;
|
||||||
|
case 'j':
|
||||||
|
action = skip_seconds.bind(this, -10 * player.playbackRate());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
// Ignore numpad numbers
|
||||||
|
if (code > 57) break;
|
||||||
|
|
||||||
|
const percent = (code - 48) * 10;
|
||||||
|
action = set_time_percent.bind(this, percent);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'c': action = toggle_captions; break;
|
||||||
|
case 'f': action = toggle_fullscreen; break;
|
||||||
|
|
||||||
|
case 'N':
|
||||||
|
case 'MediaTrackNext':
|
||||||
|
action = next_video;
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
case 'MediaTrackPrevious':
|
||||||
|
// TODO: Add support to play back previous video.
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
// TODO: Add support for next-frame-stepping.
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
// TODO: Add support for previous-frame-stepping.
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '>': action = increase_playback_rate.bind(this, 1); break;
|
||||||
|
case '<': action = increase_playback_rate.bind(this, -1); break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.info('Unhandled key down event: %s:', decoratedKey, e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
e.preventDefault();
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// Add support for controlling the player volume by scrolling over it. Adapted from
|
||||||
|
// https://github.com/ctd1500/videojs-hotkeys/blob/bb4a158b2e214ccab87c2e7b95f42bc45c6bfd87/videojs.hotkeys.js#L292-L328
|
||||||
|
(function () {
|
||||||
|
const volumeStep = 0.05;
|
||||||
|
const enableVolumeScroll = true;
|
||||||
|
const enableHoverScroll = true;
|
||||||
|
const doc = document;
|
||||||
|
const pEl = document.getElementById('player');
|
||||||
|
|
||||||
|
var volumeHover = false;
|
||||||
|
var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel');
|
||||||
|
if (volumeSelector != null) {
|
||||||
|
volumeSelector.onmouseover = function () { volumeHover = true; };
|
||||||
|
volumeSelector.onmouseout = function () { volumeHover = false; };
|
||||||
|
}
|
||||||
|
|
||||||
|
var mouseScroll = function mouseScroll(event) {
|
||||||
|
var activeEl = doc.activeElement;
|
||||||
|
if (enableHoverScroll) {
|
||||||
|
// If we leave this undefined then it can match non-existent elements below
|
||||||
|
activeEl = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When controls are disabled, hotkeys will be disabled as well
|
||||||
|
if (player.controls()) {
|
||||||
|
if (volumeHover) {
|
||||||
|
if (enableVolumeScroll) {
|
||||||
|
event = window.event || event;
|
||||||
|
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (delta == 1) {
|
||||||
|
increase_volume(volumeStep);
|
||||||
|
} else if (delta == -1) {
|
||||||
|
increase_volume(-volumeStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
player.on('mousewheel', mouseScroll);
|
||||||
|
player.on("DOMMouseScroll", mouseScroll);
|
||||||
|
}());
|
||||||
|
|
||||||
|
// Since videojs-share can sometimes be blocked, we defer it until last
|
||||||
|
if (player.share) {
|
||||||
|
player.share(shareOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// show the preferred caption by default
|
||||||
|
if (player_data.preferred_caption_found) {
|
||||||
|
player.ready(() => {
|
||||||
|
player.textTracks()[1].mode = 'showing';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safari audio double duration fix
|
||||||
|
if (navigator.vendor == "Apple Computer, Inc." && video_data.params.listen) {
|
||||||
|
player.on('loadedmetadata', function () {
|
||||||
|
player.on('timeupdate', function () {
|
||||||
|
if (player.remainingTime() < player.duration() / 2) {
|
||||||
|
player.currentTime(player.duration() + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch on Invidious link
|
||||||
|
if (window.location.pathname.startsWith("/embed/")) {
|
||||||
|
const Button = videojs.getComponent('Button');
|
||||||
|
let watch_on_invidious_button = new Button(player);
|
||||||
|
|
||||||
|
// Create hyperlink for current instance
|
||||||
|
redirect_element = document.createElement("a");
|
||||||
|
redirect_element.setAttribute("href", `http://${window.location.host}/watch?v=${window.location.pathname.replace("/embed/","")}`)
|
||||||
|
redirect_element.appendChild(document.createTextNode("Invidious"))
|
||||||
|
|
||||||
|
watch_on_invidious_button.el().appendChild(redirect_element)
|
||||||
|
watch_on_invidious_button.addClass("watch-on-invidious")
|
||||||
|
|
||||||
|
cb = player.getChild('ControlBar')
|
||||||
|
cb.addChild(watch_on_invidious_button)
|
||||||
|
};
|
||||||
73
assets/js/playlist_widget.js
Normal file
73
assets/js/playlist_widget.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
var playlist_data = JSON.parse(document.getElementById('playlist_data').innerHTML);
|
||||||
|
|
||||||
|
function add_playlist_video(target) {
|
||||||
|
var select = target.parentNode.children[0].children[1];
|
||||||
|
var option = select.children[select.selectedIndex];
|
||||||
|
|
||||||
|
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
||||||
|
'&video_id=' + target.getAttribute('data-id') +
|
||||||
|
'&playlist_id=' + option.getAttribute('data-plid');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
option.innerText = '✓' + option.innerText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send('csrf_token=' + playlist_data.csrf_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_playlist_item(target) {
|
||||||
|
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
|
tile.style.display = 'none';
|
||||||
|
|
||||||
|
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
||||||
|
'&video_id=' + target.getAttribute('data-id') +
|
||||||
|
'&playlist_id=' + target.getAttribute('data-plid');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
tile.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send('csrf_token=' + playlist_data.csrf_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_playlist_item(target) {
|
||||||
|
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
|
tile.style.display = 'none';
|
||||||
|
|
||||||
|
var url = '/playlist_ajax?action_remove_video=1&redirect=false' +
|
||||||
|
'&set_video_id=' + target.getAttribute('data-index') +
|
||||||
|
'&playlist_id=' + target.getAttribute('data-plid');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
tile.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send('csrf_token=' + playlist_data.csrf_token);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
200
assets/js/sse.js
Normal file
200
assets/js/sse.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2016 Maxime Petazzoni <maxime.petazzoni@bulix.org>.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var SSE = function (url, options) {
|
||||||
|
if (!(this instanceof SSE)) {
|
||||||
|
return new SSE(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.INITIALIZING = -1;
|
||||||
|
this.CONNECTING = 0;
|
||||||
|
this.OPEN = 1;
|
||||||
|
this.CLOSED = 2;
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
this.headers = options.headers || {};
|
||||||
|
this.payload = options.payload !== undefined ? options.payload : '';
|
||||||
|
this.method = options.method || (this.payload && 'POST' || 'GET');
|
||||||
|
|
||||||
|
this.FIELD_SEPARATOR = ':';
|
||||||
|
this.listeners = {};
|
||||||
|
|
||||||
|
this.xhr = null;
|
||||||
|
this.readyState = this.INITIALIZING;
|
||||||
|
this.progress = 0;
|
||||||
|
this.chunk = '';
|
||||||
|
|
||||||
|
this.addEventListener = function(type, listener) {
|
||||||
|
if (this.listeners[type] === undefined) {
|
||||||
|
this.listeners[type] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.listeners[type].indexOf(listener) === -1) {
|
||||||
|
this.listeners[type].push(listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.removeEventListener = function(type, listener) {
|
||||||
|
if (this.listeners[type] === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filtered = [];
|
||||||
|
this.listeners[type].forEach(function(element) {
|
||||||
|
if (element !== listener) {
|
||||||
|
filtered.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
delete this.listeners[type];
|
||||||
|
} else {
|
||||||
|
this.listeners[type] = filtered;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dispatchEvent = function(e) {
|
||||||
|
if (!e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.source = this;
|
||||||
|
|
||||||
|
var onHandler = 'on' + e.type;
|
||||||
|
if (this.hasOwnProperty(onHandler)) {
|
||||||
|
this[onHandler].call(this, e);
|
||||||
|
if (e.defaultPrevented) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.listeners[e.type]) {
|
||||||
|
return this.listeners[e.type].every(function(callback) {
|
||||||
|
callback(e);
|
||||||
|
return !e.defaultPrevented;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._setReadyState = function (state) {
|
||||||
|
var event = new CustomEvent('readystatechange');
|
||||||
|
event.readyState = state;
|
||||||
|
this.readyState = state;
|
||||||
|
this.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
this._onStreamFailure = function(e) {
|
||||||
|
this.dispatchEvent(new CustomEvent('error'));
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onStreamProgress = function(e) {
|
||||||
|
if (this.xhr.status !== 200 && this.readyState !== this.CLOSED) {
|
||||||
|
this._onStreamFailure(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.readyState == this.CONNECTING) {
|
||||||
|
this.dispatchEvent(new CustomEvent('open'));
|
||||||
|
this._setReadyState(this.OPEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = this.xhr.responseText.substring(this.progress);
|
||||||
|
this.progress += data.length;
|
||||||
|
data.split(/(\r\n|\r|\n){2}/g).forEach(function(part) {
|
||||||
|
if (part.trim().length === 0) {
|
||||||
|
this.dispatchEvent(this._parseEventChunk(this.chunk.trim()));
|
||||||
|
this.chunk = '';
|
||||||
|
} else {
|
||||||
|
this.chunk += part;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
this._onStreamLoaded = function(e) {
|
||||||
|
this._onStreamProgress(e);
|
||||||
|
|
||||||
|
// Parse the last chunk.
|
||||||
|
this.dispatchEvent(this._parseEventChunk(this.chunk));
|
||||||
|
this.chunk = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a received SSE event chunk into a constructed event object.
|
||||||
|
*/
|
||||||
|
this._parseEventChunk = function(chunk) {
|
||||||
|
if (!chunk || chunk.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var e = {'id': null, 'retry': null, 'data': '', 'event': 'message'};
|
||||||
|
chunk.split(/\n|\r\n|\r/).forEach(function(line) {
|
||||||
|
line = line.trimRight();
|
||||||
|
var index = line.indexOf(this.FIELD_SEPARATOR);
|
||||||
|
if (index <= 0) {
|
||||||
|
// Line was either empty, or started with a separator and is a comment.
|
||||||
|
// Either way, ignore.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var field = line.substring(0, index);
|
||||||
|
if (!(field in e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = line.substring(index + 1).trimLeft();
|
||||||
|
if (field === 'data') {
|
||||||
|
e[field] += value;
|
||||||
|
} else {
|
||||||
|
e[field] = value;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
var event = new CustomEvent(e.event);
|
||||||
|
event.data = e.data;
|
||||||
|
event.id = e.id;
|
||||||
|
return event;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._checkStreamClosed = function() {
|
||||||
|
if (this.xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
this._setReadyState(this.CLOSED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stream = function() {
|
||||||
|
this._setReadyState(this.CONNECTING);
|
||||||
|
|
||||||
|
this.xhr = new XMLHttpRequest();
|
||||||
|
this.xhr.addEventListener('progress', this._onStreamProgress.bind(this));
|
||||||
|
this.xhr.addEventListener('load', this._onStreamLoaded.bind(this));
|
||||||
|
this.xhr.addEventListener('readystatechange', this._checkStreamClosed.bind(this));
|
||||||
|
this.xhr.addEventListener('error', this._onStreamFailure.bind(this));
|
||||||
|
this.xhr.addEventListener('abort', this._onStreamFailure.bind(this));
|
||||||
|
this.xhr.open(this.method, this.url);
|
||||||
|
for (var header in this.headers) {
|
||||||
|
this.xhr.setRequestHeader(header, this.headers[header]);
|
||||||
|
}
|
||||||
|
this.xhr.send(this.payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.close = function() {
|
||||||
|
if (this.readyState === this.CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xhr.abort();
|
||||||
|
this.xhr = null;
|
||||||
|
this._setReadyState(this.CLOSED);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export our SSE module for npm.js
|
||||||
|
if (typeof exports !== 'undefined') {
|
||||||
|
exports.SSE = SSE;
|
||||||
|
}
|
||||||
90
assets/js/subscribe_widget.js
Normal file
90
assets/js/subscribe_widget.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
var subscribe_data = JSON.parse(document.getElementById('subscribe_data').innerHTML);
|
||||||
|
|
||||||
|
var subscribe_button = document.getElementById('subscribe');
|
||||||
|
subscribe_button.parentNode['action'] = 'javascript:void(0)';
|
||||||
|
|
||||||
|
if (subscribe_button.getAttribute('data-type') === 'subscribe') {
|
||||||
|
subscribe_button.onclick = subscribe;
|
||||||
|
} else {
|
||||||
|
subscribe_button.onclick = unsubscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribe(retries = 5) {
|
||||||
|
if (retries <= 0) {
|
||||||
|
console.log('Failed to subscribe.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' +
|
||||||
|
'&c=' + subscribe_data.ucid;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
var fallback = subscribe_button.innerHTML;
|
||||||
|
subscribe_button.onclick = unsubscribe;
|
||||||
|
subscribe_button.innerHTML = '<b>' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
subscribe_button.onclick = subscribe;
|
||||||
|
subscribe_button.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.log('Subscribing failed... ' + retries + '/5');
|
||||||
|
setTimeout(function () { subscribe(retries - 1) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Subscribing failed... ' + retries + '/5');
|
||||||
|
subscribe(retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send('csrf_token=' + subscribe_data.csrf_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsubscribe(retries = 5) {
|
||||||
|
if (retries <= 0) {
|
||||||
|
console.log('Failed to subscribe');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
|
||||||
|
'&c=' + subscribe_data.ucid;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
var fallback = subscribe_button.innerHTML;
|
||||||
|
subscribe_button.onclick = subscribe;
|
||||||
|
subscribe_button.innerHTML = '<b>' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
subscribe_button.onclick = unsubscribe;
|
||||||
|
subscribe_button.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.log('Unsubscribing failed... ' + retries + '/5');
|
||||||
|
setTimeout(function () { unsubscribe(retries - 1) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Unsubscribing failed... ' + retries + '/5');
|
||||||
|
unsubscribe(retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send('csrf_token=' + subscribe_data.csrf_token);
|
||||||
|
}
|
||||||
91
assets/js/themes.js
Normal file
91
assets/js/themes.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
var toggle_theme = document.getElementById('toggle_theme');
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('storage', function (e) {
|
||||||
|
if (e.key === 'dark_mode') {
|
||||||
|
update_mode(e.newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
update_mode(dark_mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
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');
|
||||||
|
} 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
7
assets/js/video.min.js
vendored
7
assets/js/video.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
/*! @name videojs-contrib-quality-levels @version 2.0.7 @license Apache-2.0 */
|
|
||||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("video.js"),require("global/document")):"function"==typeof define&&define.amd?define(["video.js","global/document"],t):e.videojsContribQualityLevels=t(e.videojs,e.document)}(this,function(e,t){"use strict";e=e&&e.hasOwnProperty("default")?e.default:e,t=t&&t.hasOwnProperty("default")?t.default:t;var n=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},r=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t},i=function(i){function o(){n(this,o);var l=r(this,i.call(this)),s=l;if(e.browser.IS_IE8)for(var u in s=t.createElement("custom"),o.prototype)"constructor"!==u&&(s[u]=o.prototype[u]);return s.levels_=[],s.selectedIndex_=-1,Object.defineProperty(s,"selectedIndex",{get:function(){return s.selectedIndex_}}),Object.defineProperty(s,"length",{get:function(){return s.levels_.length}}),r(l,s)}return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}(o,i),o.prototype.addQualityLevel=function(r){var i=this.getQualityLevelById(r.id);if(i)return i;var o=this.levels_.length;return i=new function r(i){n(this,r);var o=this;if(e.browser.IS_IE8)for(var l in o=t.createElement("custom"),r.prototype)"constructor"!==l&&(o[l]=r.prototype[l]);return o.id=i.id,o.label=o.id,o.width=i.width,o.height=i.height,o.bitrate=i.bandwidth,o.enabled_=i.enabled,Object.defineProperty(o,"enabled",{get:function(){return o.enabled_()},set:function(e){o.enabled_(e)}}),o}(r),""+o in this||Object.defineProperty(this,o,{get:function(){return this.levels_[o]}}),this.levels_.push(i),this.trigger({qualityLevel:i,type:"addqualitylevel"}),i},o.prototype.removeQualityLevel=function(e){for(var t=null,n=0,r=this.length;n<r;n++)if(this[n]===e){t=this.levels_.splice(n,1)[0],this.selectedIndex_===n?this.selectedIndex_=-1:this.selectedIndex_>n&&this.selectedIndex_--;break}return t&&this.trigger({qualityLevel:e,type:"removequalitylevel"}),t},o.prototype.getQualityLevelById=function(e){for(var t=0,n=this.length;t<n;t++){var r=this[t];if(r.id===e)return r}return null},o.prototype.dispose=function(){this.selectedIndex_=-1,this.levels_.length=0},o}(e.EventTarget);for(var o in i.prototype.allowedEvents_={change:"change",addqualitylevel:"addqualitylevel",removequalitylevel:"removequalitylevel"},i.prototype.allowedEvents_)i.prototype["on"+o]=null;var l=function(t){return n=this,e.mergeOptions({},t),r=n.qualityLevels,o=new i,n.on("dispose",function e(){o.dispose(),n.qualityLevels=r,n.off("dispose",e)}),n.qualityLevels=function(){return o},n.qualityLevels.VERSION="__VERSION__",o;var n,r,o};return(e.registerPlugin||e.plugin)("qualityLevels",l),l.VERSION="__VERSION__",l});
|
|
||||||
3
assets/js/videojs-dash.min.js
vendored
3
assets/js/videojs-dash.min.js
vendored
File diff suppressed because one or more lines are too long
14
assets/js/videojs-http-streaming.min.js
vendored
14
assets/js/videojs-http-streaming.min.js
vendored
File diff suppressed because one or more lines are too long
4
assets/js/videojs-markers.min.js
vendored
4
assets/js/videojs-markers.min.js
vendored
File diff suppressed because one or more lines are too long
7
assets/js/videojs-share.min.js
vendored
7
assets/js/videojs-share.min.js
vendored
File diff suppressed because one or more lines are too long
1
assets/js/videojs-youtube-annotations.min.js
vendored
Normal file
1
assets/js/videojs-youtube-annotations.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,413 +0,0 @@
|
|||||||
/*
|
|
||||||
* Video.js Hotkeys
|
|
||||||
* https://github.com/ctd1500/videojs-hotkeys
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Chris Dougherty
|
|
||||||
* Licensed under the Apache-2.0 license.
|
|
||||||
*/
|
|
||||||
|
|
||||||
;(function(root, factory) {
|
|
||||||
if (typeof window !== 'undefined' && window.videojs) {
|
|
||||||
factory(window.videojs);
|
|
||||||
} else if (typeof define === 'function' && define.amd) {
|
|
||||||
define('videojs-hotkeys', ['video.js'], function (module) {
|
|
||||||
return factory(module.default || module);
|
|
||||||
});
|
|
||||||
} else if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports = factory(require('video.js'));
|
|
||||||
}
|
|
||||||
}(this, function (videojs) {
|
|
||||||
"use strict";
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window['videojs_hotkeys'] = { version: "0.2.22" };
|
|
||||||
}
|
|
||||||
|
|
||||||
var hotkeys = function(options) {
|
|
||||||
var player = this;
|
|
||||||
var pEl = player.el();
|
|
||||||
var doc = document;
|
|
||||||
var def_options = {
|
|
||||||
volumeStep: 0.1,
|
|
||||||
seekStep: 5,
|
|
||||||
enableMute: true,
|
|
||||||
enableVolumeScroll: true,
|
|
||||||
enableHoverScroll: true,
|
|
||||||
enableFullscreen: true,
|
|
||||||
enableNumbers: true,
|
|
||||||
enableJogStyle: false,
|
|
||||||
alwaysCaptureHotkeys: false,
|
|
||||||
enableModifiersForNumbers: true,
|
|
||||||
enableInactiveFocus: true,
|
|
||||||
skipInitialFocus: false,
|
|
||||||
playPauseKey: playPauseKey,
|
|
||||||
rewindKey: rewindKey,
|
|
||||||
forwardKey: forwardKey,
|
|
||||||
volumeUpKey: volumeUpKey,
|
|
||||||
volumeDownKey: volumeDownKey,
|
|
||||||
muteKey: muteKey,
|
|
||||||
fullscreenKey: fullscreenKey,
|
|
||||||
customKeys: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
var cPlay = 1,
|
|
||||||
cRewind = 2,
|
|
||||||
cForward = 3,
|
|
||||||
cVolumeUp = 4,
|
|
||||||
cVolumeDown = 5,
|
|
||||||
cMute = 6,
|
|
||||||
cFullscreen = 7;
|
|
||||||
|
|
||||||
// Use built-in merge function from Video.js v5.0+ or v4.4.0+
|
|
||||||
var mergeOptions = videojs.mergeOptions || videojs.util.mergeOptions;
|
|
||||||
options = mergeOptions(def_options, options || {});
|
|
||||||
|
|
||||||
var volumeStep = options.volumeStep,
|
|
||||||
seekStep = options.seekStep,
|
|
||||||
enableMute = options.enableMute,
|
|
||||||
enableVolumeScroll = options.enableVolumeScroll,
|
|
||||||
enableHoverScroll = options.enableHoverScroll,
|
|
||||||
enableFull = options.enableFullscreen,
|
|
||||||
enableNumbers = options.enableNumbers,
|
|
||||||
enableJogStyle = options.enableJogStyle,
|
|
||||||
alwaysCaptureHotkeys = options.alwaysCaptureHotkeys,
|
|
||||||
enableModifiersForNumbers = options.enableModifiersForNumbers,
|
|
||||||
enableInactiveFocus = options.enableInactiveFocus,
|
|
||||||
skipInitialFocus = options.skipInitialFocus;
|
|
||||||
|
|
||||||
// Set default player tabindex to handle keydown and doubleclick events
|
|
||||||
if (!pEl.hasAttribute('tabIndex')) {
|
|
||||||
pEl.setAttribute('tabIndex', '-1');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove player outline to fix video performance issue
|
|
||||||
pEl.style.outline = "none";
|
|
||||||
|
|
||||||
if (alwaysCaptureHotkeys || !player.autoplay()) {
|
|
||||||
if (!skipInitialFocus) {
|
|
||||||
player.one('play', function() {
|
|
||||||
pEl.focus(); // Fixes the .vjs-big-play-button handing focus back to body instead of the player
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableInactiveFocus) {
|
|
||||||
player.on('userinactive', function() {
|
|
||||||
// When the control bar fades, re-apply focus to the player if last focus was a control button
|
|
||||||
var cancelFocusingPlayer = function() {
|
|
||||||
clearTimeout(focusingPlayerTimeout);
|
|
||||||
};
|
|
||||||
var focusingPlayerTimeout = setTimeout(function() {
|
|
||||||
player.off('useractive', cancelFocusingPlayer);
|
|
||||||
var activeElement = doc.activeElement;
|
|
||||||
var controlBar = pEl.querySelector('.vjs-control-bar');
|
|
||||||
if (activeElement && activeElement.parentElement == controlBar) {
|
|
||||||
pEl.focus();
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
player.one('useractive', cancelFocusingPlayer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
player.on('play', function() {
|
|
||||||
// Fix allowing the YouTube plugin to have hotkey support.
|
|
||||||
var ifblocker = pEl.querySelector('.iframeblocker');
|
|
||||||
if (ifblocker && ifblocker.style.display === '') {
|
|
||||||
ifblocker.style.display = "block";
|
|
||||||
ifblocker.style.bottom = "39px";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var keyDown = function keyDown(event) {
|
|
||||||
var ewhich = event.which, wasPlaying, seekTime;
|
|
||||||
var ePreventDefault = event.preventDefault;
|
|
||||||
var duration = player.duration();
|
|
||||||
// When controls are disabled, hotkeys will be disabled as well
|
|
||||||
if (player.controls()) {
|
|
||||||
|
|
||||||
// Don't catch keys if any control buttons are focused, unless alwaysCaptureHotkeys is true
|
|
||||||
var activeEl = doc.activeElement;
|
|
||||||
if (alwaysCaptureHotkeys ||
|
|
||||||
activeEl == pEl ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-tech') ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-control-bar') ||
|
|
||||||
activeEl == pEl.querySelector('.iframeblocker')) {
|
|
||||||
|
|
||||||
switch (checkKeys(event, player)) {
|
|
||||||
// Spacebar toggles play/pause
|
|
||||||
case cPlay:
|
|
||||||
ePreventDefault();
|
|
||||||
if (alwaysCaptureHotkeys) {
|
|
||||||
// Prevent control activation with space
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player.paused()) {
|
|
||||||
player.play();
|
|
||||||
} else {
|
|
||||||
player.pause();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Seeking with the left/right arrow keys
|
|
||||||
case cRewind: // Seek Backward
|
|
||||||
wasPlaying = !player.paused();
|
|
||||||
ePreventDefault();
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.pause();
|
|
||||||
}
|
|
||||||
seekTime = player.currentTime() - seekStepD(event);
|
|
||||||
// The flash player tech will allow you to seek into negative
|
|
||||||
// numbers and break the seekbar, so try to prevent that.
|
|
||||||
if (seekTime <= 0) {
|
|
||||||
seekTime = 0;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case cForward: // Seek Forward
|
|
||||||
wasPlaying = !player.paused();
|
|
||||||
ePreventDefault();
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.pause();
|
|
||||||
}
|
|
||||||
seekTime = player.currentTime() + seekStepD(event);
|
|
||||||
// Fixes the player not sending the end event if you
|
|
||||||
// try to seek past the duration on the seekbar.
|
|
||||||
if (seekTime >= duration) {
|
|
||||||
seekTime = wasPlaying ? duration - .001 : duration;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
if (wasPlaying) {
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Volume control with the up/down arrow keys
|
|
||||||
case cVolumeDown:
|
|
||||||
ePreventDefault();
|
|
||||||
if (!enableJogStyle) {
|
|
||||||
player.volume(player.volume() - volumeStep);
|
|
||||||
} else {
|
|
||||||
seekTime = player.currentTime() - 1;
|
|
||||||
if (player.currentTime() <= 1) {
|
|
||||||
seekTime = 0;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case cVolumeUp:
|
|
||||||
ePreventDefault();
|
|
||||||
if (!enableJogStyle) {
|
|
||||||
player.volume(player.volume() + volumeStep);
|
|
||||||
} else {
|
|
||||||
seekTime = player.currentTime() + 1;
|
|
||||||
if (seekTime >= duration) {
|
|
||||||
seekTime = duration;
|
|
||||||
}
|
|
||||||
player.currentTime(seekTime);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Toggle Mute with the M key
|
|
||||||
case cMute:
|
|
||||||
if (enableMute) {
|
|
||||||
player.muted(!player.muted());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Toggle Fullscreen with the F key
|
|
||||||
case cFullscreen:
|
|
||||||
if (enableFull) {
|
|
||||||
if (player.isFullscreen()) {
|
|
||||||
player.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
player.requestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Number keys from 0-9 skip to a percentage of the video. 0 is 0% and 9 is 90%
|
|
||||||
if ((ewhich > 47 && ewhich < 59) || (ewhich > 95 && ewhich < 106)) {
|
|
||||||
// Do not handle if enableModifiersForNumbers set to false and keys are Ctrl, Cmd or Alt
|
|
||||||
if (enableModifiersForNumbers || !(event.metaKey || event.ctrlKey || event.altKey)) {
|
|
||||||
if (enableNumbers) {
|
|
||||||
var sub = 48;
|
|
||||||
if (ewhich > 95) {
|
|
||||||
sub = 96;
|
|
||||||
}
|
|
||||||
var number = ewhich - sub;
|
|
||||||
ePreventDefault();
|
|
||||||
player.currentTime(player.duration() * number * 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle any custom hotkeys
|
|
||||||
for (var customKey in options.customKeys) {
|
|
||||||
var customHotkey = options.customKeys[customKey];
|
|
||||||
// Check for well formed custom keys
|
|
||||||
if (customHotkey && customHotkey.key && customHotkey.handler) {
|
|
||||||
// Check if the custom key's condition matches
|
|
||||||
if (customHotkey.key(event)) {
|
|
||||||
ePreventDefault();
|
|
||||||
customHotkey.handler(player, options, event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var doubleClick = function doubleClick(event) {
|
|
||||||
// When controls are disabled, hotkeys will be disabled as well
|
|
||||||
if (player.controls()) {
|
|
||||||
|
|
||||||
// Don't catch clicks if any control buttons are focused
|
|
||||||
var activeEl = event.relatedTarget || event.toElement || doc.activeElement;
|
|
||||||
if (activeEl == pEl ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-tech') ||
|
|
||||||
activeEl == pEl.querySelector('.iframeblocker')) {
|
|
||||||
|
|
||||||
if (enableFull) {
|
|
||||||
if (player.isFullscreen()) {
|
|
||||||
player.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
player.requestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var volumeHover = false;
|
|
||||||
var volumeSelector = pEl.querySelector('.vjs-volume-menu-button') || pEl.querySelector('.vjs-volume-panel');
|
|
||||||
volumeSelector.onmouseover = function() { volumeHover = true; }
|
|
||||||
volumeSelector.onmouseout = function() { volumeHover = false; }
|
|
||||||
|
|
||||||
var mouseScroll = function mouseScroll(event) {
|
|
||||||
if (enableHoverScroll) {
|
|
||||||
// If we leave this undefined then it can match non-existent elements below
|
|
||||||
var activeEl = 0;
|
|
||||||
} else {
|
|
||||||
var activeEl = doc.activeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When controls are disabled, hotkeys will be disabled as well
|
|
||||||
if (player.controls()) {
|
|
||||||
if (alwaysCaptureHotkeys ||
|
|
||||||
activeEl == pEl ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-tech') ||
|
|
||||||
activeEl == pEl.querySelector('.iframeblocker') ||
|
|
||||||
activeEl == pEl.querySelector('.vjs-control-bar') ||
|
|
||||||
volumeHover) {
|
|
||||||
|
|
||||||
if (enableVolumeScroll) {
|
|
||||||
event = window.event || event;
|
|
||||||
var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (delta == 1) {
|
|
||||||
player.volume(player.volume() + volumeStep);
|
|
||||||
} else if (delta == -1) {
|
|
||||||
player.volume(player.volume() - volumeStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var checkKeys = function checkKeys(e, player) {
|
|
||||||
// Allow some modularity in defining custom hotkeys
|
|
||||||
|
|
||||||
// Play/Pause check
|
|
||||||
if (options.playPauseKey(e, player)) {
|
|
||||||
return cPlay;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek Backward check
|
|
||||||
if (options.rewindKey(e, player)) {
|
|
||||||
return cRewind;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek Forward check
|
|
||||||
if (options.forwardKey(e, player)) {
|
|
||||||
return cForward;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume Up check
|
|
||||||
if (options.volumeUpKey(e, player)) {
|
|
||||||
return cVolumeUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume Down check
|
|
||||||
if (options.volumeDownKey(e, player)) {
|
|
||||||
return cVolumeDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mute check
|
|
||||||
if (options.muteKey(e, player)) {
|
|
||||||
return cMute;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fullscreen check
|
|
||||||
if (options.fullscreenKey(e, player)) {
|
|
||||||
return cFullscreen;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function playPauseKey(e) {
|
|
||||||
// Space bar or MediaPlayPause
|
|
||||||
return (e.which === 32 || e.which === 179);
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewindKey(e) {
|
|
||||||
// Left Arrow or MediaRewind
|
|
||||||
return (e.which === 37 || e.which === 177);
|
|
||||||
}
|
|
||||||
|
|
||||||
function forwardKey(e) {
|
|
||||||
// Right Arrow or MediaForward
|
|
||||||
return (e.which === 39 || e.which === 176);
|
|
||||||
}
|
|
||||||
|
|
||||||
function volumeUpKey(e) {
|
|
||||||
// Up Arrow
|
|
||||||
return (e.which === 38);
|
|
||||||
}
|
|
||||||
|
|
||||||
function volumeDownKey(e) {
|
|
||||||
// Down Arrow
|
|
||||||
return (e.which === 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
function muteKey(e) {
|
|
||||||
// M key
|
|
||||||
return (e.which === 77);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullscreenKey(e) {
|
|
||||||
// F key
|
|
||||||
return (e.which === 70);
|
|
||||||
}
|
|
||||||
|
|
||||||
function seekStepD(e) {
|
|
||||||
// SeekStep caller, returns an int, or a function returning an int
|
|
||||||
return (typeof seekStep === "function" ? seekStep(e) : seekStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
player.on('keydown', keyDown);
|
|
||||||
player.on('dblclick', doubleClick);
|
|
||||||
player.on('mousewheel', mouseScroll);
|
|
||||||
player.on("DOMMouseScroll", mouseScroll);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
var registerPlugin = videojs.registerPlugin || videojs.plugin;
|
|
||||||
registerPlugin('hotkeys', hotkeys);
|
|
||||||
}));
|
|
||||||
2
assets/js/videojs.hotkeys.min.js
vendored
2
assets/js/videojs.hotkeys.min.js
vendored
@@ -1,2 +0,0 @@
|
|||||||
/* videojs-hotkeys v0.2.22 - https://github.com/ctd1500/videojs-hotkeys */
|
|
||||||
!function(e,t){"undefined"!=typeof window&&window.videojs?t(window.videojs):"function"==typeof define&&define.amd?define("videojs-hotkeys",["video.js"],function(e){return t(e.default||e)}):"undefined"!=typeof module&&module.exports&&(module.exports=t(require("video.js")))}(0,function(s){"use strict";"undefined"!=typeof window&&(window.videojs_hotkeys={version:"0.2.22"});(s.registerPlugin||s.plugin)("hotkeys",function(m){var y=this,v=y.el(),f=document,e={volumeStep:.1,seekStep:5,enableMute:!0,enableVolumeScroll:!0,enableHoverScroll:!0,enableFullscreen:!0,enableNumbers:!0,enableJogStyle:!1,alwaysCaptureHotkeys:!1,enableModifiersForNumbers:!0,enableInactiveFocus:!0,skipInitialFocus:!1,playPauseKey:function(e){return 32===e.which||179===e.which},rewindKey:function(e){return 37===e.which||177===e.which},forwardKey:function(e){return 39===e.which||176===e.which},volumeUpKey:function(e){return 38===e.which},volumeDownKey:function(e){return 40===e.which},muteKey:function(e){return 77===e.which},fullscreenKey:function(e){return 70===e.which},customKeys:{}},t=s.mergeOptions||s.util.mergeOptions,d=(m=t(e,m||{})).volumeStep,n=m.seekStep,p=m.enableMute,r=m.enableVolumeScroll,o=m.enableHoverScroll,b=m.enableFullscreen,h=m.enableNumbers,w=m.enableJogStyle,k=m.alwaysCaptureHotkeys,S=m.enableModifiersForNumbers,u=m.enableInactiveFocus,l=m.skipInitialFocus;v.hasAttribute("tabIndex")||v.setAttribute("tabIndex","-1"),v.style.outline="none",!k&&y.autoplay()||l||y.one("play",function(){v.focus()}),u&&y.on("userinactive",function(){var n=function(){clearTimeout(e)},e=setTimeout(function(){y.off("useractive",n);var e=f.activeElement,t=v.querySelector(".vjs-control-bar");e&&e.parentElement==t&&v.focus()},10);y.one("useractive",n)}),y.on("play",function(){var e=v.querySelector(".iframeblocker");e&&""===e.style.display&&(e.style.display="block",e.style.bottom="39px")});var i=!1,c=v.querySelector(".vjs-volume-menu-button")||v.querySelector(".vjs-volume-panel");c.onmouseover=function(){i=!0},c.onmouseout=function(){i=!1};var a=function(e){if(o)var t=0;else t=f.activeElement;if(y.controls()&&(k||t==v||t==v.querySelector(".vjs-tech")||t==v.querySelector(".iframeblocker")||t==v.querySelector(".vjs-control-bar")||i)&&r){e=window.event||e;var n=Math.max(-1,Math.min(1,e.wheelDelta||-e.detail));e.preventDefault(),1==n?y.volume(y.volume()+d):-1==n&&y.volume(y.volume()-d)}},K=function(e,t){return m.playPauseKey(e,t)?1:m.rewindKey(e,t)?2:m.forwardKey(e,t)?3:m.volumeUpKey(e,t)?4:m.volumeDownKey(e,t)?5:m.muteKey(e,t)?6:m.fullscreenKey(e,t)?7:void 0};function q(e){return"function"==typeof n?n(e):n}return y.on("keydown",function(e){var t,n,r=e.which,o=e.preventDefault,u=y.duration();if(y.controls()){var l=f.activeElement;if(k||l==v||l==v.querySelector(".vjs-tech")||l==v.querySelector(".vjs-control-bar")||l==v.querySelector(".iframeblocker"))switch(K(e,y)){case 1:o(),k&&e.stopPropagation(),y.paused()?y.play():y.pause();break;case 2:t=!y.paused(),o(),t&&y.pause(),(n=y.currentTime()-q(e))<=0&&(n=0),y.currentTime(n),t&&y.play();break;case 3:t=!y.paused(),o(),t&&y.pause(),u<=(n=y.currentTime()+q(e))&&(n=t?u-.001:u),y.currentTime(n),t&&y.play();break;case 5:o(),w?(n=y.currentTime()-1,y.currentTime()<=1&&(n=0),y.currentTime(n)):y.volume(y.volume()-d);break;case 4:o(),w?(u<=(n=y.currentTime()+1)&&(n=u),y.currentTime(n)):y.volume(y.volume()+d);break;case 6:p&&y.muted(!y.muted());break;case 7:b&&(y.isFullscreen()?y.exitFullscreen():y.requestFullscreen());break;default:if((47<r&&r<59||95<r&&r<106)&&(S||!(e.metaKey||e.ctrlKey||e.altKey))&&h){var i=48;95<r&&(i=96);var c=r-i;o(),y.currentTime(y.duration()*c*.1)}for(var a in m.customKeys){var s=m.customKeys[a];s&&s.key&&s.handler&&s.key(e)&&(o(),s.handler(y,m,e))}}}}),y.on("dblclick",function(e){if(y.controls()){var t=e.relatedTarget||e.toElement||f.activeElement;t!=v&&t!=v.querySelector(".vjs-tech")&&t!=v.querySelector(".iframeblocker")||b&&(y.isFullscreen()?y.exitFullscreen():y.requestFullscreen())}}),y.on("mousewheel",a),y.on("DOMMouseScroll",a),this})});
|
|
||||||
@@ -1,52 +1,467 @@
|
|||||||
|
var video_data = JSON.parse(document.getElementById('video_data').innerHTML);
|
||||||
|
|
||||||
|
String.prototype.supplant = function (o) {
|
||||||
|
return this.replace(/{([^{}]*)}/g, function (a, b) {
|
||||||
|
var r = o[b];
|
||||||
|
return typeof r === 'string' || typeof r === 'number' ? r : a;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function toggle_parent(target) {
|
function toggle_parent(target) {
|
||||||
body = target.parentNode.parentNode.children[1];
|
body = target.parentNode.parentNode.children[1];
|
||||||
if (body.style.display === null || body.style.display === "") {
|
if (body.style.display === null || body.style.display === '') {
|
||||||
target.innerHTML = "[ + ]";
|
target.innerHTML = '[ + ]';
|
||||||
body.style.display = "none";
|
body.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
target.innerHTML = "[ - ]";
|
target.innerHTML = '[ - ]';
|
||||||
body.style.display = "";
|
body.style.display = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle_comments(target) {
|
function toggle_comments(event) {
|
||||||
body = target.parentNode.parentNode.parentNode.children[1];
|
var target = event.target;
|
||||||
if (body.style.display === null || body.style.display === "") {
|
body = target.parentNode.parentNode.parentNode.children[1];
|
||||||
target.innerHTML = "[ + ]";
|
if (body.style.display === null || body.style.display === '') {
|
||||||
body.style.display = "none";
|
target.innerHTML = '[ + ]';
|
||||||
} else {
|
body.style.display = 'none';
|
||||||
target.innerHTML = "[ - ]";
|
} else {
|
||||||
body.style.display = "";
|
target.innerHTML = '[ - ]';
|
||||||
}
|
body.style.display = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function swap_comments(source) {
|
function swap_comments(event) {
|
||||||
if (source == "youtube") {
|
var source = event.target.getAttribute('data-comments');
|
||||||
get_youtube_comments();
|
|
||||||
} else if (source == "reddit") {
|
if (source === 'youtube') {
|
||||||
get_reddit_comments();
|
get_youtube_comments();
|
||||||
}
|
} else if (source === 'reddit') {
|
||||||
|
get_reddit_comments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String.prototype.supplant = function(o) {
|
function hide_youtube_replies(event) {
|
||||||
return this.replace(/{([^{}]*)}/g, function(a, b) {
|
var target = event.target;
|
||||||
var r = o[b];
|
|
||||||
return typeof r === "string" || typeof r === "number" ? r : a;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function show_youtube_replies(target) {
|
sub_text = target.getAttribute('data-inner-text');
|
||||||
body = target.parentNode.parentNode.children[1];
|
inner_text = target.getAttribute('data-sub-text');
|
||||||
body.style.display = "";
|
|
||||||
|
|
||||||
target.innerHTML = "Hide replies";
|
body = target.parentNode.parentNode.children[1];
|
||||||
target.setAttribute("onclick", "hide_youtube_replies(this)");
|
body.style.display = 'none';
|
||||||
|
|
||||||
|
target.innerHTML = sub_text;
|
||||||
|
target.onclick = show_youtube_replies;
|
||||||
|
target.setAttribute('data-inner-text', inner_text);
|
||||||
|
target.setAttribute('data-sub-text', sub_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide_youtube_replies(target) {
|
function show_youtube_replies(event) {
|
||||||
body = target.parentNode.parentNode.children[1];
|
var target = event.target;
|
||||||
body.style.display = "none";
|
|
||||||
|
|
||||||
target.innerHTML = "Show replies";
|
sub_text = target.getAttribute('data-inner-text');
|
||||||
target.setAttribute("onclick", "show_youtube_replies(this)");
|
inner_text = target.getAttribute('data-sub-text');
|
||||||
|
|
||||||
|
body = target.parentNode.parentNode.children[1];
|
||||||
|
body.style.display = '';
|
||||||
|
|
||||||
|
target.innerHTML = sub_text;
|
||||||
|
target.onclick = hide_youtube_replies;
|
||||||
|
target.setAttribute('data-inner-text', inner_text);
|
||||||
|
target.setAttribute('data-sub-text', sub_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var continue_button = document.getElementById('continue');
|
||||||
|
if (continue_button) {
|
||||||
|
continue_button.onclick = continue_autoplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
function next_video() {
|
||||||
|
var url = new URL('https://example.com/watch?v=' + video_data.next_video);
|
||||||
|
|
||||||
|
if (video_data.params.autoplay || video_data.params.continue_autoplay) {
|
||||||
|
url.searchParams.set('autoplay', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.listen !== video_data.preferences.listen) {
|
||||||
|
url.searchParams.set('listen', video_data.params.listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.speed !== video_data.preferences.speed) {
|
||||||
|
url.searchParams.set('speed', video_data.params.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.local !== video_data.preferences.local) {
|
||||||
|
url.searchParams.set('local', video_data.params.local);
|
||||||
|
}
|
||||||
|
|
||||||
|
url.searchParams.set('continue', '1');
|
||||||
|
location.assign(url.pathname + url.search);
|
||||||
|
}
|
||||||
|
|
||||||
|
function continue_autoplay(event) {
|
||||||
|
if (event.target.checked) {
|
||||||
|
player.on('ended', function () {
|
||||||
|
next_video();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
player.off('ended');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function number_with_separator(val) {
|
||||||
|
while (/(\d+)(\d{3})/.test(val.toString())) {
|
||||||
|
val = val.toString().replace(/(\d+)(\d{3})/, '$1' + ',' + '$2');
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_playlist(plid, retries) {
|
||||||
|
if (retries == undefined) retries = 5;
|
||||||
|
playlist = document.getElementById('playlist');
|
||||||
|
|
||||||
|
if (retries <= 0) {
|
||||||
|
console.log('Failed to pull playlist');
|
||||||
|
playlist.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playlist.innerHTML = ' \
|
||||||
|
<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3> \
|
||||||
|
<hr>'
|
||||||
|
|
||||||
|
if (plid.startsWith('RD')) {
|
||||||
|
var plid_url = '/api/v1/mixes/' + plid +
|
||||||
|
'?continuation=' + video_data.id +
|
||||||
|
'&format=html&hl=' + video_data.preferences.locale;
|
||||||
|
} else {
|
||||||
|
var plid_url = '/api/v1/playlists/' + plid +
|
||||||
|
'?index=' + video_data.index +
|
||||||
|
'&continuation=' + video_data.id +
|
||||||
|
'&format=html&hl=' + video_data.preferences.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', plid_url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
playlist.innerHTML = xhr.response.playlistHtml;
|
||||||
|
var nextVideo = document.getElementById(xhr.response.nextVideo);
|
||||||
|
nextVideo.parentNode.parentNode.scrollTop = nextVideo.offsetTop;
|
||||||
|
|
||||||
|
if (xhr.response.nextVideo) {
|
||||||
|
player.on('ended', function () {
|
||||||
|
var url = new URL('https://example.com/watch?v=' + xhr.response.nextVideo);
|
||||||
|
|
||||||
|
url.searchParams.set('list', plid);
|
||||||
|
if (!plid.startsWith('RD')) {
|
||||||
|
url.searchParams.set('index', xhr.response.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.autoplay || video_data.params.continue_autoplay) {
|
||||||
|
url.searchParams.set('autoplay', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.listen !== video_data.preferences.listen) {
|
||||||
|
url.searchParams.set('listen', video_data.params.listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.speed !== video_data.preferences.speed) {
|
||||||
|
url.searchParams.set('speed', video_data.params.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.local !== video_data.preferences.local) {
|
||||||
|
url.searchParams.set('local', video_data.params.local);
|
||||||
|
}
|
||||||
|
|
||||||
|
location.assign(url.pathname + url.search);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playlist.innerHTML = '';
|
||||||
|
document.getElementById('continue').style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
playlist = document.getElementById('playlist');
|
||||||
|
playlist.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
|
||||||
|
|
||||||
|
console.log('Pulling playlist timed out... ' + retries + '/5');
|
||||||
|
setTimeout(function () { get_playlist(plid, retries - 1) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
playlist = document.getElementById('playlist');
|
||||||
|
playlist.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
|
||||||
|
|
||||||
|
console.log('Pulling playlist timed out... ' + retries + '/5');
|
||||||
|
get_playlist(plid, retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_reddit_comments(retries) {
|
||||||
|
if (retries == undefined) retries = 5;
|
||||||
|
comments = document.getElementById('comments');
|
||||||
|
|
||||||
|
if (retries <= 0) {
|
||||||
|
console.log('Failed to pull comments');
|
||||||
|
comments.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallback = comments.innerHTML;
|
||||||
|
comments.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
|
var url = '/api/v1/comments/' + video_data.id +
|
||||||
|
'?source=reddit&format=html' +
|
||||||
|
'&hl=' + video_data.preferences.locale;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
comments.innerHTML = ' \
|
||||||
|
<div> \
|
||||||
|
<h3> \
|
||||||
|
<a href="javascript:void(0)">[ - ]</a> \
|
||||||
|
{title} \
|
||||||
|
</h3> \
|
||||||
|
<p> \
|
||||||
|
<b> \
|
||||||
|
<a href="javascript:void(0)" data-comments="youtube"> \
|
||||||
|
{youtubeCommentsText} \
|
||||||
|
</a> \
|
||||||
|
</b> \
|
||||||
|
</p> \
|
||||||
|
<b> \
|
||||||
|
<a rel="noopener" target="_blank" href="https://reddit.com{permalink}">{redditPermalinkText}</a> \
|
||||||
|
</b> \
|
||||||
|
</div> \
|
||||||
|
<div>{contentHtml}</div> \
|
||||||
|
<hr>'.supplant({
|
||||||
|
title: xhr.response.title,
|
||||||
|
youtubeCommentsText: video_data.youtube_comments_text,
|
||||||
|
redditPermalinkText: video_data.reddit_permalink_text,
|
||||||
|
permalink: xhr.response.permalink,
|
||||||
|
contentHtml: xhr.response.contentHtml
|
||||||
|
});
|
||||||
|
|
||||||
|
comments.children[0].children[0].children[0].onclick = toggle_comments;
|
||||||
|
comments.children[0].children[1].children[0].onclick = swap_comments;
|
||||||
|
} else {
|
||||||
|
if (video_data.params.comments[1] === 'youtube') {
|
||||||
|
console.log('Pulling comments failed... ' + retries + '/5');
|
||||||
|
setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
|
||||||
|
} else {
|
||||||
|
comments.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.log('Pulling comments failed... ' + retries + '/5');
|
||||||
|
setTimeout(function () { get_reddit_comments(retries - 1) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Pulling comments failed... ' + retries + '/5');
|
||||||
|
get_reddit_comments(retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_comments(retries) {
|
||||||
|
if (retries == undefined) retries = 5;
|
||||||
|
comments = document.getElementById('comments');
|
||||||
|
|
||||||
|
if (retries <= 0) {
|
||||||
|
console.log('Failed to pull comments');
|
||||||
|
comments.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallback = comments.innerHTML;
|
||||||
|
comments.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
|
var url = '/api/v1/comments/' + video_data.id +
|
||||||
|
'?format=html' +
|
||||||
|
'&hl=' + video_data.preferences.locale +
|
||||||
|
'&thin_mode=' + video_data.preferences.thin_mode;
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
comments.innerHTML = ' \
|
||||||
|
<div> \
|
||||||
|
<h3> \
|
||||||
|
<a href="javascript:void(0)">[ - ]</a> \
|
||||||
|
{commentsText} \
|
||||||
|
</h3> \
|
||||||
|
<b> \
|
||||||
|
<a href="javascript:void(0)" data-comments="reddit"> \
|
||||||
|
{redditComments} \
|
||||||
|
</a> \
|
||||||
|
</b> \
|
||||||
|
</div> \
|
||||||
|
<div>{contentHtml}</div> \
|
||||||
|
<hr>'.supplant({
|
||||||
|
contentHtml: xhr.response.contentHtml,
|
||||||
|
redditComments: video_data.reddit_comments_text,
|
||||||
|
commentsText: video_data.comments_text.supplant(
|
||||||
|
{ commentCount: number_with_separator(xhr.response.commentCount) }
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
comments.children[0].children[0].children[0].onclick = toggle_comments;
|
||||||
|
comments.children[0].children[1].children[0].onclick = swap_comments;
|
||||||
|
} else {
|
||||||
|
if (video_data.params.comments[1] === 'youtube') {
|
||||||
|
setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
|
||||||
|
} else {
|
||||||
|
comments.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onerror = function () {
|
||||||
|
comments.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
console.log('Pulling comments failed... ' + retries + '/5');
|
||||||
|
setTimeout(function () { get_youtube_comments(retries - 1) }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
comments.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
console.log('Pulling comments failed... ' + retries + '/5');
|
||||||
|
get_youtube_comments(retries - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_youtube_replies(target, load_more, load_replies) {
|
||||||
|
var continuation = target.getAttribute('data-continuation');
|
||||||
|
|
||||||
|
var body = target.parentNode.parentNode;
|
||||||
|
var fallback = body.innerHTML;
|
||||||
|
body.innerHTML =
|
||||||
|
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||||
|
|
||||||
|
var url = '/api/v1/comments/' + video_data.id +
|
||||||
|
'?format=html' +
|
||||||
|
'&hl=' + video_data.preferences.locale +
|
||||||
|
'&thin_mode=' + video_data.preferences.thin_mode +
|
||||||
|
'&continuation=' + continuation
|
||||||
|
if (load_replies) {
|
||||||
|
url += '&action=action_get_comment_replies';
|
||||||
|
}
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
if (load_more) {
|
||||||
|
body = body.parentNode.parentNode;
|
||||||
|
body.removeChild(body.lastElementChild);
|
||||||
|
body.innerHTML += xhr.response.contentHtml;
|
||||||
|
} else {
|
||||||
|
body.removeChild(body.lastElementChild);
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
var a = document.createElement('a');
|
||||||
|
p.appendChild(a);
|
||||||
|
|
||||||
|
a.href = 'javascript:void(0)';
|
||||||
|
a.onclick = hide_youtube_replies;
|
||||||
|
a.setAttribute('data-sub-text', video_data.hide_replies_text);
|
||||||
|
a.setAttribute('data-inner-text', video_data.show_replies_text);
|
||||||
|
a.innerText = video_data.hide_replies_text;
|
||||||
|
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = xhr.response.contentHtml;
|
||||||
|
|
||||||
|
body.appendChild(p);
|
||||||
|
body.appendChild(div);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.ontimeout = function () {
|
||||||
|
console.log('Pulling comments failed.');
|
||||||
|
body.innerHTML = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.play_next) {
|
||||||
|
player.on('ended', function () {
|
||||||
|
var url = new URL('https://example.com/watch?v=' + video_data.next_video);
|
||||||
|
|
||||||
|
if (video_data.params.autoplay || video_data.params.continue_autoplay) {
|
||||||
|
url.searchParams.set('autoplay', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.listen !== video_data.preferences.listen) {
|
||||||
|
url.searchParams.set('listen', video_data.params.listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.speed !== video_data.preferences.speed) {
|
||||||
|
url.searchParams.set('speed', video_data.params.speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.local !== video_data.preferences.local) {
|
||||||
|
url.searchParams.set('local', video_data.params.local);
|
||||||
|
}
|
||||||
|
|
||||||
|
url.searchParams.set('continue', '1');
|
||||||
|
location.assign(url.pathname + url.search);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', function (e) {
|
||||||
|
if (video_data.plid) {
|
||||||
|
get_playlist(video_data.plid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video_data.params.comments[0] === 'youtube') {
|
||||||
|
get_youtube_comments();
|
||||||
|
} else if (video_data.params.comments[0] === 'reddit') {
|
||||||
|
get_reddit_comments();
|
||||||
|
} else if (video_data.params.comments[1] === 'youtube') {
|
||||||
|
get_youtube_comments();
|
||||||
|
} else if (video_data.params.comments[1] === 'reddit') {
|
||||||
|
get_reddit_comments();
|
||||||
|
} else {
|
||||||
|
comments = document.getElementById('comments');
|
||||||
|
comments.innerHTML = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
50
assets/js/watched_widget.js
Normal file
50
assets/js/watched_widget.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
var watched_data = JSON.parse(document.getElementById('watched_data').innerHTML);
|
||||||
|
|
||||||
|
function mark_watched(target) {
|
||||||
|
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
|
tile.style.display = 'none';
|
||||||
|
|
||||||
|
var url = '/watch_ajax?action_mark_watched=1&redirect=false' +
|
||||||
|
'&id=' + target.getAttribute('data-id');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
tile.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send('csrf_token=' + watched_data.csrf_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mark_unwatched(target) {
|
||||||
|
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
||||||
|
tile.style.display = 'none';
|
||||||
|
var count = document.getElementById('count')
|
||||||
|
count.innerText = count.innerText - 1;
|
||||||
|
|
||||||
|
var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' +
|
||||||
|
'&id=' + target.getAttribute('data-id');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
xhr.timeout = 10000;
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function () {
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
if (xhr.status != 200) {
|
||||||
|
count.innerText = count.innerText - 1 + 2;
|
||||||
|
tile.style.display = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send('csrf_token=' + watched_data.csrf_token);
|
||||||
|
}
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /search
|
Disallow: /
|
||||||
Disallow: /login
|
|
||||||
|
|||||||
4
assets/videojs/.gitignore
vendored
Normal file
4
assets/videojs/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
||||||
868
config/config.example.yml
Normal file
868
config/config.example.yml
Normal file
@@ -0,0 +1,868 @@
|
|||||||
|
#########################################
|
||||||
|
#
|
||||||
|
# Database configuration
|
||||||
|
#
|
||||||
|
#########################################
|
||||||
|
|
||||||
|
##
|
||||||
|
## Database configuration with separate parameters.
|
||||||
|
## This setting is MANDATORY, unless 'database_url' is used.
|
||||||
|
##
|
||||||
|
db:
|
||||||
|
user: kemal
|
||||||
|
password: kemal
|
||||||
|
host: localhost
|
||||||
|
port: 5432
|
||||||
|
dbname: invidious
|
||||||
|
|
||||||
|
##
|
||||||
|
## Database configuration using a single URI. This is an
|
||||||
|
## alternative to the 'db' parameter above. If both forms
|
||||||
|
## are used, then only database_url is used.
|
||||||
|
## This setting is MANDATORY, unless 'db' is used.
|
||||||
|
##
|
||||||
|
## Note: The 'database_url' setting allows the use of UNIX
|
||||||
|
## sockets. To do so, remove the IP address (or FQDN) and port
|
||||||
|
## and append the 'host' parameter. E.g:
|
||||||
|
## postgres://kemal:kemal@/invidious?host=/var/run/postgresql
|
||||||
|
##
|
||||||
|
## Accepted values: a postgres:// URI
|
||||||
|
## Default: postgres://kemal:kemal@localhost:5432/invidious
|
||||||
|
##
|
||||||
|
#database_url: postgres://kemal:kemal@localhost:5432/invidious
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable automatic table integrity check. This will create
|
||||||
|
## the required tables and columns if anything is missing.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#check_tables: false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#########################################
|
||||||
|
#
|
||||||
|
# Server config
|
||||||
|
#
|
||||||
|
#########################################
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Network (inbound)
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Port to listen on for incoming connections.
|
||||||
|
##
|
||||||
|
## Note: Ports lower than 1024 requires either root privileges
|
||||||
|
## (not recommended) or the "CAP_NET_BIND_SERVICE" capability
|
||||||
|
## (See https://stackoverflow.com/a/414258 and `man capabilities`)
|
||||||
|
##
|
||||||
|
## Accepted values: 1-65535
|
||||||
|
## Default: 3000
|
||||||
|
##
|
||||||
|
#port: 3000
|
||||||
|
|
||||||
|
##
|
||||||
|
## When the invidious instance is behind a proxy, and the proxy
|
||||||
|
## listens on a different port than the instance does, this lets
|
||||||
|
## invidious know about it. This is used to craft absolute URLs
|
||||||
|
## to the instance (e.g in the API).
|
||||||
|
##
|
||||||
|
## Note: This setting is MANDATORY if invidious is behind a
|
||||||
|
## reverse proxy.
|
||||||
|
##
|
||||||
|
## Accepted values: 1-65535
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#external_port:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Interface address to listen on for incoming connections.
|
||||||
|
##
|
||||||
|
## Accepted values: a valid IPv4 or IPv6 address.
|
||||||
|
## default: 0.0.0.0 (listen on all interfaces)
|
||||||
|
##
|
||||||
|
#host_binding: 0.0.0.0
|
||||||
|
|
||||||
|
##
|
||||||
|
## Domain name under which this instance is hosted. This is
|
||||||
|
## used to craft absolute URLs to the instance (e.g in the API).
|
||||||
|
## The domain MUST be defined if your instance is accessed from
|
||||||
|
## a domain name (like 'example.com').
|
||||||
|
##
|
||||||
|
## Accepted values: a fully qualified domain name (FQDN)
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
domain:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Tell Invidious that it is behind a proxy that provides only
|
||||||
|
## HTTPS, so all links must use the https:// scheme. This
|
||||||
|
## setting MUST be set to true if invidious is behind a
|
||||||
|
## reverse proxy serving HTTPs.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
https_only: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable 'Strict-Transport-Security'. Make sure that
|
||||||
|
## the domain specified under 'domain' is served securely.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#hsts: true
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Network (outbound)
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Disable proxying server-wide. Can be disable as a whole, or
|
||||||
|
## only for a single function.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false, dash, livestreams, downloads, local
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#disable_proxy: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Size of the HTTP pool used to connect to youtube. Each
|
||||||
|
## domain ('youtube.com', 'ytimg.com', ...) has its own pool.
|
||||||
|
##
|
||||||
|
## Accepted values: a positive integer
|
||||||
|
## Default: 100
|
||||||
|
##
|
||||||
|
#pool_size: 100
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable the use of QUIC (HTTP/3) when connecting
|
||||||
|
## to the youtube API and websites ('youtube.com', 'ytimg.com').
|
||||||
|
## QUIC's main advantages are its lower latency and lower bandwidth
|
||||||
|
## use, compared to its predecessors. However, the current version
|
||||||
|
## of QUIC used in invidious is still based on the IETF draft 31,
|
||||||
|
## meaning that the underlying library may still not be fully
|
||||||
|
## optimized. You can read more about QUIC at the link below:
|
||||||
|
## https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-31
|
||||||
|
##
|
||||||
|
## Note: you should try both options and see what is the best for your
|
||||||
|
## instance. In general QUIC is recommended for public instances. Your
|
||||||
|
## mileage may vary.
|
||||||
|
##
|
||||||
|
## Note 2: Using QUIC prevents some captcha challenges from appearing.
|
||||||
|
## See: https://github.com/iv-org/invidious/issues/957#issuecomment-576424042
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#use_quic: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Additional cookies to be sent when requesting the youtube API.
|
||||||
|
##
|
||||||
|
## Accepted values: a string in the format "name1=value1; name2=value2..."
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#cookies:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Force connection to youtube over a specific IP family.
|
||||||
|
##
|
||||||
|
## Note: This may sometimes resolve issues involving rate-limiting.
|
||||||
|
## See https://github.com/ytdl-org/youtube-dl/issues/21729.
|
||||||
|
##
|
||||||
|
## Accepted values: ipv4, ipv6
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#force_resolve:
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Logging
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Path to log file. Can be absolute or relative to the invidious
|
||||||
|
## binary. This is overridden if "-o OUTPUT" or "--output=OUTPUT"
|
||||||
|
## are passed on the command line.
|
||||||
|
##
|
||||||
|
## Accepted values: a filesystem path or 'STDOUT'
|
||||||
|
## Default: STDOUT
|
||||||
|
##
|
||||||
|
#output: STDOUT
|
||||||
|
|
||||||
|
##
|
||||||
|
## Logging Verbosity. This is overridden if "-l LEVEL" or
|
||||||
|
## "--log-level=LEVEL" are passed on the command line.
|
||||||
|
##
|
||||||
|
## Accepted values: All, Trace, Debug, Info, Warn, Error, Fatal, Off
|
||||||
|
## Default: Info
|
||||||
|
##
|
||||||
|
#log_level: Info
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Features
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable the "Popular" tab on the main page.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#popular_enabled: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable statstics (available at /api/v1/stats).
|
||||||
|
## The following data is available:
|
||||||
|
## - Software name ("invidious") and version+branch (same data as
|
||||||
|
## displayed in the footer, e.g: "2021.05.13-75e5b49" / "master")
|
||||||
|
## - The value of the 'registration_enabled' config (true/false)
|
||||||
|
## - Number of currently registered users
|
||||||
|
## - Number of registered users who connected in the last month
|
||||||
|
## - Number of registered users who connected in the last 6 months
|
||||||
|
## - Timestamp of the last server restart
|
||||||
|
## - Timestamp of the last "Channel Refresh" job execution
|
||||||
|
##
|
||||||
|
## Warning: This setting MUST be set to true if you plan to run
|
||||||
|
## a public instance. It is used by api.invidious.io to refresh
|
||||||
|
## your instance's status.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#statistics_enabled: false
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Users and accounts
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Allow/Forbid Invidious (local) account creation. Invidious
|
||||||
|
## accounts allow users to subscribe to channels and to create
|
||||||
|
## playlists without a Google account.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#registration_enabled: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## Allow/Forbid users to log-in. This setting affects the ability
|
||||||
|
## to connect with BOTH Google and Invidious (local) accounts.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#login_enabled: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable the captcha challenge on the login page.
|
||||||
|
##
|
||||||
|
## Note: this is a basic captcha challenge that doesn't
|
||||||
|
## depend on any third parties.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#captcha_enabled: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## List of usernames that will be granted administrator rights.
|
||||||
|
## A user with administrator rights will be able to change the
|
||||||
|
## server configuration options listed below in /preferences,
|
||||||
|
## in addition to the usual user preferences.
|
||||||
|
##
|
||||||
|
## Server-wide settings:
|
||||||
|
## - popular_enabled
|
||||||
|
## - captcha_enabled
|
||||||
|
## - login_enabled
|
||||||
|
## - registration_enabled
|
||||||
|
## - statistics_enabled
|
||||||
|
## Default user preferences:
|
||||||
|
## - default_home
|
||||||
|
## - feed_menu
|
||||||
|
##
|
||||||
|
## Accepted values: an array of strings
|
||||||
|
## Default: [""]
|
||||||
|
##
|
||||||
|
#admins: [""]
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Background jobs
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Number of threads to use when crawling channel videos (during
|
||||||
|
## subscriptions update).
|
||||||
|
##
|
||||||
|
## Notes:
|
||||||
|
## - Setting this to 0 will disable the channel videos crawl job.
|
||||||
|
## - This setting is overridden if "-c THREADS" or
|
||||||
|
## "--channel-threads=THREADS" are passed on the command line.
|
||||||
|
##
|
||||||
|
## Accepted values: a positive integer
|
||||||
|
## Default: 1
|
||||||
|
##
|
||||||
|
channel_threads: 1
|
||||||
|
|
||||||
|
##
|
||||||
|
## Time interval between two executions of the job that crawls
|
||||||
|
## channel videos (subscriptions update).
|
||||||
|
##
|
||||||
|
## Accepted values: a valid time interval (like 1h30m or 90m)
|
||||||
|
## Default: 30m
|
||||||
|
##
|
||||||
|
#channel_refresh_interval: 30m
|
||||||
|
|
||||||
|
##
|
||||||
|
## Forcefully dump and re-download the entire list of uploaded
|
||||||
|
## videos when crawling channel (during subscriptions update).
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
full_refresh: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Number of threads to use when updating RSS feeds.
|
||||||
|
##
|
||||||
|
## Notes:
|
||||||
|
## - Setting this to 0 will disable the channel videos crawl job.
|
||||||
|
## - This setting is overridden if "-f THREADS" or
|
||||||
|
## "--feed-threads=THREADS" are passed on the command line.
|
||||||
|
##
|
||||||
|
## Accepted values: a positive integer
|
||||||
|
## Default: 1
|
||||||
|
##
|
||||||
|
feed_threads: 1
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable the polling job that keeps the decryption
|
||||||
|
## function (for "secured" videos) up to date.
|
||||||
|
##
|
||||||
|
## Note: This part of the code is currently broken, so changing
|
||||||
|
## this setting has no impact.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#decrypt_polling: true
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Captcha API
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## URL of the captcha solving service.
|
||||||
|
##
|
||||||
|
## Accepted values: any URL
|
||||||
|
## Default: https://api.anti-captcha.com
|
||||||
|
##
|
||||||
|
#captcha_api_url: https://api.anti-captcha.com
|
||||||
|
|
||||||
|
##
|
||||||
|
## API key for the captcha solving service.
|
||||||
|
##
|
||||||
|
## Accepted values: a string
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#captcha_key:
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Miscellaneous
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## custom banner displayed at the top of every page. This can
|
||||||
|
## used for instance announcements, e.g.
|
||||||
|
##
|
||||||
|
## Accepted values: any string. HTML is accepted.
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#banner:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Subscribe to channels using PubSubHub (Google PubSubHubbub service).
|
||||||
|
## PubSubHub allows Invidious to be instantly notified when a new video
|
||||||
|
## is published on any subscribed channels. When PubSubHub is not used,
|
||||||
|
## Invidious will check for new videos every minute.
|
||||||
|
##
|
||||||
|
## Note: This setting is recommended for public instances.
|
||||||
|
##
|
||||||
|
## Note 2:
|
||||||
|
## - Requires a public instance (it uses /feed/webhook/v1)
|
||||||
|
## - Requires 'domain' and 'hmac_key' to be set.
|
||||||
|
## - Setting this parameter to any number greater than zero will
|
||||||
|
## enable channel subscriptions via PubSubHub, but will limit the
|
||||||
|
## amount of concurrent subscriptions.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false, a positive integer
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#use_pubsub_feeds: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## HMAC signing key used for CSRF tokens and pubsub
|
||||||
|
## subscriptions verification.
|
||||||
|
##
|
||||||
|
## Accepted values: a string
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#hmac_key:
|
||||||
|
|
||||||
|
##
|
||||||
|
## List of video IDs where the "download" widget must be
|
||||||
|
## disabled, in order to comply with DMCA requests.
|
||||||
|
##
|
||||||
|
## Accepted values: an array of string
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#dmca_content:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Cache video annotations in the database.
|
||||||
|
##
|
||||||
|
## Warning: empty annotations or annotations that only contain
|
||||||
|
## cards won't be cached.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#cache_annotations: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Source code URL. If your instance is running a modified source
|
||||||
|
## code, you MUST publish it somewhere and set this option.
|
||||||
|
##
|
||||||
|
## Accepted values: a string
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#modified_source_code_url: ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#########################################
|
||||||
|
#
|
||||||
|
# Default user preferences
|
||||||
|
#
|
||||||
|
#########################################
|
||||||
|
|
||||||
|
##
|
||||||
|
## NOTE: All the settings below define the default user
|
||||||
|
## preferences. They will apply to ALL users connecting
|
||||||
|
## without a preferences cookie (so either on the first
|
||||||
|
## connection to the instance or after clearing the
|
||||||
|
## browser's cookies).
|
||||||
|
##
|
||||||
|
|
||||||
|
default_user_preferences:
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Internationalization
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default user interface language (locale).
|
||||||
|
##
|
||||||
|
## Note: When hosting a public instance, overriding the
|
||||||
|
## default (english) is not recommended, as it may
|
||||||
|
## people using other languages.
|
||||||
|
##
|
||||||
|
## Accepted values:
|
||||||
|
## ar (Arabic)
|
||||||
|
## da (Danish)
|
||||||
|
## de (German)
|
||||||
|
## en-US (english, US)
|
||||||
|
## el (Greek)
|
||||||
|
## eo (Esperanto)
|
||||||
|
## es (Spanish)
|
||||||
|
## fa (Persian)
|
||||||
|
## fi (Finnish)
|
||||||
|
## fr (French)
|
||||||
|
## he (Hebrew)
|
||||||
|
## hr (Hungarian)
|
||||||
|
## id (Indonesian)
|
||||||
|
## is (Icelandic)
|
||||||
|
## it (Italian)
|
||||||
|
## ja (Japanese)
|
||||||
|
## nb-NO (Norwegian, Bokmål)
|
||||||
|
## nl (Dutch)
|
||||||
|
## pl (Polish)
|
||||||
|
## pt-BR (Portuguese, Brazil)
|
||||||
|
## pt-PT (Portuguese, Portugal)
|
||||||
|
## ro (Romanian)
|
||||||
|
## ru (Russian)
|
||||||
|
## sv (Swedish)
|
||||||
|
## tr (Turkish)
|
||||||
|
## uk (Ukrainian)
|
||||||
|
## zh-CN (Chinese, China) (a.k.a "Simplified Chinese")
|
||||||
|
## zh-TW (Chinese, Taiwan) (a.k.a "Traditional Chinese")
|
||||||
|
##
|
||||||
|
## Default: en-US
|
||||||
|
##
|
||||||
|
#locale: en-US
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default geographical location for content.
|
||||||
|
##
|
||||||
|
## Accepted values:
|
||||||
|
## AE, AR, AT, AU, AZ, BA, BD, BE, BG, BH, BO, BR, BY, CA, CH, CL, CO, CR,
|
||||||
|
## CY, CZ, DE, DK, DO, DZ, EC, EE, EG, ES, FI, FR, GB, GE, GH, GR, GT, HK,
|
||||||
|
## HN, HR, HU, ID, IE, IL, IN, IQ, IS, IT, JM, JO, JP, KE, KR, KW, KZ, LB,
|
||||||
|
## LI, LK, LT, LU, LV, LY, MA, ME, MK, MT, MX, MY, NG, NI, NL, NO, NP, NZ,
|
||||||
|
## OM, PA, PE, PG, PH, PK, PL, PR, PT, PY, QA, RO, RS, RU, SA, SE, SG, SI,
|
||||||
|
## SK, SN, SV, TH, TN, TR, TW, TZ, UA, UG, US, UY, VE, VN, YE, ZA, ZW
|
||||||
|
##
|
||||||
|
## Default: US
|
||||||
|
##
|
||||||
|
#region: US
|
||||||
|
|
||||||
|
##
|
||||||
|
## Top 3 preferred languages for video captions.
|
||||||
|
##
|
||||||
|
## Note: overriding the default (no preferred
|
||||||
|
## caption language) is not recommended, in order
|
||||||
|
## to not penalize people using other languages.
|
||||||
|
##
|
||||||
|
## Accepted values: a three-entries array.
|
||||||
|
## Each entry can be one of:
|
||||||
|
## "English", "English (auto-generated)",
|
||||||
|
## "Afrikaans", "Albanian", "Amharic", "Arabic",
|
||||||
|
## "Armenian", "Azerbaijani", "Bangla", "Basque",
|
||||||
|
## "Belarusian", "Bosnian", "Bulgarian", "Burmese",
|
||||||
|
## "Catalan", "Cebuano", "Chinese (Simplified)",
|
||||||
|
## "Chinese (Traditional)", "Corsican", "Croatian",
|
||||||
|
## "Czech", "Danish", "Dutch", "Esperanto", "Estonian",
|
||||||
|
## "Filipino", "Finnish", "French", "Galician", "Georgian",
|
||||||
|
## "German", "Greek", "Gujarati", "Haitian Creole", "Hausa",
|
||||||
|
## "Hawaiian", "Hebrew", "Hindi", "Hmong", "Hungarian",
|
||||||
|
## "Icelandic", "Igbo", "Indonesian", "Irish", "Italian",
|
||||||
|
## "Japanese", "Javanese", "Kannada", "Kazakh", "Khmer",
|
||||||
|
## "Korean", "Kurdish", "Kyrgyz", "Lao", "Latin", "Latvian",
|
||||||
|
## "Lithuanian", "Luxembourgish", "Macedonian",
|
||||||
|
## "Malagasy", "Malay", "Malayalam", "Maltese", "Maori",
|
||||||
|
## "Marathi", "Mongolian", "Nepali", "Norwegian Bokmål",
|
||||||
|
## "Nyanja", "Pashto", "Persian", "Polish", "Portuguese",
|
||||||
|
## "Punjabi", "Romanian", "Russian", "Samoan",
|
||||||
|
## "Scottish Gaelic", "Serbian", "Shona", "Sindhi",
|
||||||
|
## "Sinhala", "Slovak", "Slovenian", "Somali",
|
||||||
|
## "Southern Sotho", "Spanish", "Spanish (Latin America)",
|
||||||
|
## "Sundanese", "Swahili", "Swedish", "Tajik", "Tamil",
|
||||||
|
## "Telugu", "Thai", "Turkish", "Ukrainian", "Urdu",
|
||||||
|
## "Uzbek", "Vietnamese", "Welsh", "Western Frisian",
|
||||||
|
## "Xhosa", "Yiddish", "Yoruba", "Zulu"
|
||||||
|
##
|
||||||
|
## Default: ["", "", ""]
|
||||||
|
##
|
||||||
|
#captions: ["", "", ""]
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Interface
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable dark mode.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: <none>
|
||||||
|
##
|
||||||
|
#dark_mode:
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable thin mode (no video thumbnails).
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#thin_mode: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## List of feeds available on the home page.
|
||||||
|
##
|
||||||
|
## Note: "Subscriptions" and "Playlists" are only visible
|
||||||
|
## when the user is logged in.
|
||||||
|
##
|
||||||
|
## Accepted values: A list of strings
|
||||||
|
## Each entry can be one of: "Popular", "Trending",
|
||||||
|
## "Subscriptions", "Playlists"
|
||||||
|
##
|
||||||
|
## Default: ["Popular", "Trending", "Subscriptions", "Playlists"] (show all feeds)
|
||||||
|
##
|
||||||
|
#feed_menu: ["Popular", "Trending", "Subscriptions", "Playlists"]
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default feed to display on the home page.
|
||||||
|
##
|
||||||
|
## Note: setting this option to "Popular" has no
|
||||||
|
## effect when 'popular_enabled' is set to false.
|
||||||
|
##
|
||||||
|
## Accepted values: Popular, Trending, Subscriptions, Playlists, <none>
|
||||||
|
## Default: Popular
|
||||||
|
##
|
||||||
|
#default_home: Popular
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default number of results to display per page.
|
||||||
|
##
|
||||||
|
## Note: this affects invidious-generated pages only, such
|
||||||
|
## as watch history and subscription feeds. Playlists, search
|
||||||
|
## results and channel videos depend on the data returned by
|
||||||
|
## the Youtube API.
|
||||||
|
##
|
||||||
|
## Accepted values: any positive integer
|
||||||
|
## Default: 40
|
||||||
|
##
|
||||||
|
#max_results: 40
|
||||||
|
|
||||||
|
##
|
||||||
|
## Show/hide annotations.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#annotations: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Show/hide annotation.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#annotations_subscribed: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Type of comments to display below video.
|
||||||
|
##
|
||||||
|
## Accepted values: a two-entries array.
|
||||||
|
## Each entry can be one of: "youtube", "reddit", ""
|
||||||
|
##
|
||||||
|
## Default: ["youtube", ""]
|
||||||
|
##
|
||||||
|
#comments: ["youtube", ""]
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default player style.
|
||||||
|
##
|
||||||
|
## Accepted values: invidious, youtube
|
||||||
|
## Default: invidious
|
||||||
|
##
|
||||||
|
#player_style: invidious
|
||||||
|
|
||||||
|
##
|
||||||
|
## Show/Hide the "related videos" sidebar when
|
||||||
|
## watching a video.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#related_videos: true
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Video player behavior
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Automatically play videos on page load.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#autoplay: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Automatically load the "next" video (either next in
|
||||||
|
## playlist or proposed) when the current video ends.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#continue: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Autoplay next video by default.
|
||||||
|
##
|
||||||
|
## Note: Only effective if 'continue' is set to true.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#continue_autoplay: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## Play videos in Audio-only mode by default.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#listen: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Loop videos automatically.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#video_loop: false
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Video playback settings
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default video quality.
|
||||||
|
##
|
||||||
|
## Accepted values: dash, hd720, medium, small
|
||||||
|
## Default: hd720
|
||||||
|
##
|
||||||
|
#quality: hd720
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default dash video quality.
|
||||||
|
##
|
||||||
|
## Note: this setting only takes effet if the
|
||||||
|
## 'quality' parameter is set to "dash".
|
||||||
|
##
|
||||||
|
## Accepted values:
|
||||||
|
## auto, best, 4320p, 2160p, 1440p, 1080p,
|
||||||
|
## 720p, 480p, 360p, 240p, 144p, worst
|
||||||
|
## Default: auto
|
||||||
|
##
|
||||||
|
#quality_dash: auto
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default video playback speed.
|
||||||
|
##
|
||||||
|
## Accepted values: 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0
|
||||||
|
## Default: 1.0
|
||||||
|
##
|
||||||
|
#speed: 1.0
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default volume.
|
||||||
|
##
|
||||||
|
## Accepted values: 0-100
|
||||||
|
## Default: 100
|
||||||
|
##
|
||||||
|
#volume: 100
|
||||||
|
|
||||||
|
##
|
||||||
|
## Allow 360° videos to be played.
|
||||||
|
##
|
||||||
|
## Note: This feature requires a WebGL-enabled browser.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#vr_mode: true
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Subscription feed
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## In the "Subscription" feed, only show the latest video
|
||||||
|
## of each channel the user is subscribed to.
|
||||||
|
##
|
||||||
|
## Note: when combined with 'unseen_only', the latest unseen
|
||||||
|
## video of each channel will be displayed instead of the
|
||||||
|
## latest by date.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#latest_only: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Enable/Disable user subscriptions desktop notifications.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#notifications_only: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## In the "Subscription" feed, Only show the videos that the
|
||||||
|
## user haven't watched yet (i.e which are not in their watch
|
||||||
|
## history).
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#unseen_only: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Default sorting parameter for subscription feeds.
|
||||||
|
##
|
||||||
|
## Accepted values:
|
||||||
|
## 'alphabetically'
|
||||||
|
## 'alphabetically - reverse'
|
||||||
|
## 'channel name'
|
||||||
|
## 'channel name - reverse'
|
||||||
|
## 'published'
|
||||||
|
## 'published - reverse'
|
||||||
|
##
|
||||||
|
## Default: published
|
||||||
|
##
|
||||||
|
#sort: published
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Miscellaneous
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
##
|
||||||
|
## Proxy videos through instance by default.
|
||||||
|
##
|
||||||
|
## Warning: As most users won't change this setting in their
|
||||||
|
## preferences, defaulting to true will significantly
|
||||||
|
## increase the instance's network usage, so make sure that
|
||||||
|
## your server's connection can handle it.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#local: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Show the connected user's nick at the top right.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: true
|
||||||
|
##
|
||||||
|
#show_nick: true
|
||||||
|
|
||||||
|
##
|
||||||
|
## Automatically redirect to a random instance when the user uses
|
||||||
|
## any "switch invidious instance" link (For videos, it's the plane
|
||||||
|
## icon, next to "watch on youtube" and "listen"). When set to false,
|
||||||
|
## the user is sent to https://redirect.invidious.io instead, where
|
||||||
|
## they can manually select an instance.
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#automatic_instance_redirect: false
|
||||||
|
|
||||||
|
##
|
||||||
|
## Show the entire video description by default (when set to 'false',
|
||||||
|
## only the first few lines of the description are shown and a
|
||||||
|
## "show more" button allows to expand it).
|
||||||
|
##
|
||||||
|
## Accepted values: true, false
|
||||||
|
## Default: false
|
||||||
|
##
|
||||||
|
#extend_desc: false
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
video_threads: 0
|
|
||||||
crawl_threads: 0
|
|
||||||
channel_threads: 1
|
|
||||||
feed_threads: 1
|
|
||||||
db:
|
|
||||||
user: kemal
|
|
||||||
password: kemal
|
|
||||||
host: localhost
|
|
||||||
port: 5432
|
|
||||||
dbname: invidious
|
|
||||||
full_refresh: false
|
|
||||||
https_only: false
|
|
||||||
7
config/migrate-scripts/migrate-db-17cf077.sh
Executable file
7
config/migrate-scripts/migrate-db-17cf077.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channels ADD COLUMN subscribed bool;"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "UPDATE channels SET subscribed = false;"
|
||||||
10
config/migrate-scripts/migrate-db-1c8075c.sh
Executable file
10
config/migrate-scripts/migrate-db-1c8075c.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channel_videos DROP COLUMN live_now CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channel_videos DROP COLUMN premiere_timestamp CASCADE"
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channel_videos ADD COLUMN live_now bool"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz"
|
||||||
22
config/migrate-scripts/migrate-db-1eca969.sh
Executable file
22
config/migrate-scripts/migrate-db-1eca969.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN title CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN views CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN likes CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN dislikes CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN wilson_score CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN published CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN description CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN language CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN author CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN ucid CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN allowed_regions CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN is_family_friendly CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN genre CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN genre_url CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN license CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN sub_count_text CASCADE"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE videos DROP COLUMN author_thumbnail CASCADE"
|
||||||
7
config/migrate-scripts/migrate-db-30e6d29.sh
Executable file
7
config/migrate-scripts/migrate-db-30e6d29.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channels ADD COLUMN deleted bool;"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "UPDATE channels SET deleted = false;"
|
||||||
8
config/migrate-scripts/migrate-db-3646395.sh
Executable file
8
config/migrate-scripts/migrate-db-3646395.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" < config/sql/session_ids.sql
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "INSERT INTO session_ids (SELECT unnest(id), email, CURRENT_TIMESTAMP FROM users) ON CONFLICT (id) DO NOTHING"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE users DROP COLUMN id"
|
||||||
6
config/migrate-scripts/migrate-db-3bcb98e.sh
Executable file
6
config/migrate-scripts/migrate-db-3bcb98e.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" < config/sql/annotations.sql
|
||||||
6
config/migrate-scripts/migrate-db-52cb239.sh
Executable file
6
config/migrate-scripts/migrate-db-52cb239.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channel_videos ADD COLUMN views bigint;"
|
||||||
7
config/migrate-scripts/migrate-db-6e51189.sh
Executable file
7
config/migrate-scripts/migrate-db-6e51189.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channel_videos ADD COLUMN live_now bool;"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "UPDATE channel_videos SET live_now = false;"
|
||||||
6
config/migrate-scripts/migrate-db-701b5ea.sh
Executable file
6
config/migrate-scripts/migrate-db-701b5ea.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE users ADD COLUMN feed_needs_update boolean"
|
||||||
6
config/migrate-scripts/migrate-db-88b7097.sh
Executable file
6
config/migrate-scripts/migrate-db-88b7097.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz;"
|
||||||
8
config/migrate-scripts/migrate-db-8e884fe.sh
Executable file
8
config/migrate-scripts/migrate-db-8e884fe.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
[ -z "$POSTGRES_USER" ] && POSTGRES_USER=kemal
|
||||||
|
[ -z "$POSTGRES_DB" ] && POSTGRES_DB=invidious
|
||||||
|
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channels DROP COLUMN subscribed"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "ALTER TABLE channels ADD COLUMN subscribed timestamptz"
|
||||||
|
psql "$POSTGRES_DB" "$POSTGRES_USER" -c "UPDATE channels SET subscribed = '2019-01-01 00:00:00+00'"
|
||||||
12
config/sql/annotations.sql
Normal file
12
config/sql/annotations.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
-- Table: public.annotations
|
||||||
|
|
||||||
|
-- DROP TABLE public.annotations;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.annotations
|
||||||
|
(
|
||||||
|
id text NOT NULL,
|
||||||
|
annotations xml,
|
||||||
|
CONSTRAINT annotations_id_key UNIQUE (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
GRANT ALL ON TABLE public.annotations TO current_user;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
-- DROP TABLE public.channel_videos;
|
-- DROP TABLE public.channel_videos;
|
||||||
|
|
||||||
CREATE TABLE public.channel_videos
|
CREATE TABLE IF NOT EXISTS public.channel_videos
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
title text,
|
title text,
|
||||||
@@ -11,26 +11,20 @@ CREATE TABLE public.channel_videos
|
|||||||
ucid text,
|
ucid text,
|
||||||
author text,
|
author text,
|
||||||
length_seconds integer,
|
length_seconds integer,
|
||||||
|
live_now boolean,
|
||||||
|
premiere_timestamp timestamp with time zone,
|
||||||
|
views bigint,
|
||||||
CONSTRAINT channel_videos_id_key UNIQUE (id)
|
CONSTRAINT channel_videos_id_key UNIQUE (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
GRANT ALL ON TABLE public.channel_videos TO kemal;
|
GRANT ALL ON TABLE public.channel_videos TO current_user;
|
||||||
|
|
||||||
-- Index: public.channel_videos_published_idx
|
|
||||||
|
|
||||||
-- DROP INDEX public.channel_videos_published_idx;
|
|
||||||
|
|
||||||
CREATE INDEX channel_videos_published_idx
|
|
||||||
ON public.channel_videos
|
|
||||||
USING btree
|
|
||||||
(published);
|
|
||||||
|
|
||||||
-- Index: public.channel_videos_ucid_idx
|
-- Index: public.channel_videos_ucid_idx
|
||||||
|
|
||||||
-- DROP INDEX public.channel_videos_ucid_idx;
|
-- DROP INDEX public.channel_videos_ucid_idx;
|
||||||
|
|
||||||
CREATE INDEX channel_videos_ucid_idx
|
CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx
|
||||||
ON public.channel_videos
|
ON public.channel_videos
|
||||||
USING hash
|
USING btree
|
||||||
(ucid COLLATE pg_catalog."default");
|
(ucid COLLATE pg_catalog."default");
|
||||||
|
|
||||||
|
|||||||
@@ -2,21 +2,23 @@
|
|||||||
|
|
||||||
-- DROP TABLE public.channels;
|
-- DROP TABLE public.channels;
|
||||||
|
|
||||||
CREATE TABLE public.channels
|
CREATE TABLE IF NOT EXISTS public.channels
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
author text,
|
author text,
|
||||||
updated timestamp with time zone,
|
updated timestamp with time zone,
|
||||||
|
deleted boolean,
|
||||||
|
subscribed timestamp with time zone,
|
||||||
CONSTRAINT channels_id_key UNIQUE (id)
|
CONSTRAINT channels_id_key UNIQUE (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
GRANT ALL ON TABLE public.channels TO kemal;
|
GRANT ALL ON TABLE public.channels TO current_user;
|
||||||
|
|
||||||
-- Index: public.channels_id_idx
|
-- Index: public.channels_id_idx
|
||||||
|
|
||||||
-- DROP INDEX public.channels_id_idx;
|
-- DROP INDEX public.channels_id_idx;
|
||||||
|
|
||||||
CREATE INDEX channels_id_idx
|
CREATE INDEX IF NOT EXISTS channels_id_idx
|
||||||
ON public.channels
|
ON public.channels
|
||||||
USING btree
|
USING btree
|
||||||
(id COLLATE pg_catalog."default");
|
(id COLLATE pg_catalog."default");
|
||||||
|
|||||||
@@ -2,13 +2,21 @@
|
|||||||
|
|
||||||
-- DROP TABLE public.nonces;
|
-- DROP TABLE public.nonces;
|
||||||
|
|
||||||
CREATE TABLE public.nonces
|
CREATE TABLE IF NOT EXISTS public.nonces
|
||||||
(
|
(
|
||||||
nonce text,
|
nonce text,
|
||||||
expire timestamp with time zone
|
expire timestamp with time zone,
|
||||||
)
|
CONSTRAINT nonces_id_key UNIQUE (nonce)
|
||||||
WITH (
|
|
||||||
OIDS=FALSE
|
|
||||||
);
|
);
|
||||||
|
|
||||||
GRANT ALL ON TABLE public.nonces TO kemal;
|
GRANT ALL ON TABLE public.nonces TO current_user;
|
||||||
|
|
||||||
|
-- Index: public.nonces_nonce_idx
|
||||||
|
|
||||||
|
-- DROP INDEX public.nonces_nonce_idx;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS nonces_nonce_idx
|
||||||
|
ON public.nonces
|
||||||
|
USING btree
|
||||||
|
(nonce COLLATE pg_catalog."default");
|
||||||
|
|
||||||
|
|||||||
19
config/sql/playlist_videos.sql
Normal file
19
config/sql/playlist_videos.sql
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
-- Table: public.playlist_videos
|
||||||
|
|
||||||
|
-- DROP TABLE public.playlist_videos;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.playlist_videos
|
||||||
|
(
|
||||||
|
title text,
|
||||||
|
id text,
|
||||||
|
author text,
|
||||||
|
ucid text,
|
||||||
|
length_seconds integer,
|
||||||
|
published timestamptz,
|
||||||
|
plid text references playlists(id),
|
||||||
|
index int8,
|
||||||
|
live_now boolean,
|
||||||
|
PRIMARY KEY (index,plid)
|
||||||
|
);
|
||||||
|
|
||||||
|
GRANT ALL ON TABLE public.playlist_videos TO current_user;
|
||||||
29
config/sql/playlists.sql
Normal file
29
config/sql/playlists.sql
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-- Type: public.privacy
|
||||||
|
|
||||||
|
-- DROP TYPE public.privacy;
|
||||||
|
|
||||||
|
CREATE TYPE public.privacy AS ENUM
|
||||||
|
(
|
||||||
|
'Public',
|
||||||
|
'Unlisted',
|
||||||
|
'Private'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table: public.playlists
|
||||||
|
|
||||||
|
-- DROP TABLE public.playlists;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.playlists
|
||||||
|
(
|
||||||
|
title text,
|
||||||
|
id text primary key,
|
||||||
|
author text,
|
||||||
|
description text,
|
||||||
|
video_count integer,
|
||||||
|
created timestamptz,
|
||||||
|
updated timestamptz,
|
||||||
|
privacy privacy,
|
||||||
|
index int8[]
|
||||||
|
);
|
||||||
|
|
||||||
|
GRANT ALL ON public.playlists TO current_user;
|
||||||
23
config/sql/session_ids.sql
Normal file
23
config/sql/session_ids.sql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-- Table: public.session_ids
|
||||||
|
|
||||||
|
-- DROP TABLE public.session_ids;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public.session_ids
|
||||||
|
(
|
||||||
|
id text NOT NULL,
|
||||||
|
email text,
|
||||||
|
issued timestamp with time zone,
|
||||||
|
CONSTRAINT session_ids_pkey PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
GRANT ALL ON TABLE public.session_ids TO current_user;
|
||||||
|
|
||||||
|
-- Index: public.session_ids_id_idx
|
||||||
|
|
||||||
|
-- DROP INDEX public.session_ids_id_idx;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS session_ids_id_idx
|
||||||
|
ON public.session_ids
|
||||||
|
USING btree
|
||||||
|
(id COLLATE pg_catalog."default");
|
||||||
|
|
||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
-- DROP TABLE public.users;
|
-- DROP TABLE public.users;
|
||||||
|
|
||||||
CREATE TABLE public.users
|
CREATE TABLE IF NOT EXISTS public.users
|
||||||
(
|
(
|
||||||
id text[] NOT NULL,
|
|
||||||
updated timestamp with time zone,
|
updated timestamp with time zone,
|
||||||
notifications text[],
|
notifications text[],
|
||||||
subscriptions text[],
|
subscriptions text[],
|
||||||
@@ -13,16 +12,17 @@ CREATE TABLE public.users
|
|||||||
password text,
|
password text,
|
||||||
token text,
|
token text,
|
||||||
watched text[],
|
watched text[],
|
||||||
|
feed_needs_update boolean,
|
||||||
CONSTRAINT users_email_key UNIQUE (email)
|
CONSTRAINT users_email_key UNIQUE (email)
|
||||||
);
|
);
|
||||||
|
|
||||||
GRANT ALL ON TABLE public.users TO kemal;
|
GRANT ALL ON TABLE public.users TO current_user;
|
||||||
|
|
||||||
-- Index: public.email_unique_idx
|
-- Index: public.email_unique_idx
|
||||||
|
|
||||||
-- DROP INDEX public.email_unique_idx;
|
-- DROP INDEX public.email_unique_idx;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX email_unique_idx
|
CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx
|
||||||
ON public.users
|
ON public.users
|
||||||
USING btree
|
USING btree
|
||||||
(lower(email) COLLATE pg_catalog."default");
|
(lower(email) COLLATE pg_catalog."default");
|
||||||
|
|||||||
@@ -2,38 +2,21 @@
|
|||||||
|
|
||||||
-- DROP TABLE public.videos;
|
-- DROP TABLE public.videos;
|
||||||
|
|
||||||
CREATE TABLE public.videos
|
CREATE UNLOGGED TABLE IF NOT EXISTS public.videos
|
||||||
(
|
(
|
||||||
id text NOT NULL,
|
id text NOT NULL,
|
||||||
info text,
|
info text,
|
||||||
updated timestamp with time zone,
|
updated timestamp with time zone,
|
||||||
title text,
|
|
||||||
views bigint,
|
|
||||||
likes integer,
|
|
||||||
dislikes integer,
|
|
||||||
wilson_score double precision,
|
|
||||||
published timestamp with time zone,
|
|
||||||
description text,
|
|
||||||
language text,
|
|
||||||
author text,
|
|
||||||
ucid text,
|
|
||||||
allowed_regions text[],
|
|
||||||
is_family_friendly boolean,
|
|
||||||
genre text,
|
|
||||||
genre_url text,
|
|
||||||
license text,
|
|
||||||
sub_count_text text,
|
|
||||||
author_thumbnail text,
|
|
||||||
CONSTRAINT videos_pkey PRIMARY KEY (id)
|
CONSTRAINT videos_pkey PRIMARY KEY (id)
|
||||||
);
|
);
|
||||||
|
|
||||||
GRANT ALL ON TABLE public.videos TO kemal;
|
GRANT ALL ON TABLE public.videos TO current_user;
|
||||||
|
|
||||||
-- Index: public.id_idx
|
-- Index: public.id_idx
|
||||||
|
|
||||||
-- DROP INDEX public.id_idx;
|
-- DROP INDEX public.id_idx;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX id_idx
|
CREATE UNIQUE INDEX IF NOT EXISTS id_idx
|
||||||
ON public.videos
|
ON public.videos
|
||||||
USING btree
|
USING btree
|
||||||
(id COLLATE pg_catalog."default");
|
(id COLLATE pg_catalog."default");
|
||||||
|
|||||||
@@ -1,21 +1,56 @@
|
|||||||
version: '3'
|
# Warning: This docker-compose file is made for development purposes.
|
||||||
|
# Using it will build an image from the locally cloned repository.
|
||||||
|
#
|
||||||
|
# If you want to use Invidious in production, see the docker-compose.yml file provided
|
||||||
|
# in the installation documentation: https://docs.invidious.io/Installation.md
|
||||||
|
|
||||||
|
version: "3"
|
||||||
services:
|
services:
|
||||||
postgres:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: docker/Dockerfile.postgres
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- postgresdata:/var/lib/postgresql/data
|
|
||||||
invidious:
|
invidious:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "127.0.0.1:3000:3000"
|
||||||
|
environment:
|
||||||
|
# Please read the following file for a comprehensive list of all available
|
||||||
|
# configuration options and their associated syntax:
|
||||||
|
# https://github.com/iv-org/invidious/blob/master/config/config.example.yml
|
||||||
|
INVIDIOUS_CONFIG: |
|
||||||
|
db:
|
||||||
|
dbname: invidious
|
||||||
|
user: kemal
|
||||||
|
password: kemal
|
||||||
|
host: invidious-db
|
||||||
|
port: 5432
|
||||||
|
check_tables: true
|
||||||
|
# external_port:
|
||||||
|
# domain:
|
||||||
|
# https_only: false
|
||||||
|
# statistics_enabled: false
|
||||||
|
healthcheck:
|
||||||
|
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/comments/jNQXAC9IVRw || exit 1
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 2
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- invidious-db
|
||||||
|
|
||||||
|
invidious-db:
|
||||||
|
image: docker.io/library/postgres:14
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- postgresdata:/var/lib/postgresql/data
|
||||||
|
- ./config/sql:/config/sql
|
||||||
|
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: invidious
|
||||||
|
POSTGRES_USER: kemal
|
||||||
|
POSTGRES_PASSWORD: kemal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgresdata:
|
postgresdata:
|
||||||
|
|||||||
@@ -1,15 +1,54 @@
|
|||||||
FROM archlinux/base
|
FROM crystallang/crystal:1.2.2-alpine AS builder
|
||||||
|
RUN apk add --no-cache sqlite-static yaml-static
|
||||||
|
|
||||||
RUN pacman -Sy --noconfirm shards crystal imagemagick librsvg \
|
ARG release
|
||||||
which pkgconf gcc ttf-liberation
|
|
||||||
# base-devel contains many other basic packages, that are normally assumed to already exist on a clean arch system
|
|
||||||
|
|
||||||
ADD . /invidious
|
|
||||||
|
|
||||||
WORKDIR /invidious
|
WORKDIR /invidious
|
||||||
|
COPY ./shard.yml ./shard.yml
|
||||||
|
COPY ./shard.lock ./shard.lock
|
||||||
|
RUN shards install --production
|
||||||
|
|
||||||
RUN sed -i 's/host: localhost/host: postgres/' config/config.yml && \
|
COPY --from=quay.io/invidious/lsquic-compiled /root/liblsquic.a ./lib/lsquic/src/lsquic/ext/liblsquic.a
|
||||||
shards && \
|
|
||||||
crystal build src/invidious.cr
|
|
||||||
|
|
||||||
|
COPY ./src/ ./src/
|
||||||
|
# TODO: .git folder is required for building – this is destructive.
|
||||||
|
# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION.
|
||||||
|
COPY ./.git/ ./.git/
|
||||||
|
|
||||||
|
# Required for fetching player dependencies
|
||||||
|
COPY ./scripts/ ./scripts/
|
||||||
|
COPY ./assets/ ./assets/
|
||||||
|
COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||||
|
|
||||||
|
RUN crystal spec --warnings all \
|
||||||
|
--link-flags "-lxml2 -llzma"
|
||||||
|
|
||||||
|
RUN if [ "${release}" == 1 ] ; then \
|
||||||
|
crystal build ./src/invidious.cr \
|
||||||
|
--release \
|
||||||
|
--static --warnings all \
|
||||||
|
--link-flags "-lxml2 -llzma"; \
|
||||||
|
else \
|
||||||
|
crystal build ./src/invidious.cr \
|
||||||
|
--static --warnings all \
|
||||||
|
--link-flags "-lxml2 -llzma"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk add --no-cache librsvg ttf-opensans
|
||||||
|
WORKDIR /invidious
|
||||||
|
RUN addgroup -g 1000 -S invidious && \
|
||||||
|
adduser -u 1000 -S invidious -G invidious
|
||||||
|
COPY --chown=invidious ./config/config.* ./config/
|
||||||
|
RUN mv -n config/config.example.yml config/config.yml
|
||||||
|
RUN sed -i 's/host: \(127.0.0.1\|localhost\)/host: postgres/' config/config.yml
|
||||||
|
COPY ./config/sql/ ./config/sql/
|
||||||
|
COPY ./locales/ ./locales/
|
||||||
|
COPY --from=builder /invidious/assets ./assets/
|
||||||
|
COPY --from=builder /invidious/invidious .
|
||||||
|
RUN chmod o+rX -R ./assets ./config ./locales
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
USER invidious
|
||||||
CMD [ "/invidious/invidious" ]
|
CMD [ "/invidious/invidious" ]
|
||||||
|
|||||||
53
docker/Dockerfile.arm64
Normal file
53
docker/Dockerfile.arm64
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
FROM alpine:3.15 AS builder
|
||||||
|
RUN apk add --no-cache 'crystal=1.2.2-r0' shards sqlite-static yaml-static yaml-dev libxml2-dev zlib-static openssl-libs-static openssl-dev musl-dev
|
||||||
|
|
||||||
|
ARG release
|
||||||
|
|
||||||
|
WORKDIR /invidious
|
||||||
|
COPY ./shard.yml ./shard.yml
|
||||||
|
COPY ./shard.lock ./shard.lock
|
||||||
|
RUN shards install --production
|
||||||
|
|
||||||
|
COPY --from=quay.io/invidious/lsquic-compiled /root/liblsquic.a ./lib/lsquic/src/lsquic/ext/liblsquic.a
|
||||||
|
|
||||||
|
COPY ./src/ ./src/
|
||||||
|
# TODO: .git folder is required for building – this is destructive.
|
||||||
|
# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION.
|
||||||
|
COPY ./.git/ ./.git/
|
||||||
|
|
||||||
|
# Required for fetching player dependencies
|
||||||
|
COPY ./scripts/ ./scripts/
|
||||||
|
COPY ./assets/ ./assets/
|
||||||
|
COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||||
|
|
||||||
|
RUN crystal spec --warnings all \
|
||||||
|
--link-flags "-lxml2 -llzma"
|
||||||
|
|
||||||
|
RUN if [ ${release} == 1 ] ; then \
|
||||||
|
crystal build ./src/invidious.cr \
|
||||||
|
--release \
|
||||||
|
--static --warnings all \
|
||||||
|
--link-flags "-lxml2 -llzma"; \
|
||||||
|
else \
|
||||||
|
crystal build ./src/invidious.cr \
|
||||||
|
--static --warnings all \
|
||||||
|
--link-flags "-lxml2 -llzma"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
FROM alpine:3.15
|
||||||
|
RUN apk add --no-cache librsvg ttf-opensans
|
||||||
|
WORKDIR /invidious
|
||||||
|
RUN addgroup -g 1000 -S invidious && \
|
||||||
|
adduser -u 1000 -S invidious -G invidious
|
||||||
|
COPY --chown=invidious ./config/config.* ./config/
|
||||||
|
RUN mv -n config/config.example.yml config/config.yml
|
||||||
|
RUN sed -i 's/host: \(127.0.0.1\|localhost\)/host: postgres/' config/config.yml
|
||||||
|
COPY ./config/sql/ ./config/sql/
|
||||||
|
COPY ./locales/ ./locales/
|
||||||
|
COPY --from=builder /invidious/assets ./assets/
|
||||||
|
COPY --from=builder /invidious/invidious .
|
||||||
|
RUN chmod o+rX -R ./assets ./config ./locales
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
USER invidious
|
||||||
|
CMD [ "/invidious/invidious" ]
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
FROM postgres:10
|
|
||||||
|
|
||||||
ENV POSTGRES_USER postgres
|
|
||||||
|
|
||||||
ADD ./setup.sh /setup.sh
|
|
||||||
ADD ./config/sql /config/sql
|
|
||||||
ADD ./docker/entrypoint.postgres.sh /entrypoint.sh
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
|
||||||
CMD [ "postgres" ]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
CMD="$@"
|
|
||||||
if [ ! -f /var/lib/postgresql/data/setupFinished ]; then
|
|
||||||
echo "### first run - setting up invidious database"
|
|
||||||
/usr/local/bin/docker-entrypoint.sh postgres &
|
|
||||||
sleep 10
|
|
||||||
until runuser -l postgres -c 'pg_isready' 2>/dev/null; do
|
|
||||||
>&2 echo "### Postgres is unavailable - waiting"
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
>&2 echo "### importing table schemas"
|
|
||||||
su postgres -c "/setup.sh" && touch /var/lib/postgresql/data/setupFinished
|
|
||||||
echo "### invidious database setup finished"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "running postgres /usr/local/bin/docker-entrypoint.sh $CMD"
|
|
||||||
exec /usr/local/bin/docker-entrypoint.sh $CMD
|
|
||||||
12
docker/init-invidious-db.sh
Executable file
12
docker/init-invidious-db.sh
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -eou pipefail
|
||||||
|
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/channels.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/videos.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/channel_videos.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/users.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/session_ids.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/nonces.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/annotations.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/playlists.sql
|
||||||
|
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" < config/sql/playlist_videos.sql
|
||||||
19
invidious.service
Normal file
19
invidious.service
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Invidious (An alternative YouTube front-end)
|
||||||
|
After=syslog.target
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
RestartSec=2s
|
||||||
|
Type=simple
|
||||||
|
|
||||||
|
User=invidious
|
||||||
|
Group=invidious
|
||||||
|
|
||||||
|
WorkingDirectory=/home/invidious/invidious
|
||||||
|
ExecStart=/home/invidious/invidious/invidious -o invidious.log
|
||||||
|
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
1
kubernetes/.gitignore
vendored
Normal file
1
kubernetes/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/charts/*.tgz
|
||||||
6
kubernetes/Chart.lock
Normal file
6
kubernetes/Chart.lock
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
dependencies:
|
||||||
|
- name: postgresql
|
||||||
|
repository: https://kubernetes-charts.storage.googleapis.com/
|
||||||
|
version: 8.3.0
|
||||||
|
digest: sha256:1feec3c396cbf27573dc201831ccd3376a4a6b58b2e7618ce30a89b8f5d707fd
|
||||||
|
generated: "2020-02-07T13:39:38.624846+01:00"
|
||||||
22
kubernetes/Chart.yaml
Normal file
22
kubernetes/Chart.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: invidious
|
||||||
|
description: Invidious is an alternative front-end to YouTube
|
||||||
|
version: 1.1.0
|
||||||
|
appVersion: 0.20.1
|
||||||
|
keywords:
|
||||||
|
- youtube
|
||||||
|
- proxy
|
||||||
|
- video
|
||||||
|
- privacy
|
||||||
|
home: https://invidio.us/
|
||||||
|
icon: https://raw.githubusercontent.com/iv-org/invidious/05988c1c49851b7d0094fca16aeaf6382a7f64ab/assets/favicon-32x32.png
|
||||||
|
sources:
|
||||||
|
- https://github.com/iv-org/invidious
|
||||||
|
maintainers:
|
||||||
|
- name: Leon Klingele
|
||||||
|
email: mail@leonklingele.de
|
||||||
|
dependencies:
|
||||||
|
- name: postgresql
|
||||||
|
version: ~8.3.0
|
||||||
|
repository: "https://kubernetes-charts.storage.googleapis.com/"
|
||||||
|
engine: gotpl
|
||||||
41
kubernetes/README.md
Normal file
41
kubernetes/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Invidious Helm chart
|
||||||
|
|
||||||
|
Easily deploy Invidious to Kubernetes.
|
||||||
|
|
||||||
|
## Installing Helm chart
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Build Helm dependencies
|
||||||
|
$ helm dep build
|
||||||
|
|
||||||
|
# Add PostgreSQL init scripts
|
||||||
|
$ kubectl create configmap invidious-postgresql-init \
|
||||||
|
--from-file=../config/sql/channels.sql \
|
||||||
|
--from-file=../config/sql/videos.sql \
|
||||||
|
--from-file=../config/sql/channel_videos.sql \
|
||||||
|
--from-file=../config/sql/users.sql \
|
||||||
|
--from-file=../config/sql/session_ids.sql \
|
||||||
|
--from-file=../config/sql/nonces.sql \
|
||||||
|
--from-file=../config/sql/annotations.sql \
|
||||||
|
--from-file=../config/sql/playlists.sql \
|
||||||
|
--from-file=../config/sql/playlist_videos.sql
|
||||||
|
|
||||||
|
# Install Helm app to your Kubernetes cluster
|
||||||
|
$ helm install invidious ./
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrading
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Upgrading is easy, too!
|
||||||
|
$ helm upgrade invidious ./
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstall
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Get rid of everything (except database)
|
||||||
|
$ helm delete invidious
|
||||||
|
|
||||||
|
# To also delete the database, remove all invidious-postgresql PVCs
|
||||||
|
```
|
||||||
16
kubernetes/templates/_helpers.tpl
Normal file
16
kubernetes/templates/_helpers.tpl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{{/* vim: set filetype=mustache: */}}
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "invidious.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
*/}}
|
||||||
|
{{- define "invidious.fullname" -}}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
11
kubernetes/templates/configmap.yaml
Normal file
11
kubernetes/templates/configmap.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ template "invidious.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ template "invidious.name" . }}
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
data:
|
||||||
|
INVIDIOUS_CONFIG: |
|
||||||
|
{{ toYaml .Values.config | indent 4 }}
|
||||||
61
kubernetes/templates/deployment.yaml
Normal file
61
kubernetes/templates/deployment.yaml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ template "invidious.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ template "invidious.name" . }}
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ template "invidious.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ template "invidious.name" . }}
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsUser: {{ .Values.securityContext.runAsUser }}
|
||||||
|
runAsGroup: {{ .Values.securityContext.runAsGroup }}
|
||||||
|
fsGroup: {{ .Values.securityContext.fsGroup }}
|
||||||
|
initContainers:
|
||||||
|
- name: wait-for-postgresql
|
||||||
|
image: postgres
|
||||||
|
args:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- until pg_isready -h {{ .Values.config.db.host }} -p {{ .Values.config.db.port }} -U {{ .Values.config.db.user }}; do echo waiting for database; sleep 2; done;
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
env:
|
||||||
|
- name: INVIDIOUS_CONFIG
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
key: INVIDIOUS_CONFIG
|
||||||
|
name: {{ template "invidious.fullname" . }}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: {{ .Values.securityContext.allowPrivilegeEscalation }}
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
resources:
|
||||||
|
{{ toYaml .Values.resources | indent 10 }}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 3000
|
||||||
|
path: /
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 3000
|
||||||
|
path: /
|
||||||
|
initialDelaySeconds: 15
|
||||||
|
restartPolicy: Always
|
||||||
18
kubernetes/templates/hpa.yaml
Normal file
18
kubernetes/templates/hpa.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v1
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ template "invidious.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ template "invidious.name" . }}
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ template "invidious.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
targetCPUUtilizationPercentage: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
20
kubernetes/templates/service.yaml
Normal file
20
kubernetes/templates/service.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ template "invidious.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ template "invidious.name" . }}
|
||||||
|
chart: {{ .Chart.Name }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: {{ .Values.service.port }}
|
||||||
|
targetPort: 3000
|
||||||
|
selector:
|
||||||
|
app: {{ template "invidious.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
{{- if .Values.service.loadBalancerIP }}
|
||||||
|
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
|
||||||
|
{{- end }}
|
||||||
56
kubernetes/values.yaml
Normal file
56
kubernetes/values.yaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: invidious
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: quay.io/invidious/invidious
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: Always
|
||||||
|
|
||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 16
|
||||||
|
targetCPUUtilizationPercentage: 50
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: clusterIP
|
||||||
|
port: 3000
|
||||||
|
#loadBalancerIP:
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
#requests:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 64Mi
|
||||||
|
#limits:
|
||||||
|
# cpu: 800m
|
||||||
|
# memory: 512Mi
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
|
fsGroup: 1000
|
||||||
|
|
||||||
|
# See https://github.com/helm/charts/tree/master/stable/postgresql
|
||||||
|
postgresql:
|
||||||
|
postgresqlUsername: kemal
|
||||||
|
postgresqlPassword: kemal
|
||||||
|
postgresqlDatabase: invidious
|
||||||
|
initdbUsername: kemal
|
||||||
|
initdbPassword: kemal
|
||||||
|
initdbScriptsConfigMap: invidious-postgresql-init
|
||||||
|
|
||||||
|
# Adapted from ../config/config.yml
|
||||||
|
config:
|
||||||
|
channel_threads: 1
|
||||||
|
feed_threads: 1
|
||||||
|
db:
|
||||||
|
user: kemal
|
||||||
|
password: kemal
|
||||||
|
host: invidious-postgresql
|
||||||
|
port: 5432
|
||||||
|
dbname: invidious
|
||||||
|
full_refresh: false
|
||||||
|
https_only: false
|
||||||
|
domain:
|
||||||
702
locales/ar.json
702
locales/ar.json
@@ -1,273 +1,433 @@
|
|||||||
{
|
{
|
||||||
"`x` subscribers": "`x` المشتركين",
|
"LIVE": "مُباشِر",
|
||||||
"`x` videos": "`x` الفيديوهات",
|
"Shared `x` ago": "تمَّ رفع المقطع المرئيّ مُنذ `x`",
|
||||||
"LIVE": "مباشر",
|
"Unsubscribe": "إلغاء الاشتراك",
|
||||||
"Shared `x` ago": "تم رفع الفيديو منذ `x`",
|
"Subscribe": "الإشتراك",
|
||||||
"Unsubscribe": "إلغاء الإشتراك",
|
"View channel on YouTube": "زيارة القناة على موقع يوتيوب",
|
||||||
"Subscribe": "إشتراك",
|
"View playlist on YouTube": "عرض قائمة التشغيل على اليوتيوب",
|
||||||
"Login to subscribe to `x`": "سجل الدخول للإشتراك فى `x`",
|
"newest": "الأجدد",
|
||||||
"View channel on YouTube": "زيارة القناة على موقع يوتيوب",
|
"oldest": "الأقدم",
|
||||||
"newest": "الأجدد",
|
"popular": "الأكثر شعبية",
|
||||||
"oldest": "الأقدم",
|
"last": "الأخيرة",
|
||||||
"popular": "الاكثر شعبية",
|
"Next page": "الصفحة التالية",
|
||||||
"Preview page": "معاينة الصفحة",
|
"Previous page": "الصفحة السابقة",
|
||||||
"Next page": "الصفحة الثانية",
|
"Clear watch history?": "هل تريد محو سجل المشاهدة؟",
|
||||||
"Clear watch history?": "مسح السجل ؟",
|
"New password": "كلمة مرور جديدة",
|
||||||
"Yes": "نعم",
|
"New passwords must match": "يَجبُ أن تكون كلمتا المرور متطابقتين",
|
||||||
"No": "لا",
|
"Cannot change password for Google accounts": "لا يُمكن تغيير كلمة المرور لِحسابات جوجل",
|
||||||
"Import and Export Data": "استخراج و إضافة البيانات",
|
"Authorize token?": "رمز التفويض؟",
|
||||||
"Import": "إضافة",
|
"Authorize token for `x`?": "السماح بالرمز المميز ل 'x'؟",
|
||||||
"Import Invidious data": "إضافة بيانات Invidious",
|
"Yes": "نعم",
|
||||||
"Import YouTube subscriptions": "إضافةالإشتراكات من موقع يوتيوب",
|
"No": "لا",
|
||||||
"Import FreeTube subscriptions (.db)": "إضافةالمشتركين من FreeTube (.db)",
|
"Import and Export Data": "اِستيراد البيانات وتصديرها",
|
||||||
"Import NewPipe subscriptions (.json)": "إضافة المشتركين من NewPipe (.json)",
|
"Import": "استيراد",
|
||||||
"Import NewPipe data (.zip)": "إضافة بيانات NewPipe (.zip)",
|
"Import Invidious data": "استيراد بيانات انفيدياس",
|
||||||
"Export": "استخراج",
|
"Import YouTube subscriptions": "استيراد اشتراكات يوتيوب",
|
||||||
"Export subscriptions as OPML": "استخراج المشتركين كـ OPML",
|
"Import FreeTube subscriptions (.db)": "استيراد اشتراكات فريتيوب (.db)",
|
||||||
"Export subscriptions as OPML (for NewPipe & FreeTube)": "استخراج المشتركين كـ OPML (لـ NewPipe و FreeTube)",
|
"Import NewPipe subscriptions (.json)": "استيراد اشتراكات نيو بايب (.json)",
|
||||||
"Export data as JSON": "استخراج البيانات كـ JSON",
|
"Import NewPipe data (.zip)": "استيراد بيانات نيو بايب (.zip)",
|
||||||
"Delete account?": "حذف الحساب ؟",
|
"Export": "تصدير",
|
||||||
"History": "السجل",
|
"Export subscriptions as OPML": "تصدير الاشتراكات كـOPML",
|
||||||
"Previous page": "الصفحة السابقة",
|
"Export subscriptions as OPML (for NewPipe & FreeTube)": "تصدير الاشتراكات كـOPML (لِنيو بايب و فريتيوب)",
|
||||||
"An alternative front-end to YouTube": "البديل الكامل لموقع يوتيوب",
|
"Export data as JSON": "تصدير البيانات بتنسيق JSON",
|
||||||
"JavaScript license information": "معلومات ترخيص JavaScript",
|
"Delete account?": "حذف الحساب؟",
|
||||||
"source": "المصدر",
|
"History": "السِّجل",
|
||||||
"Login": "تسجيل الدخول",
|
"An alternative front-end to YouTube": "واجهة أمامية بديلة لموقع يوتيوب",
|
||||||
"Login/Register": "تسجيل الدخول\\إنشاء حساب",
|
"JavaScript license information": "معلومات ترخيص جافا سكربت",
|
||||||
"Login to Google": "تسجيل الدخول بإستخدام جوجل",
|
"source": "المصدر",
|
||||||
"User ID:": "إسم المستخدم:",
|
"Log in": "تسجيل الدخول",
|
||||||
"Password:": "الرقم السرى:",
|
"Log in/register": "تسجيل الدخول \\ إنشاء حساب",
|
||||||
"Time (h:mm:ss):": "(يجب ان يكتب مثل هذا التنسيق) الوقت (h(ساعات):mm(دقائق):ss(ثوانى)):",
|
"Log in with Google": "تسجيل الدخول باستخدام جوجل",
|
||||||
"Text CAPTCHA": "CAPTCHA كلامية",
|
"User ID": "مُعرِّف المُستخدم",
|
||||||
"Image CAPTCHA": "CAPTCHA صورية",
|
"Password": "كلمة المرور",
|
||||||
"Sign In": "تسجيل الدخول",
|
"Time (h:mm:ss):": "الوقت (h:mm:ss):",
|
||||||
"Register": "انشاء الحساب",
|
"Text CAPTCHA": "نص الكابتشا",
|
||||||
"Email:": "الإيميل:",
|
"Image CAPTCHA": "صورة الكابتشا",
|
||||||
"Google verification code:": "رمز تحقق جوجل:",
|
"Sign In": "تسجيل الدخول",
|
||||||
"Preferences": "التفضيلات",
|
"Register": "التسجيل",
|
||||||
"Player preferences": "التفضيلات المشغل",
|
"E-mail": "البريد الإلكتروني",
|
||||||
"Always loop: ": "كرر الفيديو دائما: ",
|
"Google verification code": "رمز تحقق جوجل",
|
||||||
"Autoplay: ": "تشغيل تلقائى: ",
|
"Preferences": "الإعدادات",
|
||||||
"Autoplay next video: ": "شغل الفيديو التالى تلقائى: ",
|
"preferences_category_player": "إعدادات المُشغِّل",
|
||||||
"Listen by default: ": "تشغيل النسخة السمعية تلقائى: ",
|
"preferences_video_loop_label": "كرر المقطع المرئيّ دائما: ",
|
||||||
"Default speed: ": "السرعة الإفتراضية: ",
|
"preferences_autoplay_label": "تشغيل تلقائي: ",
|
||||||
"Preferred video quality: ": "الجودة المفضلة للفيديوهات: ",
|
"preferences_continue_label": "شغل المقطع التالي تلقائيًا: ",
|
||||||
"Player volume: ": "صوت المشغل: ",
|
"preferences_continue_autoplay_label": "شغل المقطع التالي تلقائيًا: ",
|
||||||
"Default comments: ": "إضهار التعليقات الإفتراضية لـ: ",
|
"preferences_listen_label": "تشغيل النسخة السمعية تلقائيًا: ",
|
||||||
"youtube": "يوتيوب",
|
"preferences_local_label": "بروكسي المقاطع المرئيّة؟ ",
|
||||||
"reddit": "Reddit",
|
"preferences_speed_label": "السرعة الافتراضية: ",
|
||||||
"Default captions: ": "الترجمات الإفتراضية: ",
|
"preferences_quality_label": "الجودة المفضلة للمقاطع: ",
|
||||||
"Fallback captions: ": "الترجمات المصاحبة: ",
|
"preferences_volume_label": "صوت المشغل: ",
|
||||||
"Show related videos? ": "عرض مقاطع الفيديو ذات الصلة؟",
|
"preferences_comments_label": "التعليقات الافتراضية: ",
|
||||||
"Visual preferences": "التفضيلات المرئية",
|
"youtube": "يوتيوب",
|
||||||
"Dark mode: ": "الوضع الليلى: ",
|
"reddit": "ريديت",
|
||||||
"Thin mode: ": "الوضع الخفيف: ",
|
"preferences_captions_label": "التسميات التوضيحية الإفتراضية: ",
|
||||||
"Subscription preferences": "تفضيلات الإشتراك",
|
"Fallback captions: ": "التسميات التوضيحية الاحتياطيَّة: ",
|
||||||
"Redirect homepage to feed: ": "إعادة التوجية من الصفحة الرئيسية لصفحة المشتركين (لرؤية اخر فيديوهات المشتركين): ",
|
"preferences_related_videos_label": "اعرض الفيديوهات ذات الصلة: ",
|
||||||
"Number of videos shown in feed: ": "عدد الفيديوهات التى ستظهر فى صفحة المشتركين: ",
|
"preferences_annotations_label": "اعرض الملاحظات في الفيديو تلقائيا: ",
|
||||||
"Sort videos by: ": "ترتيب الفيديو بـ: ",
|
"preferences_extend_desc_label": "توسيع وصف الفيديو تلقائيا: ",
|
||||||
"published": "احدث فيديو",
|
"preferences_vr_mode_label": "مقاطع فيديو تفاعلية ب درجة 360: ",
|
||||||
"published - reverse": "احدث فيديو - عكسى",
|
"preferences_category_visual": "التفضيلات المرئية",
|
||||||
"alphabetically": "ترتيب ابجدى",
|
"preferences_player_style_label": "شكل مشغل الفيديوهات: ",
|
||||||
"alphabetically - reverse": "ابجدى - عكسى",
|
"Dark mode: ": "الوضع الليلي: ",
|
||||||
"channel name": "بإسم القناة",
|
"preferences_dark_mode_label": "المظهر: ",
|
||||||
"channel name - reverse": "بإسم القناة - عكسى",
|
"dark": "غامق (اسود)",
|
||||||
"Only show latest video from channel: ": "فقط إظهر اخر فيديو من القناة: ",
|
"light": "فاتح (ابيض)",
|
||||||
"Only show latest unwatched video from channel: ": "فقط اظهر اخر فيديو لم يتم رؤيتة من القناة: ",
|
"preferences_thin_mode_label": "الوضع الخفيف: ",
|
||||||
"Only show unwatched: ": "فقط اظهر الذى لم يتم رؤيتة: ",
|
"preferences_category_misc": "تفضيلات متنوعة",
|
||||||
"Only show notifications (if there are any): ": "إظهار الإشعارات فقط (إذا كان هناك أي): ",
|
"preferences_automatic_instance_redirect_label": "إعادة توجيه المثيل التلقائي (إعادة التوجيه إلى redirect.invidious.io): ",
|
||||||
"Data preferences": "إعدادات التفضيلات",
|
"preferences_category_subscription": "تفضيلات الاشتراك",
|
||||||
"Clear watch history": "حذف سجل المشاهدة",
|
"preferences_annotations_subscribed_label": "عرض الملاحظات في الفيديوهات تلقائيا في القنوات المشترك بها فقط: ",
|
||||||
"Import/Export data": "إضافة\\إستخراج البيانات",
|
"Redirect homepage to feed: ": "إعادة التوجية من الصفحة الرئيسية لصفحة المشتركين (لرؤية اخر فيديوهات المشتركين): ",
|
||||||
"Manage subscriptions": "إدارة المشتركين",
|
"preferences_max_results_label": "عدد الفيديوهات التى ستظهر فى صفحة المشتركين: ",
|
||||||
"Watch history": "سجل المشاهدة",
|
"preferences_sort_label": "ترتيب الفيديوهات بـ: ",
|
||||||
"Delete account": "حذف الحساب",
|
"published": "أحدث فيديو",
|
||||||
"Save preferences": "حفظ التفضيلات",
|
"published - reverse": "أحدث فيديو - عكسي",
|
||||||
"Subscription manager": "مدير الإشتراكات",
|
"alphabetically": "ترتيب أبجدي",
|
||||||
"`x` subscriptions": "`x` مشتركين",
|
"alphabetically - reverse": "أبجدي - عكسي",
|
||||||
"Import/Export": "إضافة\\إستخراج",
|
"channel name": "باسم القناة",
|
||||||
"unsubscribe": "إلغاء الإشتراك",
|
"channel name - reverse": "باسم القناة - عكسى",
|
||||||
"Subscriptions": "الإشتراكات",
|
"Only show latest video from channel: ": "فقط أظهر آخر فيديو من القناة: ",
|
||||||
"`x` unseen notifications": "`x` إشعارات لم تشاهدها بعد ",
|
"Only show latest unwatched video from channel: ": "فقط أظهر آخر فيديو لم يتم رؤيته من القناة: ",
|
||||||
"search": "بحث",
|
"preferences_unseen_only_label": "فقط أظهر الذي لم يتم رؤيته: ",
|
||||||
"Sign out": "تسجيل الخروج",
|
"preferences_notifications_only_label": "إظهار الإشعارات فقط (إذا كان هناك أي): ",
|
||||||
"Released under the AGPLv3 by Omar Roth.": "تم الإنشاء تحت AGPLv3 بواسطة عمر روث.",
|
"Enable web notifications": "تفعيل إشعارات المتصفح",
|
||||||
"Source available here.": "الأكواد متوفرة هنا.",
|
"`x` uploaded a video": "`x` رفع فيديو",
|
||||||
"Liberapay: ": "ليبرباى: ",
|
"`x` is live": "`x` في بث مباشر",
|
||||||
"Patreon: ": "باتريون: ",
|
"preferences_category_data": "إعدادات التفضيلات",
|
||||||
"BTC: ": "بيتكوين: ",
|
"Clear watch history": "حذف سجل المشاهدة",
|
||||||
"BCH: ": "بيتكوين كاش: ",
|
"Import/export data": "إضافة\\استخراج البيانات",
|
||||||
"View JavaScript license information.": "مشاهدة معلومات حول تراخيص الجافاسكريبت.",
|
"Change password": "غير كلمة السر",
|
||||||
"Trending": "الشائع",
|
"Manage subscriptions": "إدارة الاشتراكات",
|
||||||
"Watch video on Youtube": "مشاهدة الفيديو على اليوتيوب",
|
"Manage tokens": "إدارة الرموز",
|
||||||
"Genre: ": "النوع: ",
|
"Watch history": "سجل المشاهدة",
|
||||||
"License: ": "التراخيص: ",
|
"Delete account": "حذف الحساب",
|
||||||
"Family friendly? ": "محتوى عائلى? ",
|
"preferences_category_admin": "إعدادات المدير",
|
||||||
"Wilson score: ": "درجة ويلسون: ",
|
"preferences_default_home_label": "الصفحة الرئيسية الافتراضية: ",
|
||||||
"Engagement: ": "نسبة المشاركة (عدد المشاهدات\\عدد الإعجابات): ",
|
"preferences_feed_menu_label": "قائمة التدفقات: ",
|
||||||
"Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ",
|
"preferences_show_nick_label": "إظهار اللقب في الأعلى: ",
|
||||||
"Blacklisted regions: ": "الدول الحظور فيها هذا الفيديو: ",
|
"Top enabled: ": "تفعيل 'الأفضل' ؟ ",
|
||||||
"Shared `x`": "شارك منذ `x`",
|
"CAPTCHA enabled: ": "تفعيل الكابتشا: ",
|
||||||
"Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.",
|
"Login enabled: ": "تفعيل الولوج: ",
|
||||||
"View YouTube comments": "عرض تعليقات اليوتيوب",
|
"Registration enabled: ": "تفعيل التسجيل: ",
|
||||||
"View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع Reddit",
|
"Report statistics: ": "الإبلاغ عن الإحصائيات: ",
|
||||||
"View `x` comments": "عرض `x` تعليقات",
|
"Save preferences": "حفظ الإعدادات",
|
||||||
"View Reddit comments": "عرض تعليقات ريدإت Reddit",
|
"Subscription manager": "مدير الاشتراكات",
|
||||||
"Hide replies": "إخفاء الردود",
|
"Token manager": "إداره الرمز",
|
||||||
"Show replies": "عرض الردود",
|
"Token": "الرمز",
|
||||||
"Incorrect password": "الرقم السرى غير صحيح",
|
"Import/export": "استيراد/تصدير",
|
||||||
"Quota exceeded, try again in a few hours": "تم تجاوز عدد المرات المسموح بها, حاول مرة اخرى بعد عدة ساعات",
|
"unsubscribe": "إلغاء الاشتراك",
|
||||||
"Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "غير قادر على تسجيل الدخول, تأكد من تشغيل المصادقة الثنائية 2FA.",
|
"revoke": "مسح",
|
||||||
"Invalid TFA code": "كود مصادقة ثنائية 2FA غير صحيح",
|
"Subscriptions": "الاشتراكات",
|
||||||
"Login failed. This may be because two-factor authentication is not enabled on your account.": "لم يتم تسجيل الدخول. هذا ربما بسبب ان المصادقة الثنائية 2FA معطلة فى حسابك.",
|
"search": "بحث",
|
||||||
"Invalid answer": "إجابة خاطئة",
|
"Log out": "تسجيل الخروج",
|
||||||
"Invalid CAPTCHA": "الكابتشا CAPTCHA غير صاحلة",
|
"Released under the AGPLv3 on Github.": "صدر تحت AGPLv3 على Github.",
|
||||||
"CAPTCHA is a required field": "مكان الكابتشا CAPTCHA مطلوب",
|
"Source available here.": "الأكواد متوفرة هنا.",
|
||||||
"User ID is a required field": "مكان إسم المستخدم مطلوب",
|
"View JavaScript license information.": "مشاهدة معلومات حول تراخيص الجافاسكريبت.",
|
||||||
"Password is a required field": "مكان الرقم السرى مطلوب",
|
"View privacy policy.": "عرض سياسة الخصوصية.",
|
||||||
"Invalid username or password": "إسم المستخدم او الرقم السرى غير صحيح",
|
"Trending": "الشائع",
|
||||||
"Please sign in using 'Sign in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'",
|
"Public": "عام",
|
||||||
"Password cannot be empty": "الرقم السرى لايمكن ان يكون فارغ",
|
"Unlisted": "غير مصنف",
|
||||||
"Password cannot be longer than 55 characters": "الرقم السرى لا يتعدى 55 حرف",
|
"Private": "خاص",
|
||||||
"Please sign in": "الرجاء تسجيل الدخول",
|
"View all playlists": "عرض جميع قوائم التشغيل",
|
||||||
"Invidious Private Feed for `x`": "صفحة Invidious للمشتركين الخاصة\\مخفية لـ `x`",
|
"Updated `x` ago": "تم تحديثه منذ `x`",
|
||||||
"channel:`x`": "قناة:`x`",
|
"Delete playlist `x`?": "حذف قائمة التشغيل `x`؟",
|
||||||
"Deleted or invalid channel": "قناة ممسوحة او غير صالحة",
|
"Delete playlist": "حذف قائمة التغشيل",
|
||||||
"This channel does not exist.": "القناة غير موجودة.",
|
"Create playlist": "إنشاء قائمة تشغيل",
|
||||||
"Could not get channel info.": "لم يستطع الحصول على معلومات القناة.",
|
"Title": "العنوان",
|
||||||
"Could not fetch comments": "لم يتمكن من إحضار التعليقات",
|
"Playlist privacy": "إعدادات الخصوصية",
|
||||||
"View `x` replies": "عرض `x` ردود",
|
"Editing playlist `x`": "تعديل قائمة التشغيل `x`",
|
||||||
"`x` ago": "`x` منذ",
|
"Show more": "إظهار المزيد",
|
||||||
"Load more": "عرض المزيد",
|
"Show less": "عرض اقل",
|
||||||
"`x` points": "`x` نقاط",
|
"Watch on YouTube": "مشاهدة الفيديو على اليوتيوب",
|
||||||
"Could not create mix.": "لم يستطع عمل خلط.",
|
"Switch Invidious Instance": "تبديل المثيل Invidious",
|
||||||
"Playlist is empty": "قائمة التشغيل فارغة",
|
"Broken? Try another Invidious Instance": "معطل؟ جرب مثيل Invidious آخر",
|
||||||
"Invalid playlist.": "قائمة التشغيل غير صالحة.",
|
"Hide annotations": "إخفاء الملاحظات في الفيديو",
|
||||||
"Playlist does not exist.": "قائمة التشغيل غير موجودة.",
|
"Show annotations": "عرض الملاحظات في الفيديو",
|
||||||
"Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.",
|
"Genre: ": "النوع: ",
|
||||||
"Hidden field \"challenge\" is a required field": "مكان مخفى \"تحدى\" مكان مطلوب",
|
"License: ": "التراخيص: ",
|
||||||
"Hidden field \"token\" is a required field": "مكان مخفى \"رمز\" مكان مطلوب",
|
"Family friendly? ": "محتوى عائلي؟ ",
|
||||||
"Invalid challenge": "تحدى غير صالح",
|
"Wilson score: ": "درجة ويلسون: ",
|
||||||
"Invalid token": "روز غير صالح",
|
"Engagement: ": "نسبة المشاركة: ",
|
||||||
"Invalid user": "مستخدم غير صالح",
|
"Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ",
|
||||||
"Token is expired, please try again": "الرمز منتهى الصلاحية , الرجاء المحاولة مرة اخرى",
|
"Blacklisted regions: ": "الدول المحظور فيها هذا الفيديو: ",
|
||||||
"English": "إنجليزى",
|
"Shared `x`": "شارك منذ `x`",
|
||||||
"English (auto-generated)": "إنجليزى (تم إنشائة تلقائى)",
|
"Premieres in `x`": "يعرض فى `x`",
|
||||||
"Afrikaans": "الأفريكانية",
|
"Premieres `x`": "يعرض `x`",
|
||||||
"Albanian": "الألبانية",
|
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "أهلًا! يبدو أن جافاسكريبت معطلٌ لديك. اضغط هنا لعرض التعليقات، وَضَع في اعتبارك أنها ستأخذ وقتًا أطول للتحميل.",
|
||||||
"Amharic": "الأمهرية",
|
"View YouTube comments": "عرض تعليقات اليوتيوب",
|
||||||
"Arabic": "العربية",
|
"View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع Reddit",
|
||||||
"Armenian": "الأرميني",
|
"View `x` comments": {
|
||||||
"Azerbaijani": "أذربيجان",
|
"([^.,0-9]|^)1([^.,0-9]|$)": "عرض `x` تعليقات",
|
||||||
"Bangla": "البنغالية",
|
"": "عرض `x` تعليقات"
|
||||||
"Basque": "الباسكي",
|
},
|
||||||
"Belarusian": "البيلاروسية",
|
"View Reddit comments": "عرض تعليقات ريدإت Reddit",
|
||||||
"Bosnian": "البوسنية",
|
"Hide replies": "إخفاء الردود",
|
||||||
"Bulgarian": "البلغارية",
|
"Show replies": "عرض الردود",
|
||||||
"Burmese": "البورمية",
|
"Incorrect password": "كلمة السر غير صحيحة",
|
||||||
"Catalan": "الكاتالونية",
|
"Quota exceeded, try again in a few hours": "تم تجاوز عدد المرات المسموح بها، حاول مجددًا بعد بضع ساعات",
|
||||||
"Cebuano": "السيبيونو",
|
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "غير قادر على تسجيل الدخول، تأكد من تشغيل المصادقة الثنائية 2FA.",
|
||||||
"Chinese (Simplified)": "الصينية (المبسطة)",
|
"Invalid TFA code": "كود مصادقة ثنائية 2FA غير صحيح",
|
||||||
"Chinese (Traditional)": "الصينية (التقليدية)",
|
"Login failed. This may be because two-factor authentication is not turned on for your account.": "فشل تسجيل الدخول. قد يكون هذا بسبب أن المصادقة الثنائية 2FA معطلة في حسابك.",
|
||||||
"Corsican": "الكورسيكية",
|
"Wrong answer": "إجابة خاطئة",
|
||||||
"Croatian": "الكرواتية",
|
"Erroneous CAPTCHA": "الكابتشا CAPTCHA غير صاحلة",
|
||||||
"Czech": "تشيكي",
|
"CAPTCHA is a required field": "مكان الكابتشا CAPTCHA مطلوب",
|
||||||
"Danish": "دانماركي",
|
"User ID is a required field": "مكان اسم المستخدم مطلوب",
|
||||||
"Dutch": "هولندي",
|
"Password is a required field": "مكان كلمة السر مطلوب",
|
||||||
"Esperanto": "الاسبرانتو",
|
"Wrong username or password": "اسم المستخدم او كلمة السر غير صحيح",
|
||||||
"Estonian": "الإستونية",
|
"Please sign in using 'Log in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'",
|
||||||
"Filipino": "الفلبينية",
|
"Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة",
|
||||||
"Finnish": "الفنلندية",
|
"Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا",
|
||||||
"French": "الفرنسية",
|
"Please log in": "الرجاء تسجيل الدخول",
|
||||||
"Galician": "الجاليكية",
|
"Invidious Private Feed for `x`": "تغذية Invidious خاصة ل 'x'",
|
||||||
"Georgian": "الجورجية",
|
"channel:`x`": "قناة:`x`",
|
||||||
"German": "ألمانية",
|
"Deleted or invalid channel": "قناة ممسوحة او غير صالحة",
|
||||||
"Greek": "الإغريقي",
|
"This channel does not exist.": "هذه القناة غير موجودة.",
|
||||||
"Gujarati": "الغوجاراتية",
|
"Could not get channel info.": "لم يستطع الحصول على معلومات القناة.",
|
||||||
"Haitian Creole": "الكاثوليكية الهايتية",
|
"Could not fetch comments": "لم يتمكن من إحضار التعليقات",
|
||||||
"Hausa": "الهوسا",
|
"`x` ago": "`x` منذ",
|
||||||
"Hawaiian": "هاواي",
|
"Load more": "عرض المزيد",
|
||||||
"Hebrew": "العبرية",
|
"Could not create mix.": "لم يستطع عمل خلط.",
|
||||||
"Hindi": "الهندية",
|
"Empty playlist": "قائمة التشغيل فارغة",
|
||||||
"Hmong": "همونغ",
|
"Not a playlist.": "قائمة التشغيل غير صالحة.",
|
||||||
"Hungarian": "الهنغارية",
|
"Playlist does not exist.": "قائمة التشغيل غير موجودة.",
|
||||||
"Icelandic": "أيسلندي",
|
"Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.",
|
||||||
"Igbo": "الإيبو",
|
"Hidden field \"challenge\" is a required field": "مكان مخفي \"تحدي\" مكان مطلوب",
|
||||||
"Indonesian": "الأندونيسية",
|
"Hidden field \"token\" is a required field": "مكان مخفي \"رمز\" مكان مطلوب",
|
||||||
"Irish": "الأيرلندية",
|
"Erroneous challenge": "تحدي غير صالح",
|
||||||
"Italian": "الإيطالي",
|
"Erroneous token": "روز غير صالح",
|
||||||
"Japanese": "اليابانية",
|
"No such user": "مستخدم غير صالح",
|
||||||
"Javanese": "جاوي",
|
"Token is expired, please try again": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى",
|
||||||
"Kannada": "الكانادا",
|
"English": "إنجليزي",
|
||||||
"Kazakh": "الكازاخية",
|
"English (auto-generated)": "إنجليزي (تم إنشائه تلقائيًا)",
|
||||||
"Khmer": "الخمير",
|
"Afrikaans": "الأفريكانية",
|
||||||
"Korean": "الكورية",
|
"Albanian": "الألبانية",
|
||||||
"Kurdish": "كردي",
|
"Amharic": "الأمهرية",
|
||||||
"Kyrgyz": "قيرغيزستان",
|
"Arabic": "العربية",
|
||||||
"Lao": "لاو",
|
"Armenian": "الأرمينية",
|
||||||
"Latin": "لاتينية",
|
"Azerbaijani": "أذربيجانية",
|
||||||
"Latvian": "اللاتفية",
|
"Bangla": "البنغالية",
|
||||||
"Lithuanian": "اللتوانية",
|
"Basque": "الباسكية",
|
||||||
"Luxembourgish": "اللوكسمبرجية",
|
"Belarusian": "البيلاروسية",
|
||||||
"Macedonian": "المقدونية",
|
"Bosnian": "البوسنية",
|
||||||
"Malagasy": "مدجشقر\\مدغشقر",
|
"Bulgarian": "البلغارية",
|
||||||
"Malay": "الملايو",
|
"Burmese": "البورمية",
|
||||||
"Malayalam": "المالايالامية",
|
"Catalan": "الكاتالونية",
|
||||||
"Maltese": "المالطية",
|
"Cebuano": "السيبيونو",
|
||||||
"Maori": "الماوري",
|
"Chinese (Simplified)": "الصينية (المبسطة)",
|
||||||
"Marathi": "المهاراتية",
|
"Chinese (Traditional)": "الصينية (التقليدية)",
|
||||||
"Mongolian": "المنغولية",
|
"Corsican": "الكورسيكية",
|
||||||
"Nepali": "النيبالية",
|
"Croatian": "الكرواتية",
|
||||||
"Norwegian": "النرويجية",
|
"Czech": "تشيكي",
|
||||||
"Nyanja": "نيانجا",
|
"Danish": "دانماركي",
|
||||||
"Pashto": "الباشتو",
|
"Dutch": "هولندي",
|
||||||
"Persian": "الفارسية",
|
"Esperanto": "الاسبرانتو",
|
||||||
"Polish": "البولندي",
|
"Estonian": "الإستونية",
|
||||||
"Portuguese": "البرتغالية",
|
"Filipino": "الفلبينية",
|
||||||
"Punjabi": "البنجابية",
|
"Finnish": "الفنلندية",
|
||||||
"Romanian": "روماني",
|
"French": "الفرنسية",
|
||||||
"Russian": "الروسية",
|
"Galician": "الجاليكية",
|
||||||
"Samoan": "ساموا",
|
"Georgian": "الجورجية",
|
||||||
"Scottish Gaelic": "الغيلية الاسكتلندية",
|
"German": "ألمانية",
|
||||||
"Serbian": "صربي",
|
"Greek": "الإغريقي",
|
||||||
"Shona": "شونا",
|
"Gujarati": "الغوجاراتية",
|
||||||
"Sindhi": "السندية",
|
"Haitian Creole": "الكاثوليكية الهايتية",
|
||||||
"Sinhala": "السنهالية",
|
"Hausa": "الهوسا",
|
||||||
"Slovak": "السلوفاكية",
|
"Hawaiian": "هاواي",
|
||||||
"Slovenian": "سلوفيني",
|
"Hebrew": "العبرية",
|
||||||
"Somali": "الصومالية",
|
"Hindi": "الهندية",
|
||||||
"Southern Sotho": "جنوب سوثو",
|
"Hmong": "همونغ",
|
||||||
"Spanish": "الأسبانية",
|
"Hungarian": "الهنغارية",
|
||||||
"Spanish (Latin America)": "الأسبانية (أمريكا اللاتينية)",
|
"Icelandic": "أيسلندي",
|
||||||
"Sundanese": "السودانية",
|
"Igbo": "الإيبو",
|
||||||
"Swahili": "السواحلية",
|
"Indonesian": "الأندونيسية",
|
||||||
"Swedish": "السويدية",
|
"Irish": "الأيرلندية",
|
||||||
"Tajik": "الطاجيكية",
|
"Italian": "الإيطالي",
|
||||||
"Tamil": "التاميل",
|
"Japanese": "اليابانية",
|
||||||
"Telugu": "التيلجو",
|
"Javanese": "جاوي",
|
||||||
"Thai": "التايلاندية",
|
"Kannada": "الكانادا",
|
||||||
"Turkish": "التركية",
|
"Kazakh": "الكازاخية",
|
||||||
"Ukrainian": "الأوكراني",
|
"Khmer": "الخمير",
|
||||||
"Urdu": "الأردية",
|
"Korean": "الكورية",
|
||||||
"Uzbek": "الأوزبكي",
|
"Kurdish": "كردي",
|
||||||
"Vietnamese": "الفيتنامية",
|
"Kyrgyz": "قيرغيزستان",
|
||||||
"Welsh": "الولزية",
|
"Lao": "لاو",
|
||||||
"Western Frisian": "الفريزية الغربية",
|
"Latin": "لاتينية",
|
||||||
"Xhosa": "زوسا",
|
"Latvian": "اللاتفية",
|
||||||
"Yiddish": "اليديشية",
|
"Lithuanian": "اللتوانية",
|
||||||
"Yoruba": "اليوروبا",
|
"Luxembourgish": "اللوكسمبرجية",
|
||||||
"Zulu": "الزولو",
|
"Macedonian": "المقدونية",
|
||||||
"`x` years": "`x` سنوات",
|
"Malagasy": "مدجشقر\\مدغشقر",
|
||||||
"`x` months": "`x` شهور",
|
"Malay": "الملايو",
|
||||||
"`x` weeks": "`x` اسابيع",
|
"Malayalam": "المالايالامية",
|
||||||
"`x` days": "`x` ايام",
|
"Maltese": "المالطية",
|
||||||
"`x` hours": "`x` ساعات",
|
"Maori": "الماوري",
|
||||||
"`x` minutes": "`x` دقائق",
|
"Marathi": "المهاراتية",
|
||||||
"`x` seconds": "`x` ثوانى",
|
"Mongolian": "المنغولية",
|
||||||
"Fallback comments: ": "التعليقات المصاحبة",
|
"Nepali": "النيبالية",
|
||||||
"Popular": "الشائع",
|
"Norwegian Bokmål": "النرويجية",
|
||||||
"Top": "الأفضل",
|
"Nyanja": "نيانجا",
|
||||||
"About": "حول",
|
"Pashto": "الباشتو",
|
||||||
"Rating: ": "التقييم",
|
"Persian": "الفارسية",
|
||||||
"Language: ": "اللغة"
|
"Polish": "البولندي",
|
||||||
|
"Portuguese": "البرتغالية",
|
||||||
|
"Punjabi": "البنجابية",
|
||||||
|
"Romanian": "روماني",
|
||||||
|
"Russian": "الروسية",
|
||||||
|
"Samoan": "ساموا",
|
||||||
|
"Scottish Gaelic": "الغيلية الاسكتلندية",
|
||||||
|
"Serbian": "صربي",
|
||||||
|
"Shona": "شونا",
|
||||||
|
"Sindhi": "السندية",
|
||||||
|
"Sinhala": "السنهالية",
|
||||||
|
"Slovak": "السلوفاكية",
|
||||||
|
"Slovenian": "سلوفيني",
|
||||||
|
"Somali": "الصومالية",
|
||||||
|
"Southern Sotho": "جنوب سوثو",
|
||||||
|
"Spanish": "الأسبانية",
|
||||||
|
"Spanish (Latin America)": "الأسبانية (أمريكا اللاتينية)",
|
||||||
|
"Sundanese": "السودانية",
|
||||||
|
"Swahili": "السواحلية",
|
||||||
|
"Swedish": "السويدية",
|
||||||
|
"Tajik": "الطاجيكية",
|
||||||
|
"Tamil": "التاميل",
|
||||||
|
"Telugu": "التيلجو",
|
||||||
|
"Thai": "التايلاندية",
|
||||||
|
"Turkish": "التركية",
|
||||||
|
"Ukrainian": "الأوكراني",
|
||||||
|
"Urdu": "الأردية",
|
||||||
|
"Uzbek": "الأوزبكي",
|
||||||
|
"Vietnamese": "الفيتنامية",
|
||||||
|
"Welsh": "الولزية",
|
||||||
|
"Western Frisian": "الفريزية الغربية",
|
||||||
|
"Xhosa": "زوسا",
|
||||||
|
"Yiddish": "اليديشية",
|
||||||
|
"Yoruba": "اليوروبا",
|
||||||
|
"Zulu": "الزولو",
|
||||||
|
"Fallback comments: ": "التعليقات البديلة: ",
|
||||||
|
"Popular": "الأكثر شعبية",
|
||||||
|
"Search": "بحث",
|
||||||
|
"Top": "الأفضل",
|
||||||
|
"About": "حول",
|
||||||
|
"Rating: ": "التقييم: ",
|
||||||
|
"preferences_locale_label": "اللغة: ",
|
||||||
|
"View as playlist": "عرض كا قائمة التشغيل",
|
||||||
|
"Default": "الكل",
|
||||||
|
"Music": "الاغانى",
|
||||||
|
"Gaming": "الألعاب",
|
||||||
|
"News": "الأخبار",
|
||||||
|
"Movies": "الأفلام",
|
||||||
|
"Download": "نزّل",
|
||||||
|
"Download as: ": "نزله كـ: ",
|
||||||
|
"%A %B %-d, %Y": "%A %-d %B %Y",
|
||||||
|
"(edited)": "(معدّل)",
|
||||||
|
"YouTube comment permalink": "رابط التعليق على اليوتيوب",
|
||||||
|
"permalink": "الرابط",
|
||||||
|
"`x` marked it with a ❤": "`x` أعجب بهذا",
|
||||||
|
"Audio mode": "الوضع الصوتي",
|
||||||
|
"Video mode": "وضع الفيديو",
|
||||||
|
"Videos": "الفيديوهات",
|
||||||
|
"Playlists": "قوائم التشغيل",
|
||||||
|
"Community": "المجتمع",
|
||||||
|
"relevance": "ملاؤم",
|
||||||
|
"rating": "تقييم",
|
||||||
|
"date": "التاريخ",
|
||||||
|
"views": "مشاهدات",
|
||||||
|
"content_type": "نوع المحتوى",
|
||||||
|
"duration": "المدة الزمنية",
|
||||||
|
"features": "الميزات",
|
||||||
|
"sort": "فرز",
|
||||||
|
"hour": "ساعة",
|
||||||
|
"today": "اليوم",
|
||||||
|
"week": "هذا الأسبوع",
|
||||||
|
"month": "هذا الشهر",
|
||||||
|
"year": "هذه السنة",
|
||||||
|
"video": "فيديو",
|
||||||
|
"channel": "قناة",
|
||||||
|
"playlist": "قائمة التشغيل",
|
||||||
|
"movie": "فيلم",
|
||||||
|
"show": "عرض",
|
||||||
|
"hd": "عالية الدقة",
|
||||||
|
"subtitles": "ترجمات",
|
||||||
|
"creative_commons": "المشاع الإبداعي",
|
||||||
|
"3d": "ثلاثي الأبعاد",
|
||||||
|
"live": "مباشر",
|
||||||
|
"4k": "4k",
|
||||||
|
"location": "الأماكن",
|
||||||
|
"hdr": "وضع التباين العالي",
|
||||||
|
"filter": "معامل الفرز",
|
||||||
|
"Current version: ": "الإصدار الحالي: ",
|
||||||
|
"next_steps_error_message": "بعد ذلك يجب أن تحاول: ",
|
||||||
|
"next_steps_error_message_refresh": "تحديث",
|
||||||
|
"next_steps_error_message_go_to_youtube": "انتقل إلى يوتيوب",
|
||||||
|
"short": "قصير (< 4 دقائق)",
|
||||||
|
"long": "طويل (> 20 دقيقة)",
|
||||||
|
"footer_source_code": "شفرة المصدر",
|
||||||
|
"footer_original_source_code": "شفرة المصدر الأصلية",
|
||||||
|
"footer_modfied_source_code": "شفرة المصدر المعدلة",
|
||||||
|
"adminprefs_modified_source_code_url_label": "URL إلى مستودع التعليمات البرمجية المصدرية المعدلة",
|
||||||
|
"footer_documentation": "التوثيق",
|
||||||
|
"footer_donate_page": "تبرّع",
|
||||||
|
"preferences_region_label": "بلد المحتوى: ",
|
||||||
|
"preferences_quality_dash_label": "جودة فيديو DASH المفضلة: ",
|
||||||
|
"preferences_quality_option_dash": "DASH (جودة تكييفية)",
|
||||||
|
"preferences_quality_option_hd720": "HD720",
|
||||||
|
"preferences_quality_option_medium": "متوسطة",
|
||||||
|
"preferences_quality_option_small": "صغيرة",
|
||||||
|
"preferences_quality_dash_option_auto": "تلقائي",
|
||||||
|
"preferences_quality_dash_option_best": "الأفضل",
|
||||||
|
"preferences_quality_dash_option_worst": "أسوأ",
|
||||||
|
"preferences_quality_dash_option_4320p": "4320p",
|
||||||
|
"preferences_quality_dash_option_2160p": "2160p",
|
||||||
|
"preferences_quality_dash_option_1440p": "1440p",
|
||||||
|
"preferences_quality_dash_option_1080p": "1080p",
|
||||||
|
"preferences_quality_dash_option_720p": "720p",
|
||||||
|
"preferences_quality_dash_option_480p": "480p",
|
||||||
|
"preferences_quality_dash_option_360p": "360p",
|
||||||
|
"preferences_quality_dash_option_240p": "240p",
|
||||||
|
"preferences_quality_dash_option_144p": "144p",
|
||||||
|
"purchased": "تم شراؤها",
|
||||||
|
"none": "لاشيء",
|
||||||
|
"videoinfo_started_streaming_x_ago": "بدأ البث منذ `x`",
|
||||||
|
"videoinfo_watch_on_youTube": "مشاهدة على يوتيوب",
|
||||||
|
"videoinfo_youTube_embed_link": "مضمن",
|
||||||
|
"videoinfo_invidious_embed_link": "رابط مضمن",
|
||||||
|
"user_created_playlists": "'x' إنشاء قوائم التشغيل",
|
||||||
|
"user_saved_playlists": "قوائم التشغيل المحفوظة 'x'",
|
||||||
|
"Video unavailable": "الفيديو غير متوفر",
|
||||||
|
"360": "360°",
|
||||||
|
"download_subtitles": "ترجمات - 'x' (.vtt)",
|
||||||
|
"invidious": "الخيالي",
|
||||||
|
"preferences_save_player_pos_label": "احفظ وقت الفيديو الحالي: ",
|
||||||
|
"crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!",
|
||||||
|
"generic_videos_count_0": "لا فيديوهات",
|
||||||
|
"generic_videos_count_1": "فيديو واحد",
|
||||||
|
"generic_videos_count_2": "فيديوهين",
|
||||||
|
"generic_videos_count_3": "{{count}} فيديوهات",
|
||||||
|
"generic_videos_count_4": "{{count}} فيديو",
|
||||||
|
"generic_videos_count_5": "{{count}} فيديو",
|
||||||
|
"generic_subscribers_count_0": "لا مشتركين",
|
||||||
|
"generic_subscribers_count_1": "مشترك واحد",
|
||||||
|
"generic_subscribers_count_2": "مشتركان",
|
||||||
|
"generic_subscribers_count_3": "{{count}} مشتركين",
|
||||||
|
"generic_subscribers_count_4": "{{count}} مشترك",
|
||||||
|
"generic_subscribers_count_5": "{{count}} مشترك",
|
||||||
|
"generic_views_count_0": "لا مشاهدات",
|
||||||
|
"generic_views_count_1": "مشاهدة واحدة",
|
||||||
|
"generic_views_count_2": "مشاهدتان",
|
||||||
|
"generic_views_count_3": "{{count}} مشاهدات",
|
||||||
|
"generic_views_count_4": "{{count}} مشاهدة",
|
||||||
|
"generic_views_count_5": "{{count}} مشاهدة",
|
||||||
|
"generic_subscriptions_count_0": "لا اشتراكات",
|
||||||
|
"generic_subscriptions_count_1": "اشتراك واحد",
|
||||||
|
"generic_subscriptions_count_2": "اشتراكان",
|
||||||
|
"generic_subscriptions_count_3": "{{count}} اشتراكات",
|
||||||
|
"generic_subscriptions_count_4": "{{count}} اشتراك",
|
||||||
|
"generic_subscriptions_count_5": "{{count}} اشتراك",
|
||||||
|
"generic_playlists_count_0": "لا قوائم تشغيل",
|
||||||
|
"generic_playlists_count_1": "قائمة تشغيل واحدة",
|
||||||
|
"generic_playlists_count_2": "قائمتا تشغيل",
|
||||||
|
"generic_playlists_count_3": "{{count}} قوائم تشغيل",
|
||||||
|
"generic_playlists_count_4": "{{count}} قائمة تشغيل",
|
||||||
|
"generic_playlists_count_5": "{{count}} قائمة تشغيل"
|
||||||
}
|
}
|
||||||
|
|||||||
61
locales/bn_BD.json
Normal file
61
locales/bn_BD.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"LIVE": "লাইভ",
|
||||||
|
"Shared `x` ago": "`x` আগে শেয়ার করা হয়েছে",
|
||||||
|
"Unsubscribe": "আনসাবস্ক্রাইব",
|
||||||
|
"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": "নতুন পাসওয়ার্ড অবশ্যই মিলতে হবে",
|
||||||
|
"Cannot change password for Google accounts": "গুগল অ্যাকাউন্টগুলোর জন্য পাসওয়ার্ড পরিবর্তন করা যায় না",
|
||||||
|
"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": "লগ ইন/রেজিস্টার",
|
||||||
|
"Log in with Google": "গুগল দিয়ে লগ ইন করুন",
|
||||||
|
"User ID": "ইউজার আইডি",
|
||||||
|
"Password": "পাসওয়ার্ড",
|
||||||
|
"Time (h:mm:ss):": "সময় (ঘণ্টা:মিনিট:সেকেন্ড):",
|
||||||
|
"Text CAPTCHA": "টেক্সট ক্যাপচা",
|
||||||
|
"Image CAPTCHA": "চিত্র ক্যাপচা",
|
||||||
|
"Sign In": "সাইন ইন",
|
||||||
|
"Register": "নিবন্ধন",
|
||||||
|
"E-mail": "ই-মেইল",
|
||||||
|
"Google verification code": "গুগল যাচাইকরণ কোড",
|
||||||
|
"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": "প্লেয়ার শব্দের মাত্রা: "
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user