Compare commits
1156 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
720c9bb651 | ||
|
|
f05cf5040c | ||
|
|
65191cfc8d | ||
|
|
bd2dec0392 | ||
|
|
5f9fbbb30d | ||
|
|
43f04455f1 | ||
|
|
43637a2ff4 | ||
|
|
b87bf62362 | ||
|
|
89e57d48d9 | ||
|
|
ad47aecea3 | ||
|
|
f192fd0b75 | ||
|
|
557efd2079 | ||
|
|
373c6c1d4c | ||
|
|
4c7da5dc20 | ||
|
|
bed5b00300 | ||
|
|
e36d460d57 | ||
|
|
5cffa112ba | ||
|
|
66ae60ca44 | ||
|
|
b60496bf22 | ||
|
|
ff39c59475 | ||
|
|
b6b67568b5 | ||
|
|
eecf9c42c5 | ||
|
|
fc3d61b05a | ||
|
|
b2b9760445 | ||
|
|
7afff18629 | ||
|
|
ee3effaf00 | ||
|
|
296f0478b0 | ||
|
|
693a77e73e | ||
|
|
b769cd0aa1 | ||
|
|
9eca05efc5 | ||
|
|
ca0110cdbc | ||
|
|
66ee72baf0 | ||
|
|
3c08d69bc3 | ||
|
|
0f004dc878 | ||
|
|
b7093ed4ae | ||
|
|
20289eaeaf | ||
|
|
3b16fe0390 | ||
|
|
c78c1ad628 | ||
|
|
8fe4cc5a9b | ||
|
|
930e8191be | ||
|
|
2734fb4d0d | ||
|
|
b5223148c7 | ||
|
|
ad3d0b317e | ||
|
|
e90b5583a7 | ||
|
|
394dc91bfa | ||
|
|
7de046838d | ||
|
|
955872a825 | ||
|
|
d17780f0a7 | ||
|
|
737a8063a8 | ||
|
|
423abf2479 | ||
|
|
4e436509bf | ||
|
|
f58e0e1f4f | ||
|
|
d2e72b0756 | ||
|
|
edebd860aa | ||
|
|
6b8363bd9c | ||
|
|
d0c40ffa00 | ||
|
|
b36e1484eb | ||
|
|
a5e429fea3 | ||
|
|
6d5870ccb2 | ||
|
|
6cc82520a0 | ||
|
|
7dfc599de9 | ||
|
|
aa820e1f64 | ||
|
|
38d0ac5305 | ||
|
|
438387faee | ||
|
|
5a1786d3e5 | ||
|
|
bbb80f3cf0 | ||
|
|
8ea6d4fe37 | ||
|
|
282b3d05b3 | ||
|
|
168d0280dc | ||
|
|
50e024a3ea | ||
|
|
e1d582036a | ||
|
|
354c2731d8 | ||
|
|
ce4637ea80 | ||
|
|
73ce5f372d | ||
|
|
221fa989f4 | ||
|
|
a4dfad6235 | ||
|
|
ae10810caa | ||
|
|
964e979469 | ||
|
|
556b20e669 | ||
|
|
6c102362fd | ||
|
|
49faa5d116 | ||
|
|
cf8fb4cfa4 | ||
|
|
3bb7a800e3 | ||
|
|
26b6143152 | ||
|
|
728ed7ec06 | ||
|
|
5bfbd94e51 | ||
|
|
23db19d073 | ||
|
|
bd160eb32c | ||
|
|
fb354b4746 | ||
|
|
245dcf6007 | ||
|
|
94f5c7438d | ||
|
|
d0f3e0f931 | ||
|
|
524630d671 | ||
|
|
65a0e57ab9 | ||
|
|
eccea520b7 | ||
|
|
7ec9c5b147 | ||
|
|
1ae12fa0f7 | ||
|
|
ccdd4600bb | ||
|
|
3f35ecf6f3 | ||
|
|
de13209709 | ||
|
|
34500ea3fc | ||
|
|
72c191fbc1 | ||
|
|
8c2897a0b1 | ||
|
|
31da8aa92c | ||
|
|
92c45d553e | ||
|
|
ae5829dcb0 | ||
|
|
3f5a82d663 | ||
|
|
2612860f15 | ||
|
|
e6adf6549a | ||
|
|
c23ffa382d | ||
|
|
1f4d81bfc9 | ||
|
|
455d84d5b1 | ||
|
|
72c19e7f59 | ||
|
|
e52cee2125 | ||
|
|
5c87c5d9e2 | ||
|
|
8f8ecb868e | ||
|
|
a876c9b144 | ||
|
|
2ba6e4fa35 | ||
|
|
50a7e5559b | ||
|
|
db860c25c6 | ||
|
|
074311107a | ||
|
|
19a73b3866 | ||
|
|
75c9509793 | ||
|
|
bc98d64969 | ||
|
|
a90d7b1cad | ||
|
|
43e16f5bfa | ||
|
|
622b1b3bcc | ||
|
|
12ea135feb | ||
|
|
e5197fdb8b | ||
|
|
a5539bcf59 | ||
|
|
d3db3fc512 | ||
|
|
9945ed4615 | ||
|
|
28a83e2f29 | ||
|
|
93fe1ae871 | ||
|
|
1ddc8d99d6 | ||
|
|
3c979e77cc | ||
|
|
a00fa2c3d6 | ||
|
|
7c886583c9 | ||
|
|
92a4ed5578 | ||
|
|
e8ed29eb9d | ||
|
|
ef2cc7f16b | ||
|
|
646e75d5f8 | ||
|
|
d259587491 | ||
|
|
7c99e4d1c1 | ||
|
|
aeb15d8640 | ||
|
|
771d664e5a | ||
|
|
7b708e2385 | ||
|
|
a9c281c8c4 | ||
|
|
8aa1344fa5 | ||
|
|
5fd62028ce | ||
|
|
5b49dd0735 | ||
|
|
7bb38fda3e | ||
|
|
507b96014c | ||
|
|
986db2cc67 | ||
|
|
546dee9799 | ||
|
|
4cfbf731b8 | ||
|
|
231a12f685 | ||
|
|
e54b54bfb3 | ||
|
|
58966ccff1 | ||
|
|
59c84952ed | ||
|
|
90c56debfe | ||
|
|
9cf9c4498c | ||
|
|
31ca9597e5 | ||
|
|
17e70c169b | ||
|
|
79c8e7fc32 | ||
|
|
26278aadba | ||
|
|
5ef6119294 | ||
|
|
96c4b1f16b | ||
|
|
4cf08fc6d5 | ||
|
|
bad1170421 | ||
|
|
7593d3d62e | ||
|
|
ce11a80ba9 | ||
|
|
5103be82a4 | ||
|
|
9ee91dff31 | ||
|
|
381fb89cfb | ||
|
|
9354c0ee9b | ||
|
|
51aa127a33 | ||
|
|
9d6d138fff | ||
|
|
a189cddd37 | ||
|
|
f7cf9da686 | ||
|
|
d00cc8a94a | ||
|
|
20ce2ac831 | ||
|
|
c6a6a70eaf | ||
|
|
9e256186aa | ||
|
|
f047287fe9 | ||
|
|
816123ab83 | ||
|
|
815d3b6b1e | ||
|
|
05b80a2d63 | ||
|
|
de79dc014f | ||
|
|
1a99a9e99b | ||
|
|
f49769f3c4 | ||
|
|
77cf6767b1 | ||
|
|
d1fcd671c9 | ||
|
|
5cb4944413 | ||
|
|
559ee6ed4e | ||
|
|
cc1e96f3cf | ||
|
|
b273e3a71d | ||
|
|
66fb0e2e6d | ||
|
|
baf5c9143d | ||
|
|
28a8a1554d | ||
|
|
515ca0a98c | ||
|
|
dcad7db29c | ||
|
|
4bec9e556f | ||
|
|
da7397fe75 | ||
|
|
86eb3d498c | ||
|
|
ca8caa7795 | ||
|
|
f6f71b043b | ||
|
|
281fe00a35 | ||
|
|
e5d68c5214 | ||
|
|
04f3fc42e0 | ||
|
|
ed50cb2f6a | ||
|
|
e1e8f37862 | ||
|
|
7146c6cf11 | ||
|
|
41bc53b9da | ||
|
|
bfe8c36e87 | ||
|
|
430e5b7fce | ||
|
|
8d1991ed84 | ||
|
|
c075fe9481 | ||
|
|
7e4891a93f | ||
|
|
f8f62db174 | ||
|
|
93a098e438 | ||
|
|
1f4d7c752d | ||
|
|
9b3c7c76cf | ||
|
|
ac4cc8a346 | ||
|
|
e7b1e2a4fc | ||
|
|
f8b63f4cce | ||
|
|
30c60261af | ||
|
|
9079feebbd | ||
|
|
a7d93590c6 | ||
|
|
d20c568e85 | ||
|
|
c8e38d73ff | ||
|
|
135d90a233 | ||
|
|
9f5ac02ec6 | ||
|
|
6f24b7928d | ||
|
|
b5fc29f150 | ||
|
|
6cc24c0332 | ||
|
|
924265b964 | ||
|
|
7264fd2ed4 | ||
|
|
0610b8556a | ||
|
|
9fd563450e | ||
|
|
ddf4f227b3 | ||
|
|
36a15f0091 | ||
|
|
58bd6250c9 | ||
|
|
10081a2276 | ||
|
|
fa6bee4c83 | ||
|
|
40f9f9ec8c | ||
|
|
01649955b1 | ||
|
|
5d412b759f | ||
|
|
4f7a9d725e | ||
|
|
e3ea65d7f4 | ||
|
|
d71aa240e7 | ||
|
|
e438f3cbbd | ||
|
|
d44e0a2588 | ||
|
|
258e35c94d | ||
|
|
d607b7fb6b | ||
|
|
42b9cbe38f | ||
|
|
d7edb51698 | ||
|
|
1bd85fb4ad | ||
|
|
580845ae77 | ||
|
|
32b49d49a9 | ||
|
|
5ff8a49be5 | ||
|
|
3955820eb9 | ||
|
|
609bd23cd8 | ||
|
|
0f7bc7c07f | ||
|
|
cf0be4712e | ||
|
|
50c5d3a1cd | ||
|
|
8dae887eca | ||
|
|
3be37008b7 | ||
|
|
ae00c43106 | ||
|
|
d7392c09d2 | ||
|
|
f549a32dd2 | ||
|
|
e092d7247d | ||
|
|
d3dbedf18a | ||
|
|
90ab3d0a31 | ||
|
|
cba3c8bbae | ||
|
|
4a14ad7fb0 | ||
|
|
5c04104ad2 | ||
|
|
ef2b6721c7 | ||
|
|
d7e82ceff3 | ||
|
|
973eb06584 | ||
|
|
825d97adc4 | ||
|
|
8835219c26 | ||
|
|
1dd9d324e6 | ||
|
|
afbc16a3ec | ||
|
|
98b9d5a81d | ||
|
|
6f6a3ae2fd | ||
|
|
5300e6c6e7 | ||
|
|
9f7155f42e | ||
|
|
362bef9f57 | ||
|
|
4cd1e09466 | ||
|
|
1765311586 | ||
|
|
68cfa65135 | ||
|
|
b7930014ab | ||
|
|
67d0508fd7 | ||
|
|
280abc4dea | ||
|
|
e1c727b7de | ||
|
|
d07d31d94a | ||
|
|
09f795b862 | ||
|
|
f5f47403cc | ||
|
|
31e46c5c56 | ||
|
|
219cb4b5f6 | ||
|
|
683e26e32a | ||
|
|
9bec5b5f3b | ||
|
|
4815054fed | ||
|
|
c50e095781 | ||
|
|
b4ba098158 | ||
|
|
836021c57e | ||
|
|
0a85b0dc1e | ||
|
|
3b78186702 | ||
|
|
dbbf310661 | ||
|
|
7eb760b5d6 | ||
|
|
e2268a325a | ||
|
|
3f0c661dae | ||
|
|
7dca36fa83 | ||
|
|
159be1cac4 | ||
|
|
e478a45bc0 | ||
|
|
d80ca63dad | ||
|
|
57bc4640e5 | ||
|
|
33dbeb9776 | ||
|
|
3c398d4a1d | ||
|
|
943f4c3453 | ||
|
|
8e1ea10c5a | ||
|
|
a7bc742035 | ||
|
|
82b810ed36 | ||
|
|
6eceaf474d | ||
|
|
88a1fe10a1 | ||
|
|
240504816c | ||
|
|
053dce0729 | ||
|
|
a36d152b61 | ||
|
|
e5a5c62106 | ||
|
|
db25f98251 | ||
|
|
ad7c9cc545 | ||
|
|
331e5bd5d1 | ||
|
|
c4bf32f991 | ||
|
|
20c43a3ea1 | ||
|
|
172edaf0fc | ||
|
|
4bc5e42e50 | ||
|
|
88b6a5ef45 | ||
|
|
5078609c86 | ||
|
|
e6ef4b870a | ||
|
|
68dcab39fb | ||
|
|
79c16d530d | ||
|
|
365c2de4e5 | ||
|
|
7cabb4d4ab | ||
|
|
73476779ae | ||
|
|
bd1e184023 | ||
|
|
cc7b0be150 | ||
|
|
e2eaa9143b | ||
|
|
23da8dc637 | ||
|
|
6203863523 | ||
|
|
e6d8d5076c | ||
|
|
cc3d30be2f | ||
|
|
77164dfaa7 | ||
|
|
56e72b73fe | ||
|
|
c8b6cf2732 | ||
|
|
a9b81d00c0 | ||
|
|
03bb9fea79 | ||
|
|
bb733fca96 | ||
|
|
ae7b784783 | ||
|
|
d59658fb7b | ||
|
|
20a4eb2f94 | ||
|
|
ad61be8626 | ||
|
|
209ec2673f | ||
|
|
6fffb57a8d | ||
|
|
5d6a8145c3 | ||
|
|
00fcda4f12 | ||
|
|
e989fc05d5 | ||
|
|
ea2c128937 | ||
|
|
a93799036d | ||
|
|
ce2fac6c4c | ||
|
|
bcd868850d | ||
|
|
eaa95e8cd9 | ||
|
|
a76e7254ff | ||
|
|
14da572830 | ||
|
|
c7b507ca6b | ||
|
|
6f60c49bd1 | ||
|
|
0018ccc929 | ||
|
|
177739e7cd | ||
|
|
5bf8096158 | ||
|
|
066424e087 | ||
|
|
7e08a8090c | ||
|
|
a4fb495e28 | ||
|
|
9ab69d8461 | ||
|
|
1a56d7ef91 | ||
|
|
c88b3798f0 | ||
|
|
076f853a5b | ||
|
|
405a8efad3 | ||
|
|
6f3233eefd | ||
|
|
3007698b1b | ||
|
|
0830635947 | ||
|
|
c7040e9936 | ||
|
|
da24e96fb4 | ||
|
|
a42edcd438 | ||
|
|
cf1f3e9eed | ||
|
|
c5c4c1a41c | ||
|
|
fbffe3aa67 | ||
|
|
8761eee22f | ||
|
|
068017941b | ||
|
|
a3b90bf19a | ||
|
|
22c6754efc | ||
|
|
5e1c3eea24 | ||
|
|
e11cf8986e | ||
|
|
b8c5c1a4f3 | ||
|
|
06e0ba23d7 | ||
|
|
8fb3320868 | ||
|
|
b3db546e00 | ||
|
|
312f60660a | ||
|
|
bab0da2a8f | ||
|
|
a7360b68f0 | ||
|
|
f1c00e2684 | ||
|
|
298ac96558 | ||
|
|
a54ae2b151 | ||
|
|
d0958e8771 | ||
|
|
8b4aef94be | ||
|
|
c19d46f406 | ||
|
|
5170dde1d6 | ||
|
|
c27c1f9541 | ||
|
|
424c2fd1e0 | ||
|
|
bb21bbc7fa | ||
|
|
d1493cbb57 | ||
|
|
30a709d619 | ||
|
|
c610a995c2 | ||
|
|
78125c7f37 | ||
|
|
7191c59b8a | ||
|
|
eea8cac018 | ||
|
|
5a4f28e50a | ||
|
|
f7bb497432 | ||
|
|
b8360405dd | ||
|
|
753e90bbe2 | ||
|
|
ac7d2480ea | ||
|
|
26120d1662 | ||
|
|
5ac359f1eb | ||
|
|
34b7fddfc7 | ||
|
|
390adea9d4 | ||
|
|
7e4657c6ad | ||
|
|
04069d83ea | ||
|
|
0c0c2987e8 | ||
|
|
012b1648e1 | ||
|
|
04b8987c30 | ||
|
|
9f46b7fffa | ||
|
|
b5d7e95a92 | ||
|
|
c3721c428e | ||
|
|
d6fb4c971b | ||
|
|
5466472b21 | ||
|
|
f0a66e1984 | ||
|
|
4598151034 | ||
|
|
8f2e8e492f | ||
|
|
e327f3c734 | ||
|
|
20648ae4cd | ||
|
|
4bf2cf632b | ||
|
|
0879531478 | ||
|
|
4bb8e132f5 | ||
|
|
fb689bf7da | ||
|
|
6c44fccf66 | ||
|
|
b670fc7109 | ||
|
|
d8a4375783 | ||
|
|
60530343b4 | ||
|
|
fcced2e122 | ||
|
|
af5c3f2ae6 | ||
|
|
be7c19a54f | ||
|
|
708d3d785d | ||
|
|
25b326ee48 | ||
|
|
447153ca3c | ||
|
|
1fb7d0c66b | ||
|
|
ae342845f8 | ||
|
|
0ff0758b74 | ||
|
|
7760c13ea9 | ||
|
|
3757a3514b | ||
|
|
6214df7e8f | ||
|
|
42d46accb6 | ||
|
|
ddc8133c5b | ||
|
|
a96d015a0c | ||
|
|
b1244086cd | ||
|
|
da556d3523 | ||
|
|
6f412d736b | ||
|
|
4697d150cf | ||
|
|
4c5f5a3514 | ||
|
|
0a0bedacb4 | ||
|
|
7e771c2644 | ||
|
|
9709c31bcd | ||
|
|
2e3b6602af | ||
|
|
6d5a511cb4 | ||
|
|
1a2b40d6f2 | ||
|
|
b7714d7d60 | ||
|
|
953a7914c9 | ||
|
|
092d53ef9d | ||
|
|
48b0924bd8 | ||
|
|
a2af1ae21e | ||
|
|
833c4870f8 | ||
|
|
a30b271a57 | ||
|
|
f9d8dc0b86 | ||
|
|
60f44c9603 | ||
|
|
1433d032f3 | ||
|
|
d90cd8f984 | ||
|
|
c10ca29fda | ||
|
|
c819dd6bb7 | ||
|
|
d06c2934a8 | ||
|
|
261897c699 | ||
|
|
6ef80d3166 | ||
|
|
aeb7ce5f54 | ||
|
|
1028c663eb | ||
|
|
e33aa42b19 | ||
|
|
48b6f9efb0 | ||
|
|
f4d905833f | ||
|
|
65c73e8b6e | ||
|
|
737bf6aa94 | ||
|
|
f89d90619a | ||
|
|
a3c25adc7a | ||
|
|
ae32376758 | ||
|
|
aded562304 | ||
|
|
8b479be6c5 | ||
|
|
8acbd37df3 | ||
|
|
cf6946bc4a | ||
|
|
78db179957 | ||
|
|
9a279b53a4 | ||
|
|
2aa9ad270e | ||
|
|
4ca87f0ff1 | ||
|
|
dc610bfa64 | ||
|
|
58b50b49de | ||
|
|
a79b0bdfdd | ||
|
|
b67e1b85d6 | ||
|
|
2421913587 | ||
|
|
4078f0c404 | ||
|
|
731bd33470 | ||
|
|
4351ddc00d | ||
|
|
941a61fb74 | ||
|
|
a729dc85e0 | ||
|
|
491595934f | ||
|
|
d087318286 | ||
|
|
5429b0cb7a | ||
|
|
69da82b139 | ||
|
|
fdb9bff86b | ||
|
|
cb256de9f8 | ||
|
|
b27126fd8e | ||
|
|
b9c13b8a89 | ||
|
|
2a47d97d5c | ||
|
|
0f00aecde5 | ||
|
|
7ee87ceb9e | ||
|
|
81208ab99b | ||
|
|
b513a8926b | ||
|
|
2f370d1b9c | ||
|
|
ffd62a5d16 | ||
|
|
7249c1fd96 | ||
|
|
87893819b0 | ||
|
|
c29ec82d6a | ||
|
|
98c53a15f2 | ||
|
|
d190446187 | ||
|
|
7f264b2503 | ||
|
|
128c144ebf | ||
|
|
531ad7562a | ||
|
|
c9e4dfce68 | ||
|
|
057b2346dc | ||
|
|
7f2cf10021 | ||
|
|
904f5de3e0 | ||
|
|
00bd11495f | ||
|
|
03b3512380 | ||
|
|
c5da7c1997 | ||
|
|
8b5588936e | ||
|
|
3a2178eb55 | ||
|
|
78a83c536e | ||
|
|
17e99101bc | ||
|
|
8430e5d0d1 | ||
|
|
0bfced503c | ||
|
|
39681dc7f6 | ||
|
|
39850dccf7 | ||
|
|
327d297d58 | ||
|
|
68232363ae | ||
|
|
41f78b50e7 | ||
|
|
c2983f0551 | ||
|
|
6577eda289 | ||
|
|
ad4eea68ce | ||
|
|
e1b5e7bce1 | ||
|
|
3b8481ed3f | ||
|
|
9f15a03a5a | ||
|
|
3380185449 | ||
|
|
b60f91a96a | ||
|
|
1ecb088b60 | ||
|
|
8a1645fb81 | ||
|
|
224b241cd6 | ||
|
|
907a150574 | ||
|
|
48c7735165 | ||
|
|
55983990fb | ||
|
|
120f10139c | ||
|
|
b589c25bb5 | ||
|
|
211babec37 | ||
|
|
e846900384 | ||
|
|
ad7444ed97 | ||
|
|
af4d0010fb | ||
|
|
fde6128757 | ||
|
|
cf79473fad | ||
|
|
ff62ea0597 | ||
|
|
ad7e480213 | ||
|
|
139cb64ca5 | ||
|
|
cfcf81b665 | ||
|
|
4002301fa6 | ||
|
|
b0a251c21e | ||
|
|
756cd01ab9 | ||
|
|
c7e87e14b9 | ||
|
|
fd0900e053 | ||
|
|
5ea34f66dd | ||
|
|
5547fd5486 | ||
|
|
12ac42ef5f | ||
|
|
89790609db | ||
|
|
2b7e9c403d | ||
|
|
94abcd3f0a | ||
|
|
83021fb773 | ||
|
|
63ada6099a | ||
|
|
833043143f | ||
|
|
41aacf84db | ||
|
|
1b89cadab5 | ||
|
|
969ab8d0d7 | ||
|
|
896ec74959 | ||
|
|
316fd4621b | ||
|
|
bb95118d2b | ||
|
|
fc17545860 | ||
|
|
a250440670 | ||
|
|
1e100e6d2e | ||
|
|
ccf8006f13 | ||
|
|
7ac71837b3 | ||
|
|
c68570d2bc | ||
|
|
bc06cb5750 | ||
|
|
dc093f7109 | ||
|
|
50764b3602 | ||
|
|
bd38329fd8 | ||
|
|
ceb520d49d | ||
|
|
b47a512587 | ||
|
|
2468fbde4e | ||
|
|
e109637e48 | ||
|
|
4d05ac859f | ||
|
|
285746a4a9 | ||
|
|
8154527533 | ||
|
|
4414324a08 | ||
|
|
c47f48c64b | ||
|
|
0fc7675560 | ||
|
|
3c4507b198 | ||
|
|
c556d9500b | ||
|
|
33ee150915 | ||
|
|
c82a7ab756 | ||
|
|
9da7c07dc6 | ||
|
|
3044e41d9b | ||
|
|
75363c72cf | ||
|
|
18d8ee6b79 | ||
|
|
4a1a3b19aa | ||
|
|
cefc80fa55 | ||
|
|
ada3ff1926 | ||
|
|
b4dca46e5e | ||
|
|
0449150874 | ||
|
|
2381a2c2ad | ||
|
|
c8cd2ec794 | ||
|
|
b616fef018 | ||
|
|
41b44252b1 | ||
|
|
c7c2ebf353 | ||
|
|
eecd3255f0 | ||
|
|
05d11e366b | ||
|
|
795c033a82 | ||
|
|
b6a8e6f448 | ||
|
|
2d9f9bf1af | ||
|
|
14da5da205 | ||
|
|
c8afe6995d | ||
|
|
35acb49545 | ||
|
|
36f3d70467 | ||
|
|
bfdb212770 | ||
|
|
747ea84880 | ||
|
|
8a24dbcd24 | ||
|
|
6b8e9d7ac6 | ||
|
|
5e6c71f4ad | ||
|
|
f58c30299a | ||
|
|
71979d82b6 | ||
|
|
176c4c93e7 | ||
|
|
73627fbf0d | ||
|
|
fe19a91cec | ||
|
|
c34a1eafcc | ||
|
|
63e598a725 | ||
|
|
9897fa8225 | ||
|
|
ef613f2742 | ||
|
|
5316199b8c | ||
|
|
fdf1415f6d | ||
|
|
ac0e54f39f | ||
|
|
a65b8fb507 | ||
|
|
be33b0b3b9 | ||
|
|
7b7b0e1e7e | ||
|
|
f15b00710b | ||
|
|
cfe38878d7 | ||
|
|
9854d3b5b6 | ||
|
|
fe5b3af887 | ||
|
|
2a5284d555 | ||
|
|
b1a4b8017f | ||
|
|
c0797f3687 | ||
|
|
6492e6be82 | ||
|
|
285996cbf6 | ||
|
|
c3fb400116 | ||
|
|
a12ae7aaa5 | ||
|
|
17da20f6d0 | ||
|
|
3e964f28ff | ||
|
|
c9aaf8bb8f | ||
|
|
7d85f37c21 | ||
|
|
e28de7d4d9 | ||
|
|
8a81a5c14e | ||
|
|
2d96e1db8f | ||
|
|
1a5cad40a2 | ||
|
|
c76ec91d15 | ||
|
|
617ae77825 | ||
|
|
ec8c35af18 | ||
|
|
47dc0d57a8 | ||
|
|
ccbebe7516 | ||
|
|
9a019396c8 | ||
|
|
083bbb8e9b | ||
|
|
ee09e10e6e | ||
|
|
0efdfa4a6f | ||
|
|
24d2cff3f7 | ||
|
|
6489b10527 | ||
|
|
491df54242 | ||
|
|
3aff950598 | ||
|
|
7d700103c4 | ||
|
|
c3d86fb47f | ||
|
|
b7ada8bbdf | ||
|
|
6d44e67a45 | ||
|
|
1618fa3c32 | ||
|
|
f7b6ba435b | ||
|
|
e365538699 | ||
|
|
6285760bad | ||
|
|
78ced84774 | ||
|
|
785091f340 | ||
|
|
8cd5c91aea | ||
|
|
c14e1c008a | ||
|
|
db21d78aff | ||
|
|
ff538303c6 | ||
|
|
dbe27c29da | ||
|
|
2ab20dc9b8 | ||
|
|
522034b61c | ||
|
|
faa91ce238 | ||
|
|
009bb930b5 | ||
|
|
086c32f980 | ||
|
|
115e2278d8 | ||
|
|
d6fd34668d | ||
|
|
8ed63213bb | ||
|
|
6bf3dc3e2b | ||
|
|
854b211f2c | ||
|
|
c81f17a19a | ||
|
|
b94c539269 | ||
|
|
9f5652eb15 | ||
|
|
e270737882 | ||
|
|
9bf5011882 | ||
|
|
43d971a147 | ||
|
|
f5d3e95884 | ||
|
|
0dc9d4bf35 | ||
|
|
5728111fd2 | ||
|
|
9a8e6f9c69 | ||
|
|
cc3dacdd76 | ||
|
|
b15d93eca8 | ||
|
|
ece3b45f59 | ||
|
|
be8c664b47 | ||
|
|
4ab0c88262 | ||
|
|
337ced8467 | ||
|
|
5803148837 | ||
|
|
93194e37c9 | ||
|
|
ebcdc5ebd3 | ||
|
|
951d442a7d | ||
|
|
ef36f48e33 | ||
|
|
51b9104f07 | ||
|
|
114d296fe3 | ||
|
|
3c657680cb | ||
|
|
6fe09fc459 | ||
|
|
453cc747a1 | ||
|
|
3d76b0a7b7 | ||
|
|
ff45f431ee | ||
|
|
b0dd5de51d | ||
|
|
ea7b39200f | ||
|
|
3401525ab6 | ||
|
|
ee411709fc | ||
|
|
674eb7ca6d | ||
|
|
c62870a465 | ||
|
|
75a5133004 | ||
|
|
8ad5a8f8e6 | ||
|
|
d1e410ef33 | ||
|
|
1a37662502 | ||
|
|
70e6e670f3 | ||
|
|
f3f61c85cc | ||
|
|
5759a311aa | ||
|
|
533470d080 | ||
|
|
f6dcd74dfb | ||
|
|
603bcff935 | ||
|
|
b31e029145 | ||
|
|
de8e0bdd94 | ||
|
|
258859462f | ||
|
|
cd19f1c999 | ||
|
|
5555ba7040 | ||
|
|
21ad16861a | ||
|
|
1b9ee36cfd | ||
|
|
72e2f33074 | ||
|
|
1fda7f6c50 | ||
|
|
7cf313cedb | ||
|
|
45febec802 | ||
|
|
8f7039bbb3 | ||
|
|
69afcdcdb2 | ||
|
|
e03621db0b | ||
|
|
f701ed39ef | ||
|
|
d70013ee8e | ||
|
|
25d32117ed | ||
|
|
a371f86933 | ||
|
|
d6e29185e5 | ||
|
|
df60013e16 | ||
|
|
7e2858def3 | ||
|
|
ae9c43edef | ||
|
|
c5311e7c0b | ||
|
|
599e6eaf7e | ||
|
|
c60e958920 | ||
|
|
ca0db32d19 | ||
|
|
5e13042c71 | ||
|
|
a4e24722c2 | ||
|
|
b4d556399a | ||
|
|
3136d94d69 | ||
|
|
2c210941aa | ||
|
|
b11bba761a | ||
|
|
7503503ccf | ||
|
|
32d125d7a7 | ||
|
|
3eae8f0d2a | ||
|
|
1b40ecdd07 | ||
|
|
4b3829d782 | ||
|
|
3d28b226e5 | ||
|
|
e73dba8445 | ||
|
|
ce4a00cfdd | ||
|
|
5c7b3fb7f2 | ||
|
|
2515a34874 | ||
|
|
1c986b0521 | ||
|
|
5da52d648a | ||
|
|
61f515e4d2 | ||
|
|
0fe5659f08 | ||
|
|
a0ed13f18e | ||
|
|
81434fff0e | ||
|
|
bf2187bbbd | ||
|
|
9d3ce24b90 | ||
|
|
b88eba017d | ||
|
|
ac78e6831b | ||
|
|
8c8bfd6051 | ||
|
|
947060b07e | ||
|
|
091bb44ccd | ||
|
|
4213b1571d | ||
|
|
02aab959e0 | ||
|
|
acaa7221dd | ||
|
|
f5121bb1fb | ||
|
|
881fed53a4 | ||
|
|
de3cc10478 | ||
|
|
aee0fd8373 | ||
|
|
70df4c2697 | ||
|
|
8bf34fc1f3 | ||
|
|
d44da650bd | ||
|
|
adae573a0f | ||
|
|
f7b22f61b3 | ||
|
|
00681e1411 | ||
|
|
befd203feb | ||
|
|
b9d0fb3613 | ||
|
|
c36d444657 | ||
|
|
ec037d7c90 | ||
|
|
80e4f4edc4 | ||
|
|
8ab564164b | ||
|
|
28123cab00 | ||
|
|
35e049f577 | ||
|
|
c0fc6ff7ea | ||
|
|
91d5e9dd36 | ||
|
|
e895077a7b | ||
|
|
3ac1063c9b | ||
|
|
2ed2ea372a | ||
|
|
97e5f2595f | ||
|
|
5fffd0a2d3 | ||
|
|
0bb6502e70 | ||
|
|
287b031a12 | ||
|
|
ac6eccc58a | ||
|
|
412e89b690 | ||
|
|
9e2242093c | ||
|
|
39dec26450 | ||
|
|
f12e3c5bd8 | ||
|
|
7ea57222d9 | ||
|
|
16870aa257 | ||
|
|
24601fee3e | ||
|
|
b2eb16fc82 | ||
|
|
64d1da9dc6 | ||
|
|
5656abb26c | ||
|
|
bd67664827 | ||
|
|
731167efa8 | ||
|
|
74e965399a | ||
|
|
ab588a1501 | ||
|
|
fca2841ce0 | ||
|
|
f90a53da68 | ||
|
|
fd1145c162 | ||
|
|
d641fa1c2d | ||
|
|
7c600427e6 | ||
|
|
0f415b7521 | ||
|
|
552620a7eb | ||
|
|
b5855aaab4 | ||
|
|
138e92afc5 | ||
|
|
2d4c5a8221 | ||
|
|
6f1f04681f | ||
|
|
1d263b5540 | ||
|
|
d88cfd404c | ||
|
|
ac735976d1 | ||
|
|
220fb7bdf4 | ||
|
|
c1f9de58b0 | ||
|
|
deef8ab58b | ||
|
|
6c7993cdba | ||
|
|
9fcc1c60d4 | ||
|
|
161e78f95f | ||
|
|
f7042b94ef | ||
|
|
e1f5192f4b | ||
|
|
aa9085c263 | ||
|
|
f0ebaa19a3 | ||
|
|
d5ee89db9b | ||
|
|
2a8e67ca59 | ||
|
|
b00ec559e0 | ||
|
|
a5daea497d | ||
|
|
952993a537 | ||
|
|
f192051328 | ||
|
|
a4ceca57ae | ||
|
|
21467ac670 | ||
|
|
6cf31f60de | ||
|
|
e9ccee5621 | ||
|
|
3a28de46bd | ||
|
|
dd121a068a | ||
|
|
af5bfadba3 | ||
|
|
6541782541 | ||
|
|
89af08b703 | ||
|
|
6a773f7740 | ||
|
|
5f34be12a9 | ||
|
|
81d788ef84 | ||
|
|
605e88fef4 | ||
|
|
f11c591dc5 | ||
|
|
2853bff284 | ||
|
|
3a4ff73953 | ||
|
|
4bc25692a8 | ||
|
|
0e6558e0b1 | ||
|
|
4caf62a367 | ||
|
|
519e0d4156 | ||
|
|
5aeab3fe23 | ||
|
|
15099de798 | ||
|
|
9b5943208e | ||
|
|
bdae1ff346 | ||
|
|
b4b8581383 | ||
|
|
013fb4084f | ||
|
|
d2d7e84d9b | ||
|
|
b5067aa0a9 | ||
|
|
250d6a1323 | ||
|
|
9432ba5944 | ||
|
|
d1da91e030 | ||
|
|
244778058c | ||
|
|
c9d8a03156 | ||
|
|
af8cadc143 | ||
|
|
f1447e6975 | ||
|
|
dd6d485340 | ||
|
|
d0efc0783f | ||
|
|
4dc3c636a6 | ||
|
|
2ea8c16d9e | ||
|
|
c05fe96059 | ||
|
|
63079bf80a | ||
|
|
9797957778 | ||
|
|
19395f1921 | ||
|
|
faad6a2562 | ||
|
|
fcec03a6a4 | ||
|
|
695bf53262 | ||
|
|
c5ac27119e | ||
|
|
262abde2fc | ||
|
|
b940df5bc0 | ||
|
|
041c04e327 | ||
|
|
6af86ef6a6 | ||
|
|
42950bc2a3 | ||
|
|
141c737cf3 | ||
|
|
73ea091b83 | ||
|
|
dd1b3a1f1d | ||
|
|
4da121d094 | ||
|
|
2d615ce582 | ||
|
|
7b933bfbac | ||
|
|
eec2e5030b | ||
|
|
16e1aab1c2 | ||
|
|
bc9c41f005 | ||
|
|
8382c009f4 | ||
|
|
7ad61df839 | ||
|
|
b5a4de9c9e | ||
|
|
8f3500385c | ||
|
|
1ca4ea4abe | ||
|
|
114c383c54 | ||
|
|
cbec7c1a01 | ||
|
|
6dff634299 | ||
|
|
315588bc78 | ||
|
|
ffeb135bcc | ||
|
|
6c5dcec71e | ||
|
|
20fa791632 | ||
|
|
d1ff7cd732 | ||
|
|
0446d52e78 | ||
|
|
364bf166ae | ||
|
|
7d0544a590 | ||
|
|
2d9f602256 | ||
|
|
48209ac876 | ||
|
|
968b7d4bc0 | ||
|
|
fbae3924e5 | ||
|
|
8724e04860 | ||
|
|
374896caca | ||
|
|
62f18ece85 | ||
|
|
82c75b0654 | ||
|
|
717c2afdc4 | ||
|
|
5bda4c88f0 | ||
|
|
a56aea56a8 | ||
|
|
f08670bb21 | ||
|
|
ab39636196 | ||
|
|
99434aa49a | ||
|
|
802ec4275d | ||
|
|
9ba4e7374c | ||
|
|
01218c77a6 | ||
|
|
355663f036 | ||
|
|
860a6ae18c | ||
|
|
a0e83f287d | ||
|
|
876698825f | ||
|
|
db67a91413 | ||
|
|
8e57400c58 | ||
|
|
04ee59e9d3 | ||
|
|
d8f0c4602c | ||
|
|
119e338292 | ||
|
|
be3464369b | ||
|
|
ad98dc8661 | ||
|
|
65d2d0ac83 | ||
|
|
63a53a18cc | ||
|
|
092ee4bfc2 | ||
|
|
523bbc0f41 | ||
|
|
d0802fea50 | ||
|
|
efb0f96be2 | ||
|
|
aa4c672594 | ||
|
|
0d3a5b8919 | ||
|
|
a9ebe9946a | ||
|
|
7387aef764 | ||
|
|
e6aa95f875 | ||
|
|
2c9faebcdc | ||
|
|
7c45c78788 | ||
|
|
af65ed0094 | ||
|
|
1b8f75f00e | ||
|
|
fef59dd03b | ||
|
|
7ec7219ed4 | ||
|
|
a156e98831 | ||
|
|
422f7d014c | ||
|
|
77fc20cfff | ||
|
|
64823685cc | ||
|
|
b69c9cde03 | ||
|
|
389461415d | ||
|
|
d78dca3345 | ||
|
|
c5cc955d91 | ||
|
|
f0817a3dd6 | ||
|
|
ef2c45e2cb | ||
|
|
ed501a7ee9 | ||
|
|
9d17f5340a | ||
|
|
f6967da5cc | ||
|
|
b94f9898b0 | ||
|
|
0b9cf21fae | ||
|
|
f0ffd9b024 | ||
|
|
ae6fdfe726 | ||
|
|
9291a377d3 | ||
|
|
e4dd16e9d1 | ||
|
|
120fb2896f | ||
|
|
e86b5c3454 | ||
|
|
12d91ba0b4 | ||
|
|
dbf1dd1fb9 | ||
|
|
d04c9280df | ||
|
|
d271b2c337 | ||
|
|
cd8ff60508 | ||
|
|
c3a2f9fdc6 | ||
|
|
2eb09fc65a | ||
|
|
f3cbc92286 | ||
|
|
424ed78b93 | ||
|
|
ba9f33230f | ||
|
|
717019e04e | ||
|
|
ca981220c9 | ||
|
|
a85a483732 | ||
|
|
fbc1aa994c | ||
|
|
2f9a349d31 | ||
|
|
0f19b4b7a7 | ||
|
|
799ca975ac | ||
|
|
5bfe2f800b | ||
|
|
07ddaab7fb | ||
|
|
f2a7da21e0 | ||
|
|
862d3bb5f2 | ||
|
|
02b73b41d7 | ||
|
|
0638ce0a8c | ||
|
|
62098f8e84 | ||
|
|
17b6d79157 | ||
|
|
19d7a8b9dd | ||
|
|
fbc35550b9 | ||
|
|
d4e402e5c9 | ||
|
|
f27fc6c38a | ||
|
|
979dddd20c | ||
|
|
faa6eb6ed7 | ||
|
|
f3beb72f17 | ||
|
|
6b5c9a4ce7 | ||
|
|
9e9d4b2081 | ||
|
|
61a17b1d1c | ||
|
|
9b0d416393 | ||
|
|
5c51abbd46 | ||
|
|
b313c4eb8d | ||
|
|
8e791bbe24 | ||
|
|
1e9ebd5905 | ||
|
|
f4a0b03246 | ||
|
|
1447adad1b | ||
|
|
0a99f1e501 | ||
|
|
305ce3d873 | ||
|
|
7be71c965f | ||
|
|
e90c89af89 | ||
|
|
23445c143b | ||
|
|
3f64ba0f9b | ||
|
|
ac8dd43662 | ||
|
|
8367b7d6e1 | ||
|
|
8daabf1f5a | ||
|
|
e6980e5676 | ||
|
|
c2c5331861 | ||
|
|
1cc2a182a5 | ||
|
|
ba57833fb0 | ||
|
|
1197b9aba3 | ||
|
|
57799a777a | ||
|
|
6a166b29a9 | ||
|
|
b5c45d806e | ||
|
|
fb61d39225 | ||
|
|
d8a19d4916 | ||
|
|
03cb05e2a3 | ||
|
|
91eb1de1c4 | ||
|
|
ac4ded4614 | ||
|
|
5b389ada9e | ||
|
|
d368b3a9de | ||
|
|
aac954cafc | ||
|
|
f2fb4821e3 | ||
|
|
f635d6c233 | ||
|
|
827fd88110 | ||
|
|
4a705ce088 | ||
|
|
8b82215c32 | ||
|
|
b331dc66ac | ||
|
|
bb395f0375 | ||
|
|
2c4cccab66 | ||
|
|
63a79f8758 | ||
|
|
b790cc7954 | ||
|
|
16b01a57fd | ||
|
|
4c53161e98 | ||
|
|
24f1107df8 | ||
|
|
07ffe444f3 | ||
|
|
8bc30b8234 | ||
|
|
60273329ab | ||
|
|
e06b8a2d95 | ||
|
|
7698757865 | ||
|
|
e13ab0c70f | ||
|
|
269a05499d | ||
|
|
241e6b0b77 | ||
|
|
4857bc0494 | ||
|
|
87ec328a66 | ||
|
|
8740979a80 | ||
|
|
938f6cb2c3 | ||
|
|
7d465c0229 | ||
|
|
c1da2d5552 | ||
|
|
91a6935a91 | ||
|
|
e8ed101176 | ||
|
|
acd29d8f39 | ||
|
|
5206aa2c4a | ||
|
|
728bc7ecba | ||
|
|
956ba68550 | ||
|
|
281cba8da1 |
714 changed files with 185553 additions and 12337 deletions
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
23
.github/ISSUE_TEMPLATE/request-source-extractor.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/request-source-extractor.md
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: Request source/extractor
|
||||||
|
about: Request a source or extractor to get URLs from
|
||||||
|
title: Request to add <source/extractor>
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**What is the site you want to get added?**
|
||||||
|
A link to the source or embed hoster
|
||||||
|
|
||||||
|
**What is the benefit of adding this?**
|
||||||
|
More content is not a good reason. If it's already covered via an existing source/extractor, why should we add another one?
|
||||||
|
|
||||||
|
**Are you aware of any protection the site uses?**
|
||||||
|
E.g. rate-limits or Cloudflare captchas and such
|
||||||
|
|
||||||
|
**What languages does this relate to?**
|
||||||
|
E.g. multi, German, ..
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
A movie/series IMDb/TMDB ID + source link or embed URL to test with.
|
||||||
28
.github/workflows/build-tag.yml
vendored
Normal file
28
.github/workflows/build-tag.yml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
name: Build and push latest
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v4
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Set up QEMU (for cross-arch)
|
||||||
|
uses: docker/setup-qemu-action@v4
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v4
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
uses: docker/build-push-action@v7
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
tags: webstreamr/webstreamr:latest
|
||||||
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
|
|
@ -1,15 +1,18 @@
|
||||||
name: Build
|
name: Build
|
||||||
on: push
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
-
|
-
|
||||||
name: Build and push
|
name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
push: false
|
push: false
|
||||||
tags: webstreamr/webstreamr:latest
|
tags: webstreamr/webstreamr:latest
|
||||||
|
|
|
||||||
23
.github/workflows/tests.yml
vendored
23
.github/workflows/tests.yml
vendored
|
|
@ -1,26 +1,17 @@
|
||||||
name: Tests
|
name: Tests
|
||||||
on: push
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "24"
|
||||||
|
|
||||||
- name: Cache node modules
|
|
||||||
uses: actions/cache@v4
|
|
||||||
env:
|
|
||||||
cache-name: cache-node-modules
|
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
|
||||||
${{ runner.os }}-build-
|
|
||||||
${{ runner.os }}-
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
".": "0.13.0"
|
".": "0.69.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3159
CHANGELOG.md
3159
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:22.15-alpine AS builder
|
FROM node:24 AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
@ -10,7 +10,7 @@ RUN npm run build
|
||||||
|
|
||||||
RUN npm ci --only=production
|
RUN npm ci --only=production
|
||||||
|
|
||||||
FROM node:22.15-alpine
|
FROM node:24
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /app/dist ./dist
|
||||||
|
|
|
||||||
117
README.md
Normal file
117
README.md
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
# WebStreamr
|
||||||
|
|
||||||
|
[](https://github.com/webstreamr/webstreamr/actions/workflows/tests.yml)
|
||||||
|
[](https://github.com/webstreamr/webstreamr/releases)
|
||||||
|

|
||||||
|
|
||||||
|
[Stremio](https://www.stremio.com/) add-on which provides HTTP URLs from streaming websites.
|
||||||
|
|
||||||
|
HTTP streams have limitations.
|
||||||
|
For a better experience, I'd advise using a Debrid service and WebStreamr as fallback.
|
||||||
|
[TorBox](https://torbox.app/subscription?referral=f22eb00d-27ce-4e20-85fc-68da3d018b99) is working very well.
|
||||||
|
|
||||||
|
## Public instance
|
||||||
|
|
||||||
|
A public instance is available at https://webstreamr.hayd.uk. Hosting infrastructure for this instance is donated by [ElfHosted](https://elfhosted.com), and independently maintained by [Hayduk](https://hayd.uk).
|
||||||
|
|
||||||
|
## Known issues / limitations
|
||||||
|
|
||||||
|
- PixelServer / pixeldrain has a daily limit of 6 GB per IP: https://pixeldrain.dev
|
||||||
|
- Dropload and SuperVideo on Android do not work because Stremio does not use the `Referer` header properly via HLS playlists: https://github.com/Stremio/stremio-bugs/issues/2389, maybe https://github.com/Stremio/stremio-bugs/issues/1579
|
||||||
|
- MediaFlow proxy has to be used in an inefficient way because Stremio on Android or its players cannot deal with HLS playlist with redirects: https://github.com/Stremio/stremio-bugs/issues/1574
|
||||||
|
- FlareSolverr cookies cannot be used because Cloudflare does techniques like TLS fingerprinting most likely. But FlareSolverr uses a session per host and should be quick.
|
||||||
|
- VidSrc works but rate limits heavily and is therefore only queried as fallback if nothing else is found.
|
||||||
|
- RgShows detects shared usage and blocks IPs. It therefore only works on private instances.
|
||||||
|
|
||||||
|
## MediaFlow Proxy
|
||||||
|
|
||||||
|
[MediaFlow Proxy](https://github.com/mhdzumair/mediaflow-proxy/) can be added when configuration the add-on to gain access to a couple of more file hosters.
|
||||||
|
It depends highly on the language / source used if that unlocks more streams or not.
|
||||||
|
|
||||||
|
MediaFlow proxy is needed because some hosters ip-lock streams and the add-on does not run on the same device that will stream the video.
|
||||||
|
|
||||||
|
The following hosters can be used only with MediaFlow Proxy:
|
||||||
|
|
||||||
|
- Fastream
|
||||||
|
- FileLions
|
||||||
|
- FileMoon
|
||||||
|
- LuluStream
|
||||||
|
- Mixdrop
|
||||||
|
- Streamtape
|
||||||
|
- VOE
|
||||||
|
|
||||||
|
MediaFlow proxy can either be self-hosted or acquired via bundle from [ElfHosted](https://docs.elfhosted.com/app/mediaflow-proxy/).
|
||||||
|
|
||||||
|
## Hosting
|
||||||
|
|
||||||
|
Don't want to use the public instance, or concerned about reliability during periods of high use? It's open-source, you can host it yourself!
|
||||||
|
|
||||||
|
### ElfHosted (easy mode)
|
||||||
|
|
||||||
|
Self-hosting to stressful? ElfHosted offer [ready-to-go, turn-key WebStreamr instances](https://store.elfhosted.com/product/webstreamr/) with $1, 7-day trials. Additionally, 33% of your subscription directly supports your developer! ❤️
|
||||||
|
|
||||||
|
(*ElfHosted also offer advanced private hosting of the [top Stremio Addons](https://store.elfhosted.com/product-category/stremio-addons/elf/webstreamr/), as well as [turn-key bundles providing streaming from RealDebrid with Plex, Emby, or Jellyfin](https://store.elfhosted.com/product-category/streaming-bundles/elf/webstreamr/)*)
|
||||||
|
|
||||||
|
### Self-Hosting
|
||||||
|
|
||||||
|
You can run the latest WebStreamr via Docker. E.g.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run \
|
||||||
|
--detach=true \
|
||||||
|
--name webstreamr \
|
||||||
|
--rm \
|
||||||
|
--pull always \
|
||||||
|
--publish 51546:51546 \
|
||||||
|
--env TMDB_ACCESS_TOKEN="YOUR_TOKEN" \
|
||||||
|
--volume /tmp:/tmp \
|
||||||
|
webstreamr/webstreamr
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
|
||||||
|
#### `CACHE_DIR`
|
||||||
|
|
||||||
|
Optional. Directory for persistent caches using SQLite files. Default: OS tmp dir.
|
||||||
|
|
||||||
|
#### `CONFIGURATION_DESCRIPTION`
|
||||||
|
|
||||||
|
Optional. To customize the description shown on the configuration page.
|
||||||
|
|
||||||
|
#### `DISABLED_EXTRACTORS`
|
||||||
|
|
||||||
|
Optional. Comma separated list of extractors which should be disabled. E.g. `doodstream,vidsrc`
|
||||||
|
|
||||||
|
#### `DISABLED_SOURCES`
|
||||||
|
|
||||||
|
Optional. Comma separated list of sources which should be disabled. E.g. `frembed,vidsrc`
|
||||||
|
|
||||||
|
#### `FLARESOLVERR_ENDPOINT`
|
||||||
|
|
||||||
|
Optional. If domains show Cloudflare challenges, FlareSolverr can be used to work around them. E.g. `http://flaresolverr:8191`
|
||||||
|
Proxy configuration is passed-through and only a single session is used to save resources. Byparr is not supported.
|
||||||
|
|
||||||
|
#### `MANIFEST_ID`
|
||||||
|
|
||||||
|
Optional. Add-on manifest ID. Default: `webstreamr`
|
||||||
|
|
||||||
|
#### `MANIFEST_NAME`
|
||||||
|
|
||||||
|
Optional. Add-on manifest name. Default: `WebStreamr`
|
||||||
|
|
||||||
|
#### `PORT`
|
||||||
|
|
||||||
|
Optional. Port of the node web server. Default: `51546`
|
||||||
|
|
||||||
|
#### `PROXY_CONFIG`
|
||||||
|
|
||||||
|
Optional. Proxies which should be used based on domain. Supports minimatch. E.g. `dood.to:http://USERNAME:PASSWORD@IP:PORT,*:socks5://172.17.0.1:1080` would use an http proxy for dood.to and a socks5 proxy for all other domains.
|
||||||
|
|
||||||
|
Some hosters are a bit picky when it comes to IPs. Best case is if you use a residential IP.
|
||||||
|
If you can't do that, then I suggest to use a VPN / proxy like Cloudflare WARP.
|
||||||
|
DoodStream is not working with WARP.
|
||||||
|
Free Webshare proxies seem to work with it.
|
||||||
|
|
||||||
|
#### `TMDB_ACCESS_TOKEN`
|
||||||
|
|
||||||
|
**Required**. TMDB access token to get information like title and year for content. Use the [API Read Access Token](https://www.themoviedb.org/settings/api).
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import eslint from '@eslint/js';
|
import eslint from '@eslint/js';
|
||||||
import stylistic from '@stylistic/eslint-plugin';
|
import stylistic from '@stylistic/eslint-plugin';
|
||||||
|
import importPlugin from 'eslint-plugin-import';
|
||||||
import tseslint from 'typescript-eslint';
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
|
importPlugin.flatConfigs.recommended,
|
||||||
|
importPlugin.flatConfigs.typescript,
|
||||||
tseslint.configs.strict,
|
tseslint.configs.strict,
|
||||||
tseslint.configs.stylistic,
|
tseslint.configs.stylistic,
|
||||||
stylistic.configs.customize({
|
stylistic.configs.customize({
|
||||||
|
|
@ -13,4 +16,11 @@ export default tseslint.config(
|
||||||
{
|
{
|
||||||
ignores: ['./dist'],
|
ignores: ['./dist'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-member-accessibility': 'error',
|
||||||
|
'import/no-unresolved': ['error', { ignore: ['typescript-eslint', 'stremio-addon-sdk'] }],
|
||||||
|
'import/order': ['error', { 'alphabetize': { order: 'asc', caseInsensitive: true }, 'named': { enabled: true }, 'newlines-between': 'never' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import type { Config } from 'jest';
|
import type { Config } from 'jest';
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
|
automock: false,
|
||||||
|
clearMocks: true,
|
||||||
collectCoverage: true,
|
collectCoverage: true,
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
'<rootDir>/src/**/*.ts',
|
'<rootDir>/src/**/*.ts',
|
||||||
|
|
@ -11,6 +13,7 @@ const config: Config = {
|
||||||
coverageDirectory: '<rootDir>/coverage',
|
coverageDirectory: '<rootDir>/coverage',
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
'/src/controller/',
|
'/src/controller/',
|
||||||
|
'/src/utils/dispatcher.ts',
|
||||||
],
|
],
|
||||||
coverageProvider: 'babel',
|
coverageProvider: 'babel',
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
|
|
@ -22,10 +25,15 @@ const config: Config = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resetModules: true,
|
resetModules: true,
|
||||||
restoreMocks: true,
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
|
testEnvironmentOptions: {
|
||||||
|
globalsCleanup: 'on',
|
||||||
|
},
|
||||||
transform: {
|
transform: {
|
||||||
'^.+.tsx?$': ['ts-jest', {}],
|
'^.+.tsx?$': ['ts-jest', {
|
||||||
|
tsconfig: 'tsconfig.dev.json',
|
||||||
|
}],
|
||||||
},
|
},
|
||||||
modulePathIgnorePatterns: [
|
modulePathIgnorePatterns: [
|
||||||
'<rootDir>/dist',
|
'<rootDir>/dist',
|
||||||
|
|
|
||||||
17
jest.setup.ts
Normal file
17
jest.setup.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
process.env['CACHE_DIR'] = '/dev/null';
|
||||||
|
process.env['HOST'] = 'example.test';
|
||||||
|
process.env['PROTOCOL'] = 'https';
|
||||||
|
|
||||||
|
if (!process.env['TMDB_ACCESS_TOKEN']) {
|
||||||
|
process.env['TMDB_ACCESS_TOKEN'] = 'some access token';
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mock('randomstring', () => ({
|
||||||
|
generate: jest.fn(() => 'mocked-random-string'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(Date, 'now').mockImplementation(() => 639837296000);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log = console.warn = console.error = console.info = console.debug = () => { /* disable in favor of logger */ };
|
||||||
7773
package-lock.json
generated
7773
package-lock.json
generated
File diff suppressed because it is too large
Load diff
55
package.json
55
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "webstreamr",
|
"name": "webstreamr",
|
||||||
"description": "Provides HTTP URLs from streaming websites.",
|
"description": "Provides HTTP URLs from streaming websites.",
|
||||||
"version": "0.13.0",
|
"version": "0.69.1",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyse": "tsc --noEmit --project ./tsconfig.dev.json",
|
"analyse": "tsc --noEmit --project ./tsconfig.dev.json",
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"lint:fix": "eslint --fix",
|
"lint:fix": "eslint --fix",
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
"test:update-fixtures": "TEST_UPDATE_FIXTURES=1 npm run test",
|
||||||
"ci": "npm run lint && npm run analyse && npm run test"
|
"ci": "npm run lint && npm run analyse && npm run test"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -27,35 +28,61 @@
|
||||||
"url": "https://github.com/webstreamr/webstreamr/issues"
|
"url": "https://github.com/webstreamr/webstreamr/issues"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^22.0.0"
|
"node": "^22.0.0 || ^24.0.0"
|
||||||
|
},
|
||||||
|
"devEngines": {
|
||||||
|
"runtime": {
|
||||||
|
"name": "node",
|
||||||
|
"onFail": "error"
|
||||||
|
},
|
||||||
|
"packageManager": {
|
||||||
|
"name": "npm",
|
||||||
|
"onFail": "error"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/ttlcache": "^1.4.1",
|
"@keyv/sqlite": "^4.0.5",
|
||||||
"axios-cache-interceptor": "^1.8.0",
|
"async-mutex": "^0.5.0",
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"axios-cache-interceptor": "^1.8.3",
|
||||||
|
"axios-mock-adapter": "^2.1.0",
|
||||||
"axios-retry": "^4.5.0",
|
"axios-retry": "^4.5.0",
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
|
"cacheable": "^2.0.0",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"country-emoji": "^1.5.6",
|
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"express-rate-limit": "^8.2.1",
|
||||||
|
"fast-levenshtein": "^3.0.0",
|
||||||
|
"glob": "^13.0.0",
|
||||||
|
"http-proxy-agent": "^7.0.2",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
|
"memoizee": "^0.4.17",
|
||||||
|
"minimatch": "^10.0.3",
|
||||||
|
"randomstring": "^1.3.1",
|
||||||
|
"rot13-cipher": "^1.0.0",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
|
"socks-proxy-agent": "^8.0.5",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"tough-cookie": "^6.0.0",
|
||||||
"unpacker": "^1.0.1",
|
"unpacker": "^1.0.1",
|
||||||
"user-agents": "^1.1.536",
|
|
||||||
"winston": "^3.17.0"
|
"winston": "^3.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.26.0",
|
"@eslint/js": "^9.26.0",
|
||||||
"@stylistic/eslint-plugin": "^4.2.0",
|
"@stylistic/eslint-plugin": "^5.0.0",
|
||||||
|
"@tsconfig/node24": "^24.0.0",
|
||||||
"@types/bytes": "^3.1.5",
|
"@types/bytes": "^3.1.5",
|
||||||
"@types/express": "^5.0.1",
|
"@types/express": "^5.0.1",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/fast-levenshtein": "^0.0.4",
|
||||||
"@types/make-fetch-happen": "^10.0.4",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^22.15.3",
|
"@types/memoizee": "^0.4.12",
|
||||||
|
"@types/node": "^24.0.0",
|
||||||
|
"@types/randomstring": "^1.3.0",
|
||||||
"@types/stremio-addon-sdk": "^1.6.11",
|
"@types/stremio-addon-sdk": "^1.6.11",
|
||||||
"@types/user-agents": "^1.0.4",
|
"babel-jest": "^30.0.0",
|
||||||
"axios-mock-adapter": "^2.1.0",
|
|
||||||
"babel-jest": "^29.7.0",
|
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.26.0",
|
||||||
"jest": "^29.7.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
"jest": "^30.0.0",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"ts-jest": "^29.3.2",
|
"ts-jest": "^29.3.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,35 @@
|
||||||
import { Request, Response, Router } from 'express';
|
import { Request, Response, Router } from 'express';
|
||||||
|
import { Extractor } from '../extractor';
|
||||||
import { landingTemplate } from '../landingTemplate';
|
import { landingTemplate } from '../landingTemplate';
|
||||||
import { buildManifest } from '../utils';
|
import { Source } from '../source';
|
||||||
import { Handler } from '../handler';
|
|
||||||
import { Config } from '../types';
|
import { Config } from '../types';
|
||||||
|
import { buildManifest, getDefaultConfig, isElfHostedInstance } from '../utils';
|
||||||
|
|
||||||
export class ConfigureController {
|
export class ConfigureController {
|
||||||
public readonly router: Router;
|
public readonly router: Router;
|
||||||
|
|
||||||
private readonly handlers: Handler[];
|
private readonly sources: Source[];
|
||||||
|
private readonly extractors: Extractor[];
|
||||||
|
|
||||||
constructor(handlers: Handler[]) {
|
public constructor(sources: Source[], extractors: Extractor[]) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
|
|
||||||
this.handlers = handlers;
|
this.sources = sources;
|
||||||
|
this.extractors = extractors;
|
||||||
|
|
||||||
this.router.get('/configure', this.getConfigure.bind(this));
|
this.router.get('/configure', this.getConfigure.bind(this));
|
||||||
this.router.get('/:config/configure', this.getConfigure.bind(this));
|
this.router.get('/:config/configure', this.getConfigure.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly getConfigure = (req: Request, res: Response) => {
|
private getConfigure(req: Request, res: Response) {
|
||||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
const config: Config = JSON.parse(req.params['config'] as string || JSON.stringify(getDefaultConfig()));
|
||||||
|
|
||||||
const manifest = buildManifest(this.handlers, config);
|
// Convenience preset for ElfHosted WebStreamr bundle including Media Flow Proxy
|
||||||
|
if (!req.params['config'] && isElfHostedInstance(req)) {
|
||||||
|
config.mediaFlowProxyUrl = `${req.protocol}://${req.host.replace('webstreamr', 'mediaflow-proxy')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = buildManifest(this.sources, this.extractors, config);
|
||||||
|
|
||||||
res.setHeader('content-type', 'text/html');
|
res.setHeader('content-type', 'text/html');
|
||||||
res.send(landingTemplate(manifest));
|
res.send(landingTemplate(manifest));
|
||||||
|
|
|
||||||
53
src/controller/ExtractController.ts
Normal file
53
src/controller/ExtractController.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Mutex } from 'async-mutex';
|
||||||
|
import { Request, Response, Router } from 'express';
|
||||||
|
import winston from 'winston';
|
||||||
|
import { ExtractorRegistry } from '../extractor';
|
||||||
|
import { contextFromRequestAndResponse, Fetcher } from '../utils';
|
||||||
|
|
||||||
|
export class ExtractController {
|
||||||
|
public readonly router: Router;
|
||||||
|
|
||||||
|
private readonly logger: winston.Logger;
|
||||||
|
private readonly extractorRegistry: ExtractorRegistry;
|
||||||
|
|
||||||
|
private readonly locks = new Map<string, Mutex>();
|
||||||
|
|
||||||
|
public constructor(logger: winston.Logger, _fetcher: Fetcher, extractorRegistry: ExtractorRegistry) {
|
||||||
|
this.router = Router();
|
||||||
|
|
||||||
|
this.logger = logger;
|
||||||
|
this.extractorRegistry = extractorRegistry;
|
||||||
|
|
||||||
|
this.router.get('/extract', this.extract.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async extract(req: Request, res: Response) {
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
res.status(405).send('Method Not Allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = contextFromRequestAndResponse(req, res);
|
||||||
|
|
||||||
|
const index = parseInt(req.query['index'] as string);
|
||||||
|
const url = new URL(req.query['url'] as string);
|
||||||
|
|
||||||
|
this.logger.info(`Lazy extract index ${index} of URL ${url} for ip ${ctx.ip}`, ctx);
|
||||||
|
|
||||||
|
let mutex = this.locks.get(url.href);
|
||||||
|
if (!mutex) {
|
||||||
|
mutex = new Mutex();
|
||||||
|
this.locks.set(url.href, mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutex.runExclusive(async () => {
|
||||||
|
const urlResults = await this.extractorRegistry.handle(ctx, url);
|
||||||
|
|
||||||
|
res.redirect(urlResults[index]?.url.href as string);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!mutex.isLocked()) {
|
||||||
|
this.locks.delete(url.href);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,29 @@
|
||||||
import { Request, Response, Router } from 'express';
|
import { Request, Response, Router } from 'express';
|
||||||
import { buildManifest } from '../utils';
|
import { Extractor } from '../extractor';
|
||||||
import { Handler } from '../handler';
|
import { Source } from '../source';
|
||||||
import { Config } from '../types';
|
import { Config } from '../types';
|
||||||
|
import { buildManifest, getDefaultConfig } from '../utils';
|
||||||
|
|
||||||
export class ManifestController {
|
export class ManifestController {
|
||||||
public readonly router: Router;
|
public readonly router: Router;
|
||||||
|
|
||||||
private readonly handlers: Handler[];
|
private readonly sources: Source[];
|
||||||
|
private readonly extractors: Extractor[];
|
||||||
|
|
||||||
constructor(handlers: Handler[]) {
|
public constructor(sources: Source[], extractors: Extractor[]) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
|
|
||||||
this.handlers = handlers;
|
this.sources = sources;
|
||||||
|
this.extractors = extractors;
|
||||||
|
|
||||||
this.router.get('/manifest.json', this.getManifest.bind(this));
|
this.router.get('/manifest.json', this.getManifest.bind(this));
|
||||||
this.router.get('/:config/manifest.json', this.getManifest.bind(this));
|
this.router.get('/:config/manifest.json', this.getManifest.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly getManifest = (req: Request, res: Response) => {
|
private getManifest(req: Request, res: Response) {
|
||||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
const config: Config = JSON.parse(req.params['config'] as string || JSON.stringify(getDefaultConfig()));
|
||||||
|
|
||||||
const manifest = buildManifest(this.handlers, config);
|
const manifest = buildManifest(this.sources, this.extractors, config);
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
res.send(manifest);
|
res.send(manifest);
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,70 @@
|
||||||
|
import { Mutex } from 'async-mutex';
|
||||||
import { Request, Response, Router } from 'express';
|
import { Request, Response, Router } from 'express';
|
||||||
|
import { ContentType } from 'stremio-addon-sdk';
|
||||||
import winston from 'winston';
|
import winston from 'winston';
|
||||||
import { Handler } from '../handler';
|
import { Source } from '../source';
|
||||||
import { Config } from '../types';
|
import { contextFromRequestAndResponse, envIsProd, Id, ImdbId, StreamResolver, TmdbId } from '../utils';
|
||||||
import { StreamResolver } from '../utils';
|
|
||||||
|
|
||||||
export class StreamController {
|
export class StreamController {
|
||||||
public readonly router: Router;
|
public readonly router: Router;
|
||||||
|
|
||||||
private readonly logger: winston.Logger;
|
private readonly logger: winston.Logger;
|
||||||
private readonly handlers: Handler[];
|
private readonly sources: Source[];
|
||||||
private readonly streamResolver: StreamResolver;
|
private readonly streamResolver: StreamResolver;
|
||||||
|
|
||||||
constructor(logger: winston.Logger, handlers: Handler[], streams: StreamResolver) {
|
private readonly locks = new Map<string, Mutex>();
|
||||||
|
|
||||||
|
public constructor(logger: winston.Logger, sources: Source[], streams: StreamResolver) {
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
|
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.handlers = handlers;
|
this.sources = sources;
|
||||||
this.streamResolver = streams;
|
this.streamResolver = streams;
|
||||||
|
|
||||||
|
this.router.get('/stream/:type/:id.json', this.getStream.bind(this));
|
||||||
this.router.get('/:config/stream/:type/:id.json', this.getStream.bind(this));
|
this.router.get('/:config/stream/:type/:id.json', this.getStream.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly getStream = async (req: Request, res: Response) => {
|
private async getStream(req: Request, res: Response) {
|
||||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
const type: ContentType = (req.params['type'] || '') as ContentType;
|
||||||
const type: string = req.params['type'] || '';
|
const rawId: string = req.params['id'] as string || '';
|
||||||
const id: string = req.params['id'] || '';
|
|
||||||
|
|
||||||
this.logger.info(`Search stream for type "${type}" and id "${id}"`);
|
let id: Id;
|
||||||
|
if (rawId.startsWith('tmdb:')) {
|
||||||
|
id = TmdbId.fromString(rawId.replace('tmdb:', ''));
|
||||||
|
} else if (rawId.startsWith('tt')) {
|
||||||
|
id = ImdbId.fromString(rawId);
|
||||||
|
} else {
|
||||||
|
res.status(400).send(`Unsupported ID: ${rawId}`);
|
||||||
|
|
||||||
const selectedHandlers = this.handlers.filter(handler => handler.id in config);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const streams = await this.streamResolver.resolve({ ip: (req.ip as string) }, selectedHandlers, type, id);
|
const ctx = contextFromRequestAndResponse(req, res);
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
this.logger.info(`Search stream for type "${type}" and id "${rawId}" for ip ${ctx.ip}`, ctx);
|
||||||
res.send(JSON.stringify({ streams }));
|
|
||||||
|
const sources = this.sources.filter(source => source.countryCodes.filter(countryCode => countryCode in ctx.config).length);
|
||||||
|
|
||||||
|
let mutex = this.locks.get(rawId);
|
||||||
|
if (!mutex) {
|
||||||
|
mutex = new Mutex();
|
||||||
|
this.locks.set(rawId, mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutex.runExclusive(async () => {
|
||||||
|
const { streams, ttl } = await this.streamResolver.resolve(ctx, sources, type, id);
|
||||||
|
|
||||||
|
if (ttl && envIsProd()) {
|
||||||
|
res.setHeader('Cache-Control', `public, max-age=${Math.floor(ttl / 1000)}, immutable`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.setHeader('Content-Type', 'application/json');
|
||||||
|
res.send(JSON.stringify({ streams }));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!mutex.isLocked()) {
|
||||||
|
this.locks.delete(rawId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './ExtractController';
|
||||||
export * from './ConfigureController';
|
export * from './ConfigureController';
|
||||||
export * from './ManifestController';
|
export * from './ManifestController';
|
||||||
export * from './StreamController';
|
export * from './StreamController';
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import { EmbedExtractor } from './types';
|
|
||||||
import { extractUrlFromPacked, Fetcher } from '../utils';
|
|
||||||
import { Context } from '../types';
|
|
||||||
import bytes from 'bytes';
|
|
||||||
|
|
||||||
export class Dropload implements EmbedExtractor {
|
|
||||||
readonly id = 'dropload';
|
|
||||||
|
|
||||||
readonly label = 'Dropload';
|
|
||||||
|
|
||||||
readonly ttl = 900000; // 15m
|
|
||||||
|
|
||||||
private readonly fetcher: Fetcher;
|
|
||||||
|
|
||||||
constructor(fetcher: Fetcher) {
|
|
||||||
this.fetcher = fetcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly supports = (url: URL): boolean => null !== url.host.match(/dropload/);
|
|
||||||
|
|
||||||
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
|
|
||||||
const normalizedUrl = url.toString().replace('/e/', '').replace('/embed-', '/');
|
|
||||||
const html = await this.fetcher.text(ctx, new URL(normalizedUrl));
|
|
||||||
|
|
||||||
const heightMatch = html.match(/\d{3,}x(\d{3,}),/) as string[];
|
|
||||||
|
|
||||||
const sizeMatch = html.match(/([\d.]+ ?[GM]B)/) as string[];
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
|
|
||||||
label: this.label,
|
|
||||||
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
|
|
||||||
height: parseInt(heightMatch[1] as string) as number,
|
|
||||||
bytes: bytes.parse(sizeMatch[1] as string) as number,
|
|
||||||
countryCode,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import winston from 'winston';
|
|
||||||
import { EmbedExtractorRegistry } from './EmbedExtractorRegistry';
|
|
||||||
import { Context } from '../types';
|
|
||||||
import { Fetcher } from '../utils';
|
|
||||||
jest.mock('../utils/Fetcher');
|
|
||||||
|
|
||||||
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
|
||||||
// @ts-expect-error No constructor args needed
|
|
||||||
const fetcher = new Fetcher();
|
|
||||||
const embedExtractors = new EmbedExtractorRegistry(logger, fetcher);
|
|
||||||
|
|
||||||
describe('EmbedExtractorRegistry', () => {
|
|
||||||
const ctx: Context = { ip: '127.0.0.1' };
|
|
||||||
|
|
||||||
test('returns undefined when no embed extractor can be found', async () => {
|
|
||||||
const urlResult = await embedExtractors.handle(ctx, new URL('https://some-url.test'), 'en');
|
|
||||||
|
|
||||||
expect(urlResult).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns from memory cache if possible', async () => {
|
|
||||||
const urlResult1 = await embedExtractors.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
|
|
||||||
const urlResult2 = await embedExtractors.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
|
|
||||||
|
|
||||||
expect(urlResult2).toBe(urlResult1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import TTLCache from '@isaacs/ttlcache';
|
|
||||||
import winston from 'winston';
|
|
||||||
import { EmbedExtractor } from './types';
|
|
||||||
import { Context, UrlResult } from '../types';
|
|
||||||
import { Fetcher } from '../utils';
|
|
||||||
import { Dropload } from './Dropload';
|
|
||||||
import { SuperVideo } from './SuperVideo';
|
|
||||||
|
|
||||||
export class EmbedExtractorRegistry {
|
|
||||||
private readonly logger: winston.Logger;
|
|
||||||
private readonly embedExtractors: EmbedExtractor[];
|
|
||||||
private readonly urlResultCache: TTLCache<string, UrlResult>;
|
|
||||||
|
|
||||||
constructor(logger: winston.Logger, fetcher: Fetcher) {
|
|
||||||
this.logger = logger;
|
|
||||||
this.embedExtractors = [
|
|
||||||
new Dropload(fetcher),
|
|
||||||
new SuperVideo(fetcher),
|
|
||||||
];
|
|
||||||
this.urlResultCache = new TTLCache({ max: 1024 });
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly handle = async (ctx: Context, url: URL, countryCode: string): Promise<UrlResult | undefined> => {
|
|
||||||
let urlResult = this.urlResultCache.get(url.href);
|
|
||||||
if (urlResult) {
|
|
||||||
return urlResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
const embedExtractor = this.embedExtractors.find(embedExtractor => embedExtractor.supports(url));
|
|
||||||
if (undefined === embedExtractor) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(`Extract stream URL using ${embedExtractor.id} extractor from ${url}`);
|
|
||||||
|
|
||||||
urlResult = await embedExtractor.extract(ctx, url, countryCode);
|
|
||||||
this.urlResultCache.set(url.href, urlResult, { ttl: embedExtractor.ttl });
|
|
||||||
|
|
||||||
return urlResult;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import bytes from 'bytes';
|
|
||||||
import { EmbedExtractor } from './types';
|
|
||||||
import { extractUrlFromPacked, Fetcher } from '../utils';
|
|
||||||
import { Context } from '../types';
|
|
||||||
|
|
||||||
export class SuperVideo implements EmbedExtractor {
|
|
||||||
readonly id = 'supervideo';
|
|
||||||
|
|
||||||
readonly label = 'SuperVideo';
|
|
||||||
|
|
||||||
readonly ttl = 900000; // 15m
|
|
||||||
|
|
||||||
private readonly fetcher: Fetcher;
|
|
||||||
|
|
||||||
constructor(fetcher: Fetcher) {
|
|
||||||
this.fetcher = fetcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly supports = (url: URL): boolean => null !== url.host.match(/supervideo/);
|
|
||||||
|
|
||||||
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
|
|
||||||
const normalizedUrl = url.toString().replace('/e/', '/').replace('/embed-', '/');
|
|
||||||
const html = await this.fetcher.text(ctx, new URL(normalizedUrl));
|
|
||||||
|
|
||||||
const heightAndSizeMatch = html.match(/\d{3,}x(\d{3,}), ([\d.]+ ?[GM]B)/) as string[];
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
|
|
||||||
label: this.label,
|
|
||||||
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
|
|
||||||
height: parseInt(heightAndSizeMatch[1] as string) as number,
|
|
||||||
bytes: bytes.parse(heightAndSizeMatch[2] as string) as number,
|
|
||||||
countryCode,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export * from './EmbedExtractorRegistry';
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import { Context, UrlResult } from '../types';
|
|
||||||
|
|
||||||
export interface EmbedExtractor {
|
|
||||||
readonly id: string;
|
|
||||||
|
|
||||||
readonly label: string;
|
|
||||||
|
|
||||||
readonly ttl: number;
|
|
||||||
|
|
||||||
readonly supports: (url: URL) => boolean;
|
|
||||||
|
|
||||||
readonly extract: (ctx: Context, url: URL, countryCode: string) => Promise<UrlResult>;
|
|
||||||
}
|
|
||||||
16
src/error/BlockedError.ts
Normal file
16
src/error/BlockedError.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { BlockedReason } from '../types';
|
||||||
|
|
||||||
|
export class BlockedError extends Error {
|
||||||
|
public readonly url: URL;
|
||||||
|
public readonly reason: BlockedReason;
|
||||||
|
public readonly headers: AxiosResponse['headers'];
|
||||||
|
|
||||||
|
public constructor(url: URL, reason: BlockedReason, headers: AxiosResponse['headers']) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
this.reason = reason;
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/error/HttpError.ts
Normal file
17
src/error/HttpError.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
export class HttpError extends Error {
|
||||||
|
public readonly url: URL;
|
||||||
|
public readonly status: number;
|
||||||
|
public readonly statusText: string;
|
||||||
|
public readonly headers: AxiosResponse['headers'];
|
||||||
|
|
||||||
|
public constructor(url: URL, status: number, statusText: string, headers: AxiosResponse['headers']) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
this.status = status;
|
||||||
|
this.statusText = statusText;
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/error/NotFoundError.ts
Normal file
1
src/error/NotFoundError.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export class NotFoundError extends Error {}
|
||||||
9
src/error/QueueIsFullError.ts
Normal file
9
src/error/QueueIsFullError.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export class QueueIsFullError extends Error {
|
||||||
|
public readonly url: URL;
|
||||||
|
|
||||||
|
public constructor(url: URL) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/error/TimeoutError.ts
Normal file
9
src/error/TimeoutError.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export class TimeoutError extends Error {
|
||||||
|
public readonly url: URL;
|
||||||
|
|
||||||
|
public constructor(url: URL) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/error/TooManyRequestsError.ts
Normal file
11
src/error/TooManyRequestsError.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export class TooManyRequestsError extends Error {
|
||||||
|
public readonly url: URL;
|
||||||
|
public readonly retryAfter: number;
|
||||||
|
|
||||||
|
public constructor(url: URL, retryAfter: number) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
this.retryAfter = retryAfter;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/error/TooManyTimeoutsError.ts
Normal file
9
src/error/TooManyTimeoutsError.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export class TooManyTimeoutsError extends Error {
|
||||||
|
public readonly url: URL;
|
||||||
|
|
||||||
|
public constructor(url: URL) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/error/index.ts
Normal file
74
src/error/index.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { BlockedReason, Context } from '../types';
|
||||||
|
import { BlockedError } from './BlockedError';
|
||||||
|
import { HttpError } from './HttpError';
|
||||||
|
import { QueueIsFullError } from './QueueIsFullError';
|
||||||
|
import { TimeoutError } from './TimeoutError';
|
||||||
|
import { TooManyRequestsError } from './TooManyRequestsError';
|
||||||
|
import { TooManyTimeoutsError } from './TooManyTimeoutsError';
|
||||||
|
|
||||||
|
export * from './BlockedError';
|
||||||
|
export * from './HttpError';
|
||||||
|
export * from './NotFoundError';
|
||||||
|
export * from './QueueIsFullError';
|
||||||
|
export * from './TimeoutError';
|
||||||
|
export * from './TooManyRequestsError';
|
||||||
|
export * from './TooManyTimeoutsError';
|
||||||
|
|
||||||
|
export const logErrorAndReturnNiceString = (ctx: Context, logger: winston.Logger, source: string, error: unknown): string => {
|
||||||
|
if (error instanceof BlockedError) {
|
||||||
|
if (error.reason === BlockedReason.media_flow_proxy_auth) {
|
||||||
|
return '⚠️ MediaFlow Proxy authentication failed. Please set the correct password.';
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn(`${source}: Request to ${error.url} was blocked, reason: ${error.reason}, headers: ${JSON.stringify(error.headers)}.`, ctx);
|
||||||
|
|
||||||
|
return `⚠️ Request to ${error.url.host} was blocked. Reason: ${error.reason}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof TooManyRequestsError) {
|
||||||
|
logger.warn(`${source}: Request to ${error.url} was rate limited for ${error.retryAfter} seconds.`, ctx);
|
||||||
|
|
||||||
|
return `🚦 Request to ${error.url.host} was rate-limited. Please try again later or consider self-hosting.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof TooManyTimeoutsError) {
|
||||||
|
logger.warn(`${source}: Too many timeouts when requesting ${error.url}.`, ctx);
|
||||||
|
|
||||||
|
return `🚦 Too many recent timeouts when requesting ${error.url.host}. Please try again later.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof TimeoutError) {
|
||||||
|
logger.warn(`${source}: Request to ${error.url} timed out.`, ctx);
|
||||||
|
|
||||||
|
return `🐢 Request to ${error.url.host} timed out.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof DOMException && ['AbortError', 'TimeoutError'].includes(error.name)) {
|
||||||
|
// sometimes this gets through, no idea why..
|
||||||
|
logger.warn(`${source}: Request timed out.`, ctx);
|
||||||
|
|
||||||
|
return '🐢 Request timed out.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof QueueIsFullError) {
|
||||||
|
logger.warn(`${source}: Request queue for ${error.url.host} is full.`, ctx);
|
||||||
|
|
||||||
|
return `⏳ Request queue for ${error.url.host} is full. Please try again later or consider self-hosting.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof HttpError) {
|
||||||
|
logger.error(`${source}: Error when requesting url ${error.url}, HTTP status ${error.status} (${error.statusText}), headers: ${JSON.stringify(error.headers)}, stack: ${error.stack}.`, ctx);
|
||||||
|
|
||||||
|
if (error.status >= 500) {
|
||||||
|
return `❌ Remote server ${error.url.host} has issues. We can't fix this, please try later again.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `❌ Request to ${error.url.host} failed with status ${error.status} (${error.statusText}). Request-id: ${ctx.id}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cause = (error as Error & { cause?: unknown }).cause;
|
||||||
|
logger.error(`${source} error: ${error}, cause: ${cause}, stack: ${(error as Error).stack}`, ctx);
|
||||||
|
|
||||||
|
return `❌ Request failed. Request-id: ${ctx.id}.`;
|
||||||
|
};
|
||||||
24
src/extractor/DoodStream.test.ts
Normal file
24
src/extractor/DoodStream.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { DoodStream } from './DoodStream';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new DoodStream(new FetcherMock(`${__dirname}/__fixtures__/DoodStream`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||||
|
|
||||||
|
describe('DoodStream', () => {
|
||||||
|
test('dood.to', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('http://dood.to/e/sk1m9eumzyjj'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can guess height from title', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://do7go.com/e/dfx8me4un4ul'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('not found', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://myvidplay.com/e/cz7cus0bvlzr'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
55
src/extractor/DoodStream.ts
Normal file
55
src/extractor/DoodStream.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { buildMediaFlowProxyExtractorRedirectUrl, supportsMediaFlowProxy } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class DoodStream extends Extractor {
|
||||||
|
public readonly id = 'doodstream';
|
||||||
|
|
||||||
|
public readonly label = 'DoodStream';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 21600000; // 6h
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
/** @see https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/doodstream.py */
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/dood|do[0-9]go|doood|dooood|ds2play|ds2video|dsvplay|d0o0d|do0od|d0000d|d000d|myvidplay|vidply|all3do|doply|vide0|vvide0|d-s/) && supportsMediaFlowProxy(ctx);
|
||||||
|
};
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
const videoId = url.pathname.replace(/\/+$/, '').split('/').at(-1) as string;
|
||||||
|
|
||||||
|
return new URL(`http://dood.to/e/${videoId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
if (/Video not found/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('title').text().trim().replace(/ - DoodStream$/, '').trim();
|
||||||
|
|
||||||
|
const downloadHtml = await this.fetcher.text(ctx, new URL(url.href.replace('/e/', '/d/')));
|
||||||
|
const sizeMatch = downloadHtml.match(/([\d.]+ ?[GM]B)/);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: buildMediaFlowProxyExtractorRedirectUrl(ctx, 'Doodstream', url, headers),
|
||||||
|
format: Format.mp4,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title,
|
||||||
|
...(sizeMatch && { bytes: bytes.parse(sizeMatch[1] as string) as number }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
32
src/extractor/Dropload.test.ts
Normal file
32
src/extractor/Dropload.test.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { Dropload } from './Dropload';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Dropload(new FetcherMock(`${__dirname}/__fixtures__/Dropload`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('Dropload', () => {
|
||||||
|
test('dropload.io', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://dropload.io/embed-lyo2h1snpe5c.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dropload unknown height and size', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://dropload.io/gf2apij3ql37'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('file not found', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://dropload.io/asdfghijklmn.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('processing / internal problem', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://dropload.io/7xtxuac84xyh.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('download URL', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://dropload.io/d/xhcmgcc2txnv'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
58
src/extractor/Dropload.ts
Normal file
58
src/extractor/Dropload.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { extractUrlFromPacked, guessHeightFromPlaylist } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class Dropload extends Extractor {
|
||||||
|
public readonly id = 'dropload';
|
||||||
|
|
||||||
|
public readonly label = 'Dropload';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 10800000; // 3h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/dropload|dr0pstream/);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly normalize = (url: URL): URL => new URL(url.href.replace('/d/', '/').replace('/e/', '/').replace('/embed-', '/'));
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
if (html.includes('File Not Found') || html.includes('Pending in queue')) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlistUrl = extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]);
|
||||||
|
const playlistHeaders = { Referer: 'https://dr0pstream.com/' };
|
||||||
|
|
||||||
|
const heightMatch = html.match(/\d{3,}x(\d{3,}),/);
|
||||||
|
const height = heightMatch
|
||||||
|
? parseInt(heightMatch[1] as string)
|
||||||
|
: meta.height ?? await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, { headers: playlistHeaders });
|
||||||
|
|
||||||
|
const sizeMatch = html.match(/([\d.]+ ?[GM]B)/);
|
||||||
|
const size = sizeMatch ? bytes.parse(sizeMatch[1] as string) as number : undefined;
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('.videoplayer h1').text().trim();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: playlistUrl,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title,
|
||||||
|
...(size && { bytes: size }),
|
||||||
|
...(height && { height }),
|
||||||
|
},
|
||||||
|
requestHeaders: playlistHeaders,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
20
src/extractor/ExternalUrl.test.ts
Normal file
20
src/extractor/ExternalUrl.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExternalUrl } from './ExternalUrl';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new ExternalUrl(new FetcherMock(`${__dirname}/__fixtures__/ExternalUrl`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ includeExternalUrls: 'on' });
|
||||||
|
|
||||||
|
describe('ExternalUrl', () => {
|
||||||
|
test('404 - not found', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://streamtape.com/e/gjA1OQ4klyHxgJ'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('netu.fanstream.us', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://netu.fanstream.us/e/0DFgfkcXOsDP'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
27
src/extractor/ExternalUrl.ts
Normal file
27
src/extractor/ExternalUrl.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { showExternalUrls } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class ExternalUrl extends Extractor {
|
||||||
|
public readonly id = 'external';
|
||||||
|
|
||||||
|
public readonly label = 'External';
|
||||||
|
|
||||||
|
public override readonly ttl = 21600000; // 6h
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
return showExternalUrls(ctx.config) && null !== url.host.match(/.*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(_ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: url,
|
||||||
|
format: Format.unknown,
|
||||||
|
isExternal: true,
|
||||||
|
label: `${url.host}`,
|
||||||
|
meta,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
62
src/extractor/Extractor.ts
Normal file
62
src/extractor/Extractor.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta, UrlResult } from '../types';
|
||||||
|
import { Fetcher } from '../utils';
|
||||||
|
|
||||||
|
export abstract class Extractor {
|
||||||
|
public abstract readonly id: string;
|
||||||
|
|
||||||
|
public abstract readonly label: string;
|
||||||
|
|
||||||
|
public readonly ttl: number = 900000; // 15m
|
||||||
|
|
||||||
|
public readonly cacheVersion: number | undefined = undefined;
|
||||||
|
|
||||||
|
public readonly viaMediaFlowProxy: boolean = false;
|
||||||
|
|
||||||
|
protected readonly fetcher: Fetcher;
|
||||||
|
|
||||||
|
public constructor(fetcher: Fetcher) {
|
||||||
|
this.fetcher = fetcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract supports(ctx: Context, url: URL): boolean;
|
||||||
|
|
||||||
|
public normalize(url: URL): URL {
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected abstract extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]>;
|
||||||
|
|
||||||
|
public async extract(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
|
||||||
|
try {
|
||||||
|
return (await this.extractInternal(ctx, url, meta)).map(
|
||||||
|
urlResult => ({
|
||||||
|
...urlResult,
|
||||||
|
label: this.formatLabel(urlResult.label ?? this.label),
|
||||||
|
ttl: this.ttl,
|
||||||
|
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof NotFoundError) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
format: Format.unknown,
|
||||||
|
isExternal: true,
|
||||||
|
error,
|
||||||
|
label: this.formatLabel(this.label),
|
||||||
|
ttl: 0,
|
||||||
|
meta,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private formatLabel(label: string): string {
|
||||||
|
return this.viaMediaFlowProxy ? `${label} (MFP)` : label;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/extractor/ExtractorRegistry.test.ts
Normal file
63
src/extractor/ExtractorRegistry.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { createExtractors } from './index';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, createExtractors(new FetcherMock(`${__dirname}/__fixtures__/ExtractorRegistry`)));
|
||||||
|
|
||||||
|
describe('ExtractorRegistry', () => {
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
test('returns error result from extractor', async () => {
|
||||||
|
const urlResult = await extractorRegistry.handle(ctx, new URL('https://some-url.test'));
|
||||||
|
|
||||||
|
expect(urlResult).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns external URLs if enabled by config', async () => {
|
||||||
|
const urlResult = await extractorRegistry.handle({ ...ctx, config: { ...ctx.config, includeExternalUrls: 'on' } }, new URL('https://mixdrop.ag/e/3nzwveprim63or6'));
|
||||||
|
|
||||||
|
expect(urlResult).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not return external URLs by default', async () => {
|
||||||
|
const urlResult = await extractorRegistry.handle(ctx, new URL('https://mixdrop.ag/e/l7v73zqrfdj19z'));
|
||||||
|
|
||||||
|
expect(urlResult).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns from memory cache if possible', async () => {
|
||||||
|
const urlResults1 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'));
|
||||||
|
const urlResults2 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'));
|
||||||
|
|
||||||
|
expect(urlResults1).not.toStrictEqual([]);
|
||||||
|
expect(urlResults2).not.toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ignores not found errors but caches them', async () => {
|
||||||
|
const urlResults1 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/asdfghijklmn.html'));
|
||||||
|
const urlResults2 = await extractorRegistry.handle(ctx, new URL('https://dropload.io/asdfghijklmn.html'));
|
||||||
|
|
||||||
|
expect(urlResults1).toStrictEqual([]);
|
||||||
|
expect(urlResults2).toStrictEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns external url for error', async () => {
|
||||||
|
const urlResults = await extractorRegistry.handle(ctx, new URL('https://dropload.io/mocked-blocked.html'));
|
||||||
|
expect(urlResults).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('empty results are cached', async () => {
|
||||||
|
const urlResults = await extractorRegistry.handle(ctx, new URL('https://dropload.io/asdfghijklmn.html'), { title: 'title' });
|
||||||
|
expect(urlResults).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stats returns something', async () => {
|
||||||
|
const stats = extractorRegistry.stats();
|
||||||
|
|
||||||
|
expect(stats).toHaveProperty('urlResultCache');
|
||||||
|
expect(stats.urlResultCache).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
112
src/extractor/ExtractorRegistry.ts
Normal file
112
src/extractor/ExtractorRegistry.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { Cacheable, CacheableMemory, Keyv } from 'cacheable';
|
||||||
|
import winston from 'winston';
|
||||||
|
import { Context, Format, Meta, UrlResult } from '../types';
|
||||||
|
import { createKeyvSqlite, envGet, isExtractorDisabled } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class ExtractorRegistry {
|
||||||
|
private readonly logger: winston.Logger;
|
||||||
|
private readonly extractors: Extractor[];
|
||||||
|
|
||||||
|
private readonly urlResultCache: Cacheable;
|
||||||
|
private readonly lazyUrlResultCache: Cacheable;
|
||||||
|
|
||||||
|
public constructor(logger: winston.Logger, extractors: Extractor[]) {
|
||||||
|
this.logger = logger;
|
||||||
|
this.extractors = extractors;
|
||||||
|
|
||||||
|
this.urlResultCache = new Cacheable({
|
||||||
|
nonBlocking: true,
|
||||||
|
primary: new Keyv({ store: new CacheableMemory({ lruSize: 1024 }) }),
|
||||||
|
secondary: createKeyvSqlite('extractor-cache'),
|
||||||
|
stats: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.lazyUrlResultCache = new Cacheable({
|
||||||
|
nonBlocking: true,
|
||||||
|
primary: new Keyv({ store: new CacheableMemory({ lruSize: 1024 }) }),
|
||||||
|
secondary: createKeyvSqlite('extractor-lazy-cache'),
|
||||||
|
stats: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public stats() {
|
||||||
|
return {
|
||||||
|
urlResultCache: this.urlResultCache.stats,
|
||||||
|
lazyUrlResultCache: this.lazyUrlResultCache.stats,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
public async handle(ctx: Context, url: URL, meta?: Meta, allowLazy?: boolean): Promise<UrlResult[]> {
|
||||||
|
const extractor = this.extractors.find(extractor => !isExtractorDisabled(ctx.config, extractor) && extractor.supports(ctx, url));
|
||||||
|
if (!extractor) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedUrl = extractor.normalize(url);
|
||||||
|
const cacheKey = this.determineCacheKey(ctx, extractor, normalizedUrl);
|
||||||
|
|
||||||
|
const storedDataRaw = await this.urlResultCache.getRaw<UrlResult[]>(cacheKey);
|
||||||
|
const expires = storedDataRaw?.expires;
|
||||||
|
if (storedDataRaw && expires) {
|
||||||
|
const ttl = expires - Date.now();
|
||||||
|
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (ttl > 0) {
|
||||||
|
return (storedDataRaw.value as UrlResult[]).map(urlResult => ({ ...urlResult, ttl, url: new URL(urlResult.url) }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lazyUrlResults = await this.lazyUrlResultCache.get<UrlResult[]>(normalizedUrl.href) ?? [];
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (
|
||||||
|
lazyUrlResults.length && allowLazy && !extractor.viaMediaFlowProxy
|
||||||
|
&& lazyUrlResults.every(urlResult => urlResult.format !== Format.hls) // related to Android issues, e.g. https://github.com/Stremio/stremio-bugs/issues/1574 or https://github.com/Stremio/stremio-bugs/issues/1579
|
||||||
|
) {
|
||||||
|
// generate lazy extract urls
|
||||||
|
return lazyUrlResults.map((urlResult, index) => {
|
||||||
|
const extractUrl = new URL(`${envGet('PROTOCOL')}:${envGet('HOST')}/extract/`);
|
||||||
|
|
||||||
|
extractUrl.searchParams.set('index', `${index}`);
|
||||||
|
extractUrl.searchParams.set('url', url.href);
|
||||||
|
|
||||||
|
return { ...urlResult, url: extractUrl };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.info(`Extract ${url} using ${extractor.id} extractor`, ctx);
|
||||||
|
|
||||||
|
const mergedMeta: Meta = { ...meta, ...lazyUrlResults[0]?.meta };
|
||||||
|
const urlResults = await extractor.extract(ctx, normalizedUrl, { extractorId: extractor.id, ...mergedMeta });
|
||||||
|
|
||||||
|
if (!Object.keys(mergedMeta).length || urlResults.some(urlResult => urlResult.error)) {
|
||||||
|
await this.urlResultCache.delete(cacheKey);
|
||||||
|
await this.lazyUrlResultCache.delete(normalizedUrl.href);
|
||||||
|
|
||||||
|
return urlResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ttl = urlResults.length ? extractor.ttl : 43200000; // 12h
|
||||||
|
|
||||||
|
await this.urlResultCache.set<UrlResult[]>(cacheKey, urlResults, ttl);
|
||||||
|
|
||||||
|
if (extractor.id !== 'external') {
|
||||||
|
await this.lazyUrlResultCache.set<UrlResult[]>(normalizedUrl.href, urlResults, 2629800000); // 1 month
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlResults;
|
||||||
|
};
|
||||||
|
|
||||||
|
private determineCacheKey(ctx: Context, extractor: Extractor, url: URL): string {
|
||||||
|
let suffix = '';
|
||||||
|
if (extractor.viaMediaFlowProxy) {
|
||||||
|
suffix += `_${ctx.config.mediaFlowProxyUrl}`;
|
||||||
|
}
|
||||||
|
if (extractor.cacheVersion) {
|
||||||
|
suffix += `_${extractor.cacheVersion}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${extractor.id}_${url}${suffix}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/extractor/Fastream.test.ts
Normal file
20
src/extractor/Fastream.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { Fastream } from './Fastream';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Fastream(new FetcherMock(`${__dirname}/__fixtures__/Fastream`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow-proxy.test', mediaFlowProxyPassword: 'asdfg' });
|
||||||
|
|
||||||
|
describe('Fastream', () => {
|
||||||
|
test('fastream.to embed', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://fastream.to/embed-3aooif4ozt10.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('expired or deleted', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://fastream.to/embed-a9rp1d1pqom7.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
50
src/extractor/Fastream.ts
Normal file
50
src/extractor/Fastream.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { buildMediaFlowProxyExtractorStreamUrl, supportsMediaFlowProxy } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class Fastream extends Extractor {
|
||||||
|
public readonly id = 'fastream';
|
||||||
|
|
||||||
|
public readonly label = 'Fastream';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/fastream/) && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/e/', '/embed-').replace('/d/', '/embed-'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const downloadUrl = new URL(url.href.replace('/embed-', '/d/'));
|
||||||
|
const html = await this.fetcher.text(ctx, downloadUrl, { headers });
|
||||||
|
|
||||||
|
if (/No such file/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Fastream', url, headers);
|
||||||
|
|
||||||
|
const heightAndSizeMatch = html.match(/\d{3,}x(\d{3,}), ([\d.]+ ?[GM]B)/) as string[];
|
||||||
|
const titleMatch = html.match(/>Download (.*?)</) as string[];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: playlistUrl,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
bytes: bytes.parse(heightAndSizeMatch[2] as string) as number,
|
||||||
|
height: parseInt(heightAndSizeMatch[1] as string),
|
||||||
|
title: titleMatch[1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
28
src/extractor/FileLions.test.ts
Normal file
28
src/extractor/FileLions.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { FileLions } from './FileLions';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new FileLions(new FetcherMock(`${__dirname}/__fixtures__/FileLions`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||||
|
|
||||||
|
describe('FileLions', () => {
|
||||||
|
test('filelions f', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://filelions.to/f/tyn45apubte2'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mivalyo v referer lock', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://mivalyo.com/v/tah5znapz3e5'), { referer: 'https://kinoger.com' })).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('file not found', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://filelions.to/v/ylcp2cu5qanb'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleted by administration', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://callistanise.com/f/cy4t5nkerjrt'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
99
src/extractor/FileLions.ts
Normal file
99
src/extractor/FileLions.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { buildMediaFlowProxyExtractorStreamUrl, supportsMediaFlowProxy, unpackEval } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
/** @see https://github.com/Gujal00/ResolveURL/commits/master/script.module.resolveurl/lib/resolveurl/plugins/filelions.py */
|
||||||
|
export class FileLions extends Extractor {
|
||||||
|
public readonly id = 'filelions';
|
||||||
|
|
||||||
|
public readonly label = 'FileLions';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
const supportedDomain = null !== url.host.match(/.*lions?/)
|
||||||
|
|| [
|
||||||
|
'6sfkrspw4u.sbs',
|
||||||
|
'ajmidyadfihayh.sbs',
|
||||||
|
'alhayabambi.sbs',
|
||||||
|
'anime7u.com',
|
||||||
|
'azipcdn.com',
|
||||||
|
'bingezove.com',
|
||||||
|
'callistanise.com',
|
||||||
|
'coolciima.online',
|
||||||
|
'dhtpre.com',
|
||||||
|
'dingtezuni.com',
|
||||||
|
'dintezuvio.com',
|
||||||
|
'e4xb5c2xnz.sbs',
|
||||||
|
'egsyxutd.sbs',
|
||||||
|
'fdewsdc.sbs',
|
||||||
|
'gsfomqu.sbs',
|
||||||
|
'javplaya.com',
|
||||||
|
'katomen.online',
|
||||||
|
'lumiawatch.top',
|
||||||
|
'minochinos.com',
|
||||||
|
'mivalyo.com',
|
||||||
|
'moflix-stream.click',
|
||||||
|
'motvy55.store',
|
||||||
|
'movearnpre.com',
|
||||||
|
'peytonepre.com',
|
||||||
|
'ryderjet.com',
|
||||||
|
'smoothpre.com',
|
||||||
|
'taylorplayer.com',
|
||||||
|
'techradar.ink',
|
||||||
|
'videoland.sbs',
|
||||||
|
'vidhide.com',
|
||||||
|
'vidhide.fun',
|
||||||
|
'vidhidefast.com',
|
||||||
|
'vidhidehub.com',
|
||||||
|
'vidhideplus.com',
|
||||||
|
'vidhidepre.com',
|
||||||
|
'vidhidepro.com',
|
||||||
|
'vidhidevip.com',
|
||||||
|
].includes(url.host);
|
||||||
|
|
||||||
|
return supportedDomain && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/v/', '/f/').replace('/download/', '/f/').replace('/file/', '/f/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
if (html.includes('This video can be watched as embed only')) {
|
||||||
|
return await this.extractInternal(ctx, new URL(url.href.replace('/f/', '/v/')), meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/File Not Found|deleted by administration/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpacked = unpackEval(html);
|
||||||
|
const heightMatch = unpacked.match(/(\d{3,})p/) as string[];
|
||||||
|
|
||||||
|
const sizeMatch = html.match(/([\d.]+ ?[GM]B)/);
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('meta[name="description"]').attr('content') as string;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'FileLions', url, headers),
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
height: parseInt(heightMatch[1] as string),
|
||||||
|
...(sizeMatch && { bytes: bytes.parse(sizeMatch[1] as string) as number }),
|
||||||
|
...(title && { title }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
24
src/extractor/FileMoon.test.ts
Normal file
24
src/extractor/FileMoon.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { FileMoon } from './FileMoon';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new FileMoon(new FetcherMock(`${__dirname}/__fixtures__/FileMoon`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||||
|
|
||||||
|
describe('FileMoon', () => {
|
||||||
|
test('z1ekv717 d', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://z1ekv717.fun/d/wkhcbggdxf1d'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('page not found', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://filemoon.sx/e/n7i8zodwjqr9'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('missing height', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://filemoon.to/d/ugj7f1kq94p1'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
82
src/extractor/FileMoon.ts
Normal file
82
src/extractor/FileMoon.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import {
|
||||||
|
buildMediaFlowProxyExtractorStreamUrl,
|
||||||
|
supportsMediaFlowProxy,
|
||||||
|
unpackEval,
|
||||||
|
} from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
/** @see https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/filemoon.py */
|
||||||
|
export class FileMoon extends Extractor {
|
||||||
|
public readonly id = 'filemoon';
|
||||||
|
|
||||||
|
public readonly label = 'FileMoon';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
const supportedDomain = null !== url.host.match(/filemoon/)
|
||||||
|
|| [
|
||||||
|
'1azayf9w.xyz',
|
||||||
|
'222i8x.lol',
|
||||||
|
'81u6xl9d.xyz',
|
||||||
|
'8mhlloqo.fun',
|
||||||
|
'96ar.com',
|
||||||
|
'bf0skv.org',
|
||||||
|
'boosteradx.online',
|
||||||
|
'c1z39.com',
|
||||||
|
'cinegrab.com',
|
||||||
|
'f51rm.com',
|
||||||
|
'furher.in',
|
||||||
|
'kerapoxy.cc',
|
||||||
|
'l1afav.net',
|
||||||
|
'moonmov.pro',
|
||||||
|
'smdfs40r.skin',
|
||||||
|
'xcoic.com',
|
||||||
|
'z1ekv717.fun',
|
||||||
|
].includes(url.host);
|
||||||
|
|
||||||
|
return supportedDomain && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/e/', '/d/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta, originalUrl?: URL): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
if (/Page not found/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('h3').text().trim();
|
||||||
|
|
||||||
|
const iframeUrlMatches = Array.from(html.matchAll(/iframe.*?src=["'](.*?)["']/g));
|
||||||
|
if (iframeUrlMatches.length) {
|
||||||
|
// Use last match because there can be fake adblock catcher urls before
|
||||||
|
return await this.extractInternal(ctx, new URL((iframeUrlMatches[iframeUrlMatches.length - 1] as RegExpExecArray)[1] as string), { title, ...meta }, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'FileMoon', originalUrl as URL, headers);
|
||||||
|
|
||||||
|
const unpacked = unpackEval(html);
|
||||||
|
const heightMatch = unpacked.match(/(\d{3,})p/);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: playlistUrl,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
...(heightMatch && { height: parseInt(heightMatch[1] as string) }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
16
src/extractor/Fsst.test.ts
Normal file
16
src/extractor/Fsst.test.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { Fsst } from './Fsst';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Fsst(new FetcherMock(`${__dirname}/__fixtures__/Fsst`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('Fsst', () => {
|
||||||
|
test('Wake up Dead Man', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://fsst.online/embed/948429/'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
41
src/extractor/Fsst.ts
Normal file
41
src/extractor/Fsst.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class Fsst extends Extractor {
|
||||||
|
public readonly id = 'fsst';
|
||||||
|
|
||||||
|
public readonly label = 'Fsst';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 10800000; // 3h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/fsst/);
|
||||||
|
};
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers, noProxyHeaders: true });
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('title').text().trim();
|
||||||
|
|
||||||
|
const filesMatch = html.match(/file:"(.*)"/) as string[];
|
||||||
|
|
||||||
|
const lastFile = (filesMatch[1] as string).split(',').pop() as string;
|
||||||
|
|
||||||
|
const heightAndUrlMatch = lastFile.match(/\[?([\d]*)p?]?(.*)/) as string[];
|
||||||
|
const fileHref = heightAndUrlMatch[2] as string;
|
||||||
|
|
||||||
|
return [{
|
||||||
|
url: await this.fetcher.getFinalRedirectUrl(ctx, new URL(fileHref), { headers, noProxyHeaders: true }, 1),
|
||||||
|
format: Format.mp4,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
height: parseInt(heightAndUrlMatch[1] as string),
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
}
|
||||||
28
src/extractor/HubCloud.test.ts
Normal file
28
src/extractor/HubCloud.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { HubCloud } from './HubCloud';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new HubCloud(new FetcherMock(`${__dirname}/__fixtures__/HubCloud`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('HubCloud', () => {
|
||||||
|
test('handle dexter original sin 2024 s01e01', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://hubcloud.one/drive/idt1evqfuviqiei'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handle crayon shin-chan 1993', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://hubcloud.one/drive/bffzqlpqfllfcld'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handle dark 2017 s03e08', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://hubcloud.one/drive/nknlofk8snfnknh'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handle goat 2026', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://hubcloud.foo/drive/p94k4dccjwxjcx4'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
102
src/extractor/HubCloud.ts
Normal file
102
src/extractor/HubCloud.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { findCountryCodes, findHeight } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class HubCloud extends Extractor {
|
||||||
|
public readonly id = 'hubcloud';
|
||||||
|
|
||||||
|
public readonly label = 'HubCloud';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 43200000; // 12h
|
||||||
|
|
||||||
|
public override readonly cacheVersion = 1;
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/hubcloud/);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const redirectHtml = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
const redirectUrlMatch = redirectHtml.match(/var url ?= ?'(.*?)'/) as string[];
|
||||||
|
|
||||||
|
const linksHtml = await this.fetcher.text(ctx, new URL(redirectUrlMatch[1] as string), { headers: { Referer: url.href } });
|
||||||
|
const $ = cheerio.load(linksHtml);
|
||||||
|
|
||||||
|
const title = $('title').text().trim();
|
||||||
|
const countryCodes = [...new Set([...meta.countryCodes ?? [], ...findCountryCodes(title)])];
|
||||||
|
const height = meta.height ?? findHeight(title);
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
...$('a')
|
||||||
|
.filter((_i, el) => {
|
||||||
|
const text = $(el).text();
|
||||||
|
|
||||||
|
return text.includes('FSL') && !text.includes('FSLv2');
|
||||||
|
})
|
||||||
|
.map((_i, el) => {
|
||||||
|
const url = new URL($(el).attr('href') as string);
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
format: Format.unknown,
|
||||||
|
label: `${this.label} (FSL)`,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
bytes: bytes.parse($('#size').text()) as number,
|
||||||
|
extractorId: `${this.id}_fsl`,
|
||||||
|
countryCodes,
|
||||||
|
height,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}).toArray(),
|
||||||
|
...$('a')
|
||||||
|
.filter((_i, el) => {
|
||||||
|
const text = $(el).text();
|
||||||
|
|
||||||
|
return text.includes('FSLv2');
|
||||||
|
})
|
||||||
|
.map((_i, el) => {
|
||||||
|
const url = new URL($(el).attr('href') as string);
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
format: Format.unknown,
|
||||||
|
label: `${this.label} (FSLv2)`,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
bytes: bytes.parse($('#size').text()) as number,
|
||||||
|
extractorId: `${this.id}_fslv2`,
|
||||||
|
countryCodes,
|
||||||
|
height,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}).toArray(),
|
||||||
|
...$('a')
|
||||||
|
.filter((_i, el) => $(el).text().includes('PixelServer'))
|
||||||
|
.map((_i, el) => {
|
||||||
|
const userUrl = new URL(($(el).attr('href') as string).replace('/api/file/', '/u/'));
|
||||||
|
const url = new URL(userUrl.href.replace('/u/', '/api/file/'));
|
||||||
|
url.searchParams.set('download', '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
url,
|
||||||
|
format: Format.unknown,
|
||||||
|
label: `${this.label} (PixelServer)`,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
bytes: bytes.parse($('#size').text()) as number,
|
||||||
|
extractorId: `${this.id}_pixelserver`,
|
||||||
|
countryCodes,
|
||||||
|
height,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
requestHeaders: { Referer: userUrl.href },
|
||||||
|
};
|
||||||
|
}).toArray(),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
}
|
||||||
29
src/extractor/HubDrive.test.ts
Normal file
29
src/extractor/HubDrive.test.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { HubCloud } from './HubCloud';
|
||||||
|
import { HubDrive } from './HubDrive';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(
|
||||||
|
logger,
|
||||||
|
[
|
||||||
|
new HubDrive(
|
||||||
|
new FetcherMock(`${__dirname}/__fixtures__/HubDrive`),
|
||||||
|
new HubCloud(new FetcherMock(`${__dirname}/__fixtures__/HubDrive/HubCloud`)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('HubDrive', () => {
|
||||||
|
test('handle avatar', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://hubdrive.space/file/7283903021'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handle missing hubcloud', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://hubdrive.space/file/2243124026'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
40
src/extractor/HubDrive.ts
Normal file
40
src/extractor/HubDrive.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { Context, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { Fetcher } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
import { HubCloud } from './HubCloud';
|
||||||
|
|
||||||
|
export class HubDrive extends Extractor {
|
||||||
|
public readonly id = 'hubdrive';
|
||||||
|
|
||||||
|
public readonly label = 'HubDrive';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 43200000; // 12h
|
||||||
|
|
||||||
|
public override readonly cacheVersion = 1;
|
||||||
|
|
||||||
|
private readonly hubCloud: HubCloud;
|
||||||
|
|
||||||
|
public constructor(fetcher: Fetcher, hubCloud: HubCloud) {
|
||||||
|
super(fetcher);
|
||||||
|
|
||||||
|
this.hubCloud = hubCloud;
|
||||||
|
}
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/hubdrive/);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const hubCloudUrl = $('a:contains("HubCloud")')
|
||||||
|
.map((_i, el) => new URL($(el).attr('href') as string))
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
return hubCloudUrl ? this.hubCloud.extract(ctx, hubCloudUrl, meta) : [];
|
||||||
|
};
|
||||||
|
}
|
||||||
28
src/extractor/KinoGer.test.ts
Normal file
28
src/extractor/KinoGer.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { KinoGer } from './KinoGer';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new KinoGer(new FetcherMock(`${__dirname}/__fixtures__/KinoGer`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('KinoGer', () => {
|
||||||
|
test('Blood & Sinners', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://kinoger.re/#ge5fhb'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Dead City', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://kinoger.re/#x6tsx9'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('filma365.strp2p.site', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://filma365.strp2p.site/#vih6bx'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('kinoger.p2pplay.pro', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://kinoger.p2pplay.pro/#k53k9'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
78
src/extractor/KinoGer.ts
Normal file
78
src/extractor/KinoGer.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { guessHeightFromPlaylist } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
/** @see https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/kinoger.py */
|
||||||
|
export class KinoGer extends Extractor {
|
||||||
|
public readonly id = 'kinoger';
|
||||||
|
|
||||||
|
public readonly label = 'KinoGer';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 21600000; // 6h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return [
|
||||||
|
'asianembed.cam',
|
||||||
|
'disneycdn.net',
|
||||||
|
'dzo.vidplayer.live',
|
||||||
|
'filedecrypt.link',
|
||||||
|
'filma365.strp2p.site',
|
||||||
|
'flimmer.rpmvip.com',
|
||||||
|
'flixfilmesonline.strp2p.site',
|
||||||
|
'kinoger.p2pplay.pro',
|
||||||
|
'kinoger.re',
|
||||||
|
'moflix.rpmplay.xyz',
|
||||||
|
'moflix.upns.xyz',
|
||||||
|
'player.upn.one',
|
||||||
|
'securecdn.shop',
|
||||||
|
'shiid4u.upn.one',
|
||||||
|
'srbe84.vidplayer.live',
|
||||||
|
'strp2p.site',
|
||||||
|
't1.p2pplay.pro',
|
||||||
|
'tuktuk.rpmvid.com',
|
||||||
|
'ultrastream.online',
|
||||||
|
'videoland.cfd',
|
||||||
|
'videoshar.uns.bio',
|
||||||
|
'w1tv.xyz',
|
||||||
|
'wasuytm.store',
|
||||||
|
].includes(url.host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(`${url.origin}/api/v1/video?id=${url.hash.slice(1)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = {
|
||||||
|
'Origin': url.origin,
|
||||||
|
'Referer': url.origin + '/',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
|
||||||
|
};
|
||||||
|
|
||||||
|
const hexData = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
const encrypted = Buffer.from(hexData.slice(0, -1), 'hex');
|
||||||
|
const key = Buffer.from('6b69656d7469656e6d75613931316361', 'hex');
|
||||||
|
const iv = Buffer.from('313233343536373839306f6975797472', 'hex');
|
||||||
|
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
|
||||||
|
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]).toString();
|
||||||
|
|
||||||
|
const { source, title } = JSON.parse(decrypted) as { source: string; title: string };
|
||||||
|
|
||||||
|
const m3u8Url = new URL(source);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: m3u8Url,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
height: meta.height ?? await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers }),
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
requestHeaders: headers,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
24
src/extractor/LuluStream.test.ts
Normal file
24
src/extractor/LuluStream.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { LuluStream } from './LuluStream';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new LuluStream(new FetcherMock(`${__dirname}/__fixtures__/LuluStream`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||||
|
|
||||||
|
describe('LuluStream', () => {
|
||||||
|
test('streamhihi d', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://streamhihi.com/d/mk9m58lz8ts6'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('streamhihi', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://streamhihi.com/mk9m58lz8ts6'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no such file', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://lulustream.com/e/uthq0o0sljnx'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
67
src/extractor/LuluStream.ts
Normal file
67
src/extractor/LuluStream.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import {
|
||||||
|
buildMediaFlowProxyExtractorStreamUrl,
|
||||||
|
supportsMediaFlowProxy,
|
||||||
|
} from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
/** @see https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/lulustream.py */
|
||||||
|
export class LuluStream extends Extractor {
|
||||||
|
public readonly id = 'lulustream';
|
||||||
|
|
||||||
|
public readonly label = 'LuluStream';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
const supportedDomain = null !== url.host.match(/lulu/)
|
||||||
|
|| [
|
||||||
|
'732eg54de642sa.sbs',
|
||||||
|
'cdn1.site',
|
||||||
|
'd00ds.site',
|
||||||
|
'streamhihi.com',
|
||||||
|
].includes(url.host);
|
||||||
|
|
||||||
|
return supportedDomain && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
const videoId = url.pathname.replace(/\/+$/, '').split('/').at(-1) as string;
|
||||||
|
|
||||||
|
return new URL(`/e/${videoId}`, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const fileUrl = new URL(url.href.replace('/e/', '/d/'));
|
||||||
|
const html = await this.fetcher.text(ctx, fileUrl, { headers });
|
||||||
|
|
||||||
|
if (/No such file|File Not Found/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('h1').text().trim();
|
||||||
|
|
||||||
|
const heightAndSizeMatch = html.match(/\d{3,}x(\d{3,}), ([\d.]+ ?[GM]B)/) as string[];
|
||||||
|
|
||||||
|
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'LuluStream', url, headers);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: playlistUrl,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title,
|
||||||
|
bytes: bytes.parse(heightAndSizeMatch[2] as string) as number,
|
||||||
|
height: parseInt(heightAndSizeMatch[1] as string),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
20
src/extractor/Mixdrop.test.ts
Normal file
20
src/extractor/Mixdrop.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { Mixdrop } from './Mixdrop';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Mixdrop(new FetcherMock(`${__dirname}/__fixtures__/Mixdrop`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow-proxy.test', mediaFlowProxyPassword: 'asdfg' });
|
||||||
|
|
||||||
|
describe('Mixdrop', () => {
|
||||||
|
test('mixdrop.my /e/', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://mixdrop.my/e/knq0kj8waq44l8'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleted or expired file', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://mixdrop.ag/e/123456789'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
46
src/extractor/Mixdrop.ts
Normal file
46
src/extractor/Mixdrop.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { buildMediaFlowProxyExtractorRedirectUrl, supportsMediaFlowProxy } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class Mixdrop extends Extractor {
|
||||||
|
public readonly id = 'mixdrop';
|
||||||
|
|
||||||
|
public readonly label = 'Mixdrop';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/mixdrop|mixdrp|mixdroop|m1xdrop/) && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly normalize = (url: URL): URL => new URL(url.href.replace('/f/', '/e/'));
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const fileUrl = new URL(url.href.replace('/e/', '/f/'));
|
||||||
|
const html = await this.fetcher.text(ctx, fileUrl);
|
||||||
|
|
||||||
|
if (/can't find the (file|video)/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeMatch = html.match(/([\d.,]+ ?[GM]B)/) as string[];
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('.title b').text().trim();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: buildMediaFlowProxyExtractorRedirectUrl(ctx, 'Mixdrop', url),
|
||||||
|
format: Format.mp4,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
bytes: bytes.parse((sizeMatch[1] as string).replace(',', '')) as number,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
24
src/extractor/RgShows.test.ts
Normal file
24
src/extractor/RgShows.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { RgShows } from './RgShows';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new RgShows(new FetcherMock(`${__dirname}/__fixtures__/RgShows`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('RgShows', () => {
|
||||||
|
test('handle one battle after another', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://api.rgshows.ru/main/movie/1054867'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handle stranger things s05e01', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://api.rgshows.ru/main/tv/66732/5/1'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handle parasite 2019 (mp4)', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://api.rgshows.ru/main/movie/496243'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
49
src/extractor/RgShows.ts
Normal file
49
src/extractor/RgShows.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { BlockedError } from '../error';
|
||||||
|
import { BlockedReason, Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { guessHeightFromPlaylist } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
interface RgShowsApiData {
|
||||||
|
stream: {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RgShows extends Extractor {
|
||||||
|
public readonly id = 'rgshows';
|
||||||
|
|
||||||
|
public readonly label = 'RgShows';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 10800000; // 3h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/rgshows/);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { 'Referer': 'https://www.rgshows.ru/', 'Origin': 'https://www.rgshows.ru', 'User-Agent': 'Mozilla' };
|
||||||
|
|
||||||
|
const data = await this.fetcher.json(ctx, url, { headers }) as RgShowsApiData;
|
||||||
|
|
||||||
|
const streamUrl = new URL(data.stream.url);
|
||||||
|
/* istanbul ignore if */
|
||||||
|
if (streamUrl.host.includes('vidzee')) {
|
||||||
|
throw new BlockedError(url, BlockedReason.unknown, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMp4 = streamUrl.href.includes('mp4');
|
||||||
|
const isHls = streamUrl.href.includes('m3u8') || streamUrl.href.includes('txt');
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: streamUrl,
|
||||||
|
format: isMp4 ? Format.mp4 : (isHls ? Format.hls : /* istanbul ignore next */ Format.unknown),
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
...(isHls && { height: meta.height ?? await guessHeightFromPlaylist(ctx, this.fetcher, streamUrl, { headers }) }),
|
||||||
|
},
|
||||||
|
requestHeaders: headers,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
36
src/extractor/SaveFiles.test.ts
Normal file
36
src/extractor/SaveFiles.test.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { SaveFiles } from './SaveFiles';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new SaveFiles(new FetcherMock(`${__dirname}/__fixtures__/SaveFiles`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('SafeFiles', () => {
|
||||||
|
test('savefiles', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://savefiles.com/ip0k0dj2g0i3'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('savefiles /e/', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://savefiles.com/e/edptfmhyjr39'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('savefiles /d/', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://savefiles.com/d/s9g6zb5kjbqd'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('savefiles locked file', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://savefiles.com/omqq55i59nvv'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('savefiles deleted file', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://savefiles.com/bejqk3vlsv02'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('streamhls /e/', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://streamhls.to/e/ip0k0dj2g0i3'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
48
src/extractor/SaveFiles.ts
Normal file
48
src/extractor/SaveFiles.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class SaveFiles extends Extractor {
|
||||||
|
public readonly id = 'savefiles';
|
||||||
|
|
||||||
|
public readonly label = 'SaveFiles';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 21600000; // 6h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/savefiles|streamhls/);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/e/', '/').replace('/d/', '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
if (/file was locked|file was deleted/i.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileMatch = html.match(/file:"(.*?)"/) as string[];
|
||||||
|
const sizeMatch = html.match(/\[\d{3,}x(\d{3,})/) as string[];
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('.download-title').text().trim();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: new URL(fileMatch[1] as string),
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title,
|
||||||
|
height: parseInt(sizeMatch[1] as string),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
20
src/extractor/StreamEmbed.test.ts
Normal file
20
src/extractor/StreamEmbed.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { StreamEmbed } from './StreamEmbed';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new StreamEmbed(new FetcherMock(`${__dirname}/__fixtures__/StreamEmbed`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('StreamEmbed', () => {
|
||||||
|
test('watch.gxplayer.xyz', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://watch.gxplayer.xyz/watch?v=MEKI92PU'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('video is not ready', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://watch.gxplayer.xyz/watch?v=PBO90WAS'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
39
src/extractor/StreamEmbed.ts
Normal file
39
src/extractor/StreamEmbed.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class StreamEmbed extends Extractor {
|
||||||
|
public readonly id = 'streamembed';
|
||||||
|
|
||||||
|
public readonly label = 'StreamEmbed';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 259200000; // 3d
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/bullstream|mp4player|watch\.gxplayer/);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
if (/Video is not ready/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const video = JSON.parse((html.match(/video ?= ?(.*);/) as string[])[1] as string);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: new URL(`/m3u8/${video.uid}/${video.md5}/master.txt?s=1&id=${video.id}&cache=${video.status}`, url.origin),
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
height: parseInt(JSON.parse(video.quality)[0]),
|
||||||
|
title: decodeURIComponent(video.title),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
16
src/extractor/Streamtape.test.ts
Normal file
16
src/extractor/Streamtape.test.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { Streamtape } from './Streamtape';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Streamtape(new FetcherMock(`${__dirname}/__fixtures__/Streamtape`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||||
|
|
||||||
|
describe('Streamtape', () => {
|
||||||
|
test('streamtape.com /e', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://streamtape.com/e/DjmZBG0K9gHkJ7z/WALL-E.2008.1080p.BrRip.x264.YIFY.mp4'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
70
src/extractor/Streamtape.ts
Normal file
70
src/extractor/Streamtape.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { buildMediaFlowProxyExtractorRedirectUrl, supportsMediaFlowProxy } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class Streamtape extends Extractor {
|
||||||
|
public readonly id = 'streamtape';
|
||||||
|
|
||||||
|
public readonly label = 'Streamtape';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
const supportedDomain = null !== url.host.match(/streamtape/)
|
||||||
|
|| [
|
||||||
|
'strtape.cloud',
|
||||||
|
'streamta.pe',
|
||||||
|
'strcloud.link',
|
||||||
|
'strcloud.club',
|
||||||
|
'strtpe.link',
|
||||||
|
'scloud.online',
|
||||||
|
'stape.fun',
|
||||||
|
'streamadblockplus.com',
|
||||||
|
'shavetape.cash',
|
||||||
|
'streamta.site',
|
||||||
|
'streamadblocker.xyz',
|
||||||
|
'tapewithadblock.org',
|
||||||
|
'adblocktape.wiki',
|
||||||
|
'antiadtape.com',
|
||||||
|
'tapeblocker.com',
|
||||||
|
'streamnoads.com',
|
||||||
|
'tapeadvertisement.com',
|
||||||
|
'tapeadsenjoyer.com',
|
||||||
|
'watchadsontape.com',
|
||||||
|
].includes(url.host);
|
||||||
|
|
||||||
|
return supportedDomain && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/e/', '/v/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
// Only needed to properly find non-existing files via 404 response
|
||||||
|
await this.fetcher.text(ctx, new URL(url.href.replace('/v/', '/e/')), { headers });
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
const sizeMatch = html.match(/([\d.]+ ?[GM]B)/) as string[];
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('meta[name="og:title"]').attr('content') as string;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: buildMediaFlowProxyExtractorRedirectUrl(ctx, 'Streamtape', url, headers),
|
||||||
|
format: Format.mp4,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title,
|
||||||
|
bytes: bytes.parse(sizeMatch[1] as string) as number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
32
src/extractor/SuperVideo.test.ts
Normal file
32
src/extractor/SuperVideo.test.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { SuperVideo } from './SuperVideo';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new SuperVideo(new FetcherMock(`${__dirname}/__fixtures__/SuperVideo`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('SuperVideo', () => {
|
||||||
|
test('supervideo.cc /e/', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://supervideo.cc/e/q7i0sw1oytw3'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supervideo.tv /embed-/', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://supervideo.tv/embed-1p0m1fi9mok8.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('embed only', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://supervideo.cc/e/bj6szat1pval'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleted or expired file', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://supervideo.cc/ndf5shmy9lpt'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('processing video', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://supervideo.cc/3h1qqoqtldo8'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
62
src/extractor/SuperVideo.ts
Normal file
62
src/extractor/SuperVideo.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { extractUrlFromPacked, guessHeightFromPlaylist } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class SuperVideo extends Extractor {
|
||||||
|
public readonly id = 'supervideo';
|
||||||
|
|
||||||
|
public readonly label = 'SuperVideo';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 10800000; // 3h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/supervideo/);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/e/', '/').replace('/k/', '/').replace('/embed-', '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
if (html.includes('This video can be watched as embed only')) {
|
||||||
|
return await this.extractInternal(ctx, new URL(`/e${url.pathname}`, url.origin), meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/'The file was deleted|The file expired|Video is processing/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const playlistUrl = extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]);
|
||||||
|
const playlistHeaders = { Referer: 'https://supervideo.cc/' };
|
||||||
|
|
||||||
|
const heightAndSizeMatch = html.match(/\d{3,}x(\d{3,}), ([\d.]+ ?[GM]B)/);
|
||||||
|
const size = heightAndSizeMatch ? bytes.parse(heightAndSizeMatch[2] as string) as number : undefined;
|
||||||
|
const height = heightAndSizeMatch
|
||||||
|
? parseInt(heightAndSizeMatch[1] as string)
|
||||||
|
: meta.height ?? await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, { headers: playlistHeaders });
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('.download__title').text().trim();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: playlistUrl,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title,
|
||||||
|
...(size && { bytes: size }),
|
||||||
|
...(height && { height }),
|
||||||
|
},
|
||||||
|
requestHeaders: playlistHeaders,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
20
src/extractor/Uqload.test.ts
Normal file
20
src/extractor/Uqload.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { Uqload } from './Uqload';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Uqload(new FetcherMock(`${__dirname}/__fixtures__/Uqload`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||||
|
|
||||||
|
describe('Uqload', () => {
|
||||||
|
test('uqload.net /embed-', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://uqload.net/embed-z0xbr87oz637.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('file not found', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://uqload.cx/2ua47fdfkjgd.html'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
48
src/extractor/Uqload.ts
Normal file
48
src/extractor/Uqload.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { buildMediaFlowProxyExtractorRedirectUrl, supportsMediaFlowProxy } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class Uqload extends Extractor {
|
||||||
|
public readonly id = 'uqload';
|
||||||
|
|
||||||
|
public readonly label = 'Uqload';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/uqload/) && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/embed-', '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const html = await this.fetcher.text(ctx, url);
|
||||||
|
|
||||||
|
if (/File Not Found/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const heightMatch = html.match(/\d{3,}x(\d{3,})/);
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('h1').text().trim();
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: buildMediaFlowProxyExtractorRedirectUrl(ctx, 'Uqload', url),
|
||||||
|
format: Format.mp4,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title,
|
||||||
|
...(heightMatch && {
|
||||||
|
height: parseInt(heightMatch[1] as string),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
20
src/extractor/VidSrc.test.ts
Normal file
20
src/extractor/VidSrc.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { VidSrc } from './VidSrc';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new VidSrc(new FetcherMock(`${__dirname}/__fixtures__/VidSrc`), ['vidsrc-embed.ru'])]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('VidSrc', () => {
|
||||||
|
test('Full Metal Jacket', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vidsrc-embed.ru/embed/movie/tt0093058'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Black Mirror', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vidsrc-embed.ru/embed/tv/tt2085059/4-2'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
86
src/extractor/VidSrc.ts
Normal file
86
src/extractor/VidSrc.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { BlockedError, TooManyRequestsError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta, NonEmptyArray } from '../types';
|
||||||
|
import { Fetcher, guessHeightFromPlaylist } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class VidSrc extends Extractor {
|
||||||
|
public readonly id = 'vidsrc';
|
||||||
|
|
||||||
|
public readonly label = 'VidSrc';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 10800000; // 3h
|
||||||
|
|
||||||
|
private readonly domains: NonEmptyArray<string>;
|
||||||
|
|
||||||
|
public constructor(fetcher: Fetcher, domains: NonEmptyArray<string>) {
|
||||||
|
super(fetcher);
|
||||||
|
|
||||||
|
this.domains = domains;
|
||||||
|
}
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/vidsrc|vsrc/);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
// While this is a crappy thing to do, they seem to be blocking overly strict IMO
|
||||||
|
const randomIp = `${Math.floor(Math.random() * 223) + 1}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`;
|
||||||
|
const newCtx = { ...ctx, ip: randomIp };
|
||||||
|
|
||||||
|
return this.extractUsingRandomDomain(newCtx, url, meta, [...this.domains]);
|
||||||
|
};
|
||||||
|
|
||||||
|
private async extractUsingRandomDomain(ctx: Context, url: URL, meta: Meta, domains: string[]): Promise<InternalUrlResult[]> {
|
||||||
|
const domainIndex = Math.floor(Math.random() * domains.length);
|
||||||
|
const [domain] = domains.splice(domainIndex, 1) as [string];
|
||||||
|
|
||||||
|
const newUrl = new URL(url);
|
||||||
|
newUrl.hostname = domain;
|
||||||
|
|
||||||
|
let html: string;
|
||||||
|
try {
|
||||||
|
html = await this.fetcher.text(ctx, newUrl, { queueLimit: 1 });
|
||||||
|
} catch (error) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (domains.length && (error instanceof TooManyRequestsError || error instanceof BlockedError)) {
|
||||||
|
return this.extractUsingRandomDomain(ctx, url, meta, domains);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = cheerio.load(html.replace('<!--', '').replace('-->', '')); // server HTML is commented-out
|
||||||
|
|
||||||
|
const iframeUrl = new URL(($('#player_iframe').attr('src') as string).replace(/^\/\//, 'https://'));
|
||||||
|
const title = $('title').text().trim();
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
$('.server')
|
||||||
|
.map((_i, el) => ({ serverName: $(el).text(), dataHash: $(el).data('hash') }))
|
||||||
|
.toArray()
|
||||||
|
.filter(({ serverName }) => serverName === 'CloudStream Pro')
|
||||||
|
.map(async ({ serverName, dataHash }) => {
|
||||||
|
const rcpUrl = new URL(`/rcp/${dataHash}`, iframeUrl.origin);
|
||||||
|
const iframeHtml = await this.fetcher.text(ctx, rcpUrl, { headers: { Referer: newUrl.origin } });
|
||||||
|
const srcMatch = iframeHtml.match(`src:\\s?'(.*)'`) as string[];
|
||||||
|
|
||||||
|
const playerHtml = await this.fetcher.text(ctx, new URL(srcMatch[1] as string, iframeUrl.origin), { headers: { Referer: rcpUrl.href } });
|
||||||
|
const fileMatch = playerHtml.match(`(https:\\/\\/.*?{v\\d}.*?) or`) as string[];
|
||||||
|
const m3u8Url = new URL((fileMatch[1] as string).replace(/{v\d}/, iframeUrl.host));
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: m3u8Url,
|
||||||
|
format: Format.hls,
|
||||||
|
label: serverName,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
height: await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers: { Referer: iframeUrl.href } }),
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/extractor/Vidora.test.ts
Normal file
20
src/extractor/Vidora.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { Vidora } from './Vidora';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Vidora(new FetcherMock(`${__dirname}/__fixtures__/Vidora`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('Vidora', () => {
|
||||||
|
test('vidora.stream', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vidora.stream/5iz6c1elaqgq'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('vidora.stream /embed/', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vidora.stream/embed/5iz6c1elaqgq'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
43
src/extractor/Vidora.ts
Normal file
43
src/extractor/Vidora.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { extractUrlFromPacked, guessHeightFromPlaylist } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class Vidora extends Extractor {
|
||||||
|
public readonly id = 'vidora';
|
||||||
|
|
||||||
|
public readonly label = 'Vidora';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 43200000; // 12h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/vidora/);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(url.href.replace('/embed/', '/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const html = await this.fetcher.text(ctx, url);
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('title').text().trim().replace(/^Watch /, '').trim();
|
||||||
|
|
||||||
|
const m3u8Url = extractUrlFromPacked(html, [/file: ?"(.*?)"/]);
|
||||||
|
const headers = { Origin: url.origin };
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: m3u8Url,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
height: meta.height ?? await guessHeightFromPlaylist(ctx, this.fetcher, m3u8Url, { headers }),
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
requestHeaders: headers,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
30
src/extractor/VixSrc.test.ts
Normal file
30
src/extractor/VixSrc.test.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { VixSrc } from './VixSrc';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new VixSrc(new FetcherMock(`${__dirname}/__fixtures__/VixSrc`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('VixSrc', () => {
|
||||||
|
test('Full Metal Jacket', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vixsrc.to/movie/600'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Black Mirror', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vixsrc.to/tv/42009/4/2'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Rental Family', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vixsrc.to/movie/1208348'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Black Mirror is excluded if no matching language was found', async () => {
|
||||||
|
const ctx = createTestContext({ de: 'on' });
|
||||||
|
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://vixsrc.to/tv/42009/4/3'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
65
src/extractor/VixSrc.ts
Normal file
65
src/extractor/VixSrc.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Context, CountryCode, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { CustomRequestConfig, guessHeightFromPlaylist, hasMultiEnabled, iso639FromCountryCode } from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class VixSrc extends Extractor {
|
||||||
|
public readonly id = 'vixsrc';
|
||||||
|
|
||||||
|
public readonly label = 'VixSrc';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 21600000; // 6h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/vixsrc/);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url);
|
||||||
|
|
||||||
|
const tokenMatch = html.match(/['"]token['"]: ?['"](.*?)['"]/) as string[];
|
||||||
|
const expiresMatch = html.match(/['"]expires['"]: ?['"](.*?)['"]/) as string[];
|
||||||
|
const urlMatch = html.match(/url: ?['"](.*?)['"]/) as string[];
|
||||||
|
|
||||||
|
const baseUrl = new URL(`${urlMatch[1]}`);
|
||||||
|
const playlistUrl = new URL(`${baseUrl.origin}${baseUrl.pathname}.m3u8?${baseUrl.searchParams}`);
|
||||||
|
playlistUrl.searchParams.append('token', tokenMatch[1] as string);
|
||||||
|
playlistUrl.searchParams.append('expires', expiresMatch[1] as string);
|
||||||
|
playlistUrl.searchParams.append('h', '1');
|
||||||
|
|
||||||
|
const countryCodes = meta.countryCodes ?? [CountryCode.multi, ...(await this.determineCountryCodesFromPlaylist(ctx, playlistUrl, { headers }))];
|
||||||
|
|
||||||
|
if (!hasMultiEnabled(ctx.config) && !countryCodes.some(countryCode => countryCode in ctx.config)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: playlistUrl,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
countryCodes,
|
||||||
|
height: meta.height ?? await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, { headers }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
private async determineCountryCodesFromPlaylist(ctx: Context, playlistUrl: URL, init?: CustomRequestConfig): Promise<CountryCode[]> {
|
||||||
|
const playlist = await this.fetcher.text(ctx, playlistUrl, init);
|
||||||
|
|
||||||
|
const countryCodes: CountryCode[] = [];
|
||||||
|
|
||||||
|
(Object.keys(CountryCode) as CountryCode[]).forEach((countryCode) => {
|
||||||
|
const iso639 = iso639FromCountryCode(countryCode);
|
||||||
|
|
||||||
|
if (!countryCodes.includes(countryCode) && (new RegExp(`#EXT-X-MEDIA:TYPE=AUDIO.*LANGUAGE="${iso639}"`)).test(playlist)) {
|
||||||
|
countryCodes.push(countryCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return countryCodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/extractor/Voe.test.ts
Normal file
28
src/extractor/Voe.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { Voe } from './Voe';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new Voe(new FetcherMock(`${__dirname}/__fixtures__/Voe`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext({ mediaFlowProxyUrl: 'https://mediaflow.test.org', mediaFlowProxyPassword: 'test' });
|
||||||
|
|
||||||
|
describe('Voe', () => {
|
||||||
|
test('jilliandescribecompany', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://jilliandescribecompany.com/e/ea21l02gcygw'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('premium only without resolution', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://jilliandescribecompany.com/qqfyi04w52mj'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('encoding error', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://voe.sx/e/c2yxvit4f6bz'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('embed only urls which otherwise lead to 404', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://mikaylaarealike.com/e/gqlhm9hbobwu'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
180
src/extractor/Voe.ts
Normal file
180
src/extractor/Voe.ts
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
import bytes from 'bytes';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { NotFoundError } from '../error';
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import {
|
||||||
|
buildMediaFlowProxyExtractorStreamUrl, guessHeightFromPlaylist,
|
||||||
|
supportsMediaFlowProxy,
|
||||||
|
} from '../utils';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
/** @see https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/voesx.py */
|
||||||
|
export class Voe extends Extractor {
|
||||||
|
public readonly id = 'voe';
|
||||||
|
|
||||||
|
public readonly label = 'VOE';
|
||||||
|
|
||||||
|
public override viaMediaFlowProxy = true;
|
||||||
|
|
||||||
|
public supports(ctx: Context, url: URL): boolean {
|
||||||
|
const supportedDomain = null !== url.host.match(/voe/)
|
||||||
|
|| [
|
||||||
|
'19turanosephantasia.com',
|
||||||
|
'20demidistance9elongations.com',
|
||||||
|
'30sensualizeexpression.com',
|
||||||
|
'321naturelikefurfuroid.com',
|
||||||
|
'35volitantplimsoles5.com',
|
||||||
|
'449unceremoniousnasoseptal.com',
|
||||||
|
'745mingiestblissfully.com',
|
||||||
|
'adrianmissionminute.com',
|
||||||
|
'alleneconomicmatter.com',
|
||||||
|
'antecoxalbobbing1010.com',
|
||||||
|
'apinchcaseation.com',
|
||||||
|
'audaciousdefaulthouse.com',
|
||||||
|
'availedsmallest.com',
|
||||||
|
'bigclatterhomesguideservice.com',
|
||||||
|
'boonlessbestselling244.com',
|
||||||
|
'bradleyviewdoctor.com',
|
||||||
|
'brittneystandardwestern.com',
|
||||||
|
'brucevotewithin.com',
|
||||||
|
'christopheruntilpoint.com',
|
||||||
|
'chromotypic.com',
|
||||||
|
'chuckle-tube.com',
|
||||||
|
'cindyeyefinal.com',
|
||||||
|
'counterclockwisejacky.com',
|
||||||
|
'crownmakermacaronicism.com',
|
||||||
|
'crystaltreatmenteast.com',
|
||||||
|
'cyamidpulverulence530.com',
|
||||||
|
'diananatureforeign.com',
|
||||||
|
'donaldlineelse.com',
|
||||||
|
'edwardarriveoften.com',
|
||||||
|
'erikcoldperson.com',
|
||||||
|
'figeterpiazine.com',
|
||||||
|
'fittingcentermondaysunday.com',
|
||||||
|
'fraudclatterflyingcar.com',
|
||||||
|
'gamoneinterrupted.com',
|
||||||
|
'generatesnitrosate.com',
|
||||||
|
'goofy-banana.com',
|
||||||
|
'graceaddresscommunity.com',
|
||||||
|
'greaseball6eventual20.com',
|
||||||
|
'guidon40hyporadius9.com',
|
||||||
|
'heatherdiscussionwhen.com',
|
||||||
|
'housecardsummerbutton.com',
|
||||||
|
'jamessoundcost.com',
|
||||||
|
'jamiesamewalk.com',
|
||||||
|
'jasminetesttry.com',
|
||||||
|
'jayservicestuff.com',
|
||||||
|
'jennifercertaindevelopment.com',
|
||||||
|
'jilliandescribecompany.com',
|
||||||
|
'johnalwayssame.com',
|
||||||
|
'jonathansociallike.com',
|
||||||
|
'josephseveralconcern.com',
|
||||||
|
'kathleenmemberhistory.com',
|
||||||
|
'kellywhatcould.com',
|
||||||
|
'kennethofficialitem.com',
|
||||||
|
'kinoger.ru',
|
||||||
|
'kristiesoundsimply.com',
|
||||||
|
'lancewhosedifficult.com',
|
||||||
|
'launchreliantcleaverriver.com',
|
||||||
|
'lauradaydo.com',
|
||||||
|
'lisatrialidea.com',
|
||||||
|
'loriwithinfamily.com',
|
||||||
|
'lukecomparetwo.com',
|
||||||
|
'lukesitturn.com',
|
||||||
|
'mariatheserepublican.com',
|
||||||
|
'matriculant401merited.com',
|
||||||
|
'maxfinishseveral.com',
|
||||||
|
'metagnathtuggers.com',
|
||||||
|
'michaelapplysome.com',
|
||||||
|
'mikaylaarealike.com',
|
||||||
|
'nathanfromsubject.com',
|
||||||
|
'nectareousoverelate.com',
|
||||||
|
'nonesnanking.com',
|
||||||
|
'paulkitchendark.com',
|
||||||
|
'realfinanceblogcenter.com',
|
||||||
|
'rebeccaneverbase.com',
|
||||||
|
'reputationsheriffkennethsand.com',
|
||||||
|
'richardsignfish.com',
|
||||||
|
'roberteachfinal.com',
|
||||||
|
'robertordercharacter.com',
|
||||||
|
'robertplacespace.com',
|
||||||
|
'sandratableother.com',
|
||||||
|
'sandrataxeight.com',
|
||||||
|
'scatch176duplicities.com',
|
||||||
|
'sethniceletter.com',
|
||||||
|
'shannonpersonalcost.com',
|
||||||
|
'simpulumlamerop.com',
|
||||||
|
'smoki.cc',
|
||||||
|
'stevenimaginelittle.com',
|
||||||
|
'strawberriesporail.com',
|
||||||
|
'telyn610zoanthropy.com',
|
||||||
|
'timberwoodanotia.com',
|
||||||
|
'toddpartneranimal.com',
|
||||||
|
'toxitabellaeatrebates306.com',
|
||||||
|
'uptodatefinishconferenceroom.com',
|
||||||
|
'v-o-e-unblock.com',
|
||||||
|
'valeronevijao.com',
|
||||||
|
'walterprettytheir.com',
|
||||||
|
'wolfdyslectic.com',
|
||||||
|
'yodelswartlike.com',
|
||||||
|
].includes(url.host);
|
||||||
|
|
||||||
|
return supportedDomain && supportsMediaFlowProxy(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override normalize(url: URL): URL {
|
||||||
|
return new URL(`/${url.pathname.replace(/\/+$/, '').split('/').at(-1)}`, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
let html: string;
|
||||||
|
try {
|
||||||
|
html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
} catch (error) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (error instanceof NotFoundError && !url.href.includes('/e/')) {
|
||||||
|
return await this.extractInternal(ctx, new URL(`/e${url.pathname}`, url.origin), meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirectMatch = html.match(/window\.location\.href\s*=\s*'([^']+)/);
|
||||||
|
if (redirectMatch && redirectMatch[1]) {
|
||||||
|
return await this.extractInternal(ctx, new URL(redirectMatch[1]), meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/An error occurred during encoding/.test(html)) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const title = $('meta[name="description"]').attr('content')?.trim().replace(/^Watch /, '').replace(/ at VOE$/, '').trim();
|
||||||
|
|
||||||
|
const sizeMatch = html.matchAll(/[\d.]+ ?[GM]B/g).toArray().at(-1);
|
||||||
|
const size = sizeMatch ? bytes.parse(sizeMatch[0] as string) as number : /* istanbul ignore next */null;
|
||||||
|
|
||||||
|
const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Voe', url, headers);
|
||||||
|
|
||||||
|
const heightMatch = html.match(/<b>(\d{3,})p<\/b>/);
|
||||||
|
const height = heightMatch
|
||||||
|
? parseInt(heightMatch[1] as string)
|
||||||
|
: meta.height ?? await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: playlistUrl,
|
||||||
|
format: Format.hls,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
height,
|
||||||
|
title,
|
||||||
|
...(size && size > 16777216 && { bytes: size }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
20
src/extractor/YouTube.test.ts
Normal file
20
src/extractor/YouTube.test.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import winston from 'winston';
|
||||||
|
import { createTestContext } from '../test';
|
||||||
|
import { FetcherMock } from '../utils';
|
||||||
|
import { ExtractorRegistry } from './ExtractorRegistry';
|
||||||
|
import { YouTube } from './YouTube';
|
||||||
|
|
||||||
|
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
|
||||||
|
const extractorRegistry = new ExtractorRegistry(logger, [new YouTube(new FetcherMock(`${__dirname}/__fixtures__/YouTube`))]);
|
||||||
|
|
||||||
|
const ctx = createTestContext();
|
||||||
|
|
||||||
|
describe('YouTube', () => {
|
||||||
|
test('Solaris', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://www.youtube.com/watch?v=Z8ZhQPaw4rE'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unsupported format', async () => {
|
||||||
|
expect(await extractorRegistry.handle(ctx, new URL('https://youtu.be/Z8ZhQPaw4rE?feature=shared'))).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
34
src/extractor/YouTube.ts
Normal file
34
src/extractor/YouTube.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Context, Format, InternalUrlResult, Meta } from '../types';
|
||||||
|
import { Extractor } from './Extractor';
|
||||||
|
|
||||||
|
export class YouTube extends Extractor {
|
||||||
|
public readonly id = 'youtube';
|
||||||
|
|
||||||
|
public readonly label = 'YouTube';
|
||||||
|
|
||||||
|
public override readonly ttl: number = 21600000; // 6h
|
||||||
|
|
||||||
|
public supports(_ctx: Context, url: URL): boolean {
|
||||||
|
return null !== url.host.match(/youtube/) && url.searchParams.has('v');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<InternalUrlResult[]> {
|
||||||
|
const headers = { Referer: meta.referer ?? url.href };
|
||||||
|
|
||||||
|
const html = await this.fetcher.text(ctx, url, { headers });
|
||||||
|
|
||||||
|
const titleMatch = html.match(/"title":{"runs":\[{"text":"(.*?)"/) as string[];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url,
|
||||||
|
format: Format.unknown,
|
||||||
|
ytId: url.searchParams.get('v') as string,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
title: titleMatch[1] as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
2
src/extractor/__fixtures__/DoodStream/http:dood.toddfx8me4un4ul
generated
Normal file
2
src/extractor/__fixtures__/DoodStream/http:dood.toddfx8me4un4ul
generated
Normal file
File diff suppressed because one or more lines are too long
2
src/extractor/__fixtures__/DoodStream/http:dood.todsk1m9eumzyjj
generated
Normal file
2
src/extractor/__fixtures__/DoodStream/http:dood.todsk1m9eumzyjj
generated
Normal file
File diff suppressed because one or more lines are too long
2
src/extractor/__fixtures__/DoodStream/http:dood.toecz7cus0bvlzr
generated
Normal file
2
src/extractor/__fixtures__/DoodStream/http:dood.toecz7cus0bvlzr
generated
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<!doctype html><html lang="en"><head> <meta charset="utf-8"> <title>Video not found | DoodStream</title> <link rel="preconnect" href="//i.doodcdn.io"><link rel="preconnect" href="//cdnjs.cloudflare.com"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <link rel="stylesheet" href="//i.doodcdn.io/theme_2/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="//i.doodcdn.io/theme_2/css/style.css" /></head><body> <style type="text/css"> .not_found img {height:250px !important;} @media (max-width: 768px){.not_found h1{font-size:1.8rem;}.not_found img {height:200px !important;margin: 0 auto 15px;}} @media (max-width: 480px){.not_found h1{font-size:1.4rem;}.not_found img {height:120px !important;margin: 0 auto 15px;}.not_found p{margin-top: -5px;}} </style> <div class="container pt-5 not_found"> <div class="row"> <div class="col-md-12 pt-2 text-center"> <img src="//i.doodcdn.io/img/no_video_3.svg"> <h1>Not Found</h1> <p>video you are looking for is not found.</p> </div> </div> </div><script data-cfasync="false" src="//d1f05vr3sjsuy7.cloudfront.net/?srvfd=908056"></script><script data-cfasync="false" async type="text/javascript" src="//faqirsgoliard.top/gHzOaAdOhbZ/71405"></script><script data-cfasync="false" async type="text/javascript" src="//faqirsgoliard.top/r67c0fc81985e5/70849"></script><script type="text/javascript" data-cfasync="false">/*<![CDATA[/* */(function(){var g=window,r="ca0967284e67bc53cb5b626985619403",w=[["siteId",317+185*82*507-3600486],["minBid",0],["popundersPerIP","0"],["delayBetween",300],["default","https://strettechoco.com/iYa7dlULceKw/27615"],["defaultPerDay",0],["topmostLayer","never"]],n=["d3d3LmJsb2NrYWRzbm90LmNvbS9iYWphLm1pbi5jc3M=","ZG5oZmk1bm4yZHQ2Ny5jbG91ZGZyb250Lm5ldC9RVHd0eC91YWxnZWJyYS5taW4uanM=","d3d3LmJibHZwbGd1b21hLmNvbS9mYWphLm1pbi5jc3M=","d3d3LndqZ3BzcGt4empiZGd1LmNvbS9NTmlIWS9rYWxnZWJyYS5taW4uanM="],f=-1,v,q,h=function(){clearTimeout(q);f++;if(n[f]&&!(1766620801000<(new Date).getTime()&&1<f)){v=g.document.createElement("script");v.type="text/javascript";v.async=!0;var o=g.document.getElementsByTagName("script")[0];v.src="https://"+atob(n[f]);v.crossOrigin="anonymous";v.onerror=h;v.onload=function(){clearTimeout(q);g[r.slice(0,16)+r.slice(0,16)]||h()};q=setTimeout(h,5E3);o.parentNode.insertBefore(v,o)}};if(!g[r]){try{Object.freeze(g[r]=w)}catch(e){}h()}})();/*]]>/* */</script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015" integrity="sha512-ZpsOmlRQV6y907TI0dKBHq9Md29nnaEIPlkf84rnaERnq6zvWvPUqr2ft8M1aS28oN72PdrCzSjY4U6VaAw1EQ==" data-cf-beacon='{"version":"2024.11.0","token":"1c76230f8ab248eab0b0f2bf7c205c51","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
|
||||||
|
</body></html>
|
||||||
2
src/extractor/__fixtures__/DoodStream/http:dood.toedfx8me4un4ul
generated
Normal file
2
src/extractor/__fixtures__/DoodStream/http:dood.toedfx8me4un4ul
generated
Normal file
File diff suppressed because one or more lines are too long
2
src/extractor/__fixtures__/DoodStream/http:dood.toesk1m9eumzyjj
generated
Normal file
2
src/extractor/__fixtures__/DoodStream/http:dood.toesk1m9eumzyjj
generated
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -19,10 +19,10 @@
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
||||||
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/app.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/app.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -91,12 +91,12 @@
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
|
|
||||||
<script src="https://dropload.io/js/jquery.cookie.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src="https://dropload.io/js/jquery.cookie.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">
|
<script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||||
$.cookie('file_id', '376684', { expires: 10 });
|
$.cookie('file_id', '1062326', { expires: 10 });
|
||||||
$.cookie('aff', '1805', { expires: 10 });
|
$.cookie('aff', '1805', { expires: 10 });
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -119,54 +119,36 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="pdiv" style="position: absolute;left:0;top:0;width: 100%;height:100%;">
|
|
||||||
|
|
||||||
<!-- Over player banner ADs code start here -->
|
|
||||||
<!-- Over player banner ADs code end here -->
|
|
||||||
|
|
||||||
|
|
||||||
<!--div id="adbd" class="overdiv">
|
|
||||||
<div>Disable ADBlock plugin and allow pop-ups in your browser to watch video</div>
|
|
||||||
</div-->
|
|
||||||
|
|
||||||
<!-- <div id="play_limit_box" class="d-flex flex-column justify-content-center p-3 text-center" style="position:absolute;left:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,.55);z-index:10;">
|
|
||||||
<div class="mb-2">
|
<img src="https://img.dropload.io/7xtxuac84xyh.jpg" alt="">
|
||||||
<a class="btn btn-gradient" href="/premium.html">Upgrade your account</a>
|
|
||||||
</div>
|
<div class="encoding">
|
||||||
to watch videos with no limits!
|
<div class="encoding-body">
|
||||||
</div> -->
|
<div class="row align-items-center">
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="percent">
|
||||||
<script type="067d87f6f1b0d87bbe23bf48-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
|
75%
|
||||||
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">jwplayer.key="";</script>
|
</div>
|
||||||
<style>
|
</div>
|
||||||
.jw-text-track-cue {
|
<div class="col-auto">
|
||||||
text-shadow: 1px 1px 2px transparent !important;
|
<h6>Pending in queue</h6>
|
||||||
line-height: 1.53em;
|
<small class="text-light">Video is processing now.<br>Conversion stage: <span id='enc_pp'>...</span></small>
|
||||||
padding-left: 0.3em !important;
|
</div>
|
||||||
padding-right: 0.3em !important;
|
</div>
|
||||||
padding-bottom: 0.2em !important;
|
</div>
|
||||||
border-radius: 6px;
|
</div>
|
||||||
}
|
|
||||||
</style><script src="/js/localstorage-slim.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
|
||||||
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button type="button" class="btn lightdown"></button>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="lightdown-backdrop"></div>
|
<div class="lightdown-backdrop"></div>
|
||||||
|
|
||||||
<h1>Black Mirror 2x3 </h1>
|
<h1>Bates Motel 2x3 </h1>
|
||||||
<div class="videoplayer-controlbar">
|
<div class="videoplayer-controlbar">
|
||||||
<div class="row g-1">
|
<div class="row g-1">
|
||||||
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
|
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
|
||||||
|
|
@ -178,7 +160,7 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
|
|
||||||
<div class="col-auto d-flex align-items-center">
|
<div class="col-auto d-flex align-items-center">
|
||||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||||
on Oct 12, 2023
|
on Aug 6, 2024
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto d-flex align-items-center">
|
<div class="col-auto d-flex align-items-center">
|
||||||
|
|
@ -187,7 +169,7 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto d-flex align-items-center">
|
<div class="col-auto d-flex align-items-center">
|
||||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||||
00:44:07
|
00:42:55
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -205,13 +187,13 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=t058ulwwkwn6&v=up')" title="Like" data-cf-modified-067d87f6f1b0d87bbe23bf48-="">
|
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=7xtxuac84xyh&v=up')" title="Like" data-cf-modified-1198ec03089106bda1a7dcfc-="">
|
||||||
<i class="icon icon-like icon-size-16 me-2"></i>
|
<i class="icon icon-like icon-size-16 me-2"></i>
|
||||||
<span id="likes_num">0</span>
|
<span id="likes_num">0</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=t058ulwwkwn6&v=down')" title="Don't Like" data-cf-modified-067d87f6f1b0d87bbe23bf48-="">
|
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=7xtxuac84xyh&v=down')" title="Don't Like" data-cf-modified-1198ec03089106bda1a7dcfc-="">
|
||||||
<i class="icon icon-dislike icon-size-16 me-2"></i>
|
<i class="icon icon-dislike icon-size-16 me-2"></i>
|
||||||
<span id="dislikes_num">0</span>
|
<span id="dislikes_num">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -224,66 +206,12 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
<div class="row justify-content-center justify-content-lg-between">
|
<div class="row justify-content-center justify-content-lg-between">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-auto order-lg-last mb-3">
|
|
||||||
<div class="dropdown">
|
|
||||||
|
|
||||||
<a class="btn btn-gradient videoplayer-download" data-bs-toggle="dropdown">
|
|
||||||
|
|
||||||
Download<i class="icon icon-download icon-size-20 ms-2"></i>
|
|
||||||
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="dropdown-menu w-100 py-0 shadow">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://dropload.io/d/t058ulwwkwn6_h" class="dropdown-item d-flex align-items-center">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<span class="flex-grow-1 pe-3 text-white">
|
|
||||||
<b>HD quality</b> <br> <span class="xsmall text-light">640x360, <span class="download-size">140.0 MB</span></span>
|
|
||||||
</span>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="18"><defs><linearGradient id="a" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#3DB6D4"/><stop offset="100%" stop-color="#6BCFA4"/></linearGradient></defs><path fill-rule="evenodd" fill="#3DB6D4" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/><path fill="url(#a)" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/></svg>
|
|
||||||
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">
|
|
||||||
$(function(){
|
|
||||||
var a = $('.dropdown-menu a:first-child .download-size');
|
|
||||||
$('#size').append(a);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-12 col-lg-7 mb-3">
|
<div class="col-12 col-lg-7 mb-3">
|
||||||
<div class="copybox">
|
<div class="copybox">
|
||||||
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/t058ulwwkwn6" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
|
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/7xtxuac84xyh" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
|
||||||
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-067d87f6f1b0d87bbe23bf48-="">Copy</button>
|
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-1198ec03089106bda1a7dcfc-="">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -305,9 +233,9 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body pt-0">
|
<div class="modal-body pt-0">
|
||||||
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-067d87f6f1b0d87bbe23bf48-="">
|
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-1198ec03089106bda1a7dcfc-="">
|
||||||
<input type="hidden" name="op" value="report_file">
|
<input type="hidden" name="op" value="report_file">
|
||||||
<input type="hidden" name="file_code" value="t058ulwwkwn6">
|
<input type="hidden" name="file_code" value="7xtxuac84xyh">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Reason:</label>
|
<label class="form-label">Reason:</label>
|
||||||
<select class="form-select" name="report_type">
|
<select class="form-select" name="report_type">
|
||||||
|
|
@ -370,8 +298,7 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1d("5v").5u({5t:{2i:"/5s/2m/2l-o.2m?v=3",q:"2l-o"},5r:[{2k:"13://5q.o.12/5p/5o/5n/5m/5l.5k?t=5j&s=1z&e=5i&f=22&i=0.0&5h=0&5g=21.20.5f.5e"}],5d:"1j%",5c:"1j%",5b:"5a",59:"2j.58",57:\'56\',55:"y",a:[{2k:"/26?23=54&k=2j&2i=13://2h.o.12/53.2g",52:"51"}],1s:{50:1,2f:\'#4z\',4y:\'#4x\',4w:"4v",4u:30,4t:\'1j\',},\'4s\':{"4r":"4q"},4p:"4o",4n:"13://o.12/",4m:{},4l:y,1r:[1,1.25,1.5,2],4k:\'13://2h.o.12/4j.2g\'}).g(\'1t\',8(){d n=2d.4i("11");n.4h="n";n.2e(\'4g\',\'4-1i 4-1i-4f 4-2c-2f 4-4e 4-1i-4d\');n.2e(\'4c\',\'6.1c(6.4b()+10)\');2d.4a(\'.4-2c-49\').48(n)});d 1g,1h;d 47=0,46=0;d 6=1d();d 2a=0,45=0,44=0,j=0;$.43({42:{\'41-40\':\'3z-3y\'}});6.g(\'3x\',8(x){9(5>0&&x.p>=5&&1h!=1){1h=1;$(\'11.3w\').3v(\'3u\')}9(x.p>=j+5||x.p<j){j=x.p;1f.3t(\'1e\',3s.3r(j),{3q:2b*2b*24*7})}});6.g(\'1c\',8(x){2a=x.p});6.g(\'3p\',8(x){29(x)});6.g(\'3o\',8(){$(\'11.28\').3n();1f.3m(\'1e\')});6.g(\'3l\',8(x){});8 29(x){$(\'11.28\').27();$(\'#3k\').27();9(1g)15;1g=1;z=0;9(3j.3i===3h){z=1}$.1x(\'/26?23=3g&3f=3e&3d=22-21-20-1z-3c&3b=&z=\'+z,8(1y){$(\'#3a\').39(1y)});d j=1f.1x(\'1e\');9(j>0){1d().1c(j)}}8 38(){d a=6.16(1w);1v.1u(a);9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1w){1v.1u(\'!!=\'+i);6.1k(i)}}}}6.g(\'1t\',8(){});6.g("h",8(r){d a=6.16();9(a.k<2)15;$(\'.4-c-37-36\').35(8(){$(\'#4-c-b-h\').19(\'4-c-b-u\');$(\'.4-b-h\').m(\'l-1a\',\'w\')});6.34("/33/32.31","2z 2y",8(){$(\'.4-1q\').2x(\'4-c-1p\');$(\'.4-c-1s, .4-c-1r\').m(\'l-1b\',\'w\');9($(\'.4-1q\').2w(\'4-c-1p\')){$(\'.4-b-h\').m(\'l-1b\',\'y\');$(\'.4-b-h\').m(\'l-1a\',\'y\');$(\'.4-c-b-2v\').19(\'4-c-b-u\');$(\'.4-c-b-h\').2u(\'4-c-b-u\')}2t{$(\'.4-b-h\').m(\'l-1b\',\'w\');$(\'.4-b-h\').m(\'l-1a\',\'w\');$(\'.4-c-b-h\').19(\'4-c-b-u\')}},"2s");6.g("2r",8(r){18.2q(\'17\',r.a[r.2p].q)});9(18.1o(\'17\')){2o("1n(18.1o(\'17\'));",2n)}});d 14;8 1n(1l){d a=6.16();9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1l){9(i==14){15}14=i;6.1k(i)}}}}',36,212,'||||jw||player||function|if|tracks|submenu|settings|var|||on|audioTracks||lastt|length|aria|attr|newButton|dropload|position|name|event|||active||false||true|adb||div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|seek|jwplayer|ttt058ulwwkwn6|ls|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|open|controls|playbackRates|captions|ready|log|console|track_name|get|data|1747213564|61|130|376684|op|||dl|hide|video_ad|doPlay|prevt|60|button|document|setAttribute|color|jpg|img|url|2647|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|quality|hasClass|toggleClass|Track|Audio||svg|dualy|images|addButton|mousedown|buttons|topbar|set_audio_track|html|fviews|embed|4020dbc00ffbffc6fb1a64b82baa1d45|hash|t058ulwwkwn6|file_code|view|undefined|cRAds|window|over_player_msg|pause|remove|show|complete|play|ttl|round|Math|set|slow|fadeIn|video_ad_fadein|time|cache|no|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|t058ulwwkwn6_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|436|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|t058ulwwkwn60000|get_slides|androidhls|metadata|preload|04|duration|uniform|stretching|height|width|204|236|ii|sp|14400|GkktV2WgIMrvrgNLyjn6Bxeo2fArEAz6y9nSi_6dVAs|m3u8|master|t058ulwwkwn6_h|00075|01|hls2|srv27|sources|assets2|skin|setup|vplayer'.split('|')))
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style media="screen">
|
<style media="screen">
|
||||||
.lightdown {
|
.lightdown {
|
||||||
|
|
@ -411,13 +338,13 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">
|
<script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||||
$('.lightdown').on('click', function(){
|
$('.lightdown').on('click', function(){
|
||||||
$('body').toggleClass('body-lightdown');
|
$('body').toggleClass('body-lightdown');
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Script type="067d87f6f1b0d87bbe23bf48-text/javascript">
|
<Script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||||
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
||||||
var tab_cookie='tab_down';
|
var tab_cookie='tab_down';
|
||||||
|
|
||||||
|
|
@ -439,18 +366,20 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
encStatus('1062326',1);
|
||||||
|
|
||||||
</Script>
|
</Script>
|
||||||
<script src="https://dropload.io/js/tabber.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src="https://dropload.io/js/tabber.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
<Script type="067d87f6f1b0d87bbe23bf48-text/javascript">
|
<Script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
</Script>
|
</Script>
|
||||||
<script data-cfasync="false" async type="text/javascript" src="//sx.impubicpuddly.com/rO7FQr7AANJdtg/111561"></script>
|
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||||
<script src='https://dropload.io/tag2.js' type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src='https://dropload.io/tag2.js' type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
<script src='https://dropload.io/tag1.js' type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
|
<script src='https://dropload.io/tag1.js' type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Javascript Alt Ads code start -->
|
<!-- Javascript Alt Ads code start -->
|
||||||
|
|
@ -495,7 +424,7 @@ $.cookie('aff', '1805', { expires: 10 });
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="067d87f6f1b0d87bbe23bf48-|49" defer></script></body>
|
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="1198ec03089106bda1a7dcfc-|49" defer></script></body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
160
src/extractor/__fixtures__/Dropload/https:dropload.ioasdfghijklmn.html
generated
Normal file
160
src/extractor/__fixtures__/Dropload/https:dropload.ioasdfghijklmn.html
generated
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Dropload - Revolution Video Hosting</title>
|
||||||
|
|
||||||
|
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#FFF">
|
||||||
|
<meta name="format-detection" conten="telephone=no">
|
||||||
|
<link rel="apple-touch-icon" href="https://dropload.io/assets2/images/favicon/apple-touch-icon.png" sizes="180x180">
|
||||||
|
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon-32x32.png" sizes="32x32">
|
||||||
|
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon-16x16.png" sizes="16x16">
|
||||||
|
<link rel="manifest" href="https://dropload.io/assets2/images/favicon/manifest.json">
|
||||||
|
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon.ico">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="ad75555b092545103e6c43f1-text/javascript"></script>
|
||||||
|
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="ad75555b092545103e6c43f1-text/javascript"></script>
|
||||||
|
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="ad75555b092545103e6c43f1-text/javascript"></script>
|
||||||
|
<script src="https://dropload.io/assets2/js/app.js" type="ad75555b092545103e6c43f1-text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-center flex-nowrap">
|
||||||
|
<div class="col-auto d-lg-none me-auto">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn icon-btn text-primary" data-bs-toggle="dropdown" data-bs-offset="0, 16">
|
||||||
|
<i class="icon icon-menu icon-size-18"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
|
||||||
|
<a href="/?op=registration" class="dropdown-item">Upload</a>
|
||||||
|
|
||||||
|
<a href="/make_money.html" class="dropdown-item">Earn money</a>
|
||||||
|
<a href="/api.html" class="dropdown-item">Service API</a>
|
||||||
|
<a href="/tos" class="dropdown-item">Terms and Conditions</a>
|
||||||
|
<a href="/contact" class="dropdown-item">Contacts</a>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto me-lg-5">
|
||||||
|
<a href="/">
|
||||||
|
<img src="https://dropload.io/assets2/images/logo.svg" class="logo" alt="Dropload.io - ">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto d-none d-lg-block">
|
||||||
|
<nav class="nav">
|
||||||
|
|
||||||
|
<a href="/?op=registration" class="nav-link">Upload</a>
|
||||||
|
|
||||||
|
<a href="/make_money.html" class="nav-link">Earn money</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-auto d-none d-lg-block ms-auto">
|
||||||
|
<a href="/login.html" class="btn login-btn me-2">
|
||||||
|
<i class="icon icon-account me-2"></i>Login</a>
|
||||||
|
<a href="/?op=registration" class="btn reg-btn">Create an Account</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto d-lg-none ms-auto">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn icon-btn text-primary" data-bs-toggle="dropdown" data-bs-offset="0, 16">
|
||||||
|
<i class="icon icon-account icon-size-18"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="/login.html">Login</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="/?op=registration">Create an Account</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||||
|
<script src='https://dropload.io/tag2.js' type="ad75555b092545103e6c43f1-text/javascript"></script>
|
||||||
|
<script src='https://dropload.io/tag1.js' type="ad75555b092545103e6c43f1-text/javascript"></script>
|
||||||
|
<section class="mb-5 mb-lg-7">
|
||||||
|
<div class="container">
|
||||||
|
<div class="message mb-6">
|
||||||
|
<div class="row align-items-center justify-content-center">
|
||||||
|
<div class="col-8 col-lg-6 pe-lg-5 text-lg-end mb-4 mb-lg-0">
|
||||||
|
<img src="https://dropload.io/assets2/images/nofile.svg" class="img-fluid" alt="">
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h4 class="text-uppercase">File Not Found</h4>
|
||||||
|
<p>The file you were looking for could not be found, sorry for any inconvenience.<br><br>
|
||||||
|
Possible causes of this error could be:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The file expired
|
||||||
|
<li>The file was deleted by its owner
|
||||||
|
<li>The file was deleted by administration because it didn't comply with our Terms of Use
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row text-center text-lg-start flex-lg-nowrap">
|
||||||
|
<div class="col">
|
||||||
|
<div class="mb-3">
|
||||||
|
<a href="/">
|
||||||
|
<img src="https://dropload.io/assets2/images/logo.svg" alt="">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted mb-4 small">© 2025 Dropload - Revolution Video Hosting. All rights reserved.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col d-none d-lg-block">
|
||||||
|
<div class="nav flex-column mb-3">
|
||||||
|
<a href="/faq" class="nav-link">FAQ</a>
|
||||||
|
<a href="/contact" class="nav-link">Contact Us</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col d-none d-lg-block">
|
||||||
|
<div class="nav flex-column mb-3">
|
||||||
|
<a href="/tos" class="nav-link">Terms of service</a>
|
||||||
|
<a href="#" class="nav-link">Privacy Policy</a>
|
||||||
|
<a href="#" class="nav-link">Copyright Policy</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col d-none d-lg-block">
|
||||||
|
<div class="nav flex-column mb-3">
|
||||||
|
<a href="/api.html" class="nav-link">API</a>
|
||||||
|
<a href="/make_money.html" class="nav-link">Make Money</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="ad75555b092545103e6c43f1-|49" defer></script></body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
460
src/extractor/__fixtures__/Dropload/https:dropload.iogf2apij3ql37
generated
Normal file
460
src/extractor/__fixtures__/Dropload/https:dropload.iogf2apij3ql37
generated
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -19,10 +19,10 @@
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
||||||
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/app.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/app.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -91,11 +91,11 @@
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
|
|
||||||
<script src="https://dropload.io/js/jquery.cookie.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src="https://dropload.io/js/jquery.cookie.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||||
$.cookie('file_id', '987607', { expires: 10 });
|
$.cookie('file_id', '987607', { expires: 10 });
|
||||||
$.cookie('aff', '4', { expires: 10 });
|
$.cookie('aff', '4', { expires: 10 });
|
||||||
|
|
||||||
|
|
@ -137,8 +137,21 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
|
|
||||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
|
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
|
||||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">jwplayer.key="";</script>
|
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript">jwplayer.key="";</script>
|
||||||
|
|
||||||
|
<div id="resume-modal" style="color:#fff; font-family:Roboto, Arial; display:none; padding:2rem; justify-content:center; align-items:center; position:absolute; top:0; left:0; right:0; bottom:0; background-color: rgba(0,0,0,.1); font-size: 12px; z-index:200;">
|
||||||
|
<div style="max-width:420px; background-color: rgba(28, 37, 52, .65); -webkit-backdrop-filter: blur(20px); backdrop-filter:blur(20px); text-align:center;padding:1.5rem; border-radius:8px;border: 1px solid rgba(255,255,255,.18);">
|
||||||
|
<h5 style="margin-bottom:10px;font-weight:bold; font-size:15px;" id="resume-title"></h5>
|
||||||
|
<p style="margin-bottom:10px;opacity:.65" id="resume-text"></p>
|
||||||
|
<div style="display:flex;gap:.4rem; align-items-center;justify-content:center">
|
||||||
|
<button onclick="if (!window.__cfRLUnblockHandlers) return false; resumeVideo()" id="resume-btn" style="border:0;cursor:pointer;border-radius:5px; padding:.5rem .75rem; color:#fff;background: linear-gradient(90deg, #3EB6D3 0%, #69CEA6 100%);color:#fff" data-cf-modified-ad8da47d2c4dcd2ad69dff49-=""></button>
|
||||||
|
<button onclick="if (!window.__cfRLUnblockHandlers) return false; cancelResume()" id="cancel-text" style="border:0;cursor:pointer;border-radius:5px;padding:.5rem .75rem; color:#fff;background:#476177;color:#fff" data-cf-modified-ad8da47d2c4dcd2ad69dff49-=""></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.jw-text-track-cue {
|
.jw-text-track-cue {
|
||||||
text-shadow: 1px 1px 2px transparent !important;
|
text-shadow: 1px 1px 2px transparent !important;
|
||||||
|
|
@ -148,7 +161,7 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
padding-bottom: 0.2em !important;
|
padding-bottom: 0.2em !important;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
</style><script src="/js/localstorage-slim.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
</style><script src="/js/localstorage-slim.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
|
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -205,13 +218,13 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=lyo2h1snpe5c&v=up')" title="Like" data-cf-modified-fb4dc13d1285d9e23d3f1de2-="">
|
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=lyo2h1snpe5c&v=up')" title="Like" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">
|
||||||
<i class="icon icon-like icon-size-16 me-2"></i>
|
<i class="icon icon-like icon-size-16 me-2"></i>
|
||||||
<span id="likes_num">0</span>
|
<span id="likes_num">0</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=lyo2h1snpe5c&v=down')" title="Don't Like" data-cf-modified-fb4dc13d1285d9e23d3f1de2-="">
|
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=lyo2h1snpe5c&v=down')" title="Don't Like" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">
|
||||||
<i class="icon icon-dislike icon-size-16 me-2"></i>
|
<i class="icon icon-dislike icon-size-16 me-2"></i>
|
||||||
<span id="dislikes_num">0</span>
|
<span id="dislikes_num">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -270,7 +283,7 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||||
$(function(){
|
$(function(){
|
||||||
var a = $('.dropdown-menu a:first-child .download-size');
|
var a = $('.dropdown-menu a:first-child .download-size');
|
||||||
$('#size').append(a);
|
$('#size').append(a);
|
||||||
|
|
@ -283,7 +296,7 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
<div class="col-12 col-lg-7 mb-3">
|
<div class="col-12 col-lg-7 mb-3">
|
||||||
<div class="copybox">
|
<div class="copybox">
|
||||||
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/lyo2h1snpe5c" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
|
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/lyo2h1snpe5c" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
|
||||||
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-fb4dc13d1285d9e23d3f1de2-="">Copy</button>
|
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -305,7 +318,7 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body pt-0">
|
<div class="modal-body pt-0">
|
||||||
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-fb4dc13d1285d9e23d3f1de2-="">
|
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">
|
||||||
<input type="hidden" name="op" value="report_file">
|
<input type="hidden" name="op" value="report_file">
|
||||||
<input type="hidden" name="file_code" value="lyo2h1snpe5c">
|
<input type="hidden" name="file_code" value="lyo2h1snpe5c">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|
@ -370,7 +383,7 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1d("5w").5v({5u:{2i:"/5t/2l/2k-o.2l?v=3",q:"2k-o"},5s:[{2j:"13://5r.o.12/5q/5p/5o/5n/5m.5l?t=5k&s=1z&e=5j&f=22&i=0.0&5i=0&5h=21.20.5g.5f"}],5e:"1j%",5d:"1j%",5c:"5b",5a:"59.56",58:\'57\',55:"y",a:[{2j:"/26?23=54&k=53&2i=13://2h.o.12/52.2g",51:"50"}],1s:{4z:1,2f:\'#4y\',4x:\'#4w\',4v:"4u",4t:30,4s:\'1j\',},\'4r\':{"4q":"4p"},4o:"4n",4m:"13://o.12/",4l:{},4k:y,1r:[1,1.25,1.5,2],4j:\'13://2h.o.12/4i.2g\'}).g(\'1t\',8(){d n=2d.4h("11");n.4g="n";n.2e(\'4f\',\'4-1i 4-1i-4e 4-2c-2f 4-4d 4-1i-4c\');n.2e(\'4b\',\'6.1c(6.4a()+10)\');2d.49(\'.4-2c-48\').47(n)});d 1g,1h;d 46=0,45=0;d 6=1d();d 2a=0,44=0,43=0,j=0;$.42({41:{\'40-3z\':\'3y-3x\'}});6.g(\'3w\',8(x){9(5>0&&x.p>=5&&1h!=1){1h=1;$(\'11.3v\').3u(\'3t\')}9(x.p>=j+5||x.p<j){j=x.p;1f.3s(\'1e\',3r.3q(j),{3p:2b*2b*24*7})}});6.g(\'1c\',8(x){2a=x.p});6.g(\'3o\',8(x){29(x)});6.g(\'3n\',8(){$(\'11.28\').3m();1f.3l(\'1e\')});6.g(\'3k\',8(x){});8 29(x){$(\'11.28\').27();$(\'#3j\').27();9(1g)15;1g=1;z=0;9(3i.3h===3g){z=1}$.1x(\'/26?23=3f&3e=3d&3c=22-21-20-1z-3b&3a=&z=\'+z,8(1y){$(\'#39\').38(1y)});d j=1f.1x(\'1e\');9(j>0){1d().1c(j)}}8 37(){d a=6.16(1w);1v.1u(a);9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1w){1v.1u(\'!!=\'+i);6.1k(i)}}}}6.g(\'1t\',8(){});6.g("h",8(r){d a=6.16();9(a.k<2)15;$(\'.4-c-36-35\').34(8(){$(\'#4-c-b-h\').19(\'4-c-b-u\');$(\'.4-b-h\').m(\'l-1a\',\'w\')});6.33("/32/31.2z","2y 2x",8(){$(\'.4-1q\').2w(\'4-c-1p\');$(\'.4-c-1s, .4-c-1r\').m(\'l-1b\',\'w\');9($(\'.4-1q\').2v(\'4-c-1p\')){$(\'.4-b-h\').m(\'l-1b\',\'y\');$(\'.4-b-h\').m(\'l-1a\',\'y\');$(\'.4-c-b-2u\').19(\'4-c-b-u\');$(\'.4-c-b-h\').2t(\'4-c-b-u\')}2s{$(\'.4-b-h\').m(\'l-1b\',\'w\');$(\'.4-b-h\').m(\'l-1a\',\'w\');$(\'.4-c-b-h\').19(\'4-c-b-u\')}},"2r");6.g("2q",8(r){18.2p(\'17\',r.a[r.2o].q)});9(18.1o(\'17\')){2n("1n(18.1o(\'17\'));",2m)}});d 14;8 1n(1l){d a=6.16();9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1l){9(i==14){15}14=i;6.1k(i)}}}}',36,213,'||||jw||player||function|if|tracks|submenu|settings|var|||on|audioTracks||lastt|length|aria|attr|newButton|dropload|position|name|event|||active||false||true|adb||div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|seek|jwplayer|ttlyo2h1snpe5c|ls|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|open|controls|playbackRates|captions|ready|log|console|track_name|get|data|1746903744|61|130|987607|op|||dl|hide|video_ad|doPlay|prevt|60|button|document|setAttribute|color|jpg|img|url|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|quality|hasClass|toggleClass|Track|Audio|svg||dualy|images|addButton|mousedown|buttons|topbar|set_audio_track|html|fviews|embed|4d087dfd416ea7ee8d382d34d700a2b9|hash|lyo2h1snpe5c|file_code|view|undefined|cRAds|window|over_player_msg|pause|remove|show|complete|play|ttl|round|Math|set|slow|fadeIn|video_ad_fadein|time|cache|no|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|lyo2h1snpe5c_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|1557|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|lyo2h1snpe5c0000|7259|get_slides|androidhls||metadata|preload|7258|duration|uniform|stretching|height|width|204|236|ii|sp|14400|qTZsRGIMnUjXsQ4o2LRLlsT4tGG1AdJrRBRDGttNLPM|m3u8|master|lyo2h1snpe5c_h|00197|01|hls2|srv27|sources|assets2|skin|setup|vplayer'.split('|')))
|
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1u("7y").7x({7w:{3i:"/7v/3l/3k-y.3l?v=3",11:"3k-y"},7u:[{3j:"1d://7t.y.1c/7s/2s/7r/7q/7p.7o?t=7n&s=34&e=7m&f=36&7l=1z&i=0.0&7k=0&7j=50.35.15.7i&7h=1z&7g=1z"}],7f:"1y%",7e:"1y%",7d:"7c",7b:"7a.56",79:\'78\',77:"16",b:[{3j:"/38?37=76&p=75&3i=1d://3h.y.1c/74.3g",73:"72"}],2a:{71:1,3f:\'#70\',6z:\'#6y\',6x:"6w",6v:30,6u:\'1y\',},\'6t\':{"6s":"6r"},6q:"6p",6o:"1d://y.1c/",6n:{},6m:16,29:[1,1.25,1.5,2],6l:\'1d://3h.y.1c/6k.3g\'}).j(\'1t\',6(){h w=g.6j("1b");w.6i="w";w.3e(\'6h\',\'4-1x 4-1x-6g 4-3d-3f 4-6f 4-1x-6e\');w.3e(\'6d\',\'8.1q(8.6c()+10)\');g.6b(\'.4-3d-6a\').69(w)});h 1v,1w;h 68=0,67=0;h 8=1u();h 3c=0,66=0,65=0,n=0;$.64({63:{\'62-61\':\'5z-5y\'}});8.j(\'u\',6(x){9(5>0&&x.z>=5&&1w!=1){1w=1;$(\'1b.5x\').5w(\'5v\')}9(x.z>=n+5||x.z<n){n=x.z;18.2d(\'17\',1r.5u(n),{5t:60*60*24*7})}});8.j(\'1q\',6(x){3c=x.z});8.j(\'2f\',6(x){3b(x)});8.j(\'5s\',6(){$(\'1b.3a\').5r();18.5q(\'17\')});8.j(\'5p\',6(x){});6 3b(x){$(\'1b.3a\').39();$(\'#5o\').39();9(1v)1f;1v=1;1a=0;9(2y.5n===5m){1a=1}$.2v(\'/38?37=5l&5k=2x&5j=36-50-35-34-5i&5h=&1a=\'+1a,6(33){$(\'#5g\').5f(33)})}6 5e(){h b=8.1g(32);31.2z(b);9(b.p>1){22(i=0;i<b.p;i++){9(b[i].11==32){31.2z(\'!!=\'+i);8.20(i)}}}}8.j(\'1t\',6(){});1u().2c("/2b/5d.5c","5b j 5a",6(){h 2w=2y.1m(\'/v/2x\',\'59\');2w.58()},"57");h n=18.2v(\'17\');8.j(\'1t\',6(){9(n>0){2k(n)}});6 2i(u){1s 2t=2u.55||2u.54;9(2t.53(\'52\')){g.k("l-2r").o=\'51 4z 4y?\';g.k("l-2q").o=\'4x! 4w 4v a 0:2s. 4u 4t 4s 4r 4q 4p?\';g.k("l-19").o=\'4o, 4n\';g.k("2m-19").o=\'2l, 4m\'}27{g.k("l-2r").o=\'4l 4k?\';g.k("l-2q").o=\'4j 4i! 4h 2o 2n 4g \'+u+\'. 4f 2p 4e 4d l 4c 4b 2p 2o 2n?\';g.k("l-19").o=\'4a, 49\';g.k("2m-19").o=\'2l, 48\'}}6 2k(u){1s 2h=1r.2j(u/60);1s 2g=1r.2j(u%60).47().46(2,\'0\');2i(2h+\':\'+2g);g.k("l-1p").1o.1n=\'45\'}6 44(){8.1q(n);8.2f();g.k("l-1p").1o.1n=\'2e\'}6 43(){g.k("l-1p").1o.1n=\'2e\';18.2d(\'17\',\'\')}8.j("m",6(12){h b=8.1g();9(b.p<2)1f;$(\'.4-d-42-41\').40(6(){$(\'#4-d-c-m\').1j(\'4-d-c-13\');$(\'.4-c-m\').r(\'q-1k\',\'14\')});8.2c("/2b/3z.3y","3x 3w",6(){$(\'.4-28\').3v(\'4-d-1m\');$(\'.4-d-2a, .4-d-29\').r(\'q-1l\',\'14\');9($(\'.4-28\').3u(\'4-d-1m\')){$(\'.4-c-m\').r(\'q-1l\',\'16\');$(\'.4-c-m\').r(\'q-1k\',\'16\');$(\'.4-d-c-3t\').1j(\'4-d-c-13\');$(\'.4-d-c-m\').3s(\'4-d-c-13\')}27{$(\'.4-c-m\').r(\'q-1l\',\'14\');$(\'.4-c-m\').r(\'q-1k\',\'14\');$(\'.4-d-c-m\').1j(\'4-d-c-13\')}},"3r");8.j("3q",6(12){1i.3p(\'1h\',12.b[12.3o].11)});9(1i.26(\'1h\')){3n("23(1i.26(\'1h\'));",3m)}});h 1e;6 23(21){h b=8.1g();9(b.p>1){22(i=0;i<b.p;i++){9(b[i].11==21){9(i==1e){1f}1e=i;8.20(i)}}}}',36,287,'||||jw||function||player|if||tracks|submenu|settings|||document|var||on|getElementById|resume|audioTracks|lastt|innerHTML|length|aria|attr|||time||newButton||dropload|position||name|event|active|false||true|ttlyo2h1snpe5c|ls|btn|adb|div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|open|display|style|modal|seek|Math|const|ready|jwplayer|vvplay|vvad|icon|100|srv27|setCurrentAudioTrack|audio_name|for|audio_set|||getItem|else|controls|playbackRates|captions|images|addButton|set|none|play|seconds|minutes|translateModal|floor|showResumeModal|No|cancel|off|left|you|text|title|01|userLang|navigator|get|win|lyo2h1snpe5c|window|log||console|track_name|data|1749845553|99|987607|op|dl|hide|video_ad|doPlay|prevt|button|setAttribute|color|jpg|img|url|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|addClass|quality|hasClass|toggleClass|Track|Audio|svg|dualy|mousedown|buttons|topbar|cancelResume|resumeVideo|flex|padStart|toString|thanks|please|Yes|where|watching|to|like|Would|at|You|back|Welcome|playing|Resume|grazie|certo|Si|lasciato|avevi|dove|da|riprendere|Vuoi|interrotto|Hai|Bentornato|visione|la||Riprendi|it|startsWith|userLanguage|language||download11|focus|_blank|site|Watch|png|download2|set_audio_track|html|fviews|embed|fc780ce094c01bfee287ea721706dbe2|hash|file_code|view|undefined|cRAds|over_player_msg|pause|remove|show|complete|ttl|round|slow|fadeIn|video_ad_fadein|cache|no||Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|lyo2h1snpe5c_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|1557|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|lyo2h1snpe5c0000|7259|get_slides|androidhls|metadata|preload|7258|duration|uniform|stretching|height|width|p2|p1|93|ii|sp|srv|14400|ESjD9Le53ttKN2JPyxrhLUqTebDZvx3vcQUlNgP6Vko|m3u8|master|lyo2h1snpe5c_h|00197|hls2|srv34|sources|assets2|skin|setup|vplayer'.split('|')))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style media="screen">
|
<style media="screen">
|
||||||
|
|
@ -411,13 +424,13 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||||
$('.lightdown').on('click', function(){
|
$('.lightdown').on('click', function(){
|
||||||
$('body').toggleClass('body-lightdown');
|
$('body').toggleClass('body-lightdown');
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
<Script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||||
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
||||||
var tab_cookie='tab_down';
|
var tab_cookie='tab_down';
|
||||||
|
|
||||||
|
|
@ -440,17 +453,17 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
|
|
||||||
|
|
||||||
</Script>
|
</Script>
|
||||||
<script src="https://dropload.io/js/tabber.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src="https://dropload.io/js/tabber.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
<Script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
<Script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
</Script>
|
</Script>
|
||||||
<script data-cfasync="false" async type="text/javascript" src="//sx.impubicpuddly.com/rO7FQr7AANJdtg/111561"></script>
|
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||||
<script src='https://dropload.io/tag2.js' type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src='https://dropload.io/tag2.js' type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
<script src='https://dropload.io/tag1.js' type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
<script src='https://dropload.io/tag1.js' type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Javascript Alt Ads code start -->
|
<!-- Javascript Alt Ads code start -->
|
||||||
|
|
@ -495,7 +508,7 @@ $.cookie('aff', '4', { expires: 10 });
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="fb4dc13d1285d9e23d3f1de2-|49" defer></script></body>
|
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="ad8da47d2c4dcd2ad69dff49-|49" defer></script></body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
@ -19,10 +19,10 @@
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
||||||
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
<script src="https://dropload.io/assets2/js/app.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src="https://dropload.io/assets2/js/app.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -91,13 +91,13 @@
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
|
|
||||||
<script src="https://dropload.io/js/jquery.cookie.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src="https://dropload.io/js/jquery.cookie.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="a9bf83b31be3a1c8064935cd-text/javascript">
|
<script type="45a2a97ee4145248610fba09-text/javascript">
|
||||||
$.cookie('file_id', '1448116', { expires: 10 });
|
$.cookie('file_id', '1526758', { expires: 10 });
|
||||||
$.cookie('aff', '5', { expires: 10 });
|
$.cookie('aff', '7208', { expires: 10 });
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -137,8 +137,21 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
|
|
||||||
<script type="a9bf83b31be3a1c8064935cd-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
|
<script type="45a2a97ee4145248610fba09-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
|
||||||
<script type="a9bf83b31be3a1c8064935cd-text/javascript">jwplayer.key="";</script>
|
<script type="45a2a97ee4145248610fba09-text/javascript">jwplayer.key="";</script>
|
||||||
|
|
||||||
|
<div id="resume-modal" style="color:#fff; font-family:Roboto, Arial; display:none; padding:2rem; justify-content:center; align-items:center; position:absolute; top:0; left:0; right:0; bottom:0; background-color: rgba(0,0,0,.1); font-size: 12px; z-index:200;">
|
||||||
|
<div style="max-width:420px; background-color: rgba(28, 37, 52, .65); -webkit-backdrop-filter: blur(20px); backdrop-filter:blur(20px); text-align:center;padding:1.5rem; border-radius:8px;border: 1px solid rgba(255,255,255,.18);">
|
||||||
|
<h5 style="margin-bottom:10px;font-weight:bold; font-size:15px;" id="resume-title"></h5>
|
||||||
|
<p style="margin-bottom:10px;opacity:.65" id="resume-text"></p>
|
||||||
|
<div style="display:flex;gap:.4rem; align-items-center;justify-content:center">
|
||||||
|
<button onclick="if (!window.__cfRLUnblockHandlers) return false; resumeVideo()" id="resume-btn" style="border:0;cursor:pointer;border-radius:5px; padding:.5rem .75rem; color:#fff;background: linear-gradient(90deg, #3EB6D3 0%, #69CEA6 100%);color:#fff" data-cf-modified-45a2a97ee4145248610fba09-=""></button>
|
||||||
|
<button onclick="if (!window.__cfRLUnblockHandlers) return false; cancelResume()" id="cancel-text" style="border:0;cursor:pointer;border-radius:5px;padding:.5rem .75rem; color:#fff;background:#476177;color:#fff" data-cf-modified-45a2a97ee4145248610fba09-=""></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.jw-text-track-cue {
|
.jw-text-track-cue {
|
||||||
text-shadow: 1px 1px 2px transparent !important;
|
text-shadow: 1px 1px 2px transparent !important;
|
||||||
|
|
@ -148,7 +161,7 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
padding-bottom: 0.2em !important;
|
padding-bottom: 0.2em !important;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
</style><script src="/js/localstorage-slim.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
</style><script src="/js/localstorage-slim.js" type="45a2a97ee4145248610fba09-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
|
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -166,7 +179,7 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
<div class="lightdown-backdrop"></div>
|
<div class="lightdown-backdrop"></div>
|
||||||
|
|
||||||
<h1>Black Mirror S2 E4 1080p </h1>
|
<h1>[~Squid~Game~EnG~]~S03E01~720p~WebRip ~❤️GLD❤️~ </h1>
|
||||||
<div class="videoplayer-controlbar">
|
<div class="videoplayer-controlbar">
|
||||||
<div class="row g-1">
|
<div class="row g-1">
|
||||||
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
|
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
|
||||||
|
|
@ -178,7 +191,7 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
|
|
||||||
<div class="col-auto d-flex align-items-center">
|
<div class="col-auto d-flex align-items-center">
|
||||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||||
on Apr 10, 2025
|
on Jun 27, 2025
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-auto d-flex align-items-center">
|
<div class="col-auto d-flex align-items-center">
|
||||||
|
|
@ -187,7 +200,7 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto d-flex align-items-center">
|
<div class="col-auto d-flex align-items-center">
|
||||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||||
01:14:05
|
00:57:13
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -205,13 +218,13 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=978xyyxi3ldq&v=up')" title="Like" data-cf-modified-a9bf83b31be3a1c8064935cd-="">
|
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=xhcmgcc2txnv&v=up')" title="Like" data-cf-modified-45a2a97ee4145248610fba09-="">
|
||||||
<i class="icon icon-like icon-size-16 me-2"></i>
|
<i class="icon icon-like icon-size-16 me-2"></i>
|
||||||
<span id="likes_num">0</span>
|
<span id="likes_num">0</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=978xyyxi3ldq&v=down')" title="Don't Like" data-cf-modified-a9bf83b31be3a1c8064935cd-="">
|
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=xhcmgcc2txnv&v=down')" title="Don't Like" data-cf-modified-45a2a97ee4145248610fba09-="">
|
||||||
<i class="icon icon-dislike icon-size-16 me-2"></i>
|
<i class="icon icon-dislike icon-size-16 me-2"></i>
|
||||||
<span id="dislikes_num">0</span>
|
<span id="dislikes_num">0</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -243,14 +256,14 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href="https://dropload.io/d/978xyyxi3ldq_h" class="dropdown-item d-flex align-items-center">
|
<a href="https://dropload.io/d/xhcmgcc2txnv_h" class="dropdown-item d-flex align-items-center">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<span class="flex-grow-1 pe-3 text-white">
|
<span class="flex-grow-1 pe-3 text-white">
|
||||||
<b>HD quality</b> <br> <span class="xsmall text-light">1920x1080, <span class="download-size">835.3 MB</span></span>
|
<b>HD quality</b> <br> <span class="xsmall text-light">1280x720, <span class="download-size">317.6 MB</span></span>
|
||||||
</span>
|
</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="18"><defs><linearGradient id="a" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#3DB6D4"/><stop offset="100%" stop-color="#6BCFA4"/></linearGradient></defs><path fill-rule="evenodd" fill="#3DB6D4" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/><path fill="url(#a)" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="18"><defs><linearGradient id="a" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#3DB6D4"/><stop offset="100%" stop-color="#6BCFA4"/></linearGradient></defs><path fill-rule="evenodd" fill="#3DB6D4" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/><path fill="url(#a)" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/></svg>
|
||||||
|
|
||||||
|
|
@ -270,7 +283,7 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="a9bf83b31be3a1c8064935cd-text/javascript">
|
<script type="45a2a97ee4145248610fba09-text/javascript">
|
||||||
$(function(){
|
$(function(){
|
||||||
var a = $('.dropdown-menu a:first-child .download-size');
|
var a = $('.dropdown-menu a:first-child .download-size');
|
||||||
$('#size').append(a);
|
$('#size').append(a);
|
||||||
|
|
@ -282,8 +295,8 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
|
|
||||||
<div class="col-12 col-lg-7 mb-3">
|
<div class="col-12 col-lg-7 mb-3">
|
||||||
<div class="copybox">
|
<div class="copybox">
|
||||||
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/978xyyxi3ldq" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
|
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/xhcmgcc2txnv" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
|
||||||
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-a9bf83b31be3a1c8064935cd-="">Copy</button>
|
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-45a2a97ee4145248610fba09-="">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -305,9 +318,9 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body pt-0">
|
<div class="modal-body pt-0">
|
||||||
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-a9bf83b31be3a1c8064935cd-="">
|
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-45a2a97ee4145248610fba09-="">
|
||||||
<input type="hidden" name="op" value="report_file">
|
<input type="hidden" name="op" value="report_file">
|
||||||
<input type="hidden" name="file_code" value="978xyyxi3ldq">
|
<input type="hidden" name="file_code" value="xhcmgcc2txnv">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Reason:</label>
|
<label class="form-label">Reason:</label>
|
||||||
<select class="form-select" name="report_type">
|
<select class="form-select" name="report_type">
|
||||||
|
|
@ -370,7 +383,7 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="a9bf83b31be3a1c8064935cd-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1d("5x").5w({5v:{2i:"/5u/2l/2k-o.2l?v=3",q:"2k-o"},5t:[{2j:"13://5s.o.12/5r/5q/5p/5o/5n.5m?t=5l-5k&s=1z&e=5j&f=22&i=0.0&5i=0&5h=21.20.5g.5f"}],5e:"1j%",5d:"1j%",5c:"5b",5a:"59.58",57:\'56\',55:"y",a:[{2j:"/26?23=54&k=53&2i=13://2h.o.12/52.2g",51:"50"}],1s:{4z:1,2f:\'#4y\',4x:\'#4w\',4v:"4u",4t:30,4s:\'1j\',},\'4r\':{"4q":"4p"},4o:"4n",4m:"13://o.12/",4l:{},4k:y,1r:[1,1.25,1.5,2],4j:\'13://2h.o.12/4i.2g\'}).g(\'1t\',8(){d n=2d.4h("11");n.4g="n";n.2e(\'4f\',\'4-1i 4-1i-4e 4-2c-2f 4-4d 4-1i-4c\');n.2e(\'4b\',\'6.1c(6.4a()+10)\');2d.49(\'.4-2c-48\').47(n)});d 1g,1h;d 46=0,45=0;d 6=1d();d 2a=0,44=0,43=0,j=0;$.42({41:{\'40-3z\':\'3y-3x\'}});6.g(\'3w\',8(x){9(5>0&&x.p>=5&&1h!=1){1h=1;$(\'11.3v\').3u(\'3t\')}9(x.p>=j+5||x.p<j){j=x.p;1f.3s(\'1e\',3r.3q(j),{3p:2b*2b*24*7})}});6.g(\'1c\',8(x){2a=x.p});6.g(\'3o\',8(x){29(x)});6.g(\'3n\',8(){$(\'11.28\').3m();1f.3l(\'1e\')});6.g(\'3k\',8(x){});8 29(x){$(\'11.28\').27();$(\'#3j\').27();9(1g)15;1g=1;z=0;9(3i.3h===3g){z=1}$.1x(\'/26?23=3f&3e=3d&3c=22-21-20-1z-3b&3a=&z=\'+z,8(1y){$(\'#39\').38(1y)});d j=1f.1x(\'1e\');9(j>0){1d().1c(j)}}8 37(){d a=6.16(1w);1v.1u(a);9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1w){1v.1u(\'!!=\'+i);6.1k(i)}}}}6.g(\'1t\',8(){});6.g("h",8(r){d a=6.16();9(a.k<2)15;$(\'.4-c-36-35\').34(8(){$(\'#4-c-b-h\').19(\'4-c-b-u\');$(\'.4-b-h\').m(\'l-1a\',\'w\')});6.33("/32/31.2z","2y 2x",8(){$(\'.4-1q\').2w(\'4-c-1p\');$(\'.4-c-1s, .4-c-1r\').m(\'l-1b\',\'w\');9($(\'.4-1q\').2v(\'4-c-1p\')){$(\'.4-b-h\').m(\'l-1b\',\'y\');$(\'.4-b-h\').m(\'l-1a\',\'y\');$(\'.4-c-b-2u\').19(\'4-c-b-u\');$(\'.4-c-b-h\').2t(\'4-c-b-u\')}2s{$(\'.4-b-h\').m(\'l-1b\',\'w\');$(\'.4-b-h\').m(\'l-1a\',\'w\');$(\'.4-c-b-h\').19(\'4-c-b-u\')}},"2r");6.g("2q",8(r){18.2p(\'17\',r.a[r.2o].q)});9(18.1o(\'17\')){2n("1n(18.1o(\'17\'));",2m)}});d 14;8 1n(1l){d a=6.16();9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1l){9(i==14){15}14=i;6.1k(i)}}}}',36,214,'||||jw||player||function|if|tracks|submenu|settings|var|||on|audioTracks||lastt|length|aria|attr|newButton|dropload|position|name|event|||active||false||true|adb||div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|seek|jwplayer|tt978xyyxi3ldq|ls|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|open|controls|playbackRates|captions|ready|log|console|track_name|get|data|1747217151|61|130|1448116|op|||dl|hide|video_ad|doPlay|prevt|60|button|document|setAttribute|color|jpg|img|url|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|quality|hasClass|toggleClass|Track|Audio|svg||dualy|images|addButton|mousedown|buttons|topbar|set_audio_track|html|fviews|embed|d4934d3a59c73c7efe2e9da5b2898e4a|hash|978xyyxi3ldq|file_code|view|undefined|cRAds|window|over_player_msg|pause|remove|show|complete|play|ttl|round|Math|set|slow|fadeIn|video_ad_fadein|time|cache|no|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|978xyyxi3ldq_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|1570|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|978xyyxi3ldq0000|4445|get_slides|androidhls|metadata|preload|63|4444|duration|uniform|stretching|height|width|204|236|ii|sp|14400|zgcmoXiSQ6rkMu7qHrSpN_pWOc95IPYwk|unq9qxjYC|m3u8|master|978xyyxi3ldq_h|00289|01|hls2|srv24|sources|assets2|skin|setup|vplayer'.split('|')))
|
<script type="45a2a97ee4145248610fba09-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1t("7t").7s({7r:{3g:"/7q/3k/3j-y.3k?v=3",11:"3j-y"},7p:[{3i:"1c://7o.y.1b/7n/2q/7m/7l/7k.7j?t=7i&s=32&e=7h&f=34&i=0.0&7g=0&7f=33.61.7e.7d"}],7c:"1x%",7b:"1x%",7a:"79",78:"3h.43",77:\'76\',75:"15",b:[{3i:"/36?35=74&p=3h&3g=1c://3f.y.1b/73.3e",72:"71"}],28:{70:1,3d:\'#6z\',6y:\'#6x\',6w:"6v",6u:30,6t:\'1x\',},\'6s\':{"6r":"6q"},6p:"6o",6n:"1c://y.1b/",6m:{},6l:15,27:[1,1.25,1.5,2],6k:\'1c://3f.y.1b/6j.3e\'}).j(\'1s\',6(){h w=g.6i("1a");w.6h="w";w.3c(\'6g\',\'4-1w 4-1w-6f 4-3b-3d 4-6e 4-1w-6d\');w.3c(\'6c\',\'8.1p(8.6b()+10)\');g.6a(\'.4-3b-69\').68(w)});h 1u,1v;h 67=0,66=0;h 8=1t();h 3a=0,65=0,64=0,n=0;$.63({62:{\'5z-5y\':\'5x-5w\'}});8.j(\'u\',6(x){9(5>0&&x.z>=5&&1v!=1){1v=1;$(\'1a.5v\').5u(\'5t\')}9(x.z>=n+5||x.z<n){n=x.z;17.2b(\'16\',1q.5s(n),{5r:60*60*24*7})}});8.j(\'1p\',6(x){3a=x.z});8.j(\'2d\',6(x){39(x)});8.j(\'5q\',6(){$(\'1a.38\').5p();17.5o(\'16\')});8.j(\'5n\',6(x){});6 39(x){$(\'1a.38\').37();$(\'#5m\').37();9(1u)1e;1u=1;19=0;9(2w.5l===5k){19=1}$.2t(\'/36?35=5j&5i=2v&5h=34-33-61-32-5g&5f=&19=\'+19,6(31){$(\'#5e\').5d(31)})}6 5c(){h b=8.1f(2z);2y.2x(b);9(b.p>1){20(i=0;i<b.p;i++){9(b[i].11==2z){2y.2x(\'!!=\'+i);8.1y(i)}}}}8.j(\'1s\',6(){});1t().2a("/29/5b.5a","59 j 58",6(){h 2u=2w.1l(\'/v/2v\',\'57\');2u.56()},"55");h n=17.2t(\'16\');8.j(\'1s\',6(){9(n>0){2i(n)}});6 2g(u){1r 2r=2s.54||2s.53;9(2r.52(\'51\')){g.k("l-2p").o=\'50 4z 4y?\';g.k("l-2o").o=\'4x! 4w 4v a 0:2q. 4u 4t 4s 4r 4q 4p?\';g.k("l-18").o=\'4o, 4n\';g.k("2k-18").o=\'2j, 4m\'}23{g.k("l-2p").o=\'4l 4k?\';g.k("l-2o").o=\'4j 4i! 4h 2m 2l 4g \'+u+\'. 4f 2n 4e 4d l 4c 4b 2n 2m 2l?\';g.k("l-18").o=\'4a, 49\';g.k("2k-18").o=\'2j, 48\'}}6 2i(u){1r 2f=1q.2h(u/60);1r 2e=1q.2h(u%60).47().46(2,\'0\');2g(2f+\':\'+2e);g.k("l-1o").1n.1m=\'45\'}6 44(){8.1p(n);8.2d();g.k("l-1o").1n.1m=\'2c\'}6 42(){g.k("l-1o").1n.1m=\'2c\';17.2b(\'16\',\'\')}8.j("m",6(12){h b=8.1f();9(b.p<2)1e;$(\'.4-d-41-40\').3z(6(){$(\'#4-d-c-m\').1i(\'4-d-c-13\');$(\'.4-c-m\').r(\'q-1j\',\'14\')});8.2a("/29/3y.3x","3w 3v",6(){$(\'.4-26\').3u(\'4-d-1l\');$(\'.4-d-28, .4-d-27\').r(\'q-1k\',\'14\');9($(\'.4-26\').3t(\'4-d-1l\')){$(\'.4-c-m\').r(\'q-1k\',\'15\');$(\'.4-c-m\').r(\'q-1j\',\'15\');$(\'.4-d-c-3s\').1i(\'4-d-c-13\');$(\'.4-d-c-m\').3r(\'4-d-c-13\')}23{$(\'.4-c-m\').r(\'q-1k\',\'14\');$(\'.4-c-m\').r(\'q-1j\',\'14\');$(\'.4-d-c-m\').1i(\'4-d-c-13\')}},"3q");8.j("3p",6(12){1h.3o(\'1g\',12.b[12.3n].11)});9(1h.22(\'1g\')){3m("21(1h.22(\'1g\'));",3l)}});h 1d;6 21(1z){h b=8.1f();9(b.p>1){20(i=0;i<b.p;i++){9(b[i].11==1z){9(i==1d){1e}1d=i;8.1y(i)}}}}',36,282,'||||jw||function||player|if||tracks|submenu|settings|||document|var||on|getElementById|resume|audioTracks|lastt|innerHTML|length|aria|attr|||time||newButton||dropload|position||name|event|active|false|true|ttxhcmgcc2txnv|ls|btn|adb|div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|open|display|style|modal|seek|Math|const|ready|jwplayer|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|else|||controls|playbackRates|captions|images|addButton|set|none|play|seconds|minutes|translateModal|floor|showResumeModal|No|cancel|off|left|you|text|title|01|userLang|navigator|get|win|xhcmgcc2txnv|window|log|console|track_name||data|1752613129|130|1526758|op|dl|hide|video_ad|doPlay|prevt|button|setAttribute|color|jpg|img|url|3433|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|addClass|quality|hasClass|toggleClass|Track|Audio|svg|dualy|mousedown|buttons|topbar|cancelResume||resumeVideo|flex|padStart|toString|thanks|please|Yes|where|watching|to|like|Would|at|You|back|Welcome|playing|Resume|grazie|certo|Si|lasciato|avevi|dove|da|riprendere|Vuoi|interrotto|Hai|Bentornato|visione|la|Riprendi|it|startsWith|userLanguage|language|download11|focus|_blank|site|Watch|png|download2|set_audio_track|html|fviews|embed|436087fe7fbec4909d57378fd1de7255|hash|file_code|view|undefined|cRAds|over_player_msg|pause|remove|show|complete|ttl|round|slow|fadeIn|video_ad_fadein|cache|no|Cache|Content|||headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|xhcmgcc2txnv_xt|image|playbackRateControls|cast|aboutlink|Dropload|abouttext|HD|768|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|xhcmgcc2txnv0000|get_slides|androidhls|metadata|preload|duration|uniform|stretching|height|width|204|236|ii|sp|14400|MBKiIoVlA42YcZEixT7ZR1xJA5qKhcKgBi84GPmyT7U|m3u8|master|xhcmgcc2txnv_h|00305|hls2|srv29|sources|assets2|skin|setup|vplayer'.split('|')))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style media="screen">
|
<style media="screen">
|
||||||
|
|
@ -411,13 +424,13 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="a9bf83b31be3a1c8064935cd-text/javascript">
|
<script type="45a2a97ee4145248610fba09-text/javascript">
|
||||||
$('.lightdown').on('click', function(){
|
$('.lightdown').on('click', function(){
|
||||||
$('body').toggleClass('body-lightdown');
|
$('body').toggleClass('body-lightdown');
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Script type="a9bf83b31be3a1c8064935cd-text/javascript">
|
<Script type="45a2a97ee4145248610fba09-text/javascript">
|
||||||
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
||||||
var tab_cookie='tab_down';
|
var tab_cookie='tab_down';
|
||||||
|
|
||||||
|
|
@ -440,17 +453,17 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
|
|
||||||
|
|
||||||
</Script>
|
</Script>
|
||||||
<script src="https://dropload.io/js/tabber.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src="https://dropload.io/js/tabber.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
<Script type="a9bf83b31be3a1c8064935cd-text/javascript">
|
<Script type="45a2a97ee4145248610fba09-text/javascript">
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
</Script>
|
</Script>
|
||||||
<script data-cfasync="false" async type="text/javascript" src="//sx.impubicpuddly.com/rO7FQr7AANJdtg/111561"></script>
|
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||||
<script src='https://dropload.io/tag2.js' type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src='https://dropload.io/tag2.js' type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
<script src='https://dropload.io/tag1.js' type="a9bf83b31be3a1c8064935cd-text/javascript"></script>
|
<script src='https://dropload.io/tag1.js' type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Javascript Alt Ads code start -->
|
<!-- Javascript Alt Ads code start -->
|
||||||
|
|
@ -495,7 +508,7 @@ $.cookie('aff', '5', { expires: 10 });
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="a9bf83b31be3a1c8064935cd-|49" defer></script></body>
|
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="45a2a97ee4145248610fba09-|49" defer></script></body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3955353,RESOLUTION=1920x800,FRAME-RATE=23.974,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR
|
||||||
|
index-v1-a1.m3u8?t=jq0VCV2dOyLIIH07IGIpzruCXvbfdVqzGLWfMeo00AM&s=1759845990&e=14400&f=1571895&srv=ds3&i=0.0&sp=0&ii=2.184.0.222&p1=ds3&p2=ds3
|
||||||
|
|
||||||
|
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=68483,RESOLUTION=1920x800,CODECS="avc1.640028",URI="iframes-v1-a1.m3u8?t=jq0VCV2dOyLIIH07IGIpzruCXvbfdVqzGLWfMeo00AM&s=1759845990&e=14400&f=1571895&srv=ds3&i=0.0&sp=0&ii=2.184.0.222&p1=ds3&p2=ds3",VIDEO-RANGE=SDR
|
||||||
1
src/extractor/__fixtures__/ExternalUrl/head-https:johnalwayssame.comecqy9oue7sv0g
generated
Normal file
1
src/extractor/__fixtures__/ExternalUrl/head-https:johnalwayssame.comecqy9oue7sv0g
generated
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
1
src/extractor/__fixtures__/ExternalUrl/head-https:netu.fanstream.use0DFgfkcXOsDP
generated
Normal file
1
src/extractor/__fixtures__/ExternalUrl/head-https:netu.fanstream.use0DFgfkcXOsDP
generated
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
1
src/extractor/__fixtures__/ExternalUrl/head-https:streamtape.comegjA1OQ4klyHxgJ.error
generated
Normal file
1
src/extractor/__fixtures__/ExternalUrl/head-https:streamtape.comegjA1OQ4klyHxgJ.error
generated
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fetcher error: 404: Not Found
|
||||||
1
src/extractor/__fixtures__/ExtractorRegistry/head-https:mixdrop.age3nzwveprim63or6
generated
Normal file
1
src/extractor/__fixtures__/ExtractorRegistry/head-https:mixdrop.age3nzwveprim63or6
generated
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
1
src/extractor/__fixtures__/ExtractorRegistry/head-https:some-url.test.error
generated
Normal file
1
src/extractor/__fixtures__/ExtractorRegistry/head-https:some-url.test.error
generated
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
TypeError: fetch failed
|
||||||
160
src/extractor/__fixtures__/ExtractorRegistry/https:dropload.ioasdfghijklmn.html
generated
Normal file
160
src/extractor/__fixtures__/ExtractorRegistry/https:dropload.ioasdfghijklmn.html
generated
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Dropload - Revolution Video Hosting</title>
|
||||||
|
|
||||||
|
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#FFF">
|
||||||
|
<meta name="format-detection" conten="telephone=no">
|
||||||
|
<link rel="apple-touch-icon" href="https://dropload.io/assets2/images/favicon/apple-touch-icon.png" sizes="180x180">
|
||||||
|
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon-32x32.png" sizes="32x32">
|
||||||
|
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon-16x16.png" sizes="16x16">
|
||||||
|
<link rel="manifest" href="https://dropload.io/assets2/images/favicon/manifest.json">
|
||||||
|
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon.ico">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
|
||||||
|
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
|
||||||
|
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
|
||||||
|
<script src="https://dropload.io/assets2/js/app.js" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row align-items-center flex-nowrap">
|
||||||
|
<div class="col-auto d-lg-none me-auto">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn icon-btn text-primary" data-bs-toggle="dropdown" data-bs-offset="0, 16">
|
||||||
|
<i class="icon icon-menu icon-size-18"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
|
||||||
|
<a href="/?op=registration" class="dropdown-item">Upload</a>
|
||||||
|
|
||||||
|
<a href="/make_money.html" class="dropdown-item">Earn money</a>
|
||||||
|
<a href="/api.html" class="dropdown-item">Service API</a>
|
||||||
|
<a href="/tos" class="dropdown-item">Terms and Conditions</a>
|
||||||
|
<a href="/contact" class="dropdown-item">Contacts</a>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto me-lg-5">
|
||||||
|
<a href="/">
|
||||||
|
<img src="https://dropload.io/assets2/images/logo.svg" class="logo" alt="Dropload.io - ">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto d-none d-lg-block">
|
||||||
|
<nav class="nav">
|
||||||
|
|
||||||
|
<a href="/?op=registration" class="nav-link">Upload</a>
|
||||||
|
|
||||||
|
<a href="/make_money.html" class="nav-link">Earn money</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-auto d-none d-lg-block ms-auto">
|
||||||
|
<a href="/login.html" class="btn login-btn me-2">
|
||||||
|
<i class="icon icon-account me-2"></i>Login</a>
|
||||||
|
<a href="/?op=registration" class="btn reg-btn">Create an Account</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto d-lg-none ms-auto">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn icon-btn text-primary" data-bs-toggle="dropdown" data-bs-offset="0, 16">
|
||||||
|
<i class="icon icon-account icon-size-18"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="/login.html">Login</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="/?op=registration">Create an Account</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||||
|
<script src='https://dropload.io/tag2.js' type="67d4e7c92cd803f904f519b6-text/javascript"></script>
|
||||||
|
<script src='https://dropload.io/tag1.js' type="67d4e7c92cd803f904f519b6-text/javascript"></script>
|
||||||
|
<section class="mb-5 mb-lg-7">
|
||||||
|
<div class="container">
|
||||||
|
<div class="message mb-6">
|
||||||
|
<div class="row align-items-center justify-content-center">
|
||||||
|
<div class="col-8 col-lg-6 pe-lg-5 text-lg-end mb-4 mb-lg-0">
|
||||||
|
<img src="https://dropload.io/assets2/images/nofile.svg" class="img-fluid" alt="">
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<h4 class="text-uppercase">File Not Found</h4>
|
||||||
|
<p>The file you were looking for could not be found, sorry for any inconvenience.<br><br>
|
||||||
|
Possible causes of this error could be:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The file expired
|
||||||
|
<li>The file was deleted by its owner
|
||||||
|
<li>The file was deleted by administration because it didn't comply with our Terms of Use
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row text-center text-lg-start flex-lg-nowrap">
|
||||||
|
<div class="col">
|
||||||
|
<div class="mb-3">
|
||||||
|
<a href="/">
|
||||||
|
<img src="https://dropload.io/assets2/images/logo.svg" alt="">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted mb-4 small">© 2025 Dropload - Revolution Video Hosting. All rights reserved.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col d-none d-lg-block">
|
||||||
|
<div class="nav flex-column mb-3">
|
||||||
|
<a href="/faq" class="nav-link">FAQ</a>
|
||||||
|
<a href="/contact" class="nav-link">Contact Us</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col d-none d-lg-block">
|
||||||
|
<div class="nav flex-column mb-3">
|
||||||
|
<a href="/tos" class="nav-link">Terms of service</a>
|
||||||
|
<a href="#" class="nav-link">Privacy Policy</a>
|
||||||
|
<a href="#" class="nav-link">Copyright Policy</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col d-none d-lg-block">
|
||||||
|
<div class="nav flex-column mb-3">
|
||||||
|
<a href="/api.html" class="nav-link">API</a>
|
||||||
|
<a href="/make_money.html" class="nav-link">Make Money</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="67d4e7c92cd803f904f519b6-|49" defer></script></body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue