mirror of
https://github.com/ajayyy/SponsorBlockServer.git
synced 2025-12-06 11:36:58 +03:00
Compare commits
2147 Commits
v0.3
...
revert-566
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3a28f7df3 | ||
|
|
f763139664 | ||
|
|
da482054a4 | ||
|
|
134e89af00 | ||
|
|
5cc80f9066 | ||
|
|
e1043aba05 | ||
|
|
c0abedf67f | ||
|
|
d99b7dc2c6 | ||
|
|
579e2b90a3 | ||
|
|
3708d293dc | ||
|
|
077a9ecc50 | ||
|
|
5714f51ac0 | ||
|
|
68bb39c409 | ||
|
|
9dd8b28812 | ||
|
|
a659048afe | ||
|
|
3c6803fb62 | ||
|
|
467443a03f | ||
|
|
d8b93dec00 | ||
|
|
26b3ea6a50 | ||
|
|
f72b1abf41 | ||
|
|
53e5dcb2f0 | ||
|
|
73e5ade529 | ||
|
|
31e1f5bc3c | ||
|
|
df40047a4b | ||
|
|
ad9344c92f | ||
|
|
726983bb9b | ||
|
|
7364499f11 | ||
|
|
5e3ec895d8 | ||
|
|
a9ef3815e2 | ||
|
|
964634dc51 | ||
|
|
1e8970859f | ||
|
|
4438ce7db6 | ||
|
|
86ea0f582b | ||
|
|
e329bccca5 | ||
|
|
1275afa25b | ||
|
|
1b5a079bbd | ||
|
|
ad666ff487 | ||
|
|
7196155d3a | ||
|
|
934ce79728 | ||
|
|
65e7d24b7d | ||
|
|
d08c423c6a | ||
|
|
8361f602c7 | ||
|
|
1e3a50b884 | ||
|
|
55150cb301 | ||
|
|
2015cf1488 | ||
|
|
141f105b79 | ||
|
|
c2a3630d49 | ||
|
|
c77e71e66a | ||
|
|
345c740fdc | ||
|
|
d84276a86a | ||
|
|
16c7ad5531 | ||
|
|
7cb0a0705c | ||
|
|
4600b8a599 | ||
|
|
e9e1fd5228 | ||
|
|
48fa55cc7a | ||
|
|
ecfc2c14c8 | ||
|
|
f58da275eb | ||
|
|
0723503a98 | ||
|
|
9d1af3bdff | ||
|
|
b3cec20215 | ||
|
|
b02134c016 | ||
|
|
c3c8f38423 | ||
|
|
1dbb393e4d | ||
|
|
dfa4578d28 | ||
|
|
99cb22a5e6 | ||
|
|
665b91eb65 | ||
|
|
e942ac5e22 | ||
|
|
83b561d943 | ||
|
|
f0b0217c78 | ||
|
|
d23e9b9940 | ||
|
|
4b214767a0 | ||
|
|
8c687934c2 | ||
|
|
f63fa09605 | ||
|
|
4e93a007c2 | ||
|
|
2fc31655ff | ||
|
|
79515ccc8b | ||
|
|
b6f29b8b6d | ||
|
|
a52ecf2d37 | ||
|
|
8d518b184b | ||
|
|
3924a65e02 | ||
|
|
a4de94bede | ||
|
|
8bcc781da7 | ||
|
|
b2081fe155 | ||
|
|
ea80a413ba | ||
|
|
528f24a431 | ||
|
|
0463165f1a | ||
|
|
38b7ddfd07 | ||
|
|
79bac69c41 | ||
|
|
85fc0477ad | ||
|
|
a5501b9655 | ||
|
|
e9fcf6b445 | ||
|
|
808066a5ed | ||
|
|
93f4cfd82d | ||
|
|
d030de83bd | ||
|
|
d1d2b011f8 | ||
|
|
d2f8e3aee4 | ||
|
|
2e29666781 | ||
|
|
8b418c8851 | ||
|
|
5f80562772 | ||
|
|
69db87f5e1 | ||
|
|
fa6919a1d0 | ||
|
|
633f128e90 | ||
|
|
9f7fa53b14 | ||
|
|
bbb7102e37 | ||
|
|
3bb8d5b58b | ||
|
|
1cacb2dd69 | ||
|
|
fe185234cf | ||
|
|
ef3e48ec24 | ||
|
|
777944665d | ||
|
|
0932f63398 | ||
|
|
5834643ba0 | ||
|
|
8e5be402e1 | ||
|
|
e253c7bb47 | ||
|
|
9129cee9f0 | ||
|
|
39fcdb1d95 | ||
|
|
8d1025e17d | ||
|
|
6f0abddd3e | ||
|
|
a1b5c38e5a | ||
|
|
8db898fab5 | ||
|
|
940cacfb0a | ||
|
|
69a54f64b4 | ||
|
|
9600f56830 | ||
|
|
4e59526b4d | ||
|
|
42f2eb5eae | ||
|
|
e1a607ba39 | ||
|
|
8a236d66ed | ||
|
|
4a10faaea3 | ||
|
|
589b7d4e3e | ||
|
|
a52feee25e | ||
|
|
d6a12a5e99 | ||
|
|
4696ce8d01 | ||
|
|
bffc10e38f | ||
|
|
dde12bcc43 | ||
|
|
7921bc4582 | ||
|
|
0b830610e9 | ||
|
|
b6e9f92da8 | ||
|
|
d4c4dbffcc | ||
|
|
5c549b5295 | ||
|
|
acae9da06c | ||
|
|
1f939116a4 | ||
|
|
8495a9d6c0 | ||
|
|
d76ee7cd22 | ||
|
|
436e75e3e6 | ||
|
|
7ba654e476 | ||
|
|
f4286b15a1 | ||
|
|
76ce1017ea | ||
|
|
780555e9df | ||
|
|
79b7b6ea4c | ||
|
|
80de71a68f | ||
|
|
4b8bc418ba | ||
|
|
f4537160de | ||
|
|
900fa9f64e | ||
|
|
e6f54f11f0 | ||
|
|
6296761fe4 | ||
|
|
820a7eb02f | ||
|
|
c6795a783d | ||
|
|
37a1c7e88d | ||
|
|
31a460e750 | ||
|
|
1bda331b0c | ||
|
|
72fb4eb6ec | ||
|
|
d04230a1c4 | ||
|
|
f70a26009c | ||
|
|
c84eb839a0 | ||
|
|
df279cf48a | ||
|
|
cdc080b58b | ||
|
|
c586c9a7e7 | ||
|
|
81b0c27180 | ||
|
|
bff05dccaa | ||
|
|
9c438602f8 | ||
|
|
19ebca86c9 | ||
|
|
f48fb6c3f6 | ||
|
|
4a90ba8992 | ||
|
|
efc911a229 | ||
|
|
2e3f4f8c70 | ||
|
|
b95b6d8efe | ||
|
|
7985d131ef | ||
|
|
a384079562 | ||
|
|
46c372a764 | ||
|
|
3281954019 | ||
|
|
aece615992 | ||
|
|
6448fbfbd8 | ||
|
|
c5426e5fc4 | ||
|
|
3894d453a5 | ||
|
|
280e6684af | ||
|
|
7361c7056b | ||
|
|
2d751a0b21 | ||
|
|
399bda869f | ||
|
|
663bd96130 | ||
|
|
5b3f4b476e | ||
|
|
b855eea349 | ||
|
|
51d25cfc68 | ||
|
|
f8f02d86d5 | ||
|
|
cb7492628c | ||
|
|
e69b61fb4c | ||
|
|
5c1b502a15 | ||
|
|
d5c544f1ee | ||
|
|
5426ae826e | ||
|
|
7911819cab | ||
|
|
d8e0eac61b | ||
|
|
74c0ba37e2 | ||
|
|
a64b8f99b7 | ||
|
|
9cf2e1f0e9 | ||
|
|
10e9aef8cc | ||
|
|
d07aac3a33 | ||
|
|
b91114165e | ||
|
|
36f1d15605 | ||
|
|
894cd48182 | ||
|
|
1641a41b00 | ||
|
|
4d8ce40ef4 | ||
|
|
07c683e8f0 | ||
|
|
d0a9168531 | ||
|
|
2ed23d7fcb | ||
|
|
e7a43d79ab | ||
|
|
b792354ffb | ||
|
|
a613b68c66 | ||
|
|
66c2be6012 | ||
|
|
2a7083b9ef | ||
|
|
928eef637a | ||
|
|
be03367557 | ||
|
|
fd288dd330 | ||
|
|
0c00f7323d | ||
|
|
5a6ba891f9 | ||
|
|
90e5446078 | ||
|
|
dc0bde0e36 | ||
|
|
7c2feb80bc | ||
|
|
fc607d0cb7 | ||
|
|
3fbcadc3a0 | ||
|
|
7f7a3a37ec | ||
|
|
cc24a4902f | ||
|
|
d738e89f20 | ||
|
|
b591b7194e | ||
|
|
fc5b5ca72c | ||
|
|
8d7b010a12 | ||
|
|
fc73f491fe | ||
|
|
2683c61995 | ||
|
|
cff2325aef | ||
|
|
d7d5618985 | ||
|
|
4e3753d32c | ||
|
|
1202c264aa | ||
|
|
89e122210e | ||
|
|
aed0e38d2f | ||
|
|
665ad6fe50 | ||
|
|
992c65a39d | ||
|
|
b6e62aa68f | ||
|
|
1f844bf56a | ||
|
|
fa5f3b8a50 | ||
|
|
30ac91c537 | ||
|
|
e252411fe1 | ||
|
|
91fc56f59a | ||
|
|
0cd808a2d9 | ||
|
|
87e38c8bc4 | ||
|
|
df76b5f053 | ||
|
|
71d30c0b51 | ||
|
|
238ccc83d9 | ||
|
|
9bd6e9df4f | ||
|
|
7bdcb10994 | ||
|
|
849ca52ef8 | ||
|
|
9e2e1343da | ||
|
|
4139bf8f8c | ||
|
|
1fffd2e6ac | ||
|
|
a417299d3e | ||
|
|
d3d53d0758 | ||
|
|
517c9512e4 | ||
|
|
ed5e3373a5 | ||
|
|
6c18579a78 | ||
|
|
e6e8c8e5a1 | ||
|
|
50743070de | ||
|
|
7fb68937d0 | ||
|
|
90e68caaf7 | ||
|
|
0ead3892ba | ||
|
|
2700b5b530 | ||
|
|
e1a5ca64f0 | ||
|
|
98f202f6a9 | ||
|
|
ee436d982c | ||
|
|
dc8c35695d | ||
|
|
9b509e7289 | ||
|
|
eee44e611b | ||
|
|
5139c7aaf0 | ||
|
|
2254e7f869 | ||
|
|
68d9d3cbde | ||
|
|
571230db56 | ||
|
|
9c5db6a958 | ||
|
|
6b1ecbe90e | ||
|
|
d83d620893 | ||
|
|
db71af480d | ||
|
|
80c018c9d5 | ||
|
|
cc6c5980a1 | ||
|
|
9d7236282c | ||
|
|
7f92ac961d | ||
|
|
96db571a5e | ||
|
|
ccd1321d24 | ||
|
|
19289a149b | ||
|
|
622511c8fd | ||
|
|
c2acc6227b | ||
|
|
176f2ce7b9 | ||
|
|
9286f16e7b | ||
|
|
9e3d059d10 | ||
|
|
13ae4681cb | ||
|
|
ee56a8dea4 | ||
|
|
94eb37cb1c | ||
|
|
0ced78d798 | ||
|
|
f1005fe779 | ||
|
|
1e2437b993 | ||
|
|
a6275b3607 | ||
|
|
ba27a8a056 | ||
|
|
10397fbde2 | ||
|
|
b5434ae234 | ||
|
|
b52ca8b2a6 | ||
|
|
e799821ad9 | ||
|
|
415bb31e36 | ||
|
|
eb3d733a34 | ||
|
|
9386f25f9f | ||
|
|
ceaf9ec6f6 | ||
|
|
d229003f6e | ||
|
|
1becebdcd5 | ||
|
|
b417241ca0 | ||
|
|
ee3d94e7b7 | ||
|
|
b6da103c3d | ||
|
|
52a7d7e791 | ||
|
|
b63572ec43 | ||
|
|
66d5622393 | ||
|
|
b7fcd9ea39 | ||
|
|
c4af2449c3 | ||
|
|
eb5458427d | ||
|
|
95dd36a782 | ||
|
|
67eb165b53 | ||
|
|
d163b1d436 | ||
|
|
715d41fbb2 | ||
|
|
0b9e7029c5 | ||
|
|
9ef0eafac1 | ||
|
|
d80be9e7b3 | ||
|
|
968ffe785f | ||
|
|
847f1bbabb | ||
|
|
ada32217d9 | ||
|
|
a8ddae131d | ||
|
|
54e53f098e | ||
|
|
ed251a047a | ||
|
|
601a17c969 | ||
|
|
771fa18731 | ||
|
|
27f7b6d3c7 | ||
|
|
55aa33aa6e | ||
|
|
07926ada57 | ||
|
|
ea05284b1d | ||
|
|
230191ece9 | ||
|
|
28448f99d9 | ||
|
|
c34de1baa4 | ||
|
|
a469f2f382 | ||
|
|
506b6570f3 | ||
|
|
8562dc2240 | ||
|
|
005ae2c9fb | ||
|
|
9ca087206e | ||
|
|
c0952c15c8 | ||
|
|
47616711ce | ||
|
|
7457b51aa4 | ||
|
|
a00048aaac | ||
|
|
6499381b4f | ||
|
|
0a102c15fd | ||
|
|
e0be4744be | ||
|
|
dd7656d143 | ||
|
|
f683ed4f29 | ||
|
|
3f470a72f5 | ||
|
|
f5bafa2868 | ||
|
|
b652102c42 | ||
|
|
55db24ab74 | ||
|
|
551e103158 | ||
|
|
8fc01ba138 | ||
|
|
1c1496afbc | ||
|
|
7007ab05e1 | ||
|
|
30bac658ed | ||
|
|
f6f83fcbe4 | ||
|
|
f6c68ec29c | ||
|
|
62a9b0eddd | ||
|
|
3c09033267 | ||
|
|
2ecf4b3a9b | ||
|
|
94ca291460 | ||
|
|
0e3eeece01 | ||
|
|
00dae6d6a1 | ||
|
|
e84957a2c8 | ||
|
|
1e66a2e57a | ||
|
|
9be9d05dbe | ||
|
|
072324f0ab | ||
|
|
c1e5f0e117 | ||
|
|
ae95f7e3ea | ||
|
|
bd7dfc63ff | ||
|
|
0ca134dc8f | ||
|
|
d1d7675a8c | ||
|
|
252e2305f2 | ||
|
|
9c72e20d1b | ||
|
|
ec41102f07 | ||
|
|
08ab7e816d | ||
|
|
f103a02a34 | ||
|
|
b413795e45 | ||
|
|
6a61747573 | ||
|
|
420317a18c | ||
|
|
af2ef3d6a5 | ||
|
|
9f2d13780c | ||
|
|
acec7e58e7 | ||
|
|
7060c0ab0d | ||
|
|
ab6fcb8943 | ||
|
|
cbf352173a | ||
|
|
027ff694a0 | ||
|
|
9da1fc523a | ||
|
|
44d541bafe | ||
|
|
dad205e729 | ||
|
|
8dbfe17ee4 | ||
|
|
38a418a37a | ||
|
|
6be2f96f26 | ||
|
|
47a6a13bdb | ||
|
|
525a23abfb | ||
|
|
4f637daeaa | ||
|
|
9559628273 | ||
|
|
5a43f46ac0 | ||
|
|
c3d30b18e2 | ||
|
|
7cb58b946a | ||
|
|
9306729fdc | ||
|
|
1ae4c9a349 | ||
|
|
be204fe873 | ||
|
|
6804e7d7a8 | ||
|
|
f4c104215b | ||
|
|
479890fc0d | ||
|
|
877c3b7107 | ||
|
|
cd35a58f83 | ||
|
|
cb1cc10278 | ||
|
|
6239e53091 | ||
|
|
ef93c692e5 | ||
|
|
ee49689110 | ||
|
|
04eabd5141 | ||
|
|
da1ed70c66 | ||
|
|
186f07b20c | ||
|
|
5e49ce22b2 | ||
|
|
af7634b498 | ||
|
|
b9354e44ae | ||
|
|
17a790b6d9 | ||
|
|
46805f4830 | ||
|
|
954c3db649 | ||
|
|
23add072f3 | ||
|
|
db2f9e11f7 | ||
|
|
2983bdb616 | ||
|
|
597b26ba7c | ||
|
|
f93c197d05 | ||
|
|
9764c01428 | ||
|
|
ad7080d801 | ||
|
|
071aae5cf7 | ||
|
|
10491da7c2 | ||
|
|
b616ac990b | ||
|
|
603bad4967 | ||
|
|
ddb73af515 | ||
|
|
c1d345441c | ||
|
|
e050a6eec3 | ||
|
|
b3c05bc606 | ||
|
|
3203271fdf | ||
|
|
6e1df18c38 | ||
|
|
96a75a8335 | ||
|
|
dd47f87616 | ||
|
|
f1ed8eff84 | ||
|
|
1e441c3ebf | ||
|
|
f4b66d30ec | ||
|
|
288f7d45e7 | ||
|
|
4ac70d2d7e | ||
|
|
0f3efc6fb2 | ||
|
|
f2904da653 | ||
|
|
b11f260377 | ||
|
|
9ab939456b | ||
|
|
5c089dab13 | ||
|
|
bba1c7f965 | ||
|
|
55a8b5d514 | ||
|
|
9c0f0d8e4f | ||
|
|
f24a1415da | ||
|
|
70c98e0819 | ||
|
|
39ad5fb62a | ||
|
|
356edb1f4b | ||
|
|
5b60243b22 | ||
|
|
9f5ac2d9b9 | ||
|
|
972cb96259 | ||
|
|
c2b0ecd6f6 | ||
|
|
47f460bb2c | ||
|
|
adca0256a0 | ||
|
|
7d396f3782 | ||
|
|
8ed695bcdc | ||
|
|
f8c297ddfb | ||
|
|
0f3df8db1b | ||
|
|
f9de547b95 | ||
|
|
cc953884d9 | ||
|
|
ebee00322a | ||
|
|
a99da61039 | ||
|
|
edff48d258 | ||
|
|
3844404637 | ||
|
|
5057c86707 | ||
|
|
b1b40d410f | ||
|
|
de60415f55 | ||
|
|
931e3b8b11 | ||
|
|
8560c3f673 | ||
|
|
54db2c8c10 | ||
|
|
c6868fa839 | ||
|
|
86e61c778c | ||
|
|
e615d7c032 | ||
|
|
08003bc2f2 | ||
|
|
1a232600a1 | ||
|
|
a8d0336cae | ||
|
|
e1d6fdfefb | ||
|
|
f8ef145bb8 | ||
|
|
a187180388 | ||
|
|
749fa4bb95 | ||
|
|
5c43750835 | ||
|
|
8ec44aff1a | ||
|
|
046a535ebc | ||
|
|
0260b4889d | ||
|
|
6621ae3730 | ||
|
|
d273095525 | ||
|
|
5e4773afdd | ||
|
|
b7995832bc | ||
|
|
cbdd852566 | ||
|
|
043c8b771e | ||
|
|
55ff3230ed | ||
|
|
ed221c8599 | ||
|
|
29660d998b | ||
|
|
f520e00ed4 | ||
|
|
21f7d5d938 | ||
|
|
c9a0fb7bc3 | ||
|
|
e79a8417f4 | ||
|
|
dfbc32617b | ||
|
|
901a42d1b4 | ||
|
|
634d5d083a | ||
|
|
d67b9cdcc5 | ||
|
|
78acb4a76a | ||
|
|
5889e9e557 | ||
|
|
3931328b60 | ||
|
|
f56fbbd2c7 | ||
|
|
dbfc685bf9 | ||
|
|
34771e96fe | ||
|
|
bcb9e33a01 | ||
|
|
72de520781 | ||
|
|
3c34077056 | ||
|
|
2eb53015bc | ||
|
|
4bbaf11502 | ||
|
|
6a826b63c9 | ||
|
|
60a3c017e5 | ||
|
|
a89abd5dd8 | ||
|
|
fc99c42e02 | ||
|
|
1146aac3c2 | ||
|
|
3341500fdf | ||
|
|
81ad0dd640 | ||
|
|
ff197d2985 | ||
|
|
8fbb1bb79b | ||
|
|
c0dc174f42 | ||
|
|
d8395163b9 | ||
|
|
b4b7ccec20 | ||
|
|
d75226bde5 | ||
|
|
db700cd7e8 | ||
|
|
591e3a0051 | ||
|
|
b0bcf2b684 | ||
|
|
2e4b7a0c9c | ||
|
|
c5f163e41e | ||
|
|
c5d2cacae2 | ||
|
|
5e3e02c674 | ||
|
|
0fe85b9760 | ||
|
|
5f53859c94 | ||
|
|
5b177a3e53 | ||
|
|
a66588619a | ||
|
|
98494aec4a | ||
|
|
e74b985304 | ||
|
|
fb414ed6db | ||
|
|
c7905d4062 | ||
|
|
8a9c7c869b | ||
|
|
002298648c | ||
|
|
929856fd3f | ||
|
|
1df8117105 | ||
|
|
146ba4ff93 | ||
|
|
8dc87da462 | ||
|
|
41c92da37e | ||
|
|
2d6be12062 | ||
|
|
14ef6682df | ||
|
|
10298efc08 | ||
|
|
b09ed1cbe2 | ||
|
|
d5611fb023 | ||
|
|
f67c0b5762 | ||
|
|
468ce25858 | ||
|
|
d392b1c8fc | ||
|
|
d02d78f325 | ||
|
|
6b5dc54cc7 | ||
|
|
76cc603a3f | ||
|
|
65a661ca4d | ||
|
|
2fb7f4faa6 | ||
|
|
ed560425a0 | ||
|
|
267320bc56 | ||
|
|
e6bf20937d | ||
|
|
fe0afd58bc | ||
|
|
9bcf7ed199 | ||
|
|
fafcbe0b25 | ||
|
|
4c2b258038 | ||
|
|
bd059c3a68 | ||
|
|
70a507818f | ||
|
|
5f4f45056e | ||
|
|
7ead3ddc63 | ||
|
|
a536e4aeb8 | ||
|
|
2b237a4a8b | ||
|
|
03542ebcac | ||
|
|
b3c2b2c15d | ||
|
|
80e3a55cfe | ||
|
|
9224960dea | ||
|
|
84ea08d6d0 | ||
|
|
e9c43a22f5 | ||
|
|
fe6d2e88bd | ||
|
|
0bb6d29932 | ||
|
|
e9ebf08984 | ||
|
|
f156a5fbe9 | ||
|
|
b182945274 | ||
|
|
6507a3a68d | ||
|
|
e8b8b87190 | ||
|
|
c602285102 | ||
|
|
744b5ea4f6 | ||
|
|
68b2fba24c | ||
|
|
dbb8128a3a | ||
|
|
22b4135d62 | ||
|
|
4029b15233 | ||
|
|
90fc02e340 | ||
|
|
6b1fa7f5d0 | ||
|
|
0e8b6eb506 | ||
|
|
50d8c5f105 | ||
|
|
4da3c2d049 | ||
|
|
a3ddbef38e | ||
|
|
77a1799a7f | ||
|
|
0dbd081063 | ||
|
|
c9b18a4938 | ||
|
|
155c5a9b97 | ||
|
|
1bc9a69b79 | ||
|
|
fb6e98f93f | ||
|
|
61eb2e665b | ||
|
|
de7b985535 | ||
|
|
bbcbd3783a | ||
|
|
6db9404e1c | ||
|
|
857ee04c3c | ||
|
|
8b58d52b12 | ||
|
|
50fab971e0 | ||
|
|
df6c76ede9 | ||
|
|
0ff38b918b | ||
|
|
5713b96d13 | ||
|
|
f3d10bd19f | ||
|
|
a2f2cf9c0d | ||
|
|
2b8944bf15 | ||
|
|
db5922e4b7 | ||
|
|
0854ad1f65 | ||
|
|
75cf14a960 | ||
|
|
985039a86b | ||
|
|
acc983b829 | ||
|
|
ae6255cc69 | ||
|
|
830fdff3be | ||
|
|
c2516461ad | ||
|
|
d367998d39 | ||
|
|
6ece944536 | ||
|
|
5413d8dc9c | ||
|
|
5aa78967de | ||
|
|
50d0cbd378 | ||
|
|
8759f8dbf2 | ||
|
|
acb5a9467e | ||
|
|
a31a4d016f | ||
|
|
b39d323a22 | ||
|
|
bfd017a66a | ||
|
|
72bbc79943 | ||
|
|
677e05e46c | ||
|
|
732eeed41d | ||
|
|
ea196e84f1 | ||
|
|
df23d1510d | ||
|
|
76bfd27b33 | ||
|
|
6fe7200481 | ||
|
|
6f737ab0b6 | ||
|
|
ea4adc0e14 | ||
|
|
6ec80df8f4 | ||
|
|
762f4f6964 | ||
|
|
a1b59cba34 | ||
|
|
0584492b8c | ||
|
|
77de17c810 | ||
|
|
9ca01407e8 | ||
|
|
7472af714a | ||
|
|
164a9dab17 | ||
|
|
b2e37804f5 | ||
|
|
cb9b2ff965 | ||
|
|
ad4c34ef28 | ||
|
|
c0c2b365ae | ||
|
|
77565d7eda | ||
|
|
75cad434b6 | ||
|
|
be441a314f | ||
|
|
d5d73273de | ||
|
|
44ffa40b6c | ||
|
|
b4f8bdd719 | ||
|
|
68c6266139 | ||
|
|
65954520d0 | ||
|
|
da03958e97 | ||
|
|
f89bef74d6 | ||
|
|
6a98a215ac | ||
|
|
89ea13956a | ||
|
|
f86d2ab0e4 | ||
|
|
8fef35dbbc | ||
|
|
8a80b97e8c | ||
|
|
7f7cc3a7ca | ||
|
|
09eec5a4a5 | ||
|
|
05516a5d7d | ||
|
|
a42f023074 | ||
|
|
aaa3179d42 | ||
|
|
ce4270b96d | ||
|
|
df3d6fe9c7 | ||
|
|
dd900497f4 | ||
|
|
a1d28fbfe1 | ||
|
|
9ae16ea9b6 | ||
|
|
b9a620fc3b | ||
|
|
42624a7782 | ||
|
|
f97af4c433 | ||
|
|
98994cee01 | ||
|
|
90f891aee4 | ||
|
|
ceabeefe21 | ||
|
|
00d7e1f058 | ||
|
|
13a4bc3ee9 | ||
|
|
939ec61c0e | ||
|
|
0db3240f58 | ||
|
|
11a1bbc866 | ||
|
|
3e3f7c2972 | ||
|
|
95d14c0fdb | ||
|
|
7b2d9365a0 | ||
|
|
1c304b636f | ||
|
|
4e94cdda72 | ||
|
|
dfe669a0cd | ||
|
|
873551e1c4 | ||
|
|
66af4f60c8 | ||
|
|
3dd9024cc7 | ||
|
|
5ebb638925 | ||
|
|
7aa9524835 | ||
|
|
f6d68bb3f3 | ||
|
|
7caaf833dd | ||
|
|
a137f8a434 | ||
|
|
2ee7c82760 | ||
|
|
b730383293 | ||
|
|
d1ed4376ef | ||
|
|
f54b9f7ae1 | ||
|
|
9d1a401e3e | ||
|
|
4fa4fdf1e3 | ||
|
|
5a358caedb | ||
|
|
e2e4f79cec | ||
|
|
ed44eaffec | ||
|
|
1f9dc92074 | ||
|
|
0f4b0c2f54 | ||
|
|
68bc6469ce | ||
|
|
d7e86aac80 | ||
|
|
89a83f78cc | ||
|
|
544af7ce15 | ||
|
|
ba07137933 | ||
|
|
e0bf2afdc9 | ||
|
|
ec73ae309e | ||
|
|
caf94a7a93 | ||
|
|
4092ce2616 | ||
|
|
14c2f16cb6 | ||
|
|
a0720329d0 | ||
|
|
292b05443f | ||
|
|
98cb2b022d | ||
|
|
6f3b45bdbd | ||
|
|
4964c72e71 | ||
|
|
e3042f7623 | ||
|
|
edd11cc99c | ||
|
|
8d533d0e94 | ||
|
|
8f3a5a0e4d | ||
|
|
bd3e38fe40 | ||
|
|
6f05b5b92d | ||
|
|
9bc65d566a | ||
|
|
0cd25f0498 | ||
|
|
48ac8d1136 | ||
|
|
dd2db4bbbf | ||
|
|
9e86f463d8 | ||
|
|
733bd338e7 | ||
|
|
7df9f0b054 | ||
|
|
91ba6948d8 | ||
|
|
af4b2e4624 | ||
|
|
8fe0a45435 | ||
|
|
d55a860114 | ||
|
|
ad23ec040b | ||
|
|
5a69de730c | ||
|
|
6baa00b76d | ||
|
|
a5a90e3c84 | ||
|
|
7e1550f3c0 | ||
|
|
4584dbc047 | ||
|
|
0a869dbbd7 | ||
|
|
20e9a3e8b1 | ||
|
|
0478491f93 | ||
|
|
4797a7d938 | ||
|
|
726a081df1 | ||
|
|
ad7574308f | ||
|
|
03c95ca158 | ||
|
|
b9ebd00365 | ||
|
|
bc6db0d109 | ||
|
|
7590047c6d | ||
|
|
e85a0d4f28 | ||
|
|
16c5819f5c | ||
|
|
db48953e39 | ||
|
|
1106048b37 | ||
|
|
a2698fb70d | ||
|
|
6919b5433b | ||
|
|
c371d35e82 | ||
|
|
0b7904f891 | ||
|
|
c0072d5c72 | ||
|
|
2733cd6606 | ||
|
|
7eef74a7dc | ||
|
|
32150e4a1d | ||
|
|
0c16448065 | ||
|
|
991d384f11 | ||
|
|
aa40ac7777 | ||
|
|
88a368d0b9 | ||
|
|
246ec7c3c3 | ||
|
|
70d0387356 | ||
|
|
19f7bbcde5 | ||
|
|
dd8b2f8809 | ||
|
|
6cf268330f | ||
|
|
9d761815d8 | ||
|
|
6adfb84c0f | ||
|
|
d0b1608181 | ||
|
|
dc7831c31f | ||
|
|
06af78c770 | ||
|
|
58de2a786d | ||
|
|
452d8a47f5 | ||
|
|
6db89778a9 | ||
|
|
65fa663a1a | ||
|
|
59b1d02075 | ||
|
|
cfae20282f | ||
|
|
d06762418e | ||
|
|
a9adfbc06d | ||
|
|
5743ed5434 | ||
|
|
eecb238e18 | ||
|
|
3877cd580c | ||
|
|
393027c1ea | ||
|
|
6be034362b | ||
|
|
103280ca59 | ||
|
|
b715b30ae6 | ||
|
|
6bcc4cdfa3 | ||
|
|
a3ea732870 | ||
|
|
fd6ae8fc0e | ||
|
|
0a54b18d64 | ||
|
|
05072f5d22 | ||
|
|
28dc0fb512 | ||
|
|
2376d88481 | ||
|
|
db8543a0b4 | ||
|
|
b785658db1 | ||
|
|
2d10dd6c9c | ||
|
|
c6428bf9e4 | ||
|
|
0163a52e55 | ||
|
|
815df94db4 | ||
|
|
109578a3ed | ||
|
|
ac15686b47 | ||
|
|
93536976d0 | ||
|
|
6caab2ef06 | ||
|
|
a316403bb5 | ||
|
|
5cf7a61de1 | ||
|
|
d0deb6fe27 | ||
|
|
4ee35d3cd4 | ||
|
|
ec16828497 | ||
|
|
83b8127eb2 | ||
|
|
a42e933240 | ||
|
|
201f6cb900 | ||
|
|
c91d04760a | ||
|
|
91c52c15fd | ||
|
|
c6944bd7cf | ||
|
|
450f4a2d44 | ||
|
|
bda2ff4d23 | ||
|
|
f6d6e93847 | ||
|
|
9cdccbe7f0 | ||
|
|
2ad52e70bb | ||
|
|
656e35c080 | ||
|
|
895df94493 | ||
|
|
8d07ba7f23 | ||
|
|
7e23bc9eeb | ||
|
|
658e391f50 | ||
|
|
69e321c405 | ||
|
|
b5d9c02d9e | ||
|
|
2388dea859 | ||
|
|
178c4d9792 | ||
|
|
0aa286442f | ||
|
|
543fb535df | ||
|
|
9cb0b356ed | ||
|
|
7135aa3369 | ||
|
|
aacd297b3b | ||
|
|
bb2a007ed1 | ||
|
|
356974b478 | ||
|
|
99d72b92e4 | ||
|
|
eaaf106d7c | ||
|
|
86e26025f4 | ||
|
|
4470f0b60b | ||
|
|
3873aa61bc | ||
|
|
3335d54153 | ||
|
|
ad3f2088ef | ||
|
|
39baa4871a | ||
|
|
9d2c5ed74e | ||
|
|
6d76bea5c5 | ||
|
|
82c5c70eb7 | ||
|
|
bb448d6d55 | ||
|
|
44511acd27 | ||
|
|
5aa48cdbdf | ||
|
|
5f19d3ee09 | ||
|
|
598e4e2b79 | ||
|
|
161091513d | ||
|
|
9619e95283 | ||
|
|
6433f50edf | ||
|
|
28d637f620 | ||
|
|
4e50f0ab4b | ||
|
|
a028eaa41a | ||
|
|
5dcc90b31a | ||
|
|
a5c88693b8 | ||
|
|
9b6808273b | ||
|
|
c779c2c19e | ||
|
|
a23387c877 | ||
|
|
0f0d43cc17 | ||
|
|
94e1e8c377 | ||
|
|
5758f6512d | ||
|
|
22c4468180 | ||
|
|
1dd534cce9 | ||
|
|
ce0073e7b0 | ||
|
|
c0074c9f8c | ||
|
|
0fc39cf5f2 | ||
|
|
d4d9f2d4d7 | ||
|
|
802dd50163 | ||
|
|
9e9fcd47c0 | ||
|
|
e7d55d1e1b | ||
|
|
7cef510b29 | ||
|
|
870ade6fa9 | ||
|
|
803c3f2a29 | ||
|
|
4d9e595470 | ||
|
|
bcdbc5fd60 | ||
|
|
61d7103f82 | ||
|
|
93c69248d9 | ||
|
|
18c1735087 | ||
|
|
76fe3f1165 | ||
|
|
6b7fdb8d9e | ||
|
|
46270cfe84 | ||
|
|
2c2e9a2900 | ||
|
|
3d30eea1cb | ||
|
|
1e05c04a39 | ||
|
|
6e55f9d979 | ||
|
|
4a394dd6dd | ||
|
|
9b05ee96af | ||
|
|
97214bef1b | ||
|
|
1823a91d54 | ||
|
|
6f29b807c5 | ||
|
|
b50b215f20 | ||
|
|
33318ef4c6 | ||
|
|
2a284d7f25 | ||
|
|
9cbea88f6f | ||
|
|
2a1b645241 | ||
|
|
74626f8e3f | ||
|
|
fa1166e5d8 | ||
|
|
664db71104 | ||
|
|
cfefb7c629 | ||
|
|
e12724af15 | ||
|
|
d6a986d6cf | ||
|
|
e9bffd0cf2 | ||
|
|
9bef529486 | ||
|
|
ba7fb6525f | ||
|
|
df904e3744 | ||
|
|
b369916904 | ||
|
|
13480fe96c | ||
|
|
44711267fd | ||
|
|
9849fba97a | ||
|
|
bb02033567 | ||
|
|
aab95a1adc | ||
|
|
9f762ac206 | ||
|
|
5f8a319f48 | ||
|
|
24e82309c4 | ||
|
|
5310205911 | ||
|
|
9f7abf1865 | ||
|
|
e53f65f324 | ||
|
|
7601a1d4bf | ||
|
|
9e488b8917 | ||
|
|
b3320ab0fd | ||
|
|
d494c23059 | ||
|
|
7ef28d859f | ||
|
|
c3a5b22dad | ||
|
|
dea0bce0c4 | ||
|
|
0d6731fcc6 | ||
|
|
840ccb517e | ||
|
|
656d81e5dd | ||
|
|
50df8e7dd9 | ||
|
|
1c2dd055c1 | ||
|
|
c448bb3d9a | ||
|
|
268008945c | ||
|
|
d99ffdabd7 | ||
|
|
4f981c1229 | ||
|
|
265a01dcde | ||
|
|
c3f7b29d44 | ||
|
|
954ac1eb07 | ||
|
|
b5c6b55380 | ||
|
|
28982dc84b | ||
|
|
5d77b7b03e | ||
|
|
fa866b0677 | ||
|
|
f1f5bdb2be | ||
|
|
765e01cb00 | ||
|
|
5965879ed1 | ||
|
|
987d91f293 | ||
|
|
c869e60b04 | ||
|
|
d653f00a2d | ||
|
|
1cbd162a22 | ||
|
|
0ec87b967d | ||
|
|
0fded0022c | ||
|
|
fa901add35 | ||
|
|
f24c962785 | ||
|
|
f3e5b360c4 | ||
|
|
e417dade68 | ||
|
|
54f8f67ed5 | ||
|
|
23b9375570 | ||
|
|
58551ba37f | ||
|
|
5ad7c6a3ba | ||
|
|
71c01c0f3b | ||
|
|
326f9e6e93 | ||
|
|
9f0f9054d1 | ||
|
|
00858b6633 | ||
|
|
0463513d5d | ||
|
|
56a36f34a9 | ||
|
|
73dfe2ef11 | ||
|
|
94b82b6865 | ||
|
|
1678dcac82 | ||
|
|
497a509d60 | ||
|
|
bddadc6a9e | ||
|
|
ed0d832e08 | ||
|
|
38a09b164d | ||
|
|
b39c06a9ef | ||
|
|
09626ee6f6 | ||
|
|
3a8076fc3c | ||
|
|
31a7838851 | ||
|
|
7dff254604 | ||
|
|
d4078f0f91 | ||
|
|
90152240c7 | ||
|
|
85e78d2490 | ||
|
|
3368615a77 | ||
|
|
ee8d20a43d | ||
|
|
8f0f01e6b2 | ||
|
|
bdf0953f35 | ||
|
|
e056c30f05 | ||
|
|
b07979cbc7 | ||
|
|
01cbf67bcb | ||
|
|
ce193b60f7 | ||
|
|
2057b0cfa6 | ||
|
|
366de4955b | ||
|
|
32056ab2f1 | ||
|
|
d4e45cc3b0 | ||
|
|
48aa6d6e05 | ||
|
|
5ca3cb18e4 | ||
|
|
c701998a35 | ||
|
|
4453705938 | ||
|
|
144f3fa035 | ||
|
|
fd507d6657 | ||
|
|
9aa0ff6de6 | ||
|
|
37ea8adb73 | ||
|
|
0d6992b80b | ||
|
|
7b15957bb8 | ||
|
|
b1c5c38bdb | ||
|
|
e6b2243496 | ||
|
|
0eb298a943 | ||
|
|
22debb4374 | ||
|
|
ef71405143 | ||
|
|
0990a9b87d | ||
|
|
87727ef360 | ||
|
|
dbc8558ec8 | ||
|
|
143cdf529d | ||
|
|
919c47c993 | ||
|
|
2773c5f500 | ||
|
|
c070e5f40d | ||
|
|
31103faf92 | ||
|
|
05ec937b06 | ||
|
|
3c5a27d9f5 | ||
|
|
1ad805fda3 | ||
|
|
6171ba7c7a | ||
|
|
c60b82d40a | ||
|
|
13a04a0442 | ||
|
|
db3701d76a | ||
|
|
a02d14e425 | ||
|
|
f774df972d | ||
|
|
04ed1112a4 | ||
|
|
2075ed46e1 | ||
|
|
fdb88dd401 | ||
|
|
205958464a | ||
|
|
a7aaffcfe5 | ||
|
|
0222b1fccd | ||
|
|
b98d6fd8ca | ||
|
|
3f6baebd71 | ||
|
|
ce2aa67832 | ||
|
|
83a77dfc74 | ||
|
|
27f406f757 | ||
|
|
72e17b06fc | ||
|
|
49e1e38f05 | ||
|
|
d64a61738f | ||
|
|
d7b8c32c10 | ||
|
|
42423a86a6 | ||
|
|
8ee51a1cb0 | ||
|
|
5f2bc37e96 | ||
|
|
1ba1450e9c | ||
|
|
afabf3650b | ||
|
|
59bad90480 | ||
|
|
a342ad5bda | ||
|
|
3d0b9edb9c | ||
|
|
a23ec160c0 | ||
|
|
b5a29675d6 | ||
|
|
8b3ffe5d0d | ||
|
|
c0b1d201ad | ||
|
|
a8f7080bf2 | ||
|
|
9445a06871 | ||
|
|
d9a66a5894 | ||
|
|
bbb1db014c | ||
|
|
6b5156468c | ||
|
|
0b7ba793b4 | ||
|
|
ef86fceedd | ||
|
|
db6a205f43 | ||
|
|
e8d0da3ce3 | ||
|
|
7e977ad811 | ||
|
|
81cae514a0 | ||
|
|
2d38ef921e | ||
|
|
9f05595cd6 | ||
|
|
178ed1e5af | ||
|
|
2105cdf10b | ||
|
|
a471e057f5 | ||
|
|
ed5de984f2 | ||
|
|
301f5e7113 | ||
|
|
596dbf4ac8 | ||
|
|
9088d9fb9e | ||
|
|
5e58efb07f | ||
|
|
4a835d5306 | ||
|
|
7d62fcd8cc | ||
|
|
42e7c23ee2 | ||
|
|
50e7f4af8e | ||
|
|
3371c6a099 | ||
|
|
feba2af9ed | ||
|
|
6fcfeb2889 | ||
|
|
14f55c9ee5 | ||
|
|
e94d1d4bae | ||
|
|
d48b6ea80e | ||
|
|
8ff8f9628a | ||
|
|
4272f9de99 | ||
|
|
8e0e66d662 | ||
|
|
48349070b3 | ||
|
|
2379899f02 | ||
|
|
35e1cf5733 | ||
|
|
139dff97ef | ||
|
|
be006403ed | ||
|
|
f67a805c1f | ||
|
|
35cfb01973 | ||
|
|
fcea42bf38 | ||
|
|
ad3fe44418 | ||
|
|
ede02eaa8c | ||
|
|
ab27cbef07 | ||
|
|
c2d4f2578c | ||
|
|
fa4c77b495 | ||
|
|
e78c9703b8 | ||
|
|
28e7ec0ef9 | ||
|
|
c3dcd58390 | ||
|
|
c77814235c | ||
|
|
e8d5dbec3e | ||
|
|
d5204e9813 | ||
|
|
d29c9613b9 | ||
|
|
351c89f235 | ||
|
|
9a4cd431e8 | ||
|
|
cef6d5f365 | ||
|
|
1c116eda3b | ||
|
|
a860e96d35 | ||
|
|
d20320e87e | ||
|
|
43ae471038 | ||
|
|
00ff3ecd38 | ||
|
|
b6d6cb4359 | ||
|
|
5a09134d3f | ||
|
|
719a0956ac | ||
|
|
3c197ec3d9 | ||
|
|
0b27244c06 | ||
|
|
0a60ca6468 | ||
|
|
990572ff31 | ||
|
|
8df0c31d35 | ||
|
|
d89b26b77d | ||
|
|
401be9d9fa | ||
|
|
28341fc1f3 | ||
|
|
be277d0218 | ||
|
|
a7315eaee0 | ||
|
|
a1bcd08658 | ||
|
|
cf592554f1 | ||
|
|
cb950ac5d7 | ||
|
|
4f29e2c197 | ||
|
|
c9e0acd055 | ||
|
|
63254159ca | ||
|
|
c5fd4b41b6 | ||
|
|
f5cfd6bfb5 | ||
|
|
81c2e7b059 | ||
|
|
eb481215e3 | ||
|
|
20335e3f27 | ||
|
|
931b3fdc68 | ||
|
|
36a5b4e1d3 | ||
|
|
107b21c463 | ||
|
|
24480fd18c | ||
|
|
09e50d432e | ||
|
|
402ea35971 | ||
|
|
7f074554c4 | ||
|
|
f1d22c6ca4 | ||
|
|
7a877a9653 | ||
|
|
ce59d3f95f | ||
|
|
081f2d14b7 | ||
|
|
14a97b4879 | ||
|
|
4d8526c24d | ||
|
|
66cd88d4c2 | ||
|
|
1f4da1ab48 | ||
|
|
30a12a600a | ||
|
|
994b68bc4c | ||
|
|
d710b88c3e | ||
|
|
214ddc9807 | ||
|
|
6f18a49ba0 | ||
|
|
8b54e965a2 | ||
|
|
0ddde452e3 | ||
|
|
7dbd4a3150 | ||
|
|
98b5fa7bce | ||
|
|
33a45ce0a2 | ||
|
|
41ba37c04e | ||
|
|
f3542b7402 | ||
|
|
f5bb221ecd | ||
|
|
07ab48da1f | ||
|
|
2f50d80a75 | ||
|
|
b06a6fbb51 | ||
|
|
f2490beea2 | ||
|
|
09ab1dabdf | ||
|
|
e1cf360825 | ||
|
|
de8dd1bb8d | ||
|
|
f29bafe89a | ||
|
|
d4695f0192 | ||
|
|
2ab782f4b6 | ||
|
|
aa29ad2014 | ||
|
|
e7fed0f3cf | ||
|
|
3c3c963fd3 | ||
|
|
ae8a25e481 | ||
|
|
8312cfc0aa | ||
|
|
0bbb2aa60d | ||
|
|
9b43ce0ab7 | ||
|
|
f71cd57bc7 | ||
|
|
1570657e28 | ||
|
|
0fbfee8dc8 | ||
|
|
7269dc4e5f | ||
|
|
c5245cb8e2 | ||
|
|
15b3cb20b3 | ||
|
|
42da1b6c23 | ||
|
|
b62db5675d | ||
|
|
da92f2082d | ||
|
|
746dc4f81d | ||
|
|
7b9e06a471 | ||
|
|
7c3c1988a3 | ||
|
|
a005a961f9 | ||
|
|
fb87e180da | ||
|
|
4b1f5b4a44 | ||
|
|
0b24871415 | ||
|
|
a7cb0959e2 | ||
|
|
11e6c93932 | ||
|
|
dd74dd3b1b | ||
|
|
9351bef61c | ||
|
|
f6d79616a4 | ||
|
|
48d88614fb | ||
|
|
f45241d494 | ||
|
|
8d405c1013 | ||
|
|
df1d742339 | ||
|
|
96015d402b | ||
|
|
85a30369c4 | ||
|
|
47289db13e | ||
|
|
1770608525 | ||
|
|
183462ff85 | ||
|
|
b84241c6ad | ||
|
|
c13bc6cfbd | ||
|
|
04da532962 | ||
|
|
fb68bd46c8 | ||
|
|
4963f4dc08 | ||
|
|
c92e44bb1d | ||
|
|
1dcb63f2cc | ||
|
|
b9bcc35dd2 | ||
|
|
0b967b9f45 | ||
|
|
20ae560bb1 | ||
|
|
7fe787c5ab | ||
|
|
5fe6ce6656 | ||
|
|
47ddaaad7b | ||
|
|
8dcc1a4a53 | ||
|
|
0a8f7aa39d | ||
|
|
31071ddb17 | ||
|
|
a003733e51 | ||
|
|
13b105504b | ||
|
|
3b16cdb920 | ||
|
|
607b7cbb0a | ||
|
|
87c6dab41d | ||
|
|
859ad6ea38 | ||
|
|
3e73148390 | ||
|
|
eb2b41bc8a | ||
|
|
75981a3e5f | ||
|
|
062faba8d1 | ||
|
|
52f61c08a5 | ||
|
|
34fd78961b | ||
|
|
9cf68b8903 | ||
|
|
d0526566c4 | ||
|
|
0271556c07 | ||
|
|
8cce2a5977 | ||
|
|
e06eb96fa7 | ||
|
|
edbbc62e5c | ||
|
|
bbd478f322 | ||
|
|
17eb9604e7 | ||
|
|
588e0abdd8 | ||
|
|
b08f5c8390 | ||
|
|
344e680fe3 | ||
|
|
4225d9b3b3 | ||
|
|
1c8c76831e | ||
|
|
912f878906 | ||
|
|
ec081cf0c5 | ||
|
|
1e5849f504 | ||
|
|
10fcc7885f | ||
|
|
29665d5a03 | ||
|
|
e7337d3cb4 | ||
|
|
0904036009 | ||
|
|
c1609a826a | ||
|
|
2453c45b06 | ||
|
|
63c8f87776 | ||
|
|
f20506bf43 | ||
|
|
ec51ff835a | ||
|
|
6a58a08781 | ||
|
|
3f682d467d | ||
|
|
676fc8ea08 | ||
|
|
a732159a3a | ||
|
|
09fc3ca882 | ||
|
|
300ee0183e | ||
|
|
d7f352d699 | ||
|
|
994dba86f6 | ||
|
|
9990e0b807 | ||
|
|
30d0cb7590 | ||
|
|
52b201ff87 | ||
|
|
eb2ffff780 | ||
|
|
d3210d4e27 | ||
|
|
5c2ab9087a | ||
|
|
194c657ba7 | ||
|
|
a5f9c2a022 | ||
|
|
0051022906 | ||
|
|
cfcb6c6b64 | ||
|
|
799aef0b65 | ||
|
|
1b175d85c0 | ||
|
|
9d19c59d44 | ||
|
|
96ccbbe4a2 | ||
|
|
c7b7732092 | ||
|
|
77da67ce98 | ||
|
|
38360f379f | ||
|
|
5a60dfa988 | ||
|
|
590ed037dd | ||
|
|
e71399f5af | ||
|
|
34aadda47a | ||
|
|
0c64f4b006 | ||
|
|
72aff3a695 | ||
|
|
1122681c4f | ||
|
|
78a7f8a207 | ||
|
|
60a118f391 | ||
|
|
cd66399049 | ||
|
|
b0a4b6ebed | ||
|
|
99a4ed9e84 | ||
|
|
e93c08f33a | ||
|
|
a47906cac9 | ||
|
|
423ea9cbc6 | ||
|
|
35714d4f2d | ||
|
|
129cf8d02d | ||
|
|
f561388a1f | ||
|
|
a587247c0d | ||
|
|
72121f98de | ||
|
|
0a66dcc0d6 | ||
|
|
a7605d5699 | ||
|
|
fe3420a512 | ||
|
|
c7e78c21c9 | ||
|
|
228a9a8807 | ||
|
|
61b64fa904 | ||
|
|
6bcd6e2d80 | ||
|
|
d60e079e50 | ||
|
|
ecfd9da7a1 | ||
|
|
697f1f47dd | ||
|
|
19058d3760 | ||
|
|
3a60b6fff7 | ||
|
|
dad4fbca75 | ||
|
|
d56121fdec | ||
|
|
a3db0a005a | ||
|
|
ddf1fdc89c | ||
|
|
e70ae12f2a | ||
|
|
b6660d656f | ||
|
|
2814ce7b7f | ||
|
|
911ebddd69 | ||
|
|
5eacfff7ae | ||
|
|
a06ab724ad | ||
|
|
d8e7bf130f | ||
|
|
058c05a1f7 | ||
|
|
9d06bda4f8 | ||
|
|
346485da8c | ||
|
|
5b2f05741e | ||
|
|
cb4ecea830 | ||
|
|
112b232f9e | ||
|
|
9b0ba9031e | ||
|
|
389885893c | ||
|
|
77a4c2fe34 | ||
|
|
f6f5570d0c | ||
|
|
6a9b218e22 | ||
|
|
8088f37632 | ||
|
|
7bf09906d3 | ||
|
|
6554e142cc | ||
|
|
bf2d033ac3 | ||
|
|
bc688a3d8d | ||
|
|
5bcfe9f192 | ||
|
|
cfbf8a47d7 | ||
|
|
1eca55d96c | ||
|
|
93d7242021 | ||
|
|
612463165a | ||
|
|
c9a8dc21b1 | ||
|
|
c17f0b1e6e | ||
|
|
e5e63efffe | ||
|
|
5152d7e649 | ||
|
|
37a07ace72 | ||
|
|
46524e4298 | ||
|
|
c7eb5fed35 | ||
|
|
5c827baa1a | ||
|
|
27c2562a7f | ||
|
|
2c3dde0d2e | ||
|
|
8219b0398e | ||
|
|
11b4f642a6 | ||
|
|
514ea03655 | ||
|
|
84b86bb6a1 | ||
|
|
fcd7be632c | ||
|
|
cbf043ac7e | ||
|
|
180d9bfb73 | ||
|
|
8423165df4 | ||
|
|
02e628f533 | ||
|
|
3c89e9c015 | ||
|
|
5544491728 | ||
|
|
29d2c9c25e | ||
|
|
e883f76e54 | ||
|
|
10d445badb | ||
|
|
8f2ea30da0 | ||
|
|
a27adffec0 | ||
|
|
14489e3b4b | ||
|
|
3503024fd7 | ||
|
|
1f01c004ae | ||
|
|
cfdb0f4466 | ||
|
|
4168733825 | ||
|
|
0c4e4af228 | ||
|
|
f4cf646f80 | ||
|
|
b641a0b0b3 | ||
|
|
a4bbc9f2ba | ||
|
|
b0c7a6c537 | ||
|
|
eacd9cb6e8 | ||
|
|
8729796e87 | ||
|
|
3fe7501802 | ||
|
|
54e69b266d | ||
|
|
44f10b9ff9 | ||
|
|
e9b7eac289 | ||
|
|
2772a9dcc6 | ||
|
|
1a66be8665 | ||
|
|
2c211d4730 | ||
|
|
88855ab695 | ||
|
|
46b42da5bd | ||
|
|
da3a5fe787 | ||
|
|
ff4af3786e | ||
|
|
ac945254d6 | ||
|
|
9a9038d5e0 | ||
|
|
fa759ae7b2 | ||
|
|
f358605f70 | ||
|
|
d6ba5684e0 | ||
|
|
09c9b25178 | ||
|
|
e86f442249 | ||
|
|
e51ebdcad6 | ||
|
|
eeaa1614fa | ||
|
|
ef79439557 | ||
|
|
8c910b67b4 | ||
|
|
993f75d014 | ||
|
|
01d318d902 | ||
|
|
157a7743a3 | ||
|
|
597dff7ac3 | ||
|
|
c4c596bbf4 | ||
|
|
e21ebd18a6 | ||
|
|
57adcd3c65 | ||
|
|
ae57bfeb89 | ||
|
|
ef21ceb332 | ||
|
|
53ae826186 | ||
|
|
b040db24d4 | ||
|
|
f9f7870c0d | ||
|
|
51a5e97e11 | ||
|
|
69f95e6398 | ||
|
|
10fa36ccf0 | ||
|
|
3b24dfd4c5 | ||
|
|
21673c1ee9 | ||
|
|
8d15166931 | ||
|
|
ebc580ea76 | ||
|
|
4561148ab2 | ||
|
|
acc9537bb7 | ||
|
|
0bb4ff8417 | ||
|
|
314a7b9c56 | ||
|
|
797e0b4641 | ||
|
|
87d2827f0f | ||
|
|
aabeb5f493 | ||
|
|
7f0a35c88a | ||
|
|
29ef770759 | ||
|
|
5927a24f16 | ||
|
|
432cc42cba | ||
|
|
baa4e73ba5 | ||
|
|
eac6856c1d | ||
|
|
78ef129634 | ||
|
|
2769acecc0 | ||
|
|
7beb521d68 | ||
|
|
30823b752d | ||
|
|
e3e9c89a80 | ||
|
|
bb7cc60118 | ||
|
|
63c3b1f56b | ||
|
|
561d7bcc42 | ||
|
|
e1a9004ed5 | ||
|
|
71aa7ec0ef | ||
|
|
8129a488a9 | ||
|
|
5c4980ed2f | ||
|
|
229da1f62d | ||
|
|
cb0906a52d | ||
|
|
5f4cb63324 | ||
|
|
8cf3caa77e | ||
|
|
475c8c594a | ||
|
|
c8d5cec338 | ||
|
|
f5bd1c1eb9 | ||
|
|
a7f04ad732 | ||
|
|
5deda4603e | ||
|
|
cd373f4bca | ||
|
|
9797d7450c | ||
|
|
f8be719dc1 | ||
|
|
122efc00fc | ||
|
|
1f699ac1d1 | ||
|
|
2cd78d5d2f | ||
|
|
0bac7e8d90 | ||
|
|
0002a7549d | ||
|
|
b4b7b55ce1 | ||
|
|
5c753db661 | ||
|
|
d6977b7be0 | ||
|
|
97bb7b534a | ||
|
|
7da9de9991 | ||
|
|
69e7b35abb | ||
|
|
b3123f4d4a | ||
|
|
9d293b2cb4 | ||
|
|
bb47181daa | ||
|
|
009a489d3a | ||
|
|
06fa6eb874 | ||
|
|
12729b36fb | ||
|
|
62b008e693 | ||
|
|
08d27265fc | ||
|
|
c462323dd5 | ||
|
|
967715ab3b | ||
|
|
7b818154dd | ||
|
|
4f6ff4d177 | ||
|
|
0968394c84 | ||
|
|
809b09a1c2 | ||
|
|
b5f93edc7e | ||
|
|
0a72a64489 | ||
|
|
d6ca579495 | ||
|
|
34b8119a21 | ||
|
|
ad74c2c710 | ||
|
|
064d3ad5d5 | ||
|
|
1f584984f4 | ||
|
|
779b79c54b | ||
|
|
4c222ce2cc | ||
|
|
a7a9c0cdf6 | ||
|
|
e979cbba4e | ||
|
|
d0b34e057b | ||
|
|
e4045952c8 | ||
|
|
678e4187c5 | ||
|
|
d1d04b3fb4 | ||
|
|
830ef7e0dc | ||
|
|
2426b51512 | ||
|
|
bcf90e8094 | ||
|
|
2825cb63fb | ||
|
|
85dd187cb0 | ||
|
|
0e71f80a78 | ||
|
|
e0df3d4208 | ||
|
|
62a047337c | ||
|
|
c25d66b11b | ||
|
|
11eb742540 | ||
|
|
d7763b688d | ||
|
|
9aba0560a1 | ||
|
|
70bc239d01 | ||
|
|
a5f5f72346 | ||
|
|
73b7332639 | ||
|
|
64f93cec65 | ||
|
|
58097f0d60 | ||
|
|
41dc16453e | ||
|
|
fb7ff50feb | ||
|
|
97383a71af | ||
|
|
03a142ff62 | ||
|
|
b244b1e1ad | ||
|
|
62916f6a7e | ||
|
|
25b91af8bc | ||
|
|
cce20319ad | ||
|
|
8e33fdf49f | ||
|
|
fe91d13ff3 | ||
|
|
4cea3c2a3b | ||
|
|
7dcdc883e4 | ||
|
|
31fef27ffd | ||
|
|
50043e6574 | ||
|
|
27947dc65f | ||
|
|
5cfe974310 | ||
|
|
c6afaf81e0 | ||
|
|
61b6b90f77 | ||
|
|
36baeed86e | ||
|
|
7c0bc9afd3 | ||
|
|
138f843f5d | ||
|
|
689c0f7b31 | ||
|
|
5d731463f2 | ||
|
|
36f654f41c | ||
|
|
3c79c0f7a8 | ||
|
|
abc91cc7a5 | ||
|
|
211562a0d6 | ||
|
|
075cb9d5f2 | ||
|
|
2955ac3c9b | ||
|
|
a7933c925b | ||
|
|
b52a862f4f | ||
|
|
1fe68c66c5 | ||
|
|
be86f43947 | ||
|
|
a86dc0fc7b | ||
|
|
eaaf3b8812 | ||
|
|
d3463bbef2 | ||
|
|
43db13ab3f | ||
|
|
4a865c4fb8 | ||
|
|
70a526fb4f | ||
|
|
0839c6b6f9 | ||
|
|
7a83e7901a | ||
|
|
928d41c52d | ||
|
|
fdc20e28ff | ||
|
|
0c14560eaa | ||
|
|
a49db96d49 | ||
|
|
66642e7bdf | ||
|
|
6843e22a7b | ||
|
|
4baf13f82c | ||
|
|
f80e6887fe | ||
|
|
548061ff13 | ||
|
|
9d9cb4f139 | ||
|
|
0611ad54d0 | ||
|
|
29a6528e31 | ||
|
|
3bf92fe2ad | ||
|
|
a068e194e9 | ||
|
|
6485fd0f88 | ||
|
|
dec0971c14 | ||
|
|
bbe31149b4 | ||
|
|
b7ea0fa681 | ||
|
|
d72a0ca009 | ||
|
|
9d94067cd0 | ||
|
|
137a6dc771 | ||
|
|
ce36806169 | ||
|
|
6fe8dfc188 | ||
|
|
cb386b7f8d | ||
|
|
8b132c37a0 | ||
|
|
6e8d644574 | ||
|
|
1536cc085c | ||
|
|
87c7ec405d | ||
|
|
9bcecae9a2 | ||
|
|
fbd8113217 | ||
|
|
66c3109037 | ||
|
|
e8116de4bb | ||
|
|
c3de215b08 | ||
|
|
8de7801662 | ||
|
|
ac80b2322b | ||
|
|
70632dd33a | ||
|
|
9ee5c508e4 | ||
|
|
008963156d | ||
|
|
b79f740e2b | ||
|
|
11b0cfb536 | ||
|
|
6abf556234 | ||
|
|
75de91ae1c | ||
|
|
6dd735eab0 | ||
|
|
dfeb54d7f1 | ||
|
|
a053d87bd2 | ||
|
|
e33062feeb | ||
|
|
b0dc79d071 | ||
|
|
19f092bcbb | ||
|
|
fd81096f63 | ||
|
|
2c32460a6e | ||
|
|
9c9c2a23cc | ||
|
|
2067599843 | ||
|
|
00534d91d4 | ||
|
|
88e6c6f93c | ||
|
|
82d59e159f | ||
|
|
aa878482d3 | ||
|
|
754d3762df | ||
|
|
1e643c1c07 | ||
|
|
36ce803828 | ||
|
|
28bd24022b | ||
|
|
84c8eeccc6 | ||
|
|
1a06502806 | ||
|
|
26c72b006c | ||
|
|
b8d7eef536 | ||
|
|
5f23fdd590 | ||
|
|
84533b544a | ||
|
|
673c3c13e7 | ||
|
|
168229e602 | ||
|
|
f2265f0dce | ||
|
|
9ea98b2e2b | ||
|
|
48bb93e8d4 | ||
|
|
24cdbaeb51 | ||
|
|
febfab40de | ||
|
|
89ee0afd61 | ||
|
|
ffa6b4fac8 | ||
|
|
e46444f3b9 | ||
|
|
f4afe00f15 | ||
|
|
92b0f917d6 | ||
|
|
ba68969943 | ||
|
|
72ce7fcb56 | ||
|
|
4df31aa6b0 | ||
|
|
262d3d3dfd | ||
|
|
49cfbdd95f | ||
|
|
a161316dc2 | ||
|
|
800a3dff9d | ||
|
|
0872c727d5 | ||
|
|
d965308346 | ||
|
|
4ffb1e0cdc | ||
|
|
8fc1fe7d8e | ||
|
|
c6d28d7fc5 | ||
|
|
373548d396 | ||
|
|
f1c520fbe8 | ||
|
|
a1babfb2ce | ||
|
|
407c38b4be | ||
|
|
f868939eb9 | ||
|
|
84b143545d | ||
|
|
938d88fa2b | ||
|
|
67951b39d9 | ||
|
|
4b5a0fb59d | ||
|
|
19363c86f9 | ||
|
|
c7daa72b4e | ||
|
|
f7bde024cb | ||
|
|
f4b36867ff | ||
|
|
2f2273b8a8 | ||
|
|
f7ea7061d6 | ||
|
|
83ec23220c | ||
|
|
dc7dadd9a1 | ||
|
|
3563924c6f | ||
|
|
ec1a294ef0 | ||
|
|
eb9282c60a | ||
|
|
2dc5dea10b | ||
|
|
3066a077c7 | ||
|
|
72b317352f | ||
|
|
75a62a5729 | ||
|
|
3085b0e30c | ||
|
|
5c9aa8c9ca | ||
|
|
2c70f87b93 | ||
|
|
aca4318351 | ||
|
|
16777c30ca | ||
|
|
a897c313fb | ||
|
|
9eddc330c5 | ||
|
|
20d813d2ea | ||
|
|
14a990f7ea | ||
|
|
2597a57f3a | ||
|
|
7ae97e4c69 | ||
|
|
e5aa631da0 | ||
|
|
a268f9a892 | ||
|
|
77d08d2340 | ||
|
|
2ffed72977 | ||
|
|
81cb93d03e | ||
|
|
f7d162b955 | ||
|
|
342a661606 | ||
|
|
ece475076e | ||
|
|
8474b74cbf | ||
|
|
9cebb8769f | ||
|
|
0289b71497 | ||
|
|
dc33a3a383 | ||
|
|
b59b0f5387 | ||
|
|
dc6c6fc5e9 | ||
|
|
17649157c1 | ||
|
|
fba25fea0f | ||
|
|
a5cf59d854 | ||
|
|
a971072cb8 | ||
|
|
c946d2309e | ||
|
|
6bde59c14a | ||
|
|
db3de8ce6f | ||
|
|
f53c541538 | ||
|
|
44ea0c418a | ||
|
|
16c68dd51d | ||
|
|
c1d4ba3c80 | ||
|
|
226197a827 | ||
|
|
2516634856 | ||
|
|
d5a720fa0c | ||
|
|
5ac5c30fd6 | ||
|
|
f4db30e988 | ||
|
|
5fc839513e | ||
|
|
7878336866 | ||
|
|
cadf2640fe | ||
|
|
98b79b042b | ||
|
|
51fe7d5599 | ||
|
|
99c8d5d238 | ||
|
|
0e34423dd6 | ||
|
|
18f5d656de | ||
|
|
22fc90713a | ||
|
|
fe444560fd | ||
|
|
0cda5db916 | ||
|
|
986c9dcf5f | ||
|
|
4bfa5e7de8 | ||
|
|
ec10117179 | ||
|
|
77f856a052 | ||
|
|
15fb572b80 | ||
|
|
e71680c8e3 | ||
|
|
005e88d169 | ||
|
|
84c5a76e90 | ||
|
|
b6dd109fcb | ||
|
|
0dfe4b1ae2 | ||
|
|
c9e7ec07f5 | ||
|
|
369ebf48b2 | ||
|
|
c2e0c1be95 | ||
|
|
4ab827da06 | ||
|
|
b965e5bf56 | ||
|
|
b41c633a54 | ||
|
|
1823305a5e | ||
|
|
c4f9515c09 | ||
|
|
de81b135f4 | ||
|
|
0d773a9a61 | ||
|
|
13e4adf18f | ||
|
|
da9ab111bc | ||
|
|
adbb176697 | ||
|
|
347f2ab1e1 | ||
|
|
8e89d2bf92 | ||
|
|
9a168aae45 | ||
|
|
b03daa3c71 | ||
|
|
cd875bd0f9 | ||
|
|
743a5d69b2 | ||
|
|
74b82ea990 | ||
|
|
220f562242 | ||
|
|
46dd16caf9 | ||
|
|
325981b9c9 | ||
|
|
85dcb39f5e | ||
|
|
fea5d234ae | ||
|
|
aa4574cfde | ||
|
|
2e56228323 | ||
|
|
617f075c19 | ||
|
|
ba512f004e | ||
|
|
45d10f2667 | ||
|
|
47d532733b | ||
|
|
aad75b9eea | ||
|
|
73740bc920 | ||
|
|
33cff4a08c | ||
|
|
c16044ee2c | ||
|
|
2cbeb50927 | ||
|
|
3167c24f75 | ||
|
|
c7fd603933 | ||
|
|
c2510d302a | ||
|
|
d8203dce77 | ||
|
|
803b1fa505 | ||
|
|
ca46f4c9ac | ||
|
|
51f760332b | ||
|
|
a2caa53664 | ||
|
|
88a9628e7e | ||
|
|
34a33bbda1 | ||
|
|
261432a7fd | ||
|
|
06d1c082fb | ||
|
|
7f07794ce4 | ||
|
|
dc7cd0993a | ||
|
|
7b31c40676 | ||
|
|
81e9d8ce0f | ||
|
|
0315aa67d7 | ||
|
|
f57e73d87b | ||
|
|
3e2ab9c30a | ||
|
|
ad7d1a53d9 | ||
|
|
46abd002f2 | ||
|
|
8e7595cd34 | ||
|
|
8e150c6862 | ||
|
|
b619d91aab | ||
|
|
0060b5e5af | ||
|
|
ada64d97a6 | ||
|
|
14c0aa1ec1 | ||
|
|
b2705dfb2d | ||
|
|
8638698d61 | ||
|
|
d4328f4d54 | ||
|
|
8eecffd3d5 | ||
|
|
bf2646978a | ||
|
|
f5eb522e39 | ||
|
|
e449b5a436 | ||
|
|
318d816a37 | ||
|
|
684000074e | ||
|
|
5e252ed4af | ||
|
|
eb936d579c | ||
|
|
a278036f1d | ||
|
|
0cf84612e9 | ||
|
|
c2b17ea7a8 | ||
|
|
f4ce5618bf | ||
|
|
8290c9e1f4 | ||
|
|
abee010291 | ||
|
|
004f714924 | ||
|
|
9fcd23d63a | ||
|
|
9a317a2c23 | ||
|
|
e303405ee0 | ||
|
|
5c0062d9df | ||
|
|
bd9a411106 | ||
|
|
6a17e4d141 | ||
|
|
ad169fd6e7 | ||
|
|
f23ead56ad | ||
|
|
98f4d973e7 | ||
|
|
cb2b0aabf3 | ||
|
|
b5fcdea62f | ||
|
|
0eec924c02 | ||
|
|
e27ef39dcf | ||
|
|
37587fd12a | ||
|
|
d930853edf | ||
|
|
e9e525ec63 | ||
|
|
13aee13f18 | ||
|
|
f6df7e5f6f | ||
|
|
cfa80be4c7 | ||
|
|
cde51a3059 | ||
|
|
d767f2ff6b | ||
|
|
074e9fc0eb | ||
|
|
ee75eea939 | ||
|
|
1026f65280 | ||
|
|
b101831a1b | ||
|
|
ba555c0a7c | ||
|
|
74169e6caf | ||
|
|
70ca58f98e | ||
|
|
fd397de6b4 | ||
|
|
6cd672f9e6 | ||
|
|
097e9e446e | ||
|
|
546ded3bdd | ||
|
|
c60d7ed75e | ||
|
|
a3b01e4eae | ||
|
|
818c288ba0 | ||
|
|
ad2fe00af0 | ||
|
|
f8340d770c | ||
|
|
14e21a1009 | ||
|
|
950b8ecb55 | ||
|
|
5525462f11 | ||
|
|
35fa384a5b | ||
|
|
fee0c92280 | ||
|
|
7481b42499 | ||
|
|
7001fd2ef1 | ||
|
|
6aa4737b6e | ||
|
|
5eca6016fa | ||
|
|
585f2a2483 | ||
|
|
26b9659140 | ||
|
|
b2147d42a8 | ||
|
|
08a76dc420 | ||
|
|
f9ec4e37e9 | ||
|
|
d331ece0be | ||
|
|
57cc4f698f | ||
|
|
0b4416d9eb | ||
|
|
4c9aa11b9a | ||
|
|
3c9380c2a2 | ||
|
|
ca8f571978 | ||
|
|
70a39eeea4 | ||
|
|
329abfa617 | ||
|
|
739b59c65d | ||
|
|
06de0d61fa | ||
|
|
557c6ad05a | ||
|
|
9807d3e9c7 | ||
|
|
761fb7dafe | ||
|
|
5b577eb148 | ||
|
|
f1a7524acf | ||
|
|
8eca458e38 | ||
|
|
67c608e76f | ||
|
|
3a203d249e | ||
|
|
bd2f00d5f7 | ||
|
|
b74ca3962b | ||
|
|
77f9618e0c | ||
|
|
e5cd72657e | ||
|
|
98d754f605 | ||
|
|
c2ba3d0ab3 | ||
|
|
d682898828 | ||
|
|
d94c81ed23 | ||
|
|
73599be5b8 | ||
|
|
82ee4cca0f | ||
|
|
870b80998e | ||
|
|
2f9f4c2bc0 | ||
|
|
1ad0c195ad | ||
|
|
cc0bfef0ad | ||
|
|
6f2798e8c4 | ||
|
|
819f338c68 | ||
|
|
5d2d5819cf | ||
|
|
393c131104 | ||
|
|
af149b09a9 | ||
|
|
3087e05212 | ||
|
|
0f7d1dd801 | ||
|
|
461483ea36 | ||
|
|
7bf43cfe9a | ||
|
|
30d24de4d3 | ||
|
|
e2f430dd09 | ||
|
|
5369d48eae | ||
|
|
1bff019a64 | ||
|
|
60dfec3375 | ||
|
|
da47098ccd | ||
|
|
f1ee7bb430 | ||
|
|
be6d75c1ff | ||
|
|
9bf065e1f0 | ||
|
|
8543647cbc | ||
|
|
5c3d18b0e2 | ||
|
|
f6826b60b0 | ||
|
|
5bf86ca7bf | ||
|
|
a8cbc4fbdb | ||
|
|
67bbce0b07 | ||
|
|
dd2a8b682c | ||
|
|
cfba798b89 | ||
|
|
d226c52630 | ||
|
|
202d7eb047 | ||
|
|
891e5d795f | ||
|
|
744538eec6 | ||
|
|
97b2d2d561 | ||
|
|
1900d1ae52 | ||
|
|
3dfeefd174 | ||
|
|
7acb19756b | ||
|
|
dc4c68903f | ||
|
|
2cfe8964f3 | ||
|
|
402be52681 | ||
|
|
8f12cc527b | ||
|
|
0a78310fdd | ||
|
|
012f3e695e | ||
|
|
aad16e2359 | ||
|
|
d3998f5d13 | ||
|
|
0b6d2381dc | ||
|
|
b5070cf647 | ||
|
|
97a2aa5015 | ||
|
|
938cff5381 | ||
|
|
ca5890e6e3 | ||
|
|
b5e569a80d | ||
|
|
e868fb4c22 | ||
|
|
16d3404476 | ||
|
|
f156b21ead | ||
|
|
daef6ab06b | ||
|
|
2f92efc44b | ||
|
|
94bb1665a9 | ||
|
|
1d1c30a483 | ||
|
|
306456c3c7 | ||
|
|
bf56d5c3d7 | ||
|
|
6e6795a6f4 | ||
|
|
befb3f69bd | ||
|
|
65d90495eb | ||
|
|
8f9d991a6c | ||
|
|
fd6f6d6c9a | ||
|
|
20fbb84118 | ||
|
|
85fd0254e8 | ||
|
|
1b4c38d82b | ||
|
|
c20649e14a | ||
|
|
e20910c920 | ||
|
|
efc32f8e3a | ||
|
|
d5a679731c | ||
|
|
95ccaf9478 | ||
|
|
50bc218ccd | ||
|
|
765755a041 | ||
|
|
54455bf625 | ||
|
|
69f1bff0a1 | ||
|
|
96fff242f4 | ||
|
|
ae9d840567 | ||
|
|
b65cab0508 | ||
|
|
d0d62faf57 | ||
|
|
379bbee4ab | ||
|
|
82e3c8395c | ||
|
|
95016dab4d | ||
|
|
9da0d47208 | ||
|
|
667b87cd92 | ||
|
|
3a9971ce8b | ||
|
|
691d87d4ff | ||
|
|
505f2c7385 | ||
|
|
c9988f1144 | ||
|
|
57ada81c62 | ||
|
|
9cf4a7ed55 | ||
|
|
f798fa1af1 | ||
|
|
2a25e4a2d9 | ||
|
|
aae0a6f9d4 | ||
|
|
145cb771b7 | ||
|
|
17f7e618ec | ||
|
|
4b47769f61 | ||
|
|
e87f804070 | ||
|
|
7b413dcbb2 | ||
|
|
ff7a12b323 | ||
|
|
9cdf0596c0 | ||
|
|
909ad68444 | ||
|
|
0404dfd53f | ||
|
|
ddd5269122 | ||
|
|
ffa70d6762 | ||
|
|
a6821209d1 | ||
|
|
55a56c3e04 | ||
|
|
3f55bfea22 | ||
|
|
5346041cd1 | ||
|
|
6ae8001b79 | ||
|
|
7a6ae31864 | ||
|
|
70bab877fb | ||
|
|
d0757ec17b | ||
|
|
fa58e786d2 | ||
|
|
ef5c6fddec | ||
|
|
1b53b3c993 | ||
|
|
c4c6a07aeb | ||
|
|
d922de564d | ||
|
|
662dcf430a | ||
|
|
63eb6cd110 | ||
|
|
c924be4dc9 | ||
|
|
991f155aca | ||
|
|
9ed48b1bfc | ||
|
|
83c3a35ce4 | ||
|
|
d130af130b | ||
|
|
ff0b661f26 | ||
|
|
62095b2847 | ||
|
|
347ae87b12 | ||
|
|
b4c3edcd59 | ||
|
|
50b5f8ca21 | ||
|
|
705a3c39da | ||
|
|
6750a5e875 | ||
|
|
cd29ce1ca3 | ||
|
|
d79921f2d6 | ||
|
|
f5d820c511 | ||
|
|
1b4767cd38 | ||
|
|
58d3699a06 | ||
|
|
49af7dd65d | ||
|
|
4ceb7f3b47 | ||
|
|
a6b166588f | ||
|
|
1b4ae3ab3c | ||
|
|
8c0321f2cc | ||
|
|
a3f60477ec | ||
|
|
69258587bc | ||
|
|
9742129547 | ||
|
|
db1a3a4062 | ||
|
|
3007c8db0e | ||
|
|
a0bbffaf91 | ||
|
|
a984d75638 | ||
|
|
9b9638cc6e | ||
|
|
3091d86186 | ||
|
|
0072cdb17b | ||
|
|
6b88719cf6 | ||
|
|
4079419fa8 | ||
|
|
dc33bc33d6 | ||
|
|
d94c2bdf95 | ||
|
|
43f658f5e9 | ||
|
|
db4ddb0b8b | ||
|
|
073717cd1f | ||
|
|
29cb68ac31 | ||
|
|
b53495a0d2 | ||
|
|
363cc1da69 | ||
|
|
3d72a674e6 | ||
|
|
06f160d8ab | ||
|
|
8c235f6fcc | ||
|
|
6df7eed22a | ||
|
|
6f07fbc536 | ||
|
|
463a48f33a | ||
|
|
f449d05a38 | ||
|
|
580a9d9eba | ||
|
|
094a2fb2a0 | ||
|
|
4dca4081c1 | ||
|
|
c9ccc409a3 | ||
|
|
d5d33f0e9b | ||
|
|
dfd8d84e85 | ||
|
|
f5794f1fc3 | ||
|
|
c67fb34588 | ||
|
|
af1ae4346f | ||
|
|
9c132c5089 | ||
|
|
4e732b6367 | ||
|
|
3720681f84 | ||
|
|
2b16872936 | ||
|
|
dadbf8026e | ||
|
|
fd6071f8d6 | ||
|
|
1148803671 | ||
|
|
4379660b01 | ||
|
|
51efb9a5c1 | ||
|
|
abfbba2ad0 | ||
|
|
7e041e5b49 | ||
|
|
d7dec47de7 | ||
|
|
71527cc4b1 | ||
|
|
5fbe580c08 | ||
|
|
c59372dd62 | ||
|
|
ab0631ff63 | ||
|
|
db8c2e76e5 | ||
|
|
11c099c3dc |
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@@ -0,0 +1,17 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,json,ts,tsx}]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[package.json]
|
||||
indent_size = 2
|
||||
46
.eslintrc.js
Normal file
46
.eslintrc.js
Normal file
@@ -0,0 +1,46 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: false,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 12,
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
// TODO: Remove warn rules when not needed anymore
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"indent": ["warn", 4, { "SwitchCase": 1 }],
|
||||
"no-multiple-empty-lines": ["error", { max: 2, maxEOF: 0 }],
|
||||
"no-self-assign": "off",
|
||||
"no-trailing-spaces": "warn",
|
||||
"object-curly-spacing": ["warn", "always"],
|
||||
"prefer-template": "warn",
|
||||
"quotes": ["warn", "double", { "avoidEscape": true, "allowTemplateLiterals": true }],
|
||||
"require-await": "warn",
|
||||
"semi": "warn",
|
||||
"no-console": "warn"
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["src/**/*.ts"],
|
||||
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
|
||||
rules: {
|
||||
"@typescript-eslint/no-misused-promises": "warn",
|
||||
"@typescript-eslint/no-floating-promises" : "warn"
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
3
.github/pull_request_template.md
vendored
Normal file
3
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
- [ ] I agree to license my contribution under AGPL-3.0-only with my contribution automatically being licensed under LGPL-3.0 additionally after 6 months
|
||||
|
||||
***
|
||||
18
.github/workflows/db-backup.yml
vendored
Normal file
18
.github/workflows/db-backup.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Docker image builds
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- containers/backup-db/**
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
backup-db:
|
||||
uses: ./.github/workflows/docker-build.yml
|
||||
with:
|
||||
name: "db-backup"
|
||||
username: "ajayyy"
|
||||
folder: "./containers/backup-db"
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
54
.github/workflows/docker-build.yml
vendored
Normal file
54
.github/workflows/docker-build.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# Based on https://github.com/ajayyy/sb-mirror/blob/main/.github/workflows/docker-build.yml
|
||||
name: multi-build-docker
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
type: string
|
||||
username:
|
||||
required: true
|
||||
type: string
|
||||
folder:
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GH_TOKEN:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build_container:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ inputs.username }}/${{ inputs.name }}
|
||||
tags: |
|
||||
type-raw,value=alpine
|
||||
flavor: |
|
||||
latest=true
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_TOKEN }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: arm,arm64
|
||||
- name: Set up buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ${{ inputs.folder }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
41
.github/workflows/generate-sqlite-base.yml
vendored
Normal file
41
.github/workflows/generate-sqlite-base.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: create-sqlite-base
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- databases/**
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
make-base-db:
|
||||
name: Generate SQLite base .db
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: Set config
|
||||
run: |
|
||||
echo '{"mode": "init-db-and-exit"}' > config.json
|
||||
- name: Run Server
|
||||
timeout-minutes: 10
|
||||
run: npm start
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: SponsorTimesDB.db
|
||||
path: databases/sponsorTimes.db
|
||||
- uses: mchangrh/s3cmd-sync@f4f36b9705bdd9af7ac91964136989ac17e3b513
|
||||
with:
|
||||
args: --acl-public
|
||||
env:
|
||||
S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
S3_ACCESS_KEY_SECRET: ${{ secrets.S3_ACCESS_KEY_SECRET }}
|
||||
SOURCE_DIR: 'databases/sponsorTimes.db'
|
||||
25
.github/workflows/sb-server.yml
vendored
Normal file
25
.github/workflows/sb-server.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Docker image builds
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sb-server:
|
||||
uses: ./.github/workflows/docker-build.yml
|
||||
with:
|
||||
name: "sb-server"
|
||||
username: "ajayyy"
|
||||
folder: "."
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
rsync-host:
|
||||
needs: sb-server
|
||||
uses: ./.github/workflows/docker-build.yml
|
||||
with:
|
||||
name: "rsync-host"
|
||||
username: "ajayyy"
|
||||
folder: "./containers/rsync"
|
||||
secrets:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
12
.github/workflows/take-action.yml
vendored
Normal file
12
.github/workflows/take-action.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Assign issue to contributor
|
||||
on: [issue_comment]
|
||||
|
||||
jobs:
|
||||
assign:
|
||||
name: Take an issue
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: take the issue
|
||||
uses: bdougie/take-action@28b86cd8d25593f037406ecbf96082db2836e928
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
116
.github/workflows/test.yaml
vendored
Normal file
116
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint-build:
|
||||
name: Lint with ESLint and build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run tsc
|
||||
- name: cache dist build
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
key: dist-${{ github.sha }}
|
||||
path: |
|
||||
${{ github.workspace }}/dist
|
||||
${{ github.workspace }}/node_modules
|
||||
test-sqlite:
|
||||
name: Run Tests with SQLite
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-build
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
- id: cache
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
key: dist-${{ github.sha }}
|
||||
path: |
|
||||
${{ github.workspace }}/dist
|
||||
${{ github.workspace }}/node_modules
|
||||
- if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
- name: Run SQLite Tests
|
||||
timeout-minutes: 5
|
||||
run: npx nyc --silent npm test
|
||||
- name: cache nyc output
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
key: nyc-sqlite-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/.nyc_output
|
||||
test-postgres:
|
||||
name: Run Tests with PostgreSQL and Redis
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-build
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build the docker-compose stack
|
||||
env:
|
||||
PG_USER: ci_db_user
|
||||
PG_PASS: ci_db_pass
|
||||
run: docker-compose -f docker/docker-compose-ci.yml up -d
|
||||
- name: Check running containers
|
||||
run: docker ps
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
- id: cache
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
key: dist-${{ github.sha }}
|
||||
path: |
|
||||
${{ github.workspace }}/dist
|
||||
${{ github.workspace }}/node_modules
|
||||
- if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
- name: Run Postgres Tests
|
||||
env:
|
||||
TEST_POSTGRES: true
|
||||
timeout-minutes: 5
|
||||
run: npx nyc --silent npm test
|
||||
- name: cache nyc output
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
key: nyc-postgres-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/.nyc_output
|
||||
codecov:
|
||||
needs: [test-sqlite, test-postgres]
|
||||
name: Run Codecov
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- name: restore postgres nyc output
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
key: nyc-postgres-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/.nyc_output
|
||||
- name: restore sqlite nyc output
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
key: nyc-sqlite-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/.nyc_output
|
||||
- run: npx nyc report --reporter=lcov
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
98
.gitignore
vendored
98
.gitignore
vendored
@@ -2,47 +2,9 @@
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
@@ -53,39 +15,37 @@ typings/
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# Databases
|
||||
databases
|
||||
databases/sponsorTimes.db
|
||||
databases/sponsorTimes.db-shm
|
||||
databases/sponsorTimes.db-wal
|
||||
databases/private.db
|
||||
databases/private.db-shm
|
||||
databases/private.db-wal
|
||||
databases/sponsorTimesReal.db
|
||||
test/databases/sponsorTimes.db
|
||||
test/databases/sponsorTimes.db-shm
|
||||
test/databases/sponsorTimes.db-wal
|
||||
test/databases/private.db
|
||||
databases/cache
|
||||
docker/database-export
|
||||
|
||||
# Config files
|
||||
config.json
|
||||
docker/database.env
|
||||
|
||||
# Other
|
||||
working
|
||||
|
||||
# Mac files
|
||||
.DS_Store
|
||||
/.idea/
|
||||
/dist/
|
||||
|
||||
# nyc coverage output
|
||||
.nyc_output/
|
||||
coverage/
|
||||
14
.nycrc.json
Normal file
14
.nycrc.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "@istanbuljs/nyc-config-typescript",
|
||||
"check-coverage": false,
|
||||
"ski-full": true,
|
||||
"reporter": ["text", "html"],
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/routes/addUnlistedVideo.ts",
|
||||
"src/cronjob/downvoteSegmentArchiveJob.ts",
|
||||
"src/databases/*"
|
||||
]
|
||||
}
|
||||
283
DatabaseSchema.md
Normal file
283
DatabaseSchema.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# SponsorTimesDB
|
||||
|
||||
[vipUsers](#vipUsers)
|
||||
[sponsorTimes](#sponsorTimes)
|
||||
[userNames](#userNames)
|
||||
[categoryVotes](#categoryVotes)
|
||||
[lockCategories](#lockCategories)
|
||||
[warnings](#warnings)
|
||||
[shadowBannedUsers](#shadowBannedUsers)
|
||||
[unlistedVideos](#unlistedVideos)
|
||||
[config](#config)
|
||||
[archivedSponsorTimes](#archivedSponsorTimes)
|
||||
|
||||
### vipUsers
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| userID | TEXT | not null |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| vipUsers_index | userID |
|
||||
|
||||
### sponsorTimes
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| startTime | REAL | not null |
|
||||
| endTime | REAL | not null |
|
||||
| votes | INTEGER | not null |
|
||||
| locked | INTEGER | not null, default '0' |
|
||||
| incorrectVotes | INTEGER | not null, default 1 |
|
||||
| UUID | TEXT | not null, unique |
|
||||
| userID | TEXT | not null |
|
||||
| timeSubmitted | INTEGER | not null |
|
||||
| views | INTEGER | not null |
|
||||
| category | TEXT | not null, default 'sponsor' |
|
||||
| actionType | TEXT | not null, default 'skip' |
|
||||
| service | TEXT | not null, default 'YouTube' |
|
||||
| videoDuration | INTEGER | not null, default '0' |
|
||||
| hidden | INTEGER | not null, default '0' |
|
||||
| reputation | REAL | not null, default '0' |
|
||||
| shadowHidden | INTEGER | not null |
|
||||
| hashedVideoID | TEXT | not null, default '', sha256 |
|
||||
| userAgent | TEXT | not null, default '' |
|
||||
| description | TEXT | not null, default '' |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| sponsorTime_timeSubmitted | timeSubmitted |
|
||||
| sponsorTime_userID | userID |
|
||||
| sponsorTimes_UUID | UUID |
|
||||
| sponsorTimes_hashedVideoID | hashedVideoID, category |
|
||||
| sponsorTimes_videoID | videoID, service, category, timeSubmitted |
|
||||
|
||||
### userNames
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| userID | TEXT | not null |
|
||||
| userName | TEXT | not null |
|
||||
| locked | INTEGER | not nul, default '0' |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| userNames_userID | userID |
|
||||
|
||||
### categoryVotes
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| UUID | TEXT | not null |
|
||||
| category | TEXT | not null |
|
||||
| votes | INTEGER | not null, default 0 |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| categoryVotes_UUID_public | UUID, category |
|
||||
|
||||
### lockCategories
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| userID | TEXT | not null |
|
||||
| actionType | TEXT | not null, default 'skip' |
|
||||
| category | TEXT | not null |
|
||||
| hashedVideoID | TEXT | not null, default '' |
|
||||
| reason | TEXT | not null, default '' |
|
||||
| service | TEXT | not null, default 'YouTube' |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| lockCategories_videoID | videoID, service, category |
|
||||
|
||||
### warnings
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| userID | TEXT | not null |
|
||||
| issueTime | INTEGER | not null |
|
||||
| issuerUserID | TEXT | not null |
|
||||
| enabled | INTEGER | not null |
|
||||
| reason | TEXT | not null, default '' |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| warnings_index | userID |
|
||||
| warnings_issueTime | issueTime |
|
||||
|
||||
### shadowBannedUsers
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| userID | TEXT | not null |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| shadowBannedUsers_index | userID |
|
||||
|
||||
### videoInfo
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| channelID | TEXT | not null |
|
||||
| title | TEXT | not null |
|
||||
| published | REAL | not null |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| videoInfo_videoID | timeSubmitted |
|
||||
| videoInfo_channelID | userID |
|
||||
|
||||
### unlistedVideos
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| year | TEXT | not null |
|
||||
| views | TEXT | not null |
|
||||
| channelID | TEXT | not null |
|
||||
| timeSubmitted | INTEGER | not null |
|
||||
| service | TEXT | not null, default 'YouTube' |
|
||||
|
||||
### config
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| key | TEXT | not null, unique |
|
||||
| value | TEXT | not null |
|
||||
|
||||
### archivedSponsorTimes
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| startTime | REAL | not null |
|
||||
| endTime | REAL | not null |
|
||||
| votes | INTEGER | not null |
|
||||
| locked | INTEGER | not null, default '0' |
|
||||
| incorrectVotes | INTEGER | not null, default 1 |
|
||||
| UUID | TEXT | not null, unique |
|
||||
| userID | TEXT | not null |
|
||||
| timeSubmitted | INTEGER | not null |
|
||||
| views | INTEGER | not null |
|
||||
| category | TEXT | not null, default 'sponsor' |
|
||||
| actionType | TEXT | not null, default 'skip' |
|
||||
| service | TEXT | not null, default 'YouTube' |
|
||||
| videoDuration | INTEGER | not null, default '0' |
|
||||
| hidden | INTEGER | not null, default '0' |
|
||||
| reputation | REAL | not null, default '0' |
|
||||
| shadowHidden | INTEGER | not null |
|
||||
| hashedVideoID | TEXT | not null, default '', sha256 |
|
||||
| userAgent | TEXT | not null, default '' |
|
||||
|
||||
### ratings
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| service | TEXT | not null, default 'YouTube' |
|
||||
| type | INTEGER | not null |
|
||||
| count | INTEGER | not null |
|
||||
| hashedVideoID | TEXT | not null |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| ratings_hashedVideoID_gin | hashedVideoID |
|
||||
| ratings_hashedVideoID | hashedVideoID, service |
|
||||
| ratings_videoID | videoID, service |
|
||||
|
||||
# Private
|
||||
|
||||
[votes](#votes)
|
||||
[categoryVotes](#categoryVotes)
|
||||
[sponsorTimes](#sponsorTimes)
|
||||
[config](#config)
|
||||
[ratings](#ratings)
|
||||
[tempVipLog](#tempVipLog)
|
||||
[userNameLogs](#userNameLogs)
|
||||
|
||||
### votes
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| UUID | TEXT | not null |
|
||||
| userID | TEXT | not null |
|
||||
| hashedIP | TEXT | not null |
|
||||
| type | INTEGER | not null |
|
||||
| originalVoteType | INTEGER | not null | # Since type was reused to also specify the number of votes removed when less than 0, this is being used for the actual type
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| votes_userID | UUID |
|
||||
|
||||
### categoryVotes
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| UUID | TEXT | not null |
|
||||
| userID | TEXT | not null |
|
||||
| hashedIP | TEXT | not null |
|
||||
| category | TEXT | not null |
|
||||
| timeSubmitted | INTEGER | not null |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| categoryVotes_UUID | UUID, userID, hasedIP, category |
|
||||
|
||||
### sponsorTimes
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| hashedIP | TEXT | not null |
|
||||
| timeSubmitted | INTEGER | not null |
|
||||
| service | TEXT | not null, default 'YouTube' |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| sponsorTimes_hashedIP | hashedIP |
|
||||
| privateDB_sponsorTimes_videoID_v2 | videoID, service |
|
||||
|
||||
### config
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| key | TEXT | not null |
|
||||
| value | TEXT | not null |
|
||||
|
||||
### ratings
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| videoID | TEXT | not null |
|
||||
| service | TEXT | not null, default 'YouTube' |
|
||||
| userID | TEXT | not null |
|
||||
| type | INTEGER | not null |
|
||||
| timeSubmitted | INTEGER | not null |
|
||||
| hashedIP | TEXT | not null |
|
||||
|
||||
| index | field |
|
||||
| -- | :--: |
|
||||
| ratings_videoID | videoID, service, userID, timeSubmitted |
|
||||
|
||||
### tempVipLog
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| issuerUserID | TEXT | not null |
|
||||
| targetUserID | TEXT | not null |
|
||||
| enabled | BOOLEAN | not null |
|
||||
| updatedAt | INTEGER | not null |
|
||||
|
||||
### userNameLogs
|
||||
|
||||
| Name | Type | |
|
||||
| -- | :--: | -- |
|
||||
| userID | TEXT | not null |
|
||||
| newUserName | TEXT | not null |
|
||||
| oldUserName | TEXT | not null |
|
||||
| updatedByAdmin | BOOLEAN | not null |
|
||||
| updatedAt | INTEGER | not null |
|
||||
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM node:18-alpine as builder
|
||||
RUN apk add --no-cache --virtual .build-deps python3 make g++
|
||||
COPY package.json package-lock.json tsconfig.json entrypoint.sh ./
|
||||
COPY src src
|
||||
RUN npm ci && npm run tsc
|
||||
|
||||
FROM node:18-alpine as app
|
||||
WORKDIR /usr/src/app
|
||||
RUN apk add --no-cache git postgresql-client
|
||||
COPY --from=builder ./node_modules ./node_modules
|
||||
COPY --from=builder ./dist ./dist
|
||||
COPY ./.git ./.git
|
||||
COPY entrypoint.sh .
|
||||
COPY databases/*.sql databases/
|
||||
EXPOSE 8080
|
||||
CMD ./entrypoint.sh
|
||||
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
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.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
27
LICENSE.old
Normal file
27
LICENSE.old
Normal file
@@ -0,0 +1,27 @@
|
||||
License for code prior to commit d738e89f203e9cea21b7df4acb9f4b3505f0acdd
|
||||
|
||||
You must follow LICENSE instead if you want to use any newer version.
|
||||
|
||||
----
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Ajay Ramachandran
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
28
README.MD
28
README.MD
@@ -6,12 +6,36 @@ This is the server backend for it
|
||||
|
||||
# Server
|
||||
|
||||
This is a simple Sqlite database that will hold all the timing data.
|
||||
This uses a Postgres or Sqlite database to hold all the timing data.
|
||||
|
||||
To make sure that this project doesn't die, I have made the database publicly downloadable at https://sponsor.ajay.app/database.db. So, you can download a backup or get archive.org to take a backup if you do desire.
|
||||
To make sure that this project doesn't die, I have made the database publicly downloadable at https://sponsor.ajay.app/database. You can download a backup or get archive.org to take a backup if you do desire. The database is under [this license](https://creativecommons.org/licenses/by-nc-sa/4.0/) unless you get explicit permission from me.
|
||||
|
||||
Hopefully this project can be combined with projects like [this](https://github.com/Sponsoff/sponsorship_remover) and use this data to create a neural network to predict when sponsored segments happen. That project is sadly abandoned now, so I have decided to attempt to revive this idea.
|
||||
|
||||
# Client
|
||||
|
||||
The client web browser extension is available here: https://github.com/ajayyy/SponsorBlock
|
||||
|
||||
# Build Yourself
|
||||
|
||||
This is a node.js server, so clone this repo and run `npm install` to install all dependencies.
|
||||
|
||||
Make sure to put the database files in the `./databases` folder if you want to use a pre-existing database. Otherwise, a fresh database will be created.
|
||||
|
||||
Rename `config.json.example` to `config.json` and fill the parameters inside. Make sure to remove the comments as comments are not supported in JSON.
|
||||
|
||||
Ensure all the tests pass with `npm test`
|
||||
|
||||
Run the server with `npm start`.
|
||||
|
||||
# Developing
|
||||
|
||||
If you want to make changes, run `npm run dev` to automatically reload the server and run tests whenever a file is saved.
|
||||
|
||||
# API Docs
|
||||
|
||||
Available [here](https://wiki.sponsor.ajay.app/index.php/API_Docs)
|
||||
|
||||
# License
|
||||
|
||||
This is licensed under AGPL-3.0-only.
|
||||
80
ci.json
Normal file
80
ci.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"port": 8080,
|
||||
"mockPort": 8081,
|
||||
"globalSalt": "testSalt",
|
||||
"adminUserID": "4bdfdc9cddf2c7d07a8a87b57bf6d25389fb75d1399674ee0e0938a6a60f4c3b",
|
||||
"newLeafURLs": ["placeholder"],
|
||||
"discordReportChannelWebhookURL": "http://127.0.0.1:8081/webhook/ReportChannel",
|
||||
"discordFirstTimeSubmissionsWebhookURL": "http://127.0.0.1:8081/webhook/FirstTimeSubmissions",
|
||||
"discordCompletelyIncorrectReportWebhookURL": "http://127.0.0.1:8081/webhook/CompletelyIncorrectReport",
|
||||
"discordNeuralBlockRejectWebhookURL": "http://127.0.0.1:8081/webhook/NeuralBlockReject",
|
||||
"neuralBlockURL": "http://127.0.0.1:8081/NeuralBlock",
|
||||
"userCounterURL": "http://127.0.0.1:8081/UserCounter",
|
||||
"behindProxy": true,
|
||||
"postgres": {
|
||||
"user": "ci_db_user",
|
||||
"password": "ci_db_pass",
|
||||
"host": "localhost",
|
||||
"port": 5432
|
||||
},
|
||||
"redis": {
|
||||
"enabled": true,
|
||||
"socket": {
|
||||
"host": "localhost",
|
||||
"port": 6379
|
||||
},
|
||||
"expiryTime": 86400
|
||||
},
|
||||
"createDatabaseIfNotExist": true,
|
||||
"schemaFolder": "./databases",
|
||||
"dbSchema": "./databases/_sponsorTimes.db.sql",
|
||||
"privateDBSchema": "./databases/_private.db.sql",
|
||||
"categoryList": ["sponsor", "selfpromo", "exclusive_access", "interaction", "intro", "outro", "preview", "music_offtopic", "filler", "poi_highlight", "chapter"],
|
||||
"mode": "test",
|
||||
"readOnly": false,
|
||||
"webhooks": [
|
||||
{
|
||||
"url": "http://127.0.0.1:8081/CustomWebhook",
|
||||
"key": "superSecretKey",
|
||||
"scopes": [
|
||||
"vote.up",
|
||||
"vote.down"
|
||||
]
|
||||
}, {
|
||||
"url": "http://127.0.0.1:8081/FailedWebhook",
|
||||
"key": "superSecretKey",
|
||||
"scopes": [
|
||||
"vote.up",
|
||||
"vote.down"
|
||||
]
|
||||
}, {
|
||||
"url": "http://127.0.0.1:8099/WrongPort",
|
||||
"key": "superSecretKey",
|
||||
"scopes": [
|
||||
"vote.up",
|
||||
"vote.down"
|
||||
]
|
||||
}
|
||||
],
|
||||
"hoursAfterWarningExpires": 24,
|
||||
"rateLimit": {
|
||||
"vote": {
|
||||
"windowMs": 900000,
|
||||
"max": 20,
|
||||
"message": "Too many votes, please try again later",
|
||||
"statusCode": 429
|
||||
},
|
||||
"view": {
|
||||
"windowMs": 900000,
|
||||
"max": 20,
|
||||
"statusCode": 200
|
||||
}
|
||||
},
|
||||
"patreon": {
|
||||
"clientId": "testClientID",
|
||||
"clientSecret": "testClientSecret",
|
||||
"redirectUri": "http://127.0.0.1/fake/callback"
|
||||
},
|
||||
"minReputationToSubmitFiller": -1,
|
||||
"minUserIDLength": 0
|
||||
}
|
||||
9
codecov.yml
Normal file
9
codecov.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
comment: false
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
71
config.json.example
Normal file
71
config.json.example
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
// Remove all comments from the config when using it!
|
||||
|
||||
"port": 80,
|
||||
"globalSalt": "[global salt (pepper) that is added to every ip before hashing to make it even harder for someone to decode the ip]",
|
||||
"adminUserID": "[the hashed id of the user who can perform admin actions]",
|
||||
"newLeafURLs": ["http://localhost:3241"],
|
||||
"discordReportChannelWebhookURL": null, //URL from discord if you would like notifications when someone makes a report [optional]
|
||||
"discordFirstTimeSubmissionsWebhookURL": null, //URL from discord if you would like notifications when someone makes a first time submission [optional]
|
||||
"discordCompletelyIncorrectReportWebhookURL": null, //URL from discord if you would like notifications when someone reports a submission as completely incorrect [optional]
|
||||
"neuralBlockURL": null, // URL to check submissions against neural block. Ex. https://ai.neuralblock.app
|
||||
"discordNeuralBlockRejectWebhookURL": null, //URL from discord if you would like notifications when NeuralBlock rejects a submission [optional]
|
||||
"userCounterURL": null, // For user counting. URL to instance of https://github.com/ajayyy/PrivacyUserCount
|
||||
"proxySubmission": null, // Base url to proxy submissions to persist // e.g. https://sponsor.ajay.app (no trailing slash)
|
||||
"behindProxy": "X-Forwarded-For", //Options: "X-Forwarded-For", "Cloudflare", "X-Real-IP", anything else will mean it is not behind a proxy. True defaults to "X-Forwarded-For"
|
||||
"db": "./databases/sponsorTimes.db",
|
||||
"privateDB": "./databases/private.db",
|
||||
"createDatabaseIfNotExist": true, //This will run on startup every time (unless readOnly is true) - so ensure "create table if not exists" is used in the schema
|
||||
"schemaFolder": "./databases",
|
||||
"dbSchema": "./databases/_sponsorTimes.db.sql",
|
||||
"privateDBSchema": "./databases/_private.db.sql",
|
||||
// when using redis, add `"enable_offline_queue": false` to force commands to fail instead of timing out
|
||||
"mode": "development",
|
||||
"readOnly": false,
|
||||
"webhooks": [],
|
||||
"categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "preview", "music_offtopic", "poi_highlight"], // List of supported categories any other category will be rejected
|
||||
"getTopUsersCacheTimeMinutes": 5, // cacheTime for getTopUsers result in minutes
|
||||
"maxNumberOfActiveWarnings": 3, // Users with this number of warnings will be blocked until warnings expire
|
||||
"hoursAfterWarningExpire": 24,
|
||||
"rateLimit": {
|
||||
"vote": {
|
||||
"windowMs": 900000, // 15 minutes
|
||||
"max": 20, // 20 requests in 15min time window
|
||||
"message": "Too many votes, please try again later",
|
||||
"statusCode": 429
|
||||
},
|
||||
"view": {
|
||||
"windowMs": 900000, // 15 minutes
|
||||
"max": 20, // 20 requests in 15min time window
|
||||
"statusCode": 200
|
||||
}
|
||||
},
|
||||
"maxRewardTimePerSegmentInSeconds": 86400, // maximum time a user get rewarded in the leaderboard for a single segment
|
||||
"dumpDatabase": {
|
||||
"enabled": true,
|
||||
"minTimeBetweenMs": 60000, // 1 minute between dumps
|
||||
"appExportPath": "./docker/database-export",
|
||||
"postgresExportPath": "/opt/exports",
|
||||
"tables": [{
|
||||
"name": "sponsorTimes",
|
||||
"order": "timeSubmitted"
|
||||
},
|
||||
{
|
||||
"name": "userNames"
|
||||
},
|
||||
{
|
||||
"name": "categoryVotes"
|
||||
},
|
||||
{
|
||||
"name": "lockCategories"
|
||||
},
|
||||
{
|
||||
"name": "warnings",
|
||||
"order": "issueTime"
|
||||
},
|
||||
{
|
||||
"name": "vipUsers"
|
||||
}]
|
||||
},
|
||||
"minUserIDLength": 30 // minimum length of UserID to be accepted
|
||||
}
|
||||
13
containers/backup-db/Dockerfile
Normal file
13
containers/backup-db/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM alpine as builder
|
||||
WORKDIR /scripts
|
||||
COPY ./backup.sh ./backup.sh
|
||||
COPY ./forget.sh ./forget.sh
|
||||
|
||||
FROM alpine
|
||||
RUN apk add --no-cache postgresql-client restic
|
||||
COPY --from=builder --chmod=755 /scripts /usr/src/app/
|
||||
|
||||
RUN echo '30 * * * * /usr/src/app/backup.sh' >> /etc/crontabs/root
|
||||
RUN echo '10 0 * * 1 /usr/src/app/forget.sh' >> /etc/crontabs/root
|
||||
|
||||
CMD crond -l 2 -f
|
||||
6
containers/backup-db/backup.sh
Normal file
6
containers/backup-db/backup.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
mkdir ./dump
|
||||
|
||||
pg_dump -f ./dump/sponsorTimes.dump sponsorTimes
|
||||
pg_dump -f ./dump/privateDB.dump privateDB
|
||||
|
||||
restic backup ./dump
|
||||
1
containers/backup-db/forget.sh
Normal file
1
containers/backup-db/forget.sh
Normal file
@@ -0,0 +1 @@
|
||||
restic forget --prune --keep-last 48 --keep-daily 7 --keep-weekly 8
|
||||
6
containers/rsync/Dockerfile
Normal file
6
containers/rsync/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM ghcr.io/ajayyy/sb-server:latest
|
||||
EXPOSE 873/tcp
|
||||
RUN apk add rsync>3.2.4-r0
|
||||
RUN mkdir /usr/src/app/database-export
|
||||
|
||||
CMD rsync --no-detach --daemon & ./entrypoint.sh
|
||||
47
databases/_private.db.sql
Normal file
47
databases/_private.db.sql
Normal file
@@ -0,0 +1,47 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "votes" (
|
||||
"UUID" TEXT NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"type" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "categoryVotes" (
|
||||
"UUID" TEXT NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "sponsorTimes" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "config" (
|
||||
"key" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "titleVotes" (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"videoID" TEXT NOT NULL,
|
||||
"UUID" TEXT NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"type" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "thumbnailVotes" (
|
||||
"id" SERIAL PRIMARY KEY,
|
||||
"videoID" TEXT NOT NULL,
|
||||
"UUID" TEXT NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL,
|
||||
"type" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
26
databases/_private_indexes.sql
Normal file
26
databases/_private_indexes.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- sponsorTimes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "privateDB_sponsorTimes_v4"
|
||||
ON public."sponsorTimes" USING btree
|
||||
("videoID" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST, "timeSubmitted" ASC NULLS LAST);
|
||||
|
||||
-- votes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "votes_userID"
|
||||
ON public.votes USING btree
|
||||
("UUID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- categoryVotes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "categoryVotes_UUID"
|
||||
ON public."categoryVotes" USING btree
|
||||
("UUID" COLLATE pg_catalog."default" ASC NULLS LAST, "userID" COLLATE pg_catalog."default" ASC NULLS LAST, "hashedIP" COLLATE pg_catalog."default" ASC NULLS LAST, category COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- ratings
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ratings_videoID"
|
||||
ON public."ratings" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST, "userID" COLLATE pg_catalog."default" ASC NULLS LAST, "timeSubmitted" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
90
databases/_sponsorTimes.db.sql
Normal file
90
databases/_sponsorTimes.db.sql
Normal file
@@ -0,0 +1,90 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "vipUsers" (
|
||||
"userID" TEXT NOT NULL
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "sponsorTimes" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"shadowHidden" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "userNames" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"userName" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "categoryVotes" (
|
||||
"UUID" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"votes" INTEGER NOT NULL default 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "shadowBannedIPs" (
|
||||
"hashedIP" TEXT NOT NULL PRIMARY KEY
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "config" (
|
||||
"key" TEXT NOT NULL UNIQUE,
|
||||
"value" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "titles" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"original" INTEGER default 0,
|
||||
"userID" TEXT NOT NULL,
|
||||
"service" TEXT NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"UUID" TEXT NOT NULL PRIMARY KEY
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "titleVotes" (
|
||||
"UUID" TEXT NOT NULL PRIMARY KEY,
|
||||
"votes" INTEGER NOT NULL default 0,
|
||||
"locked" INTEGER NOT NULL default 0,
|
||||
"shadowHidden" INTEGER NOT NULL default 0,
|
||||
FOREIGN KEY("UUID") REFERENCES "titles"("UUID")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "thumbnails" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"original" INTEGER default 0,
|
||||
"userID" TEXT NOT NULL,
|
||||
"service" TEXT NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"UUID" TEXT NOT NULL PRIMARY KEY
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "thumbnailTimestamps" (
|
||||
"UUID" TEXT NOT NULL PRIMARY KEY,
|
||||
"timestamp" INTEGER NOT NULL default 0,
|
||||
FOREIGN KEY("UUID") REFERENCES "thumbnails"("UUID")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "thumbnailVotes" (
|
||||
"UUID" TEXT NOT NULL PRIMARY KEY,
|
||||
"votes" INTEGER NOT NULL default 0,
|
||||
"locked" INTEGER NOT NULL default 0,
|
||||
"shadowHidden" INTEGER NOT NULL default 0,
|
||||
FOREIGN KEY("UUID") REFERENCES "thumbnails"("UUID")
|
||||
);
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto; --!sqlite-ignore
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm; --!sqlite-ignore
|
||||
|
||||
COMMIT;
|
||||
176
databases/_sponsorTimes_indexes.sql
Normal file
176
databases/_sponsorTimes_indexes.sql
Normal file
@@ -0,0 +1,176 @@
|
||||
-- sponsorTimes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "sponsorTime_timeSubmitted"
|
||||
ON public."sponsorTimes" USING btree
|
||||
("timeSubmitted" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "sponsorTime_userID"
|
||||
ON public."sponsorTimes" USING btree
|
||||
("userID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "sponsorTimes_UUID"
|
||||
ON public."sponsorTimes" USING btree
|
||||
("UUID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "sponsorTimes_hashedVideoID"
|
||||
ON public."sponsorTimes" USING btree
|
||||
(service COLLATE pg_catalog."default" ASC NULLS LAST, "hashedVideoID" text_pattern_ops ASC NULLS LAST, "startTime" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID"
|
||||
ON public."sponsorTimes" USING btree
|
||||
(service COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "startTime" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID_category"
|
||||
ON public."sponsorTimes" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "category" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "sponsorTimes_description_gin"
|
||||
ON public."sponsorTimes" USING gin
|
||||
("description" COLLATE pg_catalog."default" gin_trgm_ops, category COLLATE pg_catalog."default" gin_trgm_ops)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- userNames
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "userNames_userID"
|
||||
ON public."userNames" USING btree
|
||||
("userID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- vipUsers
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "vipUsers_index"
|
||||
ON public."vipUsers" USING btree
|
||||
("userID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- warnings
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "warnings_index"
|
||||
ON public.warnings USING btree
|
||||
("userID" COLLATE pg_catalog."default" ASC NULLS LAST, "issueTime" DESC NULLS LAST, enabled DESC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "warnings_issueTime"
|
||||
ON public.warnings USING btree
|
||||
("issueTime" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- lockCategories
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "lockCategories_videoID"
|
||||
ON public."lockCategories" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST, category COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- categoryVotes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "categoryVotes_UUID_public"
|
||||
ON public."categoryVotes" USING btree
|
||||
("UUID" COLLATE pg_catalog."default" ASC NULLS LAST, category COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- shadowBannedUsers
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "shadowBannedUsers_index"
|
||||
ON public."shadowBannedUsers" USING btree
|
||||
("userID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- videoInfo
|
||||
CREATE INDEX IF NOT EXISTS "videoInfo_videoID"
|
||||
ON public."videoInfo" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "videoInfo_channelID"
|
||||
ON public."videoInfo" USING btree
|
||||
("channelID" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- ratings
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ratings_hashedVideoID_gin"
|
||||
ON public."ratings" USING gin
|
||||
("hashedVideoID" COLLATE pg_catalog."default" gin_trgm_ops)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ratings_hashedVideoID"
|
||||
ON public."ratings" USING btree
|
||||
("hashedVideoID" COLLATE pg_catalog."default" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ratings_videoID"
|
||||
ON public."ratings" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
--- userFeatures
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "userFeatures_userID"
|
||||
ON public."userFeatures" USING btree
|
||||
("userID" COLLATE pg_catalog."default" ASC NULLS LAST, "feature" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- titles
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "titles_timeSubmitted"
|
||||
ON public."titles" USING btree
|
||||
("timeSubmitted" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "titles_userID_timeSubmitted"
|
||||
ON public."titles" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "service" COLLATE pg_catalog."default" ASC NULLS LAST, "userID" COLLATE pg_catalog."default" DESC NULLS LAST, "timeSubmitted" DESC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "titles_videoID"
|
||||
ON public."titles" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "service" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "titles_hashedVideoID"
|
||||
ON public."titles" USING btree
|
||||
("hashedVideoID" COLLATE pg_catalog."default" ASC NULLS LAST, "service" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- titleVotes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "titleVotes_votes"
|
||||
ON public."titleVotes" USING btree
|
||||
("UUID" COLLATE pg_catalog."default" ASC NULLS LAST, "votes" DESC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- thumbnails
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "thumbnails_timeSubmitted"
|
||||
ON public."thumbnails" USING btree
|
||||
("timeSubmitted" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "thumbnails_votes_timeSubmitted"
|
||||
ON public."thumbnails" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "service" COLLATE pg_catalog."default" ASC NULLS LAST, "userID" COLLATE pg_catalog."default" DESC NULLS LAST, "timeSubmitted" DESC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "thumbnails_videoID"
|
||||
ON public."thumbnails" USING btree
|
||||
("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "service" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "thumbnails_hashedVideoID"
|
||||
ON public."thumbnails" USING btree
|
||||
("hashedVideoID" COLLATE pg_catalog."default" ASC NULLS LAST, "service" COLLATE pg_catalog."default" ASC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
|
||||
-- thumbnailVotes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "thumbnailVotes_votes"
|
||||
ON public."thumbnailVotes" USING btree
|
||||
("UUID" COLLATE pg_catalog."default" ASC NULLS LAST, "votes" DESC NULLS LAST)
|
||||
TABLESPACE pg_default;
|
||||
8
databases/_upgrade_private_1.sql
Normal file
8
databases/_upgrade_private_1.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* for testing the db upgrade, don't remove because it looks empty */
|
||||
|
||||
/* Add version to config */
|
||||
INSERT INTO config (key, value) VALUES('version', 1);
|
||||
|
||||
COMMIT;
|
||||
9
databases/_upgrade_private_10.sql
Normal file
9
databases/_upgrade_private_10.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- Add primary keys
|
||||
|
||||
ALTER TABLE "votes" ADD "originalType" INTEGER NOT NULL default -1;
|
||||
|
||||
UPDATE "config" SET value = 10 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
18
databases/_upgrade_private_11.sql
Normal file
18
databases/_upgrade_private_11.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "licenseKeys" (
|
||||
"licenseKey" TEXT NOT NULL PRIMARY KEY,
|
||||
"time" INTEGER NOT NULL,
|
||||
"type" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "oauthLicenseKeys" (
|
||||
"licenseKey" TEXT NOT NULL PRIMARY KEY,
|
||||
"accessToken" TEXT NOT NULL,
|
||||
"refreshToken" TEXT NOT NULL,
|
||||
"expiresIn" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 11 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
13
databases/_upgrade_private_2.sql
Normal file
13
databases/_upgrade_private_2.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "userNameLogs" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"newUserName" TEXT NOT NULL,
|
||||
"oldUserName" TEXT NOT NULL,
|
||||
"updatedByAdmin" BOOLEAN NOT NULL,
|
||||
"updatedAt" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 2 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
10
databases/_upgrade_private_3.sql
Normal file
10
databases/_upgrade_private_3.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "sponsorTimes" ADD "service" TEXT NOT NULL default 'YouTube';
|
||||
-- UPDATE "sponsorTimes" SET "service" = "YouTube";
|
||||
|
||||
DROP INDEX IF EXISTS "privateDB_sponsorTimes_videoID";
|
||||
|
||||
UPDATE "config" SET value = 3 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
14
databases/_upgrade_private_4.sql
Normal file
14
databases/_upgrade_private_4.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "ratings" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"service" TEXT NOT NULL default 'YouTube',
|
||||
"type" INTEGER NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"hashedIP" TEXT NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 4 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
12
databases/_upgrade_private_5.sql
Normal file
12
databases/_upgrade_private_5.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "tempVipLog" (
|
||||
"issuerUserID" TEXT NOT NULL,
|
||||
"targetUserID" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL,
|
||||
"updatedAt" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 5 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
7
databases/_upgrade_private_6.sql
Normal file
7
databases/_upgrade_private_6.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
DROP INDEX IF EXISTS "sponsorTimes_hashedIP", "privateDB_sponsorTimes_videoID_v2"; --!sqlite-ignore
|
||||
|
||||
UPDATE "config" SET value = 6 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
7
databases/_upgrade_private_7.sql
Normal file
7
databases/_upgrade_private_7.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "votes" ADD "normalUserID" TEXT NOT NULL default '';
|
||||
|
||||
UPDATE "config" SET value = 7 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
15
databases/_upgrade_private_8.sql
Normal file
15
databases/_upgrade_private_8.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- Add primary keys
|
||||
|
||||
ALTER TABLE "userNameLogs" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "config" ADD PRIMARY KEY ("key"); --!sqlite-ignore
|
||||
ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "tempVipLog" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "votes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
|
||||
UPDATE "config" SET value = 8 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
9
databases/_upgrade_private_9.sql
Normal file
9
databases/_upgrade_private_9.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- Add primary keys
|
||||
|
||||
DROP INDEX IF EXISTS "privateDB_sponsorTimes_v3"; --!sqlite-ignore
|
||||
|
||||
UPDATE "config" SET value = 9 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
25
databases/_upgrade_sponsorTimes_1.sql
Normal file
25
databases/_upgrade_sponsorTimes_1.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add incorrectVotes field */
|
||||
CREATE TABLE "sqlb_temp_table_1" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"incorrectVotes" INTEGER NOT NULL default 1,
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"shadowHidden" INTEGER NOT NULL
|
||||
);
|
||||
INSERT INTO sqlb_temp_table_1 SELECT "videoID","startTime","endTime","votes",'1',"UUID","userID","timeSubmitted","views","category","shadowHidden" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_1 RENAME TO "sponsorTimes";
|
||||
|
||||
/* Add version to config */
|
||||
INSERT INTO config (key, value) VALUES('version', 1);
|
||||
|
||||
COMMIT;
|
||||
30
databases/_upgrade_sponsorTimes_10.sql
Normal file
30
databases/_upgrade_sponsorTimes_10.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add Hidden field */
|
||||
CREATE TABLE "sqlb_temp_table_10" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0',
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"service" TEXT NOT NULL DEFAULT 'YouTube',
|
||||
"videoDuration" REAL NOT NULL DEFAULT '0',
|
||||
"hidden" INTEGER NOT NULL DEFAULT '0',
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_10 SELECT "videoID","startTime","endTime","votes","locked","incorrectVotes","UUID","userID","timeSubmitted","views","category","service","videoDuration",0,"shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_10 RENAME TO "sponsorTimes";
|
||||
|
||||
UPDATE "config" SET value = 10 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
8
databases/_upgrade_sponsorTimes_11.sql
Normal file
8
databases/_upgrade_sponsorTimes_11.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Rename table: noSegments to lockCategories */
|
||||
ALTER TABLE "noSegments" RENAME TO "lockCategories";
|
||||
|
||||
UPDATE "config" SET value = 11 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
31
databases/_upgrade_sponsorTimes_12.sql
Normal file
31
databases/_upgrade_sponsorTimes_12.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add reputation field */
|
||||
CREATE TABLE "sqlb_temp_table_12" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0',
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"service" TEXT NOT NULL DEFAULT 'YouTube',
|
||||
"videoDuration" REAL NOT NULL DEFAULT '0',
|
||||
"hidden" INTEGER NOT NULL DEFAULT '0',
|
||||
"reputation" REAL NOT NULL DEFAULT 0,
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_12 SELECT "videoID","startTime","endTime","votes","locked","incorrectVotes","UUID","userID","timeSubmitted","views","category","service","videoDuration","hidden",0,"shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_12 RENAME TO "sponsorTimes";
|
||||
|
||||
UPDATE "config" SET value = 12 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
17
databases/_upgrade_sponsorTimes_13.sql
Normal file
17
databases/_upgrade_sponsorTimes_13.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add locked field */
|
||||
CREATE TABLE "sqlb_temp_table_13" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"userName" TEXT NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0'
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_13 SELECT "userID", "userName", 0 FROM "userNames";
|
||||
|
||||
DROP TABLE "userNames";
|
||||
ALTER TABLE sqlb_temp_table_13 RENAME TO "userNames";
|
||||
|
||||
UPDATE "config" SET value = 13 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
9
databases/_upgrade_sponsorTimes_14.sql
Normal file
9
databases/_upgrade_sponsorTimes_14.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "shadowBannedUsers" (
|
||||
"userID" TEXT NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 14 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
10
databases/_upgrade_sponsorTimes_15.sql
Normal file
10
databases/_upgrade_sponsorTimes_15.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "unlistedVideos" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 15 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
15
databases/_upgrade_sponsorTimes_16.sql
Normal file
15
databases/_upgrade_sponsorTimes_16.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
DROP TABLE "unlistedVideos";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "unlistedVideos" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"year" TEXT NOT NULL,
|
||||
"views" TEXT NOT NULL,
|
||||
"channelID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 16 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
19
databases/_upgrade_sponsorTimes_17.sql
Normal file
19
databases/_upgrade_sponsorTimes_17.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add reason field */
|
||||
CREATE TABLE "sqlb_temp_table_17" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"issueTime" INTEGER NOT NULL,
|
||||
"issuerUserID" TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL,
|
||||
"reason" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_17 SELECT "userID","issueTime","issuerUserID","enabled", '' FROM "warnings";
|
||||
|
||||
DROP TABLE warnings;
|
||||
ALTER TABLE sqlb_temp_table_17 RENAME TO "warnings";
|
||||
|
||||
UPDATE "config" SET value = 17 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
9
databases/_upgrade_sponsorTimes_18.sql
Normal file
9
databases/_upgrade_sponsorTimes_18.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add hash field */
|
||||
ALTER TABLE "lockCategories" ADD "hashedVideoID" TEXT NOT NULL default '';
|
||||
UPDATE "lockCategories" SET "hashedVideoID" = sha256("videoID");
|
||||
|
||||
UPDATE "config" SET value = 18 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
32
databases/_upgrade_sponsorTimes_19.sql
Normal file
32
databases/_upgrade_sponsorTimes_19.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add actionType field */
|
||||
CREATE TABLE "sqlb_temp_table_19" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0',
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"actionType" TEXT NOT NULL DEFAULT 'skip',
|
||||
"service" TEXT NOT NULL DEFAULT 'YouTube',
|
||||
"videoDuration" REAL NOT NULL DEFAULT '0',
|
||||
"hidden" INTEGER NOT NULL DEFAULT '0',
|
||||
"reputation" REAL NOT NULL DEFAULT 0,
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_19 SELECT "videoID","startTime","endTime","votes","locked","incorrectVotes","UUID","userID","timeSubmitted","views","category",'skip',"service","videoDuration","hidden","reputation","shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_19 RENAME TO "sponsorTimes";
|
||||
|
||||
UPDATE "config" SET value = 19 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
13
databases/_upgrade_sponsorTimes_2.sql
Normal file
13
databases/_upgrade_sponsorTimes_2.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add new table: noSegments */
|
||||
CREATE TABLE "noSegments" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL
|
||||
);
|
||||
|
||||
/* Add version to config */
|
||||
UPDATE "config" SET value = 2 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
8
databases/_upgrade_sponsorTimes_20.sql
Normal file
8
databases/_upgrade_sponsorTimes_20.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add hash field */
|
||||
ALTER TABLE "lockCategories" ADD "reason" TEXT NOT NULL default '';
|
||||
|
||||
UPDATE "config" SET value = 20 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
26
databases/_upgrade_sponsorTimes_21.sql
Normal file
26
databases/_upgrade_sponsorTimes_21.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "archivedSponsorTimes" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL DEFAULT '0',
|
||||
"incorrectVotes" INTEGER NOT NULL DEFAULT 1,
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"service" TEXT NOT NULL DEFAULT 'Youtube',
|
||||
"actionType" TEXT NOT NULL DEFAULT 'skip',
|
||||
"videoDuration" INTEGER NOT NULL DEFAULT '0',
|
||||
"hidden" INTEGER NOT NULL DEFAULT '0',
|
||||
"reputation" REAL NOT NULL DEFAULT '0',
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 21 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
10
databases/_upgrade_sponsorTimes_22.sql
Normal file
10
databases/_upgrade_sponsorTimes_22.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add hash field */
|
||||
ALTER TABLE "sponsorTimes" ADD "userAgent" TEXT NOT NULL default '';
|
||||
|
||||
ALTER TABLE "archivedSponsorTimes" ADD "userAgent" TEXT NOT NULL default '';
|
||||
|
||||
UPDATE "config" SET value = 22 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
12
databases/_upgrade_sponsorTimes_23.sql
Normal file
12
databases/_upgrade_sponsorTimes_23.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
DELETE FROM "userNames" WHERE ctid NOT IN ( --!sqlite-ignore
|
||||
SELECT MIN(ctid) FROM "userNames" --!sqlite-ignore
|
||||
GROUP BY "userID" --!sqlite-ignore
|
||||
); --!sqlite-ignore
|
||||
|
||||
ALTER TABLE "userNames" ADD UNIQUE("userID"); --!sqlite-ignore
|
||||
|
||||
UPDATE "config" SET value = 23 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
21
databases/_upgrade_sponsorTimes_24.sql
Normal file
21
databases/_upgrade_sponsorTimes_24.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "lockCategories" ADD "service" TEXT NOT NULL default 'YouTube';
|
||||
|
||||
UPDATE "lockCategories"
|
||||
SET "service" = "sponsorTimes"."service"
|
||||
FROM "sponsorTimes"
|
||||
WHERE "lockCategories"."videoID" = "sponsorTimes"."videoID";
|
||||
|
||||
ALTER TABLE "unlistedVideos" ADD "service" TEXT NOT NULL default 'YouTube';
|
||||
|
||||
UPDATE "unlistedVideos"
|
||||
SET "service" = "sponsorTimes"."service"
|
||||
FROM "sponsorTimes"
|
||||
WHERE "unlistedVideos"."videoID" = "sponsorTimes"."videoID";
|
||||
|
||||
DROP INDEX IF EXISTS "noSegments_videoID";
|
||||
|
||||
UPDATE "config" SET value = 24 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
13
databases/_upgrade_sponsorTimes_25.sql
Normal file
13
databases/_upgrade_sponsorTimes_25.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "videoInfo" (
|
||||
"videoID" TEXT PRIMARY KEY NOT NULL,
|
||||
"channelID" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"published" REAL NOT NULL,
|
||||
"genreUrl" REAL NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 25 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
18
databases/_upgrade_sponsorTimes_26.sql
Normal file
18
databases/_upgrade_sponsorTimes_26.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE "sqlb_temp_table_26" (
|
||||
"videoID" TEXT PRIMARY KEY NOT NULL,
|
||||
"channelID" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"published" REAL NOT NULL,
|
||||
"genreUrl" TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_26 SELECT "videoID", "channelID", "title", "published", '' FROM "videoInfo";
|
||||
|
||||
DROP TABLE "videoInfo";
|
||||
ALTER TABLE sqlb_temp_table_26 RENAME TO "videoInfo";
|
||||
|
||||
UPDATE "config" SET value = 26 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
8
databases/_upgrade_sponsorTimes_27.sql
Normal file
8
databases/_upgrade_sponsorTimes_27.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "sponsorTimes" ADD "description" TEXT NOT NULL default '';
|
||||
ALTER TABLE "archivedSponsorTimes" ADD "description" TEXT NOT NULL default '';
|
||||
|
||||
UPDATE "config" SET value = 27 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
13
databases/_upgrade_sponsorTimes_28.sql
Normal file
13
databases/_upgrade_sponsorTimes_28.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "ratings" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"service" TEXT NOT NULL default 'YouTube',
|
||||
"type" INTEGER NOT NULL,
|
||||
"count" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 28 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
21
databases/_upgrade_sponsorTimes_29.sql
Normal file
21
databases/_upgrade_sponsorTimes_29.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE "sqlb_temp_table_29" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"userID" TEXT NOT NULL,
|
||||
"actionType" TEXT NOT NULL DEFAULT 'skip',
|
||||
"category" TEXT NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default '',
|
||||
"reason" TEXT NOT NULL default '',
|
||||
"service" TEXT NOT NULL default 'YouTube'
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_29 SELECT "videoID","userID",'skip',"category","hashedVideoID","reason","service" FROM "lockCategories";
|
||||
INSERT INTO sqlb_temp_table_29 SELECT "videoID","userID",'mute',"category","hashedVideoID","reason","service" FROM "lockCategories";
|
||||
|
||||
DROP TABLE "lockCategories";
|
||||
ALTER TABLE sqlb_temp_table_29 RENAME TO "lockCategories";
|
||||
|
||||
UPDATE "config" SET value = 29 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
11
databases/_upgrade_sponsorTimes_3.sql
Normal file
11
databases/_upgrade_sponsorTimes_3.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* hash upgrade test sha256('vid') = '1ff838dc6ca9680d88455341118157d59a055fe6d0e3870f9c002847bebe4663' */
|
||||
/* Add hash field */
|
||||
ALTER TABLE "sponsorTimes" ADD "hashedVideoID" TEXT NOT NULL default '';
|
||||
UPDATE "sponsorTimes" SET "hashedVideoID" = sha256("videoID");
|
||||
|
||||
/* Bump version in config */
|
||||
UPDATE "config" SET value = 3 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
7
databases/_upgrade_sponsorTimes_30.sql
Normal file
7
databases/_upgrade_sponsorTimes_30.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
UPDATE "sponsorTimes" SET "actionType" = 'poi' WHERE "category" = 'poi_highlight';
|
||||
|
||||
UPDATE "config" SET value = 30 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
25
databases/_upgrade_sponsorTimes_31.sql
Normal file
25
databases/_upgrade_sponsorTimes_31.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* START lockCategory migrations
|
||||
no sponsor migrations
|
||||
no selfpromo migrations */
|
||||
|
||||
/* exclusive_access migrations */
|
||||
DELETE FROM "lockCategories" WHERE "category" = 'exclusive_access' AND "actionType" != 'full';
|
||||
/* delete all full locks on categories without full */
|
||||
DELETE FROM "lockCategories" WHERE "actionType" = 'full' AND "category" in ('interaction', 'intro', 'outro', 'preview', 'filler', 'music_offtopic', 'poi_highlight');
|
||||
/* delete all non-skip music_offtopic locks */
|
||||
DELETE FROM "lockCategories" WHERE "category" = 'music_offtopic' AND "actionType" != 'skip';
|
||||
/* convert all poi_highlight to actionType poi */
|
||||
UPDATE "lockCategories" SET "actionType" = 'poi' WHERE "category" = 'poi_highlight' AND "actionType" = 'skip';
|
||||
/* delete all non-skip poi_highlight locks */
|
||||
DELETE FROM "lockCategories" WHERE "category" = 'poi_highlight' AND "actionType" != 'poi';
|
||||
|
||||
/* END lockCategory migrations */
|
||||
|
||||
/* delete all redundant userName entries */
|
||||
DELETE FROM "userNames" WHERE "userName" = "userID" AND "locked" = 0;
|
||||
|
||||
UPDATE "config" SET value = 31 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
19
databases/_upgrade_sponsorTimes_32.sql
Normal file
19
databases/_upgrade_sponsorTimes_32.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- Add primary keys
|
||||
|
||||
ALTER TABLE "sponsorTimes" ADD PRIMARY KEY ("UUID"); --!sqlite-ignore
|
||||
ALTER TABLE "vipUsers" ADD PRIMARY KEY ("userID"); --!sqlite-ignore
|
||||
ALTER TABLE "userNames" ADD PRIMARY KEY ("userID"); --!sqlite-ignore
|
||||
ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "lockCategories" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "warnings" ADD PRIMARY KEY ("userID", "issueTime"); --!sqlite-ignore
|
||||
ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); --!sqlite-ignore
|
||||
ALTER TABLE "unlistedVideos" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
ALTER TABLE "config" ADD PRIMARY KEY ("key"); --!sqlite-ignore
|
||||
ALTER TABLE "archivedSponsorTimes" ADD PRIMARY KEY ("UUID"); --!sqlite-ignore
|
||||
ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore
|
||||
|
||||
UPDATE "config" SET value = 32 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
13
databases/_upgrade_sponsorTimes_33.sql
Normal file
13
databases/_upgrade_sponsorTimes_33.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "userFeatures" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"feature" INTEGER NOT NULL,
|
||||
"issuerUserID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
PRIMARY KEY ("userID", "feature")
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 33 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
7
databases/_upgrade_sponsorTimes_34.sql
Normal file
7
databases/_upgrade_sponsorTimes_34.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "videoInfo" DROP COLUMN "genreUrl";
|
||||
|
||||
UPDATE "config" SET value = 34 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
7
databases/_upgrade_sponsorTimes_35.sql
Normal file
7
databases/_upgrade_sponsorTimes_35.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "titleVotes" ADD "verification" INTEGER default 0;
|
||||
|
||||
UPDATE "config" SET value = 35 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
7
databases/_upgrade_sponsorTimes_36.sql
Normal file
7
databases/_upgrade_sponsorTimes_36.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "warnings" ADD "type" INTEGER default 0;
|
||||
|
||||
UPDATE "config" SET value = 36 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
7
databases/_upgrade_sponsorTimes_37.sql
Normal file
7
databases/_upgrade_sponsorTimes_37.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE "titles" ADD UNIQUE ("videoID", "title"); --!sqlite-ignore
|
||||
|
||||
UPDATE "config" SET value = 37 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
11
databases/_upgrade_sponsorTimes_38.sql
Normal file
11
databases/_upgrade_sponsorTimes_38.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
UPDATE "titleVotes" SET "shadowHidden" = 1
|
||||
WHERE "UUID" IN (SELECT "UUID" FROM "titles" INNER JOIN "shadowBannedUsers" "bans" ON "titles"."userID" = "bans"."userID");
|
||||
|
||||
UPDATE "thumbnailVotes" SET "shadowHidden" = 1
|
||||
WHERE "UUID" IN (SELECT "UUID" FROM "thumbnails" INNER JOIN "shadowBannedUsers" "bans" ON "thumbnails"."userID" = "bans"."userID");
|
||||
|
||||
UPDATE "config" SET value = 38 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
12
databases/_upgrade_sponsorTimes_4.sql
Normal file
12
databases/_upgrade_sponsorTimes_4.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Create warnings table */
|
||||
CREATE TABLE "warnings" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"issueTime" INTEGER NOT NULL,
|
||||
"issuerUserID" TEXT NOT NULL
|
||||
);
|
||||
|
||||
UPDATE "config" SET value = 4 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
17
databases/_upgrade_sponsorTimes_5.sql
Normal file
17
databases/_upgrade_sponsorTimes_5.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add enabled field */
|
||||
CREATE TABLE "sqlb_temp_table_5" (
|
||||
"userID" TEXT NOT NULL,
|
||||
"issueTime" INTEGER NOT NULL,
|
||||
"issuerUserID" TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL
|
||||
);
|
||||
INSERT INTO sqlb_temp_table_5 SELECT "userID","issueTime","issuerUserID",1 FROM "warnings";
|
||||
|
||||
DROP TABLE warnings;
|
||||
ALTER TABLE sqlb_temp_table_5 RENAME TO "warnings";;
|
||||
|
||||
UPDATE "config" SET value = 5 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
27
databases/_upgrade_sponsorTimes_6.sql
Normal file
27
databases/_upgrade_sponsorTimes_6.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add new voting field */
|
||||
CREATE TABLE "sqlb_temp_table_6" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0',
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_6 SELECT "videoID","startTime","endTime","votes",'0',"incorrectVotes","UUID","userID","timeSubmitted","views","category","shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_6 RENAME TO "sponsorTimes";
|
||||
|
||||
UPDATE "config" SET value = 6 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
28
databases/_upgrade_sponsorTimes_7.sql
Normal file
28
databases/_upgrade_sponsorTimes_7.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add Service field */
|
||||
CREATE TABLE "sqlb_temp_table_7" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0',
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"service" TEXT NOT NULL DEFAULT 'YouTube',
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_7 SELECT "videoID","startTime","endTime","votes","locked","incorrectVotes","UUID","userID","timeSubmitted","views","category",'YouTube', "shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_7 RENAME TO "sponsorTimes";
|
||||
|
||||
UPDATE "config" SET value = 7 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
29
databases/_upgrade_sponsorTimes_8.sql
Normal file
29
databases/_upgrade_sponsorTimes_8.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add Service field */
|
||||
CREATE TABLE "sqlb_temp_table_8" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0',
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"service" TEXT NOT NULL DEFAULT 'YouTube',
|
||||
"videoDuration" INTEGER NOT NULL DEFAULT '0',
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_8 SELECT "videoID","startTime","endTime","votes","locked","incorrectVotes","UUID","userID","timeSubmitted","views","category","service",'0', "shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_8 RENAME TO "sponsorTimes";
|
||||
|
||||
UPDATE "config" SET value = 8 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
29
databases/_upgrade_sponsorTimes_9.sql
Normal file
29
databases/_upgrade_sponsorTimes_9.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
/* Add Service field */
|
||||
CREATE TABLE "sqlb_temp_table_9" (
|
||||
"videoID" TEXT NOT NULL,
|
||||
"startTime" REAL NOT NULL,
|
||||
"endTime" REAL NOT NULL,
|
||||
"votes" INTEGER NOT NULL,
|
||||
"locked" INTEGER NOT NULL default '0',
|
||||
"incorrectVotes" INTEGER NOT NULL default '1',
|
||||
"UUID" TEXT NOT NULL UNIQUE,
|
||||
"userID" TEXT NOT NULL,
|
||||
"timeSubmitted" INTEGER NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"category" TEXT NOT NULL DEFAULT 'sponsor',
|
||||
"service" TEXT NOT NULL DEFAULT 'YouTube',
|
||||
"videoDuration" REAL NOT NULL DEFAULT '0',
|
||||
"shadowHidden" INTEGER NOT NULL,
|
||||
"hashedVideoID" TEXT NOT NULL default ''
|
||||
);
|
||||
|
||||
INSERT INTO sqlb_temp_table_9 SELECT "videoID","startTime","endTime","votes","locked","incorrectVotes","UUID","userID","timeSubmitted","views","category","service",'0', "shadowHidden","hashedVideoID" FROM "sponsorTimes";
|
||||
|
||||
DROP TABLE "sponsorTimes";
|
||||
ALTER TABLE sqlb_temp_table_9 RENAME TO "sponsorTimes";
|
||||
|
||||
UPDATE "config" SET value = 9 WHERE key = 'version';
|
||||
|
||||
COMMIT;
|
||||
3
docker/database.env.example
Normal file
3
docker/database.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
POSTGRES_USER=sponsorblock
|
||||
POSTGRES_PASSWORD=<choose-a-password>
|
||||
POSTGRES_DB=sponsorTimes
|
||||
15
docker/docker-compose-ci.yml
Normal file
15
docker/docker-compose-ci.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
version: '3'
|
||||
services:
|
||||
postgres:
|
||||
container_name: database-co
|
||||
image: postgres:alpine
|
||||
environment:
|
||||
- POSTGRES_USER=${PG_USER}
|
||||
- POSTGRES_PASSWORD=${PG_PASS}
|
||||
ports:
|
||||
- 5432:5432
|
||||
redis:
|
||||
container_name: redis-ci
|
||||
image: redis:alpine
|
||||
ports:
|
||||
- 6379:6379
|
||||
44
docker/docker-compose.yml
Normal file
44
docker/docker-compose.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
version: '3'
|
||||
services:
|
||||
database:
|
||||
container_name: database
|
||||
image: postgres:14
|
||||
env_file:
|
||||
- database.env
|
||||
volumes:
|
||||
- database-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
restart: always
|
||||
redis:
|
||||
container_name: redis
|
||||
image: redis:7.0
|
||||
command: /usr/local/etc/redis/redis.conf
|
||||
volumes:
|
||||
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
|
||||
ports:
|
||||
- 32773:6379
|
||||
sysctls:
|
||||
- net.core.somaxconn=324000
|
||||
- net.ipv4.tcp_max_syn_backlog=3240000
|
||||
restart: always
|
||||
newleaf:
|
||||
image: abeltramo/newleaf:latest
|
||||
container_name: newleaf
|
||||
restart: always
|
||||
ports:
|
||||
- 3241:3000
|
||||
volumes:
|
||||
- ./newleaf/configuration.py:/workdir/configuration.py
|
||||
rsync:
|
||||
image: mchangrh/rsync:latest
|
||||
container_name: rsync
|
||||
restart: always
|
||||
ports:
|
||||
- 873:873
|
||||
volumes:
|
||||
- ./rsync/rsyncd.conf:/etc/rsyncd.conf
|
||||
- ./database-export/:/mirror
|
||||
|
||||
volumes:
|
||||
database-data:
|
||||
5
docker/migrate/database.pgload.txt
Normal file
5
docker/migrate/database.pgload.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
load database
|
||||
from ./database.db
|
||||
into postgresql://sponsorblock:pw@127.0.0.1:5432/sponsorTimes
|
||||
|
||||
with quote identifiers;
|
||||
17
docker/newleaf/configuration.py
Normal file
17
docker/newleaf/configuration.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ==============================
|
||||
# You MUST set these settings.
|
||||
# ==============================
|
||||
|
||||
# A URL that this site can be accessed on. Do not include a trailing slash.
|
||||
website_origin = "http://newleaf:3000"
|
||||
|
||||
|
||||
# ==============================
|
||||
# These settings are optional.
|
||||
# ==============================
|
||||
|
||||
# The address of the interface to bind to.
|
||||
#bind_host = "0.0.0.0"
|
||||
|
||||
# The port to bind to.
|
||||
#bind_port = 3000
|
||||
5
docker/redis/redis.conf
Normal file
5
docker/redis/redis.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
maxmemory-policy allkeys-lru
|
||||
maxmemory 6000mb
|
||||
|
||||
appendonly no
|
||||
save ""
|
||||
15
docker/rsync/rsyncd.conf
Normal file
15
docker/rsync/rsyncd.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
pid file = /var/run/rsyncd.pid
|
||||
lock file = /var/run/rsync.lock
|
||||
log file = /var/log/rsync.log
|
||||
# replace with user accessing the files
|
||||
|
||||
[sponsorblock]
|
||||
use chroot = no
|
||||
max connections = 10
|
||||
# path to mirrored files
|
||||
path = /mirror
|
||||
comment = sponsorblock-database
|
||||
read only = true
|
||||
refuse options = c delete zl
|
||||
# disallow checksumming and compression level to reduce CPU/IO load
|
||||
# disallow deleting files clientside
|
||||
12
entrypoint.sh
Executable file
12
entrypoint.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
echo 'Entrypoint script'
|
||||
cd /usr/src/app
|
||||
|
||||
# blank config, use defaults
|
||||
test -e config.json || cat <<EOF > config.json
|
||||
{
|
||||
}
|
||||
EOF
|
||||
|
||||
node dist/src/index.js
|
||||
404
index.js
404
index.js
@@ -1,404 +0,0 @@
|
||||
var express = require('express');
|
||||
var http = require('http');
|
||||
|
||||
// Create a service (the app object is just a callback).
|
||||
var app = express();
|
||||
|
||||
//uuid service
|
||||
var uuidv1 = require('uuid/v1');
|
||||
|
||||
//hashing service
|
||||
var crypto = require('crypto');
|
||||
|
||||
//load database
|
||||
var sqlite3 = require('sqlite3').verbose();
|
||||
var db = new sqlite3.Database('./databases/sponsorTimes.db');
|
||||
|
||||
// Create an HTTP service.
|
||||
http.createServer(app).listen(80);
|
||||
|
||||
//global salt that is added to every ip before hashing to
|
||||
// make it even harder for someone to decode the ip
|
||||
var globalSalt = "49cb0d52-1aec-4b89-85fc-fab2c53062fb";
|
||||
|
||||
//setup CORS correctly
|
||||
app.use(function(req, res, next) {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||
next();
|
||||
});
|
||||
|
||||
//add the get function
|
||||
app.get('/api/getVideoSponsorTimes', function (req, res) {
|
||||
let videoID = req.query.videoID;
|
||||
|
||||
let sponsorTimes = [];
|
||||
let votes = []
|
||||
let UUIDs = [];
|
||||
|
||||
db.prepare("SELECT startTime, endTime, votes, UUID FROM sponsorTimes WHERE videoID = ? ORDER BY startTime").all(videoID, function(err, rows) {
|
||||
if (err) console.log(err);
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
//check if votes are above -2
|
||||
if (rows[i].votes < -2) {
|
||||
//too untrustworthy, just ignore it
|
||||
continue;
|
||||
}
|
||||
sponsorTimes.push([]);
|
||||
|
||||
let index = sponsorTimes.length - 1;
|
||||
|
||||
sponsorTimes[index][0] = rows[i].startTime;
|
||||
sponsorTimes[index][1] = rows[i].endTime;
|
||||
|
||||
votes[index] = rows[i].votes;
|
||||
UUIDs[index] = rows[i].UUID;
|
||||
}
|
||||
|
||||
if (sponsorTimes.length == 0) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
|
||||
organisedData = getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs);
|
||||
sponsorTimes = organisedData.sponsorTimes;
|
||||
UUIDs = organisedData.UUIDs;
|
||||
|
||||
if (sponsorTimes.length == 0) {
|
||||
res.sendStatus(404);
|
||||
} else {
|
||||
//send result
|
||||
res.send({
|
||||
sponsorTimes: sponsorTimes,
|
||||
UUIDs: UUIDs
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//add the post function
|
||||
app.get('/api/postVideoSponsorTimes', function (req, res) {
|
||||
let videoID = req.query.videoID;
|
||||
let startTime = req.query.startTime;
|
||||
let endTime = req.query.endTime;
|
||||
let userID = req.query.userID;
|
||||
|
||||
if (videoID == undefined || startTime == undefined || endTime == undefined || userID == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//x-forwarded-for if this server is behind a proxy
|
||||
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
|
||||
//hash the ip so no one can get it from the database
|
||||
let hashedIP = ip + globalSalt;
|
||||
//hash it 5000 times, this makes it very hard to brute force
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
let hashCreator = crypto.createHash('sha512');
|
||||
hashedIP = hashCreator.update(hashedIP).digest('hex');
|
||||
}
|
||||
|
||||
startTime = parseFloat(startTime);
|
||||
endTime = parseFloat(endTime);
|
||||
|
||||
let UUID = uuidv1();
|
||||
|
||||
//get current time
|
||||
let timeSubmitted = Date.now();
|
||||
|
||||
let yesterday = timeSubmitted - 86400000;
|
||||
|
||||
//check to see if this ip has submitted too many sponsors today
|
||||
db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE hashedIP = ? AND videoID = ? AND timeSubmitted > ?").get([hashedIP, videoID, yesterday], function(err, row) {
|
||||
if (row.count >= 10) {
|
||||
//too many sponsors for the same video from the same ip address
|
||||
res.sendStatus(429);
|
||||
} else {
|
||||
//check to see if the user has already submitted sponsors for this video
|
||||
db.prepare("SELECT COUNT(*) as count FROM sponsorTimes WHERE userID = ? and videoID = ?").get([userID, videoID], function(err, row) {
|
||||
if (row.count >= 4) {
|
||||
//too many sponsors for the same video from the same user
|
||||
res.sendStatus(429);
|
||||
} else {
|
||||
//check if this info has already been submitted first
|
||||
db.prepare("SELECT UUID FROM sponsorTimes WHERE startTime = ? and endTime = ? and videoID = ?").get([startTime, endTime, videoID], function(err, row) {
|
||||
if (err) console.log(err);
|
||||
|
||||
if (row == null) {
|
||||
//not a duplicate, execute query
|
||||
db.prepare("INSERT INTO sponsorTimes VALUES(?, ?, ?, ?, ?, ?, ?, ?)").run(videoID, startTime, endTime, 0, UUID, userID, hashedIP, timeSubmitted);
|
||||
|
||||
res.sendStatus(200);
|
||||
} else {
|
||||
res.sendStatus(409);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//voting endpoint
|
||||
app.get('/api/voteOnSponsorTime', function (req, res) {
|
||||
let UUID = req.query.UUID;
|
||||
let userID = req.query.userID;
|
||||
let type = req.query.type;
|
||||
|
||||
if (UUID == undefined || userID == undefined || type == undefined) {
|
||||
//invalid request
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
//check if vote has already happened
|
||||
db.prepare("SELECT type FROM votes WHERE userID = ? AND UUID = ?").get(userID, UUID, function(err, row) {
|
||||
if (err) console.log(err);
|
||||
|
||||
if (row != undefined && row.type == type) {
|
||||
//they have already done this exact vote
|
||||
res.status(405).send("Duplicate Vote");
|
||||
return;
|
||||
}
|
||||
|
||||
//-1 for downvote, 1 for upvote. Maybe more depending on reputation in the future
|
||||
let incrementAmount = 0;
|
||||
let oldIncrementAmount = 0;
|
||||
|
||||
if (type == 1) {
|
||||
//upvote
|
||||
incrementAmount = 1;
|
||||
} else if (type == 0) {
|
||||
//downvote
|
||||
incrementAmount = -1;
|
||||
} else {
|
||||
//unrecongnised type of vote
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
if (row != undefined) {
|
||||
if (row.type == 1) {
|
||||
//upvote
|
||||
oldIncrementAmount = 1;
|
||||
} else if (row.type == 0) {
|
||||
//downvote
|
||||
oldIncrementAmount = -1;
|
||||
}
|
||||
}
|
||||
|
||||
//update the votes table
|
||||
if (row != undefined) {
|
||||
db.prepare("UPDATE votes SET type = ? WHERE userID = ? AND UUID = ?").run(type, userID, UUID);
|
||||
} else {
|
||||
db.prepare("INSERT INTO votes VALUES(?, ?, ?)").run(userID, UUID, type);
|
||||
}
|
||||
|
||||
//update the vote count on this sponsorTime
|
||||
//oldIncrementAmount will be zero is row is null
|
||||
db.prepare("UPDATE sponsorTimes SET votes = votes + ? WHERE UUID = ?").run(incrementAmount - oldIncrementAmount, UUID);
|
||||
|
||||
//update the votes table
|
||||
|
||||
//added to db
|
||||
res.sendStatus(200);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/database.db', function (req, res) {
|
||||
res.sendFile("./databases/sponsorTimes.db", { root: __dirname });
|
||||
});
|
||||
|
||||
|
||||
//This function will find sponsor times that are contained inside of eachother, called similar sponsor times
|
||||
//Only one similar time will be returned, randomly generated based on the sqrt of votes.
|
||||
//This allows new less voted items to still sometimes appear to give them a chance at getting votes.
|
||||
//Sponsor times with less than -2 votes are already ignored before this function is called
|
||||
function getVoteOrganisedSponsorTimes(sponsorTimes, votes, UUIDs) {
|
||||
//list of sponsors that are contained inside eachother
|
||||
let similarSponsors = [];
|
||||
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
//see if the start time is located between the start and end time of the other sponsor time.
|
||||
for (let j = 0; j < sponsorTimes.length; j++) {
|
||||
if (sponsorTimes[j][0] > sponsorTimes[i][0] && sponsorTimes[j][0] < sponsorTimes[i][1]) {
|
||||
//sponsor j is contained in sponsor i
|
||||
similarSponsors.push([i, j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let similarSponsorsGroups = [];
|
||||
//once they have been added to a group, they don't need to be dealt with anymore
|
||||
let dealtWithSimilarSponsors = [];
|
||||
|
||||
//create lists of all the similar groups (if 1 and 2 are similar, and 2 and 3 are similar, the group is 1, 2, 3)
|
||||
for (let i = 0; i < similarSponsors.length; i++) {
|
||||
if (dealtWithSimilarSponsors.includes(i)) {
|
||||
//dealt with already
|
||||
continue;
|
||||
}
|
||||
|
||||
//this is the group of indexes that are similar
|
||||
let group = similarSponsors[i];
|
||||
for (let j = 0; j < similarSponsors.length; j++) {
|
||||
if (group.includes(similarSponsors[j][0]) || group.includes(similarSponsors[j][1])) {
|
||||
//this is a similar group
|
||||
group.push(similarSponsors[j][0]);
|
||||
group.push(similarSponsors[j][1]);
|
||||
dealtWithSimilarSponsors.push(j);
|
||||
}
|
||||
}
|
||||
similarSponsorsGroups.push(group);
|
||||
}
|
||||
|
||||
//remove duplicate indexes in group arrays
|
||||
for (let i = 0; i < similarSponsorsGroups.length; i++) {
|
||||
uniqueArray = similarSponsorsGroups[i].filter(function(item, pos, self) {
|
||||
return self.indexOf(item) == pos;
|
||||
});
|
||||
|
||||
similarSponsorsGroups[i] = uniqueArray;
|
||||
}
|
||||
|
||||
let weightedRandomIndexes = getWeightedRandomChoiceForArray(similarSponsorsGroups, votes);
|
||||
|
||||
let finalSponsorTimeIndexes = weightedRandomIndexes.finalChoices;
|
||||
//the sponsor times either chosen to be added to finalSponsorTimeIndexes or chosen not to be added
|
||||
let finalSponsorTimeIndexesDealtWith = weightedRandomIndexes.choicesDealtWith;
|
||||
|
||||
let voteSums = weightedRandomIndexes.weightSums;
|
||||
//convert these into the votes
|
||||
for (let i = 0; i < voteSums.length; i++) {
|
||||
if (voteSums[i] != undefined) {
|
||||
//it should use the sum of votes, since anyone upvoting a similar sponsor is upvoting the existence of that sponsor.
|
||||
votes[finalSponsorTimeIndexes[i]] = voteSums;
|
||||
}
|
||||
}
|
||||
|
||||
//find the indexes never dealt with and add them
|
||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||
if (!finalSponsorTimeIndexesDealtWith.includes(i)) {
|
||||
finalSponsorTimeIndexes.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
//if there are too many indexes, find the best 4
|
||||
if (finalSponsorTimeIndexes.length > 4) {
|
||||
finalSponsorTimeIndexes = getWeightedRandomChoice(finalSponsorTimeIndexes, votes, 4).finalChoices;
|
||||
}
|
||||
|
||||
//convert this to a final array to return
|
||||
let finalSponsorTimes = [];
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
finalSponsorTimes.push(sponsorTimes[finalSponsorTimeIndexes[i]]);
|
||||
}
|
||||
|
||||
//convert this to a final array of UUIDs as well
|
||||
let finalUUIDs = [];
|
||||
for (let i = 0; i < finalSponsorTimeIndexes.length; i++) {
|
||||
finalUUIDs.push(UUIDs[finalSponsorTimeIndexes[i]]);
|
||||
}
|
||||
|
||||
return {
|
||||
sponsorTimes: finalSponsorTimes,
|
||||
UUIDs: finalUUIDs
|
||||
};
|
||||
}
|
||||
|
||||
//gets the getWeightedRandomChoice for each group in an array of groups
|
||||
function getWeightedRandomChoiceForArray(choiceGroups, weights) {
|
||||
let finalChoices = [];
|
||||
//the indexes either chosen to be added to final indexes or chosen not to be added
|
||||
let choicesDealtWith = [];
|
||||
//for each choice group, what are the sums of the weights
|
||||
let weightSums = [];
|
||||
|
||||
for (let i = 0; i < choiceGroups.length; i++) {
|
||||
//find weight sums for this group
|
||||
weightSums.push(0);
|
||||
for (let j = 0; j < choiceGroups[i].length; j++) {
|
||||
//only if it is a positive vote, otherwise it is probably just a sponsor time with slightly wrong time
|
||||
if (weights[choiceGroups[i][j]] > 0) {
|
||||
weightSums[weightSums.length - 1] += weights[choiceGroups[i][j]];
|
||||
}
|
||||
}
|
||||
|
||||
//create a random choice for this group
|
||||
let randomChoice = getWeightedRandomChoice(choiceGroups[i], weights, 1)
|
||||
finalChoices.push(randomChoice.finalChoices);
|
||||
|
||||
for (let j = 0; j < randomChoice.choicesDealtWith.length; j++) {
|
||||
choicesDealtWith.push(randomChoice.choicesDealtWith[j])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
finalChoices: finalChoices,
|
||||
choicesDealtWith: choicesDealtWith,
|
||||
weightSums: weightSums
|
||||
};
|
||||
}
|
||||
|
||||
//gets a weighted random choice from the indexes array based on the weights.
|
||||
//amountOfChoices speicifies the amount of choices to return, 1 or more.
|
||||
//choices are unique
|
||||
function getWeightedRandomChoice(choices, weights, amountOfChoices) {
|
||||
if (amountOfChoices > choices.length) {
|
||||
//not possible, since all choices must be unique
|
||||
return null;
|
||||
}
|
||||
|
||||
let finalChoices = [];
|
||||
let choicesDealtWith = [];
|
||||
|
||||
let sqrtWeightsList = [];
|
||||
//the total of all the weights run through the cutom sqrt function
|
||||
let totalSqrtWeights = 0;
|
||||
for (let j = 0; j < choices.length; j++) {
|
||||
//multiplying by 10 makes around 13 votes the point where it the votes start not mattering as much (10 + 3)
|
||||
//The 3 makes -2 the minimum votes before being ignored completely
|
||||
//https://www.desmos.com/calculator/ljftxolg9j
|
||||
//this can be changed if this system increases in popularity.
|
||||
let sqrtVote = Math.sqrt((weights[choices[j]] + 3) * 10);
|
||||
sqrtWeightsList.push(sqrtVote)
|
||||
totalSqrtWeights += sqrtVote;
|
||||
|
||||
//this index has now been deat with
|
||||
choicesDealtWith.push(choices[j]);
|
||||
}
|
||||
|
||||
//iterate and find amountOfChoices choices
|
||||
let randomNumber = Math.random();
|
||||
//this array will keep adding to this variable each time one sqrt vote has been dealt with
|
||||
//this is the sum of all the sqrtVotes under this index
|
||||
let currentVoteNumber = 0;
|
||||
for (let j = 0; j < sqrtWeightsList.length; j++) {
|
||||
if (randomNumber > currentVoteNumber / totalSqrtWeights && randomNumber < (currentVoteNumber + sqrtWeightsList[j]) / totalSqrtWeights) {
|
||||
//this one was randomly generated
|
||||
finalChoices.push(choices[j]);
|
||||
//remove that from original array, for next recursion pass if it happens
|
||||
choices.splice(j, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
//add on to the count
|
||||
currentVoteNumber += sqrtWeightsList[j];
|
||||
}
|
||||
|
||||
//add on the other choices as well using recursion
|
||||
if (amountOfChoices > 1) {
|
||||
let otherChoices = getWeightedRandomChoice(choices, weights, amountOfChoices - 1).finalChoices;
|
||||
//add all these choices to the finalChoices array being returned
|
||||
for (let i = 0; i < otherChoices.length; i++) {
|
||||
finalChoices.push(otherChoices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
finalChoices: finalChoices,
|
||||
choicesDealtWith: choicesDealtWith
|
||||
};
|
||||
}
|
||||
5
nodemon.json
Normal file
5
nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts,json",
|
||||
"exec": "(npm test || echo test failed) && npm start"
|
||||
}
|
||||
10028
package-lock.json
generated
10028
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
59
package.json
59
package.json
@@ -2,17 +2,60 @@
|
||||
"name": "sponsor_block_server",
|
||||
"version": "0.1.0",
|
||||
"description": "Server that holds the SponsorBlock database",
|
||||
"main": "index.js",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node index.js"
|
||||
"test": "npm run tsc && ts-node test/test.ts",
|
||||
"cover": "nyc npm test",
|
||||
"cover:report": "nyc report",
|
||||
"dev": "nodemon",
|
||||
"dev:bash": "nodemon -x 'npm test ; npm start'",
|
||||
"postgres:docker": "docker run --init -it --rm -p 5432:5432 -e POSTGRES_USER=ci_db_user -e POSTGRES_PASSWORD=ci_db_pass postgres:14-alpine",
|
||||
"redis:docker": "docker run --init -it --rm -p 6379:6379 redis:7-alpine --save '' --appendonly no",
|
||||
"start": "ts-node src/index.ts",
|
||||
"tsc": "tsc -p tsconfig.json",
|
||||
"lint": "eslint src test",
|
||||
"lint:fix": "eslint src test --fix"
|
||||
},
|
||||
"author": "Ajay Ramachandran",
|
||||
"license": "MIT",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"http": "0.0.0",
|
||||
"sqlite3": "^4.0.9",
|
||||
"uuid": "^3.3.2"
|
||||
"axios": "^1.1.3",
|
||||
"better-sqlite3": "^8.0.1",
|
||||
"cron": "^2.1.0",
|
||||
"express": "^4.18.2",
|
||||
"express-promise-router": "^4.1.1",
|
||||
"express-rate-limit": "^6.7.0",
|
||||
"form-data": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"pg": "^8.8.0",
|
||||
"rate-limit-redis": "^3.0.1",
|
||||
"redis": "^4.5.0",
|
||||
"seedrandom": "^3.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||
"@types/better-sqlite3": "^7.6.2",
|
||||
"@types/cron": "^2.0.0",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/lodash": "^4.14.189",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/pg": "^8.6.5",
|
||||
"@types/seedrandom": "^3.0.5",
|
||||
"@types/sinon": "^10.0.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"axios-mock-adapter": "^1.21.2",
|
||||
"eslint": "^8.28.0",
|
||||
"mocha": "^10.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"nyc": "^15.1.0",
|
||||
"sinon": "^14.0.2",
|
||||
"ts-mock-imports": "^1.3.8",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
|
||||
239
src/app.ts
Normal file
239
src/app.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
import express, { Request, RequestHandler, Response, Router } from "express";
|
||||
import { config } from "./config";
|
||||
import { oldSubmitSponsorTimes } from "./routes/oldSubmitSponsorTimes";
|
||||
import { postSegmentShift } from "./routes/postSegmentShift";
|
||||
import { postWarning } from "./routes/postWarning";
|
||||
import { getIsUserVIP } from "./routes/getIsUserVIP";
|
||||
import { deleteLockCategoriesEndpoint } from "./routes/deleteLockCategories";
|
||||
import { postLockCategories } from "./routes/postLockCategories";
|
||||
import { endpoint as getUserInfo } from "./routes/getUserInfo";
|
||||
import { getDaysSavedFormatted } from "./routes/getDaysSavedFormatted";
|
||||
import { getTotalStats } from "./routes/getTotalStats";
|
||||
import { getTopUsers } from "./routes/getTopUsers";
|
||||
import { getViewsForUser } from "./routes/getViewsForUser";
|
||||
import { getSavedTimeForUser } from "./routes/getSavedTimeForUser";
|
||||
import { addUserAsVIP } from "./routes/addUserAsVIP";
|
||||
import { shadowBanUser } from "./routes/shadowBanUser";
|
||||
import { getUsername } from "./routes/getUsername";
|
||||
import { setUsername } from "./routes/setUsername";
|
||||
import { viewedVideoSponsorTime } from "./routes/viewedVideoSponsorTime";
|
||||
import { voteOnSponsorTime, getUserID as voteGetUserID } from "./routes/voteOnSponsorTime";
|
||||
import { getSkipSegmentsByHash } from "./routes/getSkipSegmentsByHash";
|
||||
import { postSkipSegments } from "./routes/postSkipSegments";
|
||||
import { getSkipSegments, oldGetVideoSponsorTimes } from "./routes/getSkipSegments";
|
||||
import { userCounter } from "./middleware/userCounter";
|
||||
import { loggerMiddleware } from "./middleware/logger";
|
||||
import { corsMiddleware } from "./middleware/cors";
|
||||
import { apiCspMiddleware } from "./middleware/apiCsp";
|
||||
import { rateLimitMiddleware } from "./middleware/requestRateLimit";
|
||||
import dumpDatabase from "./routes/dumpDatabase";
|
||||
import { endpoint as getSegmentInfo } from "./routes/getSegmentInfo";
|
||||
import { postClearCache } from "./routes/postClearCache";
|
||||
import { addUnlistedVideo } from "./routes/addUnlistedVideo";
|
||||
import { postPurgeAllSegments } from "./routes/postPurgeAllSegments";
|
||||
import { getUserID } from "./routes/getUserID";
|
||||
import { getLockCategories } from "./routes/getLockCategories";
|
||||
import { getLockCategoriesByHash } from "./routes/getLockCategoriesByHash";
|
||||
import { endpoint as getSearchSegments } from "./routes/getSearchSegments";
|
||||
import { getStatus } from "./routes/getStatus";
|
||||
import { getLockReason } from "./routes/getLockReason";
|
||||
import { getUserStats } from "./routes/getUserStats";
|
||||
import ExpressPromiseRouter from "express-promise-router";
|
||||
import { Server } from "http";
|
||||
import { youtubeApiProxy } from "./routes/youtubeApiProxy";
|
||||
import { getChapterNames } from "./routes/getChapterNames";
|
||||
import { getTopCategoryUsers } from "./routes/getTopCategoryUsers";
|
||||
import { addUserAsTempVIP } from "./routes/addUserAsTempVIP";
|
||||
import { endpoint as getVideoLabels } from "./routes/getVideoLabel";
|
||||
import { getVideoLabelsByHash } from "./routes/getVideoLabelByHash";
|
||||
import { addFeature } from "./routes/addFeature";
|
||||
import { generateTokenRequest } from "./routes/generateToken";
|
||||
import { verifyTokenRequest } from "./routes/verifyToken";
|
||||
import { getBranding, getBrandingByHashEndpoint } from "./routes/getBranding";
|
||||
import { postBranding } from "./routes/postBranding";
|
||||
import { cacheMiddlware } from "./middleware/etag";
|
||||
import { hostHeader } from "./middleware/hostHeader";
|
||||
import { getBrandingStats } from "./routes/getBrandingStats";
|
||||
import { getTopBrandingUsers } from "./routes/getTopBrandingUsers";
|
||||
import { getFeatureFlag } from "./routes/getFeatureFlag";
|
||||
|
||||
export function createServer(callback: () => void): Server {
|
||||
// Create a service (the app object is just a callback).
|
||||
const app = express();
|
||||
|
||||
const router = ExpressPromiseRouter();
|
||||
app.use(router);
|
||||
app.set("etag", false); // disable built in etag
|
||||
|
||||
//setup CORS correctly
|
||||
router.use(corsMiddleware);
|
||||
router.use(loggerMiddleware);
|
||||
router.use("/api/", apiCspMiddleware);
|
||||
router.use(hostHeader);
|
||||
router.use(cacheMiddlware);
|
||||
router.use(express.json());
|
||||
|
||||
if (config.userCounterURL) router.use(userCounter);
|
||||
|
||||
// Setup pretty JSON
|
||||
if (config.mode === "development") app.set("json spaces", 2);
|
||||
|
||||
// Set production mode
|
||||
app.set("env", config.mode || "production");
|
||||
|
||||
setupRoutes(router);
|
||||
|
||||
return app.listen(config.port, callback);
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
function setupRoutes(router: Router) {
|
||||
// Rate limit endpoint lists
|
||||
const voteEndpoints: RequestHandler[] = [voteOnSponsorTime];
|
||||
const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime];
|
||||
if (config.rateLimit && config.redisRateLimit) {
|
||||
if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote, voteGetUserID));
|
||||
if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view));
|
||||
}
|
||||
|
||||
//add the get function
|
||||
router.get("/api/getVideoSponsorTimes", oldGetVideoSponsorTimes);
|
||||
|
||||
//add the oldpost function
|
||||
router.get("/api/postVideoSponsorTimes", oldSubmitSponsorTimes);
|
||||
router.post("/api/postVideoSponsorTimes", oldSubmitSponsorTimes);
|
||||
|
||||
//add the skip segments functions
|
||||
router.get("/api/skipSegments", getSkipSegments);
|
||||
router.post("/api/skipSegments", postSkipSegments);
|
||||
|
||||
// add the privacy protecting skip segments functions
|
||||
router.get("/api/skipSegments/:prefix", getSkipSegmentsByHash);
|
||||
|
||||
//voting endpoint
|
||||
router.get("/api/voteOnSponsorTime", ...voteEndpoints);
|
||||
router.post("/api/voteOnSponsorTime", ...voteEndpoints);
|
||||
|
||||
//Endpoint when a submission is skipped
|
||||
router.get("/api/viewedVideoSponsorTime", ...viewEndpoints);
|
||||
router.post("/api/viewedVideoSponsorTime", ...viewEndpoints);
|
||||
|
||||
//To set your username for the stats view
|
||||
router.post("/api/setUsername", setUsername);
|
||||
|
||||
//get what username this user has
|
||||
router.get("/api/getUsername", getUsername);
|
||||
|
||||
//Endpoint used to hide a certain user's data
|
||||
router.post("/api/shadowBanUser", shadowBanUser);
|
||||
|
||||
//Endpoint used to make a user a VIP user with special privileges
|
||||
router.post("/api/addUserAsVIP", addUserAsVIP);
|
||||
//Endpoint to add a user as a temporary VIP
|
||||
router.post("/api/addUserAsTempVIP", addUserAsTempVIP);
|
||||
|
||||
//Gets all the views added up for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
router.get("/api/getViewsForUser", getViewsForUser);
|
||||
|
||||
//Gets all the saved time added up (views * sponsor length) for one userID
|
||||
//Useful to see how much one user has contributed
|
||||
//In minutes
|
||||
router.get("/api/getSavedTimeForUser", getSavedTimeForUser);
|
||||
|
||||
router.get("/api/getTopUsers", getTopUsers);
|
||||
router.get("/api/getTopCategoryUsers", getTopCategoryUsers);
|
||||
router.get("/api/getTopBrandingUsers", getTopBrandingUsers);
|
||||
|
||||
//send out totals
|
||||
//send the total submissions, total views and total minutes saved
|
||||
router.get("/api/getTotalStats", getTotalStats);
|
||||
|
||||
router.get("/api/brandingStats", getBrandingStats);
|
||||
|
||||
router.get("/api/getUserInfo", getUserInfo);
|
||||
router.get("/api/userInfo", getUserInfo);
|
||||
|
||||
//send out a formatted time saved total
|
||||
router.get("/api/getDaysSavedFormatted", getDaysSavedFormatted);
|
||||
|
||||
//submit video to lock categories
|
||||
router.post("/api/noSegments", postLockCategories);
|
||||
router.post("/api/lockCategories", postLockCategories);
|
||||
|
||||
router.delete("/api/noSegments", deleteLockCategoriesEndpoint);
|
||||
router.delete("/api/lockCategories", deleteLockCategoriesEndpoint);
|
||||
|
||||
//get if user is a vip
|
||||
router.get("/api/isUserVIP", getIsUserVIP);
|
||||
|
||||
//sent user a warning
|
||||
router.post("/api/warnUser", postWarning);
|
||||
|
||||
//get if user is a vip
|
||||
router.post("/api/segmentShift", postSegmentShift);
|
||||
|
||||
//get segment info
|
||||
router.get("/api/segmentInfo", getSegmentInfo);
|
||||
|
||||
//clear cache as VIP
|
||||
router.post("/api/clearCache", postClearCache);
|
||||
|
||||
//purge all segments for VIP
|
||||
router.post("/api/purgeAllSegments", postPurgeAllSegments);
|
||||
|
||||
router.post("/api/unlistedVideo", addUnlistedVideo);
|
||||
|
||||
// get userID from username
|
||||
router.get("/api/userID", getUserID);
|
||||
|
||||
// get lock categores from userID
|
||||
router.get("/api/lockCategories", getLockCategories);
|
||||
|
||||
// get privacy protecting lock categories functions
|
||||
router.get("/api/lockCategories/:prefix", getLockCategoriesByHash);
|
||||
|
||||
// get all segments that match a search
|
||||
router.get("/api/searchSegments", getSearchSegments);
|
||||
|
||||
// autocomplete chapter names
|
||||
router.get("/api/chapterNames", getChapterNames);
|
||||
|
||||
// get status
|
||||
router.get("/api/status/:value", getStatus);
|
||||
router.get("/api/status", getStatus);
|
||||
|
||||
router.get("/api/youtubeApiProxy", youtubeApiProxy);
|
||||
// get user category stats
|
||||
router.get("/api/userStats", getUserStats);
|
||||
|
||||
router.get("/api/lockReason", getLockReason);
|
||||
|
||||
router.post("/api/feature", addFeature);
|
||||
|
||||
router.get("/api/featureFlag/:name", getFeatureFlag);
|
||||
|
||||
router.get("/api/generateToken/:type", generateTokenRequest);
|
||||
router.get("/api/verifyToken", verifyTokenRequest);
|
||||
|
||||
// labels
|
||||
router.get("/api/videoLabels", getVideoLabels);
|
||||
router.get("/api/videoLabels/:prefix", getVideoLabelsByHash);
|
||||
|
||||
router.get("/api/branding", getBranding);
|
||||
router.get("/api/branding/:prefix", getBrandingByHashEndpoint);
|
||||
router.post("/api/branding", postBranding);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (config.postgres?.enabled) {
|
||||
router.get("/database", (req, res) => dumpDatabase(req, res, true));
|
||||
router.get("/database.json", (req, res) => dumpDatabase(req, res, false));
|
||||
router.get("/database/*", (req, res) => res.status(404).send("CSV downloads disabled. Please use sb-mirror rsync"));
|
||||
router.use("/download", (req, res) => res.status(404).send("CSV downloads disabled. Please use sb-mirror rsync"));
|
||||
} else {
|
||||
router.get("/database.db", function (req: Request, res: Response) {
|
||||
res.sendFile("./databases/sponsorTimes.db", { root: "./" });
|
||||
});
|
||||
}
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-misused-promises */
|
||||
250
src/config.ts
Normal file
250
src/config.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import fs from "fs";
|
||||
import { SBSConfig } from "./types/config.model";
|
||||
import packageJson from "../package.json";
|
||||
import { isNumber } from "lodash";
|
||||
|
||||
const isTestMode = process.env.npm_lifecycle_script === packageJson.scripts.test;
|
||||
const configFile = process.env.TEST_POSTGRES ? "ci.json"
|
||||
: isTestMode ? "test.json"
|
||||
: "config.json";
|
||||
export const config: SBSConfig = JSON.parse(fs.readFileSync(configFile).toString("utf8"));
|
||||
|
||||
addDefaults(config, {
|
||||
port: 8080,
|
||||
behindProxy: "X-Forwarded-For",
|
||||
db: "./databases/sponsorTimes.db",
|
||||
privateDB: "./databases/private.db",
|
||||
createDatabaseIfNotExist: true,
|
||||
schemaFolder: "./databases",
|
||||
dbSchema: "./databases/_sponsorTimes.db.sql",
|
||||
privateDBSchema: "./databases/_private.db.sql",
|
||||
readOnly: false,
|
||||
webhooks: [],
|
||||
categoryList: ["sponsor", "selfpromo", "exclusive_access", "interaction", "intro", "outro", "preview", "music_offtopic", "filler", "poi_highlight", "chapter"],
|
||||
categorySupport: {
|
||||
sponsor: ["skip", "mute", "full"],
|
||||
selfpromo: ["skip", "mute", "full"],
|
||||
exclusive_access: ["full"],
|
||||
interaction: ["skip", "mute"],
|
||||
intro: ["skip", "mute"],
|
||||
outro: ["skip", "mute"],
|
||||
preview: ["skip", "mute"],
|
||||
filler: ["skip", "mute"],
|
||||
music_offtopic: ["skip"],
|
||||
poi_highlight: ["poi"],
|
||||
chapter: ["chapter"]
|
||||
},
|
||||
deArrowTypes: ["title", "thumbnail"],
|
||||
maxTitleLength: 110,
|
||||
maxNumberOfActiveWarnings: 1,
|
||||
hoursAfterWarningExpires: 16300000,
|
||||
adminUserID: "",
|
||||
discordCompletelyIncorrectReportWebhookURL: null,
|
||||
discordFirstTimeSubmissionsWebhookURL: null,
|
||||
discordNeuralBlockRejectWebhookURL: null,
|
||||
discordFailedReportChannelWebhookURL: null,
|
||||
discordReportChannelWebhookURL: null,
|
||||
discordMaliciousReportWebhookURL: null,
|
||||
discordDeArrowLockedWebhookURL: null,
|
||||
minReputationToSubmitChapter: 0,
|
||||
minReputationToSubmitFiller: 0,
|
||||
getTopUsersCacheTimeMinutes: 240,
|
||||
globalSalt: null,
|
||||
mode: "",
|
||||
neuralBlockURL: null,
|
||||
proxySubmission: null,
|
||||
rateLimit: {
|
||||
vote: {
|
||||
windowMs: 900000,
|
||||
max: 15,
|
||||
message: "OK",
|
||||
statusCode: 200,
|
||||
},
|
||||
view: {
|
||||
windowMs: 900000,
|
||||
max: 10,
|
||||
statusCode: 200,
|
||||
message: "OK",
|
||||
}
|
||||
},
|
||||
userCounterURL: null,
|
||||
userCounterRatio: 10,
|
||||
newLeafURLs: null,
|
||||
maxRewardTimePerSegmentInSeconds: 600,
|
||||
poiMinimumStartTime: 2,
|
||||
postgres: {
|
||||
enabled: false,
|
||||
user: "",
|
||||
host: "",
|
||||
password: "",
|
||||
port: 5432,
|
||||
max: 10,
|
||||
idleTimeoutMillis: 10000,
|
||||
maxTries: 3,
|
||||
maxActiveRequests: 0,
|
||||
timeout: 60000,
|
||||
highLoadThreshold: 10
|
||||
},
|
||||
postgresReadOnly: {
|
||||
enabled: false,
|
||||
weight: 1,
|
||||
user: "",
|
||||
host: "",
|
||||
password: "",
|
||||
port: 5432,
|
||||
readTimeout: 250,
|
||||
max: 10,
|
||||
idleTimeoutMillis: 10000,
|
||||
maxTries: 3,
|
||||
fallbackOnFail: true,
|
||||
stopRetryThreshold: 800
|
||||
},
|
||||
postgresPrivateMax: 10,
|
||||
dumpDatabase: {
|
||||
enabled: false,
|
||||
minTimeBetweenMs: 180000,
|
||||
appExportPath: "./docker/database-export",
|
||||
tables: [{
|
||||
name: "sponsorTimes",
|
||||
order: "timeSubmitted"
|
||||
},
|
||||
{
|
||||
name: "userNames"
|
||||
},
|
||||
{
|
||||
name: "categoryVotes"
|
||||
},
|
||||
{
|
||||
name: "lockCategories",
|
||||
},
|
||||
{
|
||||
name: "warnings",
|
||||
order: "issueTime"
|
||||
},
|
||||
{
|
||||
name: "vipUsers"
|
||||
},
|
||||
{
|
||||
name: "unlistedVideos"
|
||||
},
|
||||
{
|
||||
name: "videoInfo"
|
||||
},
|
||||
{
|
||||
name: "ratings"
|
||||
},
|
||||
{
|
||||
name: "titles"
|
||||
},
|
||||
{
|
||||
name: "titleVotes"
|
||||
},
|
||||
{
|
||||
name: "thumbnails"
|
||||
},
|
||||
{
|
||||
name: "thumbnailTimestamps"
|
||||
},
|
||||
{
|
||||
name: "thumbnailVotes"
|
||||
}]
|
||||
},
|
||||
diskCacheURL: null,
|
||||
crons: null,
|
||||
redis: {
|
||||
enabled: false,
|
||||
socket: {
|
||||
host: "",
|
||||
port: 0
|
||||
},
|
||||
disableOfflineQueue: true,
|
||||
expiryTime: 24 * 60 * 60,
|
||||
getTimeout: 40,
|
||||
maxConnections: 15000,
|
||||
maxWriteConnections: 1000,
|
||||
commandsQueueMaxLength: 3000,
|
||||
stopWritingAfterResponseTime: 50,
|
||||
responseTimePause: 1000,
|
||||
disableHashCache: false
|
||||
},
|
||||
redisRead: {
|
||||
enabled: false,
|
||||
socket: {
|
||||
host: "",
|
||||
port: 0
|
||||
},
|
||||
disableOfflineQueue: true,
|
||||
weight: 1
|
||||
},
|
||||
redisRateLimit: true,
|
||||
patreon: {
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
minPrice: 0,
|
||||
redirectUri: "https://sponsor.ajay.app/api/generateToken/patreon"
|
||||
},
|
||||
gumroad: {
|
||||
productPermalinks: ["sponsorblock"]
|
||||
},
|
||||
tokenSeed: "",
|
||||
minUserIDLength: 30,
|
||||
deArrowPaywall: false
|
||||
});
|
||||
loadFromEnv(config);
|
||||
migrate(config);
|
||||
|
||||
// Add defaults
|
||||
function addDefaults(config: SBSConfig, defaults: SBSConfig) {
|
||||
for (const key in defaults) {
|
||||
if (!Object.prototype.hasOwnProperty.call(config, key)) {
|
||||
config[key] = defaults[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrate(config: SBSConfig) {
|
||||
// Redis change
|
||||
if (config.redis) {
|
||||
const redisConfig = config.redis as any;
|
||||
if (redisConfig.host || redisConfig.port) {
|
||||
config.redis.socket = {
|
||||
host: redisConfig.host,
|
||||
port: redisConfig.port
|
||||
};
|
||||
}
|
||||
|
||||
if (redisConfig.enable_offline_queue !== undefined) {
|
||||
config.disableOfflineQueue = !redisConfig.enable_offline_queue;
|
||||
}
|
||||
|
||||
if (redisConfig.socket?.host && redisConfig.enabled === undefined) {
|
||||
redisConfig.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.postgres && config.postgres.user && config.postgres.enabled === undefined) {
|
||||
config.postgres.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromEnv(config: SBSConfig, prefix = "") {
|
||||
for (const key in config) {
|
||||
const fullKey = (prefix ? `${prefix}_` : "") + key;
|
||||
const data = config[key];
|
||||
|
||||
if (data && typeof data === "object" && !Array.isArray(data)) {
|
||||
loadFromEnv(data, fullKey);
|
||||
} else if (process.env[fullKey]) {
|
||||
const value = process.env[fullKey];
|
||||
if (isNumber(value)) {
|
||||
config[key] = parseFloat(value);
|
||||
} else if (value.toLowerCase() === "true" || value.toLowerCase() === "false") {
|
||||
config[key] = value === "true";
|
||||
} else if (key === "newLeafURLs") {
|
||||
config[key] = [value];
|
||||
} else {
|
||||
config[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/cronjob/downvoteSegmentArchiveJob.ts
Normal file
67
src/cronjob/downvoteSegmentArchiveJob.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { CronJob } from "cron";
|
||||
|
||||
import { config as serverConfig } from "../config";
|
||||
import { Logger } from "../utils/logger";
|
||||
import { db } from "../databases/databases";
|
||||
import { DBSegment } from "../types/segments.model";
|
||||
|
||||
const jobConfig = serverConfig?.crons?.downvoteSegmentArchive;
|
||||
|
||||
export const archiveDownvoteSegment = async (dayLimit: number, voteLimit: number, runTime?: number): Promise<number> => {
|
||||
const timeNow = runTime || new Date().getTime();
|
||||
const threshold = dayLimit * 86400000;
|
||||
|
||||
Logger.info(`DownvoteSegmentArchiveJob starts at ${timeNow}`);
|
||||
try {
|
||||
// insert into archive sponsorTime
|
||||
await db.prepare(
|
||||
"run",
|
||||
`INSERT INTO "archivedSponsorTimes"
|
||||
SELECT *
|
||||
FROM "sponsorTimes"
|
||||
WHERE "votes" < ? AND (? - "timeSubmitted") > ?`,
|
||||
[
|
||||
voteLimit,
|
||||
timeNow,
|
||||
threshold
|
||||
]
|
||||
) as DBSegment[];
|
||||
|
||||
} catch (err) {
|
||||
Logger.error("Execption when insert segment in archivedSponsorTimes");
|
||||
Logger.error(err as string);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// remove from sponsorTime
|
||||
try {
|
||||
await db.prepare(
|
||||
"run",
|
||||
'DELETE FROM "sponsorTimes" WHERE "votes" < ? AND (? - "timeSubmitted") > ?',
|
||||
[
|
||||
voteLimit,
|
||||
timeNow,
|
||||
threshold
|
||||
]
|
||||
) as DBSegment[];
|
||||
|
||||
} catch (err) {
|
||||
Logger.error("Execption when deleting segment in sponsorTimes");
|
||||
Logger.error(err as string);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Logger.info("DownvoteSegmentArchiveJob finished");
|
||||
return 0;
|
||||
};
|
||||
|
||||
const DownvoteSegmentArchiveJob = new CronJob(
|
||||
jobConfig?.schedule || "0 0 * * * 0",
|
||||
() => void archiveDownvoteSegment(jobConfig?.timeThresholdInDays, jobConfig?.voteThreshold)
|
||||
);
|
||||
|
||||
if (serverConfig?.crons?.enabled && jobConfig && !jobConfig.schedule) {
|
||||
Logger.error("Invalid cron schedule for downvoteSegmentArchive");
|
||||
}
|
||||
|
||||
export default DownvoteSegmentArchiveJob;
|
||||
13
src/cronjob/index.ts
Normal file
13
src/cronjob/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Logger } from "../utils/logger";
|
||||
import { config } from "../config";
|
||||
import DownvoteSegmentArchiveJob from "./downvoteSegmentArchiveJob";
|
||||
|
||||
export function startAllCrons (): void {
|
||||
if (config?.crons?.enabled) {
|
||||
Logger.info("Crons started");
|
||||
|
||||
DownvoteSegmentArchiveJob.start();
|
||||
} else {
|
||||
Logger.info("Crons dissabled");
|
||||
}
|
||||
}
|
||||
14
src/databases/IDatabase.ts
Normal file
14
src/databases/IDatabase.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface QueryOption {
|
||||
useReplica?: boolean;
|
||||
forceReplica?: boolean;
|
||||
}
|
||||
|
||||
export interface IDatabase {
|
||||
init(): Promise<void>;
|
||||
|
||||
prepare(type: QueryType, query: string, params?: any[], options?: QueryOption): Promise<any | any[] | void>;
|
||||
|
||||
highLoad(): boolean;
|
||||
}
|
||||
|
||||
export type QueryType = "get" | "all" | "run";
|
||||
286
src/databases/Postgres.ts
Normal file
286
src/databases/Postgres.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
import { Logger } from "../utils/logger";
|
||||
import { IDatabase, QueryOption, QueryType } from "./IDatabase";
|
||||
import { Client, Pool, QueryResult, types } from "pg";
|
||||
|
||||
import fs from "fs";
|
||||
import { CustomPostgresReadOnlyConfig, CustomWritePostgresConfig } from "../types/config.model";
|
||||
import { timeoutPomise, PromiseWithState, savePromiseState, nextFulfilment } from "../utils/promise";
|
||||
|
||||
// return numeric (pg_type oid=1700) as float
|
||||
types.setTypeParser(1700, function(val) {
|
||||
return parseFloat(val);
|
||||
});
|
||||
|
||||
// return int8 (pg_type oid=20) as int
|
||||
types.setTypeParser(20, function(val) {
|
||||
return parseInt(val, 10);
|
||||
});
|
||||
|
||||
interface PostgresStats {
|
||||
activeRequests: number;
|
||||
avgReadTime: number;
|
||||
avgWriteTime: number;
|
||||
avgFailedTime: number;
|
||||
pool: {
|
||||
total: number;
|
||||
idle: number;
|
||||
waiting: number;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DatabaseConfig {
|
||||
dbSchemaFileName: string,
|
||||
dbSchemaFolder: string,
|
||||
fileNamePrefix: string,
|
||||
readOnly: boolean,
|
||||
createDbIfNotExists: boolean,
|
||||
postgres: CustomWritePostgresConfig,
|
||||
postgresReadOnly: CustomPostgresReadOnlyConfig
|
||||
}
|
||||
|
||||
export class Postgres implements IDatabase {
|
||||
private pool: Pool;
|
||||
private lastPoolFail = 0;
|
||||
|
||||
private poolRead: Pool;
|
||||
private lastPoolReadFail = 0;
|
||||
|
||||
activePostgresRequests = 0;
|
||||
readResponseTime: number[] = [];
|
||||
writeResponseTime: number[] = [];
|
||||
failedResponseTime: number[] = [];
|
||||
maxStoredTimes = 200;
|
||||
|
||||
constructor(private config: DatabaseConfig) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
this.pool = new Pool({
|
||||
...this.config.postgres
|
||||
});
|
||||
this.pool.on("error", (err, client) => {
|
||||
Logger.error(err.stack);
|
||||
this.lastPoolFail = Date.now();
|
||||
|
||||
try {
|
||||
client.release(true);
|
||||
} catch (err) {
|
||||
Logger.error(`pool (postgres): ${err}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.config.postgresReadOnly && this.config.postgresReadOnly.enabled) {
|
||||
try {
|
||||
this.poolRead = new Pool({
|
||||
...this.config.postgresReadOnly
|
||||
});
|
||||
this.poolRead.on("error", (err, client) => {
|
||||
Logger.error(err.stack);
|
||||
this.lastPoolReadFail = Date.now();
|
||||
|
||||
try {
|
||||
client.release(true);
|
||||
} catch (err) {
|
||||
Logger.error(`poolRead (postgres): ${err}`);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
Logger.error(`poolRead (postgres): ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.config.readOnly) {
|
||||
if (this.config.createDbIfNotExists) {
|
||||
await this.createDB();
|
||||
}
|
||||
|
||||
if (this.config.createDbIfNotExists && !this.config.readOnly && fs.existsSync(this.config.dbSchemaFileName)) {
|
||||
await this.pool.query(this.processUpgradeQuery(fs.readFileSync(this.config.dbSchemaFileName).toString()));
|
||||
}
|
||||
|
||||
// Upgrade database if required
|
||||
await this.upgradeDB(this.config.fileNamePrefix, this.config.dbSchemaFolder);
|
||||
|
||||
try {
|
||||
await this.applyIndexes(this.config.fileNamePrefix, this.config.dbSchemaFolder);
|
||||
} catch (e) {
|
||||
Logger.warn("Applying indexes failed. See https://github.com/ajayyy/SponsorBlockServer/wiki/Postgres-Extensions for more information.");
|
||||
Logger.warn(e as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async prepare(type: QueryType, query: string, params?: any[], options: QueryOption = {}): Promise<any[]> {
|
||||
// Convert query to use numbered parameters
|
||||
let count = 1;
|
||||
for (let char = 0; char < query.length; char++) {
|
||||
if (query.charAt(char) === "?") {
|
||||
query = `${query.slice(0, char)}$${count}${query.slice(char + 1)}`;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(`prepare (postgres): type: ${type}, query: ${query}, params: ${params}`);
|
||||
|
||||
if (this.config.postgres.maxActiveRequests && this.isReadQuery(type)
|
||||
&& this.activePostgresRequests > this.config.postgres.maxActiveRequests) {
|
||||
throw new Error("Too many active postgres requests");
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
const pendingQueries: PromiseWithState<QueryResult<any>>[] = [];
|
||||
let tries = 0;
|
||||
let lastPool: Pool = null;
|
||||
const maxTries = () => (lastPool === this.pool
|
||||
? this.config.postgres.maxTries : this.config.postgresReadOnly.maxTries);
|
||||
do {
|
||||
tries++;
|
||||
|
||||
try {
|
||||
this.activePostgresRequests++;
|
||||
lastPool = this.getPool(type, options);
|
||||
|
||||
pendingQueries.push(savePromiseState(lastPool.query({ text: query, values: params })));
|
||||
const currentPromises = [...pendingQueries];
|
||||
if (options.useReplica && maxTries() - tries > 1) currentPromises.push(savePromiseState(timeoutPomise(this.config.postgresReadOnly.readTimeout)));
|
||||
else if (this.config.postgres.timeout) currentPromises.push(savePromiseState(timeoutPomise(this.config.postgres.timeout)));
|
||||
const queryResult = await nextFulfilment(currentPromises);
|
||||
|
||||
this.updateResponseTime(type, start);
|
||||
|
||||
this.activePostgresRequests--;
|
||||
switch (type) {
|
||||
case "get": {
|
||||
const value = queryResult.rows[0];
|
||||
Logger.debug(`result (postgres): ${JSON.stringify(value)}`);
|
||||
return value;
|
||||
}
|
||||
case "all": {
|
||||
const values = queryResult.rows;
|
||||
Logger.debug(`result (postgres): ${JSON.stringify(values)}`);
|
||||
return values;
|
||||
}
|
||||
case "run": {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (lastPool === this.pool) {
|
||||
// Only applies if it is get or all request
|
||||
options.forceReplica = true;
|
||||
} else if (lastPool === this.poolRead) {
|
||||
this.lastPoolReadFail = Date.now();
|
||||
|
||||
if (maxTries() - tries <= 1) {
|
||||
options.useReplica = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateResponseTime(type, start, this.failedResponseTime);
|
||||
this.activePostgresRequests--;
|
||||
Logger.error(`prepare (postgres) try ${tries}: ${err}`);
|
||||
}
|
||||
} while (this.isReadQuery(type) && tries < maxTries()
|
||||
&& this.activePostgresRequests < this.config.postgresReadOnly.stopRetryThreshold);
|
||||
|
||||
throw new Error(`prepare (postgres): ${type} ${query} failed after ${tries} tries`);
|
||||
}
|
||||
|
||||
private getPool(type: string, options: QueryOption): Pool {
|
||||
const readAvailable = this.poolRead && options.useReplica && this.isReadQuery(type);
|
||||
const ignoreReadDueToFailure = this.config.postgresReadOnly.fallbackOnFail
|
||||
&& this.lastPoolReadFail > Date.now() - 1000 * 30;
|
||||
const readDueToFailure = this.config.postgresReadOnly.fallbackOnFail
|
||||
&& this.lastPoolFail > Date.now() - 1000 * 30;
|
||||
if (readAvailable && !ignoreReadDueToFailure && (options.forceReplica || readDueToFailure ||
|
||||
Math.random() > 1 / (this.config.postgresReadOnly.weight + 1))) {
|
||||
return this.poolRead;
|
||||
} else {
|
||||
return this.pool;
|
||||
}
|
||||
}
|
||||
|
||||
private isReadQuery(type: string): boolean {
|
||||
return type === "get" || type === "all";
|
||||
}
|
||||
|
||||
private async createDB() {
|
||||
const client = new Client({
|
||||
...this.config.postgres,
|
||||
database: "postgres"
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
|
||||
if ((await client.query(`SELECT * FROM pg_database WHERE datname = '${this.config.postgres.database}'`)).rowCount == 0) {
|
||||
await client.query(`CREATE DATABASE "${this.config.postgres.database}"
|
||||
WITH
|
||||
OWNER = ${this.config.postgres.user}
|
||||
CONNECTION LIMIT = -1;`
|
||||
);
|
||||
}
|
||||
|
||||
client.end().catch(err => Logger.error(`closing db (postgres): ${err}`));
|
||||
}
|
||||
|
||||
private async upgradeDB(fileNamePrefix: string, schemaFolder: string) {
|
||||
const versionCodeInfo = await this.pool.query("SELECT value FROM config WHERE key = 'version'");
|
||||
let versionCode = versionCodeInfo.rows[0] ? versionCodeInfo.rows[0].value : 0;
|
||||
|
||||
let path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`;
|
||||
Logger.debug(`db update: trying ${path}`);
|
||||
while (fs.existsSync(path)) {
|
||||
Logger.debug(`db update: updating ${path}`);
|
||||
await this.pool.query(this.processUpgradeQuery(fs.readFileSync(path).toString()));
|
||||
|
||||
versionCode = (await this.pool.query("SELECT value FROM config WHERE key = 'version'"))?.rows[0]?.value;
|
||||
path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`;
|
||||
Logger.debug(`db update: trying ${path}`);
|
||||
}
|
||||
Logger.debug(`db update: no file ${path}`);
|
||||
}
|
||||
|
||||
private async applyIndexes(fileNamePrefix: string, schemaFolder: string) {
|
||||
const path = `${schemaFolder}/_${fileNamePrefix}_indexes.sql`;
|
||||
if (fs.existsSync(path)) {
|
||||
await this.pool.query(fs.readFileSync(path).toString());
|
||||
} else {
|
||||
Logger.debug(`failed to apply indexes to ${fileNamePrefix}`);
|
||||
}
|
||||
}
|
||||
|
||||
private processUpgradeQuery(query: string): string {
|
||||
let result = query;
|
||||
result = result.replace(/sha256\((.*?)\)/gm, "encode(digest($1, 'sha256'), 'hex')");
|
||||
result = result.replace(/integer/gmi, "NUMERIC");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private updateResponseTime(type: string, start: number, customArray?: number[]): void {
|
||||
const responseTime = Date.now() - start;
|
||||
|
||||
const array = customArray ?? (this.isReadQuery(type) ?
|
||||
this.readResponseTime : this.writeResponseTime);
|
||||
|
||||
array.push(responseTime);
|
||||
if (array.length > this.maxStoredTimes) array.shift();
|
||||
}
|
||||
|
||||
getStats(): PostgresStats {
|
||||
return {
|
||||
activeRequests: this.activePostgresRequests,
|
||||
avgReadTime: this.readResponseTime.length > 0 ? this.readResponseTime.reduce((a, b) => a + b, 0) / this.readResponseTime.length : 0,
|
||||
avgWriteTime: this.writeResponseTime.length > 0 ? this.writeResponseTime.reduce((a, b) => a + b, 0) / this.writeResponseTime.length : 0,
|
||||
avgFailedTime: this.failedResponseTime.length > 0 ? this.failedResponseTime.reduce((a, b) => a + b, 0) / this.failedResponseTime.length : 0,
|
||||
pool: {
|
||||
total: this.pool.totalCount,
|
||||
idle: this.pool.idleCount,
|
||||
waiting: this.pool.waitingCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
highLoad() {
|
||||
return this.activePostgresRequests > this.config.postgres.highLoadThreshold;
|
||||
}
|
||||
}
|
||||
121
src/databases/Sqlite.ts
Normal file
121
src/databases/Sqlite.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { IDatabase, QueryType } from "./IDatabase";
|
||||
import Sqlite3, { Database, Database as SQLiteDatabase } from "better-sqlite3";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { getHash } from "../utils/getHash";
|
||||
import { Logger } from "../utils/logger";
|
||||
|
||||
export class Sqlite implements IDatabase {
|
||||
private db: SQLiteDatabase;
|
||||
|
||||
constructor(private config: SqliteConfig)
|
||||
{
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-await
|
||||
async prepare(type: QueryType, query: string, params: any[] = []): Promise<any[]> {
|
||||
// Logger.debug(`prepare (sqlite): type: ${type}, query: ${query}, params: ${params}`);
|
||||
const preparedQuery = this.db.prepare(Sqlite.processQuery(query));
|
||||
|
||||
switch (type) {
|
||||
case "get": {
|
||||
return preparedQuery.get(...params);
|
||||
}
|
||||
case "all": {
|
||||
return preparedQuery.all(...params);
|
||||
}
|
||||
case "run": {
|
||||
preparedQuery.run(...params);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-await
|
||||
async init(): Promise<void> {
|
||||
// Make dirs if required
|
||||
if (!fs.existsSync(path.join(this.config.dbPath, "../"))) {
|
||||
fs.mkdirSync(path.join(this.config.dbPath, "../"));
|
||||
}
|
||||
|
||||
this.db = new Sqlite3(this.config.dbPath, { readonly: this.config.readOnly, fileMustExist: !this.config.createDbIfNotExists });
|
||||
|
||||
if (this.config.createDbIfNotExists && !this.config.readOnly && fs.existsSync(this.config.dbSchemaFileName)) {
|
||||
this.db.exec(Sqlite.processUpgradeQuery(fs.readFileSync(this.config.dbSchemaFileName).toString()));
|
||||
}
|
||||
|
||||
if (!this.config.readOnly) {
|
||||
this.db.function("sha256", (str: string) => {
|
||||
return getHash(str, 1);
|
||||
});
|
||||
|
||||
// Upgrade database if required
|
||||
Sqlite.upgradeDB(this.db, this.config.fileNamePrefix, this.config.dbSchemaFolder);
|
||||
}
|
||||
|
||||
this.db.function("regexp", { deterministic: true }, (regex: string, str: string) => {
|
||||
return str.match(regex) ? 1 : 0;
|
||||
});
|
||||
|
||||
// Enable WAL mode checkpoint number
|
||||
if (this.config.enableWalCheckpointNumber) {
|
||||
this.db.exec("PRAGMA journal_mode=WAL;");
|
||||
this.db.exec("PRAGMA wal_autocheckpoint=1;");
|
||||
}
|
||||
|
||||
// Enable Memory-Mapped IO
|
||||
this.db.exec("pragma mmap_size= 500000000;");
|
||||
}
|
||||
|
||||
attachDatabase(database: string, attachAs: string): void {
|
||||
this.db.prepare(`ATTACH ? as ${attachAs}`).run(database);
|
||||
}
|
||||
|
||||
private static processQuery(query: string): string {
|
||||
if (query.includes("DISTINCT ON")) {
|
||||
const column = query.match(/DISTINCT ON \((.*)\) (.*)/)[1];
|
||||
query = query.replace(/DISTINCT ON \((.*)\)/g, "");
|
||||
|
||||
const parts = query.split("ORDER BY");
|
||||
|
||||
query = `${parts[0]} GROUP BY ${column} ORDER BY ${parts[1]}`;
|
||||
}
|
||||
|
||||
return query.replace(/ ~\* /g, " REGEXP ");
|
||||
}
|
||||
|
||||
private static upgradeDB(db: Database, fileNamePrefix: string, schemaFolder: string) {
|
||||
const versionCodeInfo = db.prepare("SELECT value FROM config WHERE key = ?").get("version");
|
||||
let versionCode = versionCodeInfo ? versionCodeInfo.value : 0;
|
||||
|
||||
let path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`;
|
||||
Logger.debug(`db update: trying ${path}`);
|
||||
while (fs.existsSync(path)) {
|
||||
Logger.debug(`db update: updating ${path}`);
|
||||
db.exec(this.processUpgradeQuery(fs.readFileSync(path).toString()));
|
||||
|
||||
versionCode = db.prepare("SELECT value FROM config WHERE key = ?").get("version").value;
|
||||
path = `${schemaFolder}/_upgrade_${fileNamePrefix}_${(parseInt(versionCode) + 1)}.sql`;
|
||||
Logger.debug(`db update: trying ${path}`);
|
||||
}
|
||||
Logger.debug(`db update: no file ${path}`);
|
||||
}
|
||||
|
||||
private static processUpgradeQuery(query: string): string {
|
||||
return query.replace(/^.*--!sqlite-ignore/gm, "");
|
||||
}
|
||||
|
||||
highLoad() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SqliteConfig {
|
||||
dbPath: string;
|
||||
dbSchemaFileName: string;
|
||||
dbSchemaFolder: string;
|
||||
fileNamePrefix: string;
|
||||
readOnly: boolean;
|
||||
createDbIfNotExists: boolean;
|
||||
enableWalCheckpointNumber: boolean
|
||||
}
|
||||
84
src/databases/databases.ts
Normal file
84
src/databases/databases.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { config } from "../config";
|
||||
import { Sqlite } from "./Sqlite";
|
||||
import { Postgres } from "./Postgres";
|
||||
import { IDatabase } from "./IDatabase";
|
||||
|
||||
let db: IDatabase;
|
||||
let privateDB: IDatabase;
|
||||
if (config.postgres?.enabled) {
|
||||
db = new Postgres({
|
||||
dbSchemaFileName: config.dbSchema,
|
||||
dbSchemaFolder: config.schemaFolder,
|
||||
fileNamePrefix: "sponsorTimes",
|
||||
readOnly: config.readOnly,
|
||||
createDbIfNotExists: config.createDatabaseIfNotExist,
|
||||
postgres: {
|
||||
...config.postgres,
|
||||
database: "sponsorTimes",
|
||||
},
|
||||
postgresReadOnly: config.postgresReadOnly ? {
|
||||
...config.postgresReadOnly,
|
||||
database: "sponsorTimes"
|
||||
} : null
|
||||
});
|
||||
|
||||
privateDB = new Postgres({
|
||||
dbSchemaFileName: config.privateDBSchema,
|
||||
dbSchemaFolder: config.schemaFolder,
|
||||
fileNamePrefix: "private",
|
||||
readOnly: config.readOnly,
|
||||
createDbIfNotExists: config.createDatabaseIfNotExist,
|
||||
postgres: {
|
||||
...config.postgres,
|
||||
max: config.postgresPrivateMax ?? config.postgres.max,
|
||||
database: "privateDB"
|
||||
},
|
||||
postgresReadOnly: config.postgresReadOnly ? {
|
||||
...config.postgresReadOnly,
|
||||
database: "privateDB"
|
||||
} : null
|
||||
});
|
||||
} else {
|
||||
db = new Sqlite({
|
||||
dbPath: config.db,
|
||||
dbSchemaFileName: config.dbSchema,
|
||||
dbSchemaFolder: config.schemaFolder,
|
||||
fileNamePrefix: "sponsorTimes",
|
||||
readOnly: config.readOnly,
|
||||
createDbIfNotExists: config.createDatabaseIfNotExist,
|
||||
enableWalCheckpointNumber: !config.readOnly && config.mode === "production"
|
||||
});
|
||||
privateDB = new Sqlite({
|
||||
dbPath: config.privateDB,
|
||||
dbSchemaFileName: config.privateDBSchema,
|
||||
dbSchemaFolder: config.schemaFolder,
|
||||
fileNamePrefix: "private",
|
||||
readOnly: config.readOnly,
|
||||
createDbIfNotExists: config.createDatabaseIfNotExist,
|
||||
enableWalCheckpointNumber: false
|
||||
});
|
||||
}
|
||||
async function initDb(): Promise<void> {
|
||||
await db.init();
|
||||
await privateDB.init();
|
||||
|
||||
if (db instanceof Sqlite) {
|
||||
// Attach private db to main db
|
||||
(db as Sqlite).attachDatabase(config.privateDB, "privateDB");
|
||||
}
|
||||
|
||||
if (config.mode === "mirror" && db instanceof Postgres) {
|
||||
const tables = config?.dumpDatabase?.tables ?? [];
|
||||
const tableNames = tables.map(table => table.name);
|
||||
for (const table of tableNames) {
|
||||
const filePath = `${config?.dumpDatabase?.appExportPath}/${table}.csv`;
|
||||
await db.prepare("run", `COPY "${table}" FROM '${filePath}' WITH (FORMAT CSV, HEADER true);`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
db,
|
||||
privateDB,
|
||||
initDb,
|
||||
};
|
||||
42
src/index.ts
Normal file
42
src/index.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { config } from "./config";
|
||||
import { initDb } from "./databases/databases";
|
||||
import { createServer } from "./app";
|
||||
import { Logger } from "./utils/logger";
|
||||
import { startAllCrons } from "./cronjob";
|
||||
import { getCommit } from "./utils/getCommit";
|
||||
import { connectionPromise } from "./utils/redis";
|
||||
|
||||
async function init() {
|
||||
process.on("unhandledRejection", (error: any) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.dir(error?.stack);
|
||||
});
|
||||
|
||||
process.on("uncaughtExceptions", (error: any) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.dir(error?.stack);
|
||||
});
|
||||
|
||||
try {
|
||||
await initDb();
|
||||
await connectionPromise;
|
||||
} catch (e) {
|
||||
Logger.error(`Init Db: ${e}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// edge case clause for creating compatible .db files, do not enable
|
||||
if (config.mode === "init-db-and-exit") process.exit(0);
|
||||
// do not enable init-db-only mode for usage.
|
||||
(global as any).HEADCOMMIT = config.mode === "development" ? "development"
|
||||
: config.mode === "test" ? "test"
|
||||
: getCommit() as string;
|
||||
createServer(() => {
|
||||
Logger.info(`Server started on port ${config.port}.`);
|
||||
|
||||
// ignite cron job after server created
|
||||
startAllCrons();
|
||||
}).setTimeout(15000);
|
||||
}
|
||||
|
||||
init().catch((err) => Logger.error(`Index.js: ${err}`));
|
||||
6
src/middleware/apiCsp.ts
Normal file
6
src/middleware/apiCsp.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
|
||||
export function apiCspMiddleware(req: Request, res: Response, next: NextFunction): void {
|
||||
res.header("Content-Security-Policy", "script-src 'none'; object-src 'none'");
|
||||
next();
|
||||
}
|
||||
8
src/middleware/cors.ts
Normal file
8
src/middleware/cors.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
|
||||
export function corsMiddleware(req: Request, res: Response, next: NextFunction): void {
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, DELETE");
|
||||
res.header("Access-Control-Allow-Headers", "Content-Type, If-None-Match");
|
||||
next();
|
||||
}
|
||||
49
src/middleware/etag.ts
Normal file
49
src/middleware/etag.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { VideoID, VideoIDHash, Service } from "../types/segments.model";
|
||||
import { QueryCacher } from "../utils/queryCacher";
|
||||
import { skipSegmentsHashKey, skipSegmentsKey, videoLabelsHashKey, videoLabelsKey } from "../utils/redisKeys";
|
||||
|
||||
type hashType = "skipSegments" | "skipSegmentsHash" | "videoLabel" | "videoLabelHash";
|
||||
type ETag = `${hashType};${VideoIDHash};${Service};${number}`;
|
||||
type hashKey = string | VideoID | VideoIDHash;
|
||||
|
||||
export function cacheMiddlware(req: Request, res: Response, next: NextFunction): void {
|
||||
const reqEtag = req.get("If-None-Match") as string;
|
||||
// if weak etag, do not handle
|
||||
if (!reqEtag || reqEtag.startsWith("W/")) return next();
|
||||
// split into components
|
||||
const [hashType, hashKey, service, lastModified] = reqEtag.split(";");
|
||||
// fetch last-modified
|
||||
getLastModified(hashType as hashType, hashKey as VideoIDHash, service as Service)
|
||||
.then(redisLastModified => {
|
||||
if (redisLastModified <= new Date(Number(lastModified) + 1000)) {
|
||||
// match cache, generate etag
|
||||
const etag = `${hashType};${hashKey};${service};${redisLastModified.getTime()}` as ETag;
|
||||
res.status(304).set("etag", etag).send();
|
||||
}
|
||||
else next();
|
||||
})
|
||||
.catch(next);
|
||||
}
|
||||
|
||||
function getLastModified(hashType: hashType, hashKey: hashKey, service: Service): Promise<Date | null> {
|
||||
let redisKey: string | null;
|
||||
if (hashType === "skipSegments") redisKey = skipSegmentsKey(hashKey as VideoID, service);
|
||||
else if (hashType === "skipSegmentsHash") redisKey = skipSegmentsHashKey(hashKey as VideoIDHash, service);
|
||||
else if (hashType === "videoLabel") redisKey = videoLabelsKey(hashKey as VideoID, service);
|
||||
else if (hashType === "videoLabelHash") redisKey = videoLabelsHashKey(hashKey as VideoIDHash, service);
|
||||
else return Promise.reject();
|
||||
return QueryCacher.getKeyLastModified(redisKey);
|
||||
}
|
||||
|
||||
export async function getEtag(hashType: hashType, hashKey: hashKey, service: Service): Promise<ETag> {
|
||||
const lastModified = await getLastModified(hashType, hashKey, service);
|
||||
return `${hashType};${hashKey};${service};${lastModified.getTime()}` as ETag;
|
||||
}
|
||||
|
||||
/* example usage
|
||||
import { getEtag } from "../middleware/etag";
|
||||
await getEtag(hashType, hashPrefix, service)
|
||||
.then(etag => res.set("ETag", etag))
|
||||
.catch(() => null);
|
||||
*/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user