Compare commits
1209 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 | ||
|
|
c5112124f5 | ||
|
|
c6d9bfb01b | ||
|
|
7f26047250 | ||
|
|
1fb59bcb6d | ||
|
|
1c1ffcd19b | ||
|
|
349c26c9b5 | ||
|
|
1454d21e4d | ||
|
|
41d2f23812 | ||
|
|
739ded0aa2 | ||
|
|
1be75645d3 | ||
|
|
f0a8ab540a | ||
|
|
5665c6a6ff | ||
|
|
d4db80789a | ||
|
|
68a7a41606 | ||
|
|
15a53de504 | ||
|
|
ce6aca5c77 | ||
|
|
11fa19c165 | ||
|
|
7271b1c100 | ||
|
|
4da2567ab2 | ||
|
|
cc429acbc3 | ||
|
|
44e140c696 | ||
|
|
3d4129fbd9 | ||
|
|
8ba6fdb5df | ||
|
|
9145e9c3f6 | ||
|
|
9c9a8e410b | ||
|
|
02c9ae3b1b | ||
|
|
23eb87e9db | ||
|
|
12b31835cb | ||
|
|
9e74bd747b | ||
|
|
4cde8607cd | ||
|
|
072ec4ac0a | ||
|
|
2128e2638a | ||
|
|
13520aa194 | ||
|
|
3c76e182ed | ||
|
|
fc024209a7 | ||
|
|
8d003dd174 | ||
|
|
89e5bb1978 | ||
|
|
09ee685d22 | ||
|
|
f2389f428b | ||
|
|
df6f14ee6b | ||
|
|
1654c0b920 | ||
|
|
1626cf03ce | ||
|
|
b7a8fc4e76 | ||
|
|
23d123851b | ||
|
|
0a57114c7d | ||
|
|
3b00036ff2 | ||
|
|
8226d0755a | ||
|
|
477c3d1ff9 | ||
|
|
ee0efa5409 | ||
|
|
b114ac11b7 | ||
|
|
efef039cde | ||
|
|
4787a1323d | ||
|
|
96dde8a634 |
699 changed files with 192954 additions and 4840 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1 +1 @@
|
|||
/fixtures/** linguist-generated
|
||||
**/__fixtures__/** linguist-generated
|
||||
|
|
|
|||
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
|
||||
on: push
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
push: false
|
||||
tags: webstreamr/webstreamr:latest
|
||||
|
|
|
|||
23
.github/workflows/tests.yml
vendored
23
.github/workflows/tests.yml
vendored
|
|
@ -1,26 +1,17 @@
|
|||
name: Tests
|
||||
on: push
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- 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 }}-
|
||||
node-version: "24"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
".": "0.5.0"
|
||||
".": "0.69.1"
|
||||
}
|
||||
|
|
|
|||
3328
CHANGELOG.md
3328
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
|
||||
|
||||
COPY package*.json ./
|
||||
|
|
@ -10,7 +10,7 @@ RUN npm run build
|
|||
|
||||
RUN npm ci --only=production
|
||||
|
||||
FROM node:22.15-alpine
|
||||
FROM node:24
|
||||
WORKDIR /app
|
||||
|
||||
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 stylistic from '@stylistic/eslint-plugin';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
importPlugin.flatConfigs.recommended,
|
||||
importPlugin.flatConfigs.typescript,
|
||||
tseslint.configs.strict,
|
||||
tseslint.configs.stylistic,
|
||||
stylistic.configs.customize({
|
||||
|
|
@ -13,4 +16,11 @@ export default tseslint.config(
|
|||
{
|
||||
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' }],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
not found tt12345678
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import type { Config } from 'jest';
|
||||
|
||||
const config: Config = {
|
||||
automock: false,
|
||||
clearMocks: true,
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/src/**/*.ts',
|
||||
|
|
@ -9,6 +11,10 @@ const config: Config = {
|
|||
'!<rootDir>/src/landingTemplate.ts',
|
||||
],
|
||||
coverageDirectory: '<rootDir>/coverage',
|
||||
coveragePathIgnorePatterns: [
|
||||
'/src/controller/',
|
||||
'/src/utils/dispatcher.ts',
|
||||
],
|
||||
coverageProvider: 'babel',
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
|
|
@ -19,12 +25,19 @@ const config: Config = {
|
|||
},
|
||||
},
|
||||
resetModules: true,
|
||||
restoreMocks: true,
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+.tsx?$': ['ts-jest', {}],
|
||||
testEnvironmentOptions: {
|
||||
globalsCleanup: 'on',
|
||||
},
|
||||
transform: {
|
||||
'^.+.tsx?$': ['ts-jest', {
|
||||
tsconfig: 'tsconfig.dev.json',
|
||||
}],
|
||||
},
|
||||
modulePathIgnorePatterns: [
|
||||
'<rootDir>/dist',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
import { cachedFetchText } from './src/utils/fetch';
|
||||
import fs from 'node:fs';
|
||||
import slugify from 'slugify';
|
||||
process.env['CACHE_DIR'] = '/dev/null';
|
||||
process.env['HOST'] = 'example.test';
|
||||
process.env['PROTOCOL'] = 'https';
|
||||
|
||||
// Mocks cachedFetchText and either returns existing fixtures or creates new ones
|
||||
jest.mock('./src/utils/fetch', () => ({
|
||||
cachedFetchText: jest.fn(),
|
||||
if (!process.env['TMDB_ACCESS_TOKEN']) {
|
||||
process.env['TMDB_ACCESS_TOKEN'] = 'some access token';
|
||||
}
|
||||
|
||||
jest.mock('randomstring', () => ({
|
||||
generate: jest.fn(() => 'mocked-random-string'),
|
||||
}));
|
||||
(cachedFetchText as jest.Mock).mockImplementation(
|
||||
async (url: string) => {
|
||||
const path = `${__dirname}/fixtures/cachedFetchText/${slugify(url)}`;
|
||||
if (fs.existsSync(path)) {
|
||||
return fs.readFileSync(path).toString();
|
||||
} else {
|
||||
const realFetchModule = jest.requireActual('./src/utils/fetch');
|
||||
const text = await realFetchModule.cachedFetchText(url);
|
||||
fs.writeFileSync(path, text);
|
||||
|
||||
return text;
|
||||
}
|
||||
},
|
||||
);
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Date, 'now').mockImplementation(() => 639837296000);
|
||||
});
|
||||
|
||||
console.log = console.warn = console.error = console.info = console.debug = () => { /* disable in favor of logger */ };
|
||||
|
|
|
|||
7858
package-lock.json
generated
7858
package-lock.json
generated
File diff suppressed because it is too large
Load diff
58
package.json
58
package.json
|
|
@ -1,16 +1,17 @@
|
|||
{
|
||||
"name": "webstreamr",
|
||||
"description": "Provides HTTP URLs from streaming websites.",
|
||||
"version": "0.5.0",
|
||||
"version": "0.69.1",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"analyse": "tsc --noEmit",
|
||||
"analyse": "tsc --noEmit --project ./tsconfig.dev.json",
|
||||
"build": "rm -rf ./dist && tsc",
|
||||
"dev": "nodemon src/index.ts",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"start": "node dist/index.js",
|
||||
"test": "jest",
|
||||
"test:update-fixtures": "TEST_UPDATE_FIXTURES=1 npm run test",
|
||||
"ci": "npm run lint && npm run analyse && npm run test"
|
||||
},
|
||||
"repository": {
|
||||
|
|
@ -27,26 +28,61 @@
|
|||
"url": "https://github.com/webstreamr/webstreamr/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.0.0"
|
||||
"node": "^22.0.0 || ^24.0.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"runtime": {
|
||||
"name": "node",
|
||||
"onFail": "error"
|
||||
},
|
||||
"packageManager": {
|
||||
"name": "npm",
|
||||
"onFail": "error"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@keyv/sqlite": "^4.0.5",
|
||||
"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",
|
||||
"bytes": "^3.1.2",
|
||||
"cacheable": "^2.0.0",
|
||||
"cheerio": "^1.0.0",
|
||||
"express": "^5.1.0",
|
||||
"make-fetch-happen": "^14.0.3",
|
||||
"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",
|
||||
"unpacker": "^1.0.1"
|
||||
"socks-proxy-agent": "^8.0.5",
|
||||
"sqlite3": "^5.1.7",
|
||||
"tough-cookie": "^6.0.0",
|
||||
"unpacker": "^1.0.1",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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/express": "^5.0.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/make-fetch-happen": "^10.0.4",
|
||||
"@types/node": "^22.15.3",
|
||||
"@types/fast-levenshtein": "^0.0.4",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/memoizee": "^0.4.12",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/randomstring": "^1.3.0",
|
||||
"@types/stremio-addon-sdk": "^1.6.11",
|
||||
"babel-jest": "^29.7.0",
|
||||
"babel-jest": "^30.0.0",
|
||||
"eslint": "^9.26.0",
|
||||
"jest": "^29.7.0",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"jest": "^30.0.0",
|
||||
"nodemon": "^3.1.10",
|
||||
"ts-jest": "^29.3.2",
|
||||
"ts-node": "^10.9.2",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,18 @@
|
|||
"description": "Automerge non-major updates",
|
||||
"matchUpdateTypes": ["minor", "patch", "digest", "lockFileMaintenance"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Update types once per week",
|
||||
"matchDatasources": ["npm"],
|
||||
"matchPackageNames": ["/^@types/"],
|
||||
"extends": ["schedule:weekly"]
|
||||
},
|
||||
{
|
||||
"description": "Update user-agents once per month",
|
||||
"matchDatasources": ["npm"],
|
||||
"matchPackageNames": ["/^user-agents$/"],
|
||||
"extends": ["schedule:monthly"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
37
src/controller/ConfigureController.ts
Normal file
37
src/controller/ConfigureController.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { Request, Response, Router } from 'express';
|
||||
import { Extractor } from '../extractor';
|
||||
import { landingTemplate } from '../landingTemplate';
|
||||
import { Source } from '../source';
|
||||
import { Config } from '../types';
|
||||
import { buildManifest, getDefaultConfig, isElfHostedInstance } from '../utils';
|
||||
|
||||
export class ConfigureController {
|
||||
public readonly router: Router;
|
||||
|
||||
private readonly sources: Source[];
|
||||
private readonly extractors: Extractor[];
|
||||
|
||||
public constructor(sources: Source[], extractors: Extractor[]) {
|
||||
this.router = Router();
|
||||
|
||||
this.sources = sources;
|
||||
this.extractors = extractors;
|
||||
|
||||
this.router.get('/configure', this.getConfigure.bind(this));
|
||||
this.router.get('/:config/configure', this.getConfigure.bind(this));
|
||||
}
|
||||
|
||||
private getConfigure(req: Request, res: Response) {
|
||||
const config: Config = JSON.parse(req.params['config'] as string || JSON.stringify(getDefaultConfig()));
|
||||
|
||||
// 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.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);
|
||||
}
|
||||
};
|
||||
}
|
||||
31
src/controller/ManifestController.ts
Normal file
31
src/controller/ManifestController.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { Request, Response, Router } from 'express';
|
||||
import { Extractor } from '../extractor';
|
||||
import { Source } from '../source';
|
||||
import { Config } from '../types';
|
||||
import { buildManifest, getDefaultConfig } from '../utils';
|
||||
|
||||
export class ManifestController {
|
||||
public readonly router: Router;
|
||||
|
||||
private readonly sources: Source[];
|
||||
private readonly extractors: Extractor[];
|
||||
|
||||
public constructor(sources: Source[], extractors: Extractor[]) {
|
||||
this.router = Router();
|
||||
|
||||
this.sources = sources;
|
||||
this.extractors = extractors;
|
||||
|
||||
this.router.get('/manifest.json', this.getManifest.bind(this));
|
||||
this.router.get('/:config/manifest.json', this.getManifest.bind(this));
|
||||
}
|
||||
|
||||
private getManifest(req: Request, res: Response) {
|
||||
const config: Config = JSON.parse(req.params['config'] as string || JSON.stringify(getDefaultConfig()));
|
||||
|
||||
const manifest = buildManifest(this.sources, this.extractors, config);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(manifest);
|
||||
};
|
||||
}
|
||||
70
src/controller/StreamController.ts
Normal file
70
src/controller/StreamController.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { Mutex } from 'async-mutex';
|
||||
import { Request, Response, Router } from 'express';
|
||||
import { ContentType } from 'stremio-addon-sdk';
|
||||
import winston from 'winston';
|
||||
import { Source } from '../source';
|
||||
import { contextFromRequestAndResponse, envIsProd, Id, ImdbId, StreamResolver, TmdbId } from '../utils';
|
||||
|
||||
export class StreamController {
|
||||
public readonly router: Router;
|
||||
|
||||
private readonly logger: winston.Logger;
|
||||
private readonly sources: Source[];
|
||||
private readonly streamResolver: StreamResolver;
|
||||
|
||||
private readonly locks = new Map<string, Mutex>();
|
||||
|
||||
public constructor(logger: winston.Logger, sources: Source[], streams: StreamResolver) {
|
||||
this.router = Router();
|
||||
|
||||
this.logger = logger;
|
||||
this.sources = sources;
|
||||
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));
|
||||
}
|
||||
|
||||
private async getStream(req: Request, res: Response) {
|
||||
const type: ContentType = (req.params['type'] || '') as ContentType;
|
||||
const rawId: string = req.params['id'] as string || '';
|
||||
|
||||
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}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = contextFromRequestAndResponse(req, res);
|
||||
|
||||
this.logger.info(`Search stream for type "${type}" and id "${rawId}" for ip ${ctx.ip}`, ctx);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
4
src/controller/index.ts
Normal file
4
src/controller/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export * from './ExtractController';
|
||||
export * from './ConfigureController';
|
||||
export * from './ManifestController';
|
||||
export * from './StreamController';
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { EmbedExtractor } from './types';
|
||||
import { cachedFetchText, extractUrlFromPacked, iso2ToFlag, scanFromResolution } from '../utils';
|
||||
|
||||
export class Dropload implements EmbedExtractor {
|
||||
readonly id = 'dropload';
|
||||
|
||||
readonly label = 'Dropload';
|
||||
|
||||
readonly extract = async (url: string, language: string) => {
|
||||
const normalizedUrl = url.replace('/e/', '').replace('/embed-', '/');
|
||||
const html = await cachedFetchText(normalizedUrl);
|
||||
|
||||
const resolution = scanFromResolution((html.match(/(\d{3,}x\d{3,}),/) as string[])[1] as string);
|
||||
|
||||
const sizeMatch = html.match(/([\d.]+) ?([GM]B)/) as string[];
|
||||
const size = `${sizeMatch[1]} ${sizeMatch[2]}`;
|
||||
|
||||
return {
|
||||
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
|
||||
name: `WebStreamr ${resolution}`,
|
||||
title: `${this.label} | 💾 ${size} | ${iso2ToFlag(language)}`,
|
||||
behaviorHints: {
|
||||
group: `webstreamr-${this.id}`,
|
||||
},
|
||||
resolution,
|
||||
size,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { EmbedExtractor } from './types';
|
||||
import { Dropload } from './Dropload';
|
||||
import { SuperVideo } from './SuperVideo';
|
||||
|
||||
type EmbedExtractorRegistryType = Record<string, EmbedExtractor>;
|
||||
|
||||
export const EmbedExtractorRegistry: EmbedExtractorRegistryType = {
|
||||
dropload: new Dropload(),
|
||||
supervideo: new SuperVideo(),
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import { EmbedExtractor } from './types';
|
||||
import { cachedFetchText, extractUrlFromPacked, iso2ToFlag, scanFromResolution } from '../utils';
|
||||
|
||||
export class SuperVideo implements EmbedExtractor {
|
||||
readonly id = 'supervideo';
|
||||
|
||||
readonly label = 'SuperVideo';
|
||||
|
||||
readonly extract = async (url: string, language: string) => {
|
||||
const normalizedUrl = url.replace('/e/', '/').replace('/embed-', '/');
|
||||
const html = await cachedFetchText(normalizedUrl);
|
||||
|
||||
const resolutionAndSizeMatch = html.match(/(\d{3,}x\d{3,}), ([\d.]+) ?([GM]B)/) as string[];
|
||||
const resolution = scanFromResolution(resolutionAndSizeMatch[1] as string);
|
||||
const size = `${resolutionAndSizeMatch[2]} ${resolutionAndSizeMatch[3]}`;
|
||||
|
||||
return {
|
||||
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
|
||||
name: `WebStreamr ${resolution}`,
|
||||
title: `${this.label} | 💾 ${size} | ${iso2ToFlag(language)}`,
|
||||
behaviorHints: {
|
||||
group: `webstreamr-${this.id}`,
|
||||
},
|
||||
resolution,
|
||||
size,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export * from './EmbedExtractorRegistry';
|
||||
export * from './types';
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { StreamWithMeta } from '../types';
|
||||
|
||||
export interface EmbedExtractor {
|
||||
readonly id: string;
|
||||
|
||||
readonly label: string;
|
||||
|
||||
readonly extract: (embedUrl: string, language: string) => Promise<StreamWithMeta>;
|
||||
}
|
||||
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
431
src/extractor/__fixtures__/Dropload/https:dropload.io7xtxuac84xyh.html
generated
Normal file
431
src/extractor/__fixtures__/Dropload/https:dropload.io7xtxuac84xyh.html
generated
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
<!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="1198ec03089106bda1a7dcfc-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="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||
<script src="https://dropload.io/assets2/js/app.js" type="1198ec03089106bda1a7dcfc-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 src="https://dropload.io/js/jquery.cookie.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||
|
||||
|
||||
|
||||
<script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||
$.cookie('file_id', '1062326', { expires: 10 });
|
||||
$.cookie('aff', '1805', { expires: 10 });
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="mb-5 mb-lg-7">
|
||||
<div class="container">
|
||||
<div class="videoplayer">
|
||||
<div class="videoplayer-embed">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<img src="https://img.dropload.io/7xtxuac84xyh.jpg" alt="">
|
||||
|
||||
<div class="encoding">
|
||||
<div class="encoding-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="percent">
|
||||
75%
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<h6>Pending in queue</h6>
|
||||
<small class="text-light">Video is processing now.<br>Conversion stage: <span id='enc_pp'>...</span></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="lightdown-backdrop"></div>
|
||||
|
||||
<h1>Bates Motel 2x3 </h1>
|
||||
<div class="videoplayer-controlbar">
|
||||
<div class="row g-1">
|
||||
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
|
||||
<div class="videoplayer-details">
|
||||
<div class="row align-items-center flex-nowrap">
|
||||
<div class="col-auto flex-shrink-1 mw-0">
|
||||
<div class="row flex-lg-nowrap">
|
||||
|
||||
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||
on Aug 6, 2024
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
<i class="icon icon-download2 icon-size-16 me-2 opacity-50"></i>
|
||||
<span id="size"></span>
|
||||
</div>
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||
00:42:55
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
|
||||
<button class="btn icon-btn me-4" data-bs-toggle="modal" data-bs-target="#modal_addplaylist">
|
||||
<i class="icon icon-addlist icon-size-16"></i>
|
||||
</button>
|
||||
<button type="button" class="btn icon-btn text-danger" data-bs-toggle="modal" data-bs-target="#modal_flag">
|
||||
<i class="icon icon-flag icon-size-16"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<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>
|
||||
<span id="likes_num">0</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<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>
|
||||
<span id="dislikes_num">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-5">
|
||||
<div class="row justify-content-center justify-content-lg-between">
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="col-12 col-lg-7 mb-3">
|
||||
<div class="copybox">
|
||||
<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-1198ec03089106bda1a7dcfc-="">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="modal fade" id="modal_flag" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Flag</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<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-1198ec03089106bda1a7dcfc-="">
|
||||
<input type="hidden" name="op" value="report_file">
|
||||
<input type="hidden" name="file_code" value="7xtxuac84xyh">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Reason:</label>
|
||||
<select class="form-select" name="report_type">
|
||||
<option>Broken video</option>
|
||||
<option>Wrong title/description</option>
|
||||
<option>Copyright restriction</option>
|
||||
<option>Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<textarea name="report_info" class="form-control" rows="2" placeholder="Details"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<input type="submit" name="add" value="Send Report" class="btn btn-gradient submit-btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal_addplaylist" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Add to</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-0">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal_share" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Share</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-0">
|
||||
|
||||
|
||||
Share code
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style media="screen">
|
||||
.lightdown {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill-rule='evenodd' fill='%23FFF' d='M10 20c-1.654 0-3-1.346-3-3v-.531c0-.625-.254-1.237-.695-1.679l-.548-.548A5.96 5.96 0 0 1 4 10c0-1.603.624-3.109 1.757-4.243A5.961 5.961 0 0 1 10 4a5.96 5.96 0 0 1 4.242 1.757A5.96 5.96 0 0 1 16 10a5.957 5.957 0 0 1-1.758 4.242l-.546.547a2.396 2.396 0 0 0-.696 1.68V17c0 1.654-1.346 3-3 3Zm-1.025-4c.017.154.025.311.025.469V17a1.001 1.001 0 0 0 2 0v-.531c0-.158.008-.315.024-.469H8.975Zm-.737-2h3.524c.152-.222.325-.431.519-.624l.546-.547A3.974 3.974 0 0 0 14 10a3.975 3.975 0 0 0-1.172-2.829c-1.512-1.51-4.146-1.51-5.657 0A3.978 3.978 0 0 0 6 10c0 1.068.416 2.072 1.171 2.828h.001l.547.548c.194.194.367.402.519.624ZM19 11h-1a1 1 0 1 1 0-2h1a1 1 0 1 1 0 2ZM2 11H1a1 1 0 1 1 0-2h1a1 1 0 0 1 0 2Zm13.657-5.657a.999.999 0 0 1-.707-1.707l.707-.707a1 1 0 0 1 1.414 1.414l-.707.707a.993.993 0 0 1-.707.293Zm-11.314 0a.993.993 0 0 1-.707-.293l-.707-.707a.999.999 0 1 1 1.414-1.414l.707.707a.999.999 0 0 1-.707 1.707ZM10 3a1 1 0 0 1-1-1V1a1 1 0 0 1 2 0v1a1 1 0 0 1-1 1Z'/%3E%3C/svg%3E") no-repeat center;
|
||||
background-color: rgba(28,37,52,.75);
|
||||
transition: .25s;
|
||||
opacity: 0;
|
||||
}
|
||||
.videoplayer-embed:hover .lightdown {
|
||||
opacity: 1;
|
||||
}
|
||||
.body-lightdown .lightdown {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill-rule='evenodd' fill='%233DB6D4' d='M10 20c-1.654 0-3-1.346-3-3v-.531c0-.625-.254-1.237-.695-1.679l-.548-.548A5.96 5.96 0 0 1 4 10c0-1.603.624-3.109 1.757-4.243A5.961 5.961 0 0 1 10 4a5.96 5.96 0 0 1 4.242 1.757A5.96 5.96 0 0 1 16 10a5.957 5.957 0 0 1-1.758 4.242l-.546.547a2.396 2.396 0 0 0-.696 1.68V17c0 1.654-1.346 3-3 3Zm-1.025-4c.017.154.025.311.025.469V17a1.001 1.001 0 0 0 2 0v-.531c0-.158.008-.315.024-.469H8.975Zm-.737-2h3.524c.152-.222.325-.431.519-.624l.546-.547A3.974 3.974 0 0 0 14 10a3.975 3.975 0 0 0-1.172-2.829c-1.512-1.51-4.146-1.51-5.657 0A3.978 3.978 0 0 0 6 10c0 1.068.416 2.072 1.171 2.828h.001l.547.548c.194.194.367.402.519.624ZM19 11h-1a1 1 0 1 1 0-2h1a1 1 0 1 1 0 2ZM2 11H1a1 1 0 1 1 0-2h1a1 1 0 0 1 0 2Zm13.657-5.657a.999.999 0 0 1-.707-1.707l.707-.707a1 1 0 0 1 1.414 1.414l-.707.707a.993.993 0 0 1-.707.293Zm-11.314 0a.993.993 0 0 1-.707-.293l-.707-.707a.999.999 0 1 1 1.414-1.414l.707.707a.999.999 0 0 1-.707 1.707ZM10 3a1 1 0 0 1-1-1V1a1 1 0 0 1 2 0v1a1 1 0 0 1-1 1Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
.lightdown-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,.9);
|
||||
display: none;
|
||||
z-index: 1999;
|
||||
}
|
||||
.body-lightdown .lightdown-backdrop {
|
||||
display: block;
|
||||
}
|
||||
.body-lightdown .videoplayer-embed {
|
||||
z-index: 2000;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||
$('.lightdown').on('click', function(){
|
||||
$('body').toggleClass('body-lightdown');
|
||||
})
|
||||
</script>
|
||||
|
||||
<Script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
||||
var tab_cookie='tab_down';
|
||||
|
||||
$('ul.tabs').on('click', 'li:not(.current)', function() {
|
||||
$(this).addClass('current').siblings().removeClass('current')
|
||||
.parents('div.section').find('div.box').eq($(this).index()).fadeIn(150).siblings('div.box').hide();
|
||||
});
|
||||
|
||||
$("#iew,#ieh").change(function (){
|
||||
var ww = $("#iew").val();
|
||||
var hh = $("#ieh").val();
|
||||
ww = ww.replace(/\D/g,'');
|
||||
hh = hh.replace(/\D/g,'');
|
||||
if(!ww)ww=640;
|
||||
if(!hh)hh=360;
|
||||
tt = $("#iet").val();
|
||||
tt = tt.replace(/ WIDTH=\d+ HEIGHT=\d+ /,' WIDTH='+ww+' HEIGHT='+hh+' ');
|
||||
$("#iet").val(tt);
|
||||
});
|
||||
|
||||
|
||||
encStatus('1062326',1);
|
||||
|
||||
</Script>
|
||||
<script src="https://dropload.io/js/tabber.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||
|
||||
|
||||
<Script type="1198ec03089106bda1a7dcfc-text/javascript">
|
||||
$(function() {
|
||||
|
||||
});
|
||||
</Script>
|
||||
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||
<script src='https://dropload.io/tag2.js' type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||
<script src='https://dropload.io/tag1.js' type="1198ec03089106bda1a7dcfc-text/javascript"></script>
|
||||
|
||||
|
||||
<!-- Javascript Alt Ads code start -->
|
||||
<!-- Javascript Alt Ads code end -->
|
||||
|
||||
|
||||
</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="1198ec03089106bda1a7dcfc-|49" defer></script></body>
|
||||
|
||||
</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 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="fb4dc13d1285d9e23d3f1de2-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/xupload.js?v=13" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
||||
<script src="https://dropload.io/assets2/js/app.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="ad8da47d2c4dcd2ad69dff49-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="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
@ -91,11 +91,11 @@
|
|||
<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('aff', '4', { expires: 10 });
|
||||
|
||||
|
|
@ -137,8 +137,21 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
</div> -->
|
||||
|
||||
|
||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
|
||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">jwplayer.key="";</script>
|
||||
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></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>
|
||||
.jw-text-track-cue {
|
||||
text-shadow: 1px 1px 2px transparent !important;
|
||||
|
|
@ -148,7 +161,7 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
padding-bottom: 0.2em !important;
|
||||
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>
|
||||
|
|
@ -205,13 +218,13 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<span id="likes_num">0</span>
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
<span id="dislikes_num">0</span>
|
||||
</button>
|
||||
|
|
@ -270,7 +283,7 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
||||
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||
$(function(){
|
||||
var a = $('.dropdown-menu a:first-child .download-size');
|
||||
$('#size').append(a);
|
||||
|
|
@ -283,7 +296,7 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
<div class="col-12 col-lg-7 mb-3">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
|
|
@ -305,7 +318,7 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<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="file_code" value="lyo2h1snpe5c">
|
||||
<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>
|
||||
|
||||
<style media="screen">
|
||||
|
|
@ -411,13 +424,13 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
}
|
||||
</style>
|
||||
|
||||
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
||||
<script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||
$('.lightdown').on('click', function(){
|
||||
$('body').toggleClass('body-lightdown');
|
||||
})
|
||||
</script>
|
||||
|
||||
<Script type="fb4dc13d1285d9e23d3f1de2-text/javascript">
|
||||
<Script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
|
||||
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
||||
var tab_cookie='tab_down';
|
||||
|
||||
|
|
@ -440,17 +453,17 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
|
||||
|
||||
</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() {
|
||||
|
||||
});
|
||||
</Script>
|
||||
<script data-cfasync="false" async type="text/javascript" src="//sx.impubicpuddly.com/rO7FQr7AANJdtg/111561"></script>
|
||||
<script src='https://dropload.io/tag2.js' type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
||||
<script src='https://dropload.io/tag1.js' type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script>
|
||||
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||
<script src='https://dropload.io/tag2.js' type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||
<script src='https://dropload.io/tag1.js' type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
|
||||
|
||||
|
||||
<!-- Javascript Alt Ads code start -->
|
||||
|
|
@ -495,7 +508,7 @@ $.cookie('aff', '4', { expires: 10 });
|
|||
</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>
|
||||
|
||||
515
src/extractor/__fixtures__/Dropload/https:dropload.ioxhcmgcc2txnv
generated
Normal file
515
src/extractor/__fixtures__/Dropload/https:dropload.ioxhcmgcc2txnv
generated
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
<!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="45a2a97ee4145248610fba09-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="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||
<script src="https://dropload.io/assets2/js/app.js" type="45a2a97ee4145248610fba09-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 src="https://dropload.io/js/jquery.cookie.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||
|
||||
|
||||
|
||||
<script type="45a2a97ee4145248610fba09-text/javascript">
|
||||
$.cookie('file_id', '1526758', { expires: 10 });
|
||||
$.cookie('aff', '7208', { expires: 10 });
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="mb-5 mb-lg-7">
|
||||
<div class="container">
|
||||
<div class="videoplayer">
|
||||
<div class="videoplayer-embed">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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">
|
||||
<a class="btn btn-gradient" href="/premium.html">Upgrade your account</a>
|
||||
</div>
|
||||
to watch videos with no limits!
|
||||
</div> -->
|
||||
|
||||
|
||||
<script type="45a2a97ee4145248610fba09-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></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>
|
||||
.jw-text-track-cue {
|
||||
text-shadow: 1px 1px 2px transparent !important;
|
||||
line-height: 1.53em;
|
||||
padding-left: 0.3em !important;
|
||||
padding-right: 0.3em !important;
|
||||
padding-bottom: 0.2em !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<button type="button" class="btn lightdown"></button>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="lightdown-backdrop"></div>
|
||||
|
||||
<h1>[~Squid~Game~EnG~]~S03E01~720p~WebRip ~❤️GLD❤️~ </h1>
|
||||
<div class="videoplayer-controlbar">
|
||||
<div class="row g-1">
|
||||
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
|
||||
<div class="videoplayer-details">
|
||||
<div class="row align-items-center flex-nowrap">
|
||||
<div class="col-auto flex-shrink-1 mw-0">
|
||||
<div class="row flex-lg-nowrap">
|
||||
|
||||
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||
on Jun 27, 2025
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
<i class="icon icon-download2 icon-size-16 me-2 opacity-50"></i>
|
||||
<span id="size"></span>
|
||||
</div>
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
|
||||
00:57:13
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto d-flex align-items-center">
|
||||
|
||||
<button class="btn icon-btn me-4" data-bs-toggle="modal" data-bs-target="#modal_addplaylist">
|
||||
<i class="icon icon-addlist icon-size-16"></i>
|
||||
</button>
|
||||
<button type="button" class="btn icon-btn text-danger" data-bs-toggle="modal" data-bs-target="#modal_flag">
|
||||
<i class="icon icon-flag icon-size-16"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<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>
|
||||
<span id="likes_num">0</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<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>
|
||||
<span id="dislikes_num">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-5">
|
||||
<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/xhcmgcc2txnv_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">1280x720, <span class="download-size">317.6 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="45a2a97ee4145248610fba09-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="copybox">
|
||||
<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-45a2a97ee4145248610fba09-="">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="modal fade" id="modal_flag" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Flag</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<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-45a2a97ee4145248610fba09-="">
|
||||
<input type="hidden" name="op" value="report_file">
|
||||
<input type="hidden" name="file_code" value="xhcmgcc2txnv">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Reason:</label>
|
||||
<select class="form-select" name="report_type">
|
||||
<option>Broken video</option>
|
||||
<option>Wrong title/description</option>
|
||||
<option>Copyright restriction</option>
|
||||
<option>Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<textarea name="report_info" class="form-control" rows="2" placeholder="Details"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<input type="submit" name="add" value="Send Report" class="btn btn-gradient submit-btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal_addplaylist" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Add to</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-0">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal_share" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Share</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body pt-0">
|
||||
|
||||
|
||||
Share code
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
||||
<style media="screen">
|
||||
.lightdown {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill-rule='evenodd' fill='%23FFF' d='M10 20c-1.654 0-3-1.346-3-3v-.531c0-.625-.254-1.237-.695-1.679l-.548-.548A5.96 5.96 0 0 1 4 10c0-1.603.624-3.109 1.757-4.243A5.961 5.961 0 0 1 10 4a5.96 5.96 0 0 1 4.242 1.757A5.96 5.96 0 0 1 16 10a5.957 5.957 0 0 1-1.758 4.242l-.546.547a2.396 2.396 0 0 0-.696 1.68V17c0 1.654-1.346 3-3 3Zm-1.025-4c.017.154.025.311.025.469V17a1.001 1.001 0 0 0 2 0v-.531c0-.158.008-.315.024-.469H8.975Zm-.737-2h3.524c.152-.222.325-.431.519-.624l.546-.547A3.974 3.974 0 0 0 14 10a3.975 3.975 0 0 0-1.172-2.829c-1.512-1.51-4.146-1.51-5.657 0A3.978 3.978 0 0 0 6 10c0 1.068.416 2.072 1.171 2.828h.001l.547.548c.194.194.367.402.519.624ZM19 11h-1a1 1 0 1 1 0-2h1a1 1 0 1 1 0 2ZM2 11H1a1 1 0 1 1 0-2h1a1 1 0 0 1 0 2Zm13.657-5.657a.999.999 0 0 1-.707-1.707l.707-.707a1 1 0 0 1 1.414 1.414l-.707.707a.993.993 0 0 1-.707.293Zm-11.314 0a.993.993 0 0 1-.707-.293l-.707-.707a.999.999 0 1 1 1.414-1.414l.707.707a.999.999 0 0 1-.707 1.707ZM10 3a1 1 0 0 1-1-1V1a1 1 0 0 1 2 0v1a1 1 0 0 1-1 1Z'/%3E%3C/svg%3E") no-repeat center;
|
||||
background-color: rgba(28,37,52,.75);
|
||||
transition: .25s;
|
||||
opacity: 0;
|
||||
}
|
||||
.videoplayer-embed:hover .lightdown {
|
||||
opacity: 1;
|
||||
}
|
||||
.body-lightdown .lightdown {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill-rule='evenodd' fill='%233DB6D4' d='M10 20c-1.654 0-3-1.346-3-3v-.531c0-.625-.254-1.237-.695-1.679l-.548-.548A5.96 5.96 0 0 1 4 10c0-1.603.624-3.109 1.757-4.243A5.961 5.961 0 0 1 10 4a5.96 5.96 0 0 1 4.242 1.757A5.96 5.96 0 0 1 16 10a5.957 5.957 0 0 1-1.758 4.242l-.546.547a2.396 2.396 0 0 0-.696 1.68V17c0 1.654-1.346 3-3 3Zm-1.025-4c.017.154.025.311.025.469V17a1.001 1.001 0 0 0 2 0v-.531c0-.158.008-.315.024-.469H8.975Zm-.737-2h3.524c.152-.222.325-.431.519-.624l.546-.547A3.974 3.974 0 0 0 14 10a3.975 3.975 0 0 0-1.172-2.829c-1.512-1.51-4.146-1.51-5.657 0A3.978 3.978 0 0 0 6 10c0 1.068.416 2.072 1.171 2.828h.001l.547.548c.194.194.367.402.519.624ZM19 11h-1a1 1 0 1 1 0-2h1a1 1 0 1 1 0 2ZM2 11H1a1 1 0 1 1 0-2h1a1 1 0 0 1 0 2Zm13.657-5.657a.999.999 0 0 1-.707-1.707l.707-.707a1 1 0 0 1 1.414 1.414l-.707.707a.993.993 0 0 1-.707.293Zm-11.314 0a.993.993 0 0 1-.707-.293l-.707-.707a.999.999 0 1 1 1.414-1.414l.707.707a.999.999 0 0 1-.707 1.707ZM10 3a1 1 0 0 1-1-1V1a1 1 0 0 1 2 0v1a1 1 0 0 1-1 1Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
.lightdown-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,.9);
|
||||
display: none;
|
||||
z-index: 1999;
|
||||
}
|
||||
.body-lightdown .lightdown-backdrop {
|
||||
display: block;
|
||||
}
|
||||
.body-lightdown .videoplayer-embed {
|
||||
z-index: 2000;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="45a2a97ee4145248610fba09-text/javascript">
|
||||
$('.lightdown').on('click', function(){
|
||||
$('body').toggleClass('body-lightdown');
|
||||
})
|
||||
</script>
|
||||
|
||||
<Script type="45a2a97ee4145248610fba09-text/javascript">
|
||||
$("#embed_div textarea").focus(function (){ $(this).select(); })
|
||||
var tab_cookie='tab_down';
|
||||
|
||||
$('ul.tabs').on('click', 'li:not(.current)', function() {
|
||||
$(this).addClass('current').siblings().removeClass('current')
|
||||
.parents('div.section').find('div.box').eq($(this).index()).fadeIn(150).siblings('div.box').hide();
|
||||
});
|
||||
|
||||
$("#iew,#ieh").change(function (){
|
||||
var ww = $("#iew").val();
|
||||
var hh = $("#ieh").val();
|
||||
ww = ww.replace(/\D/g,'');
|
||||
hh = hh.replace(/\D/g,'');
|
||||
if(!ww)ww=640;
|
||||
if(!hh)hh=360;
|
||||
tt = $("#iet").val();
|
||||
tt = tt.replace(/ WIDTH=\d+ HEIGHT=\d+ /,' WIDTH='+ww+' HEIGHT='+hh+' ');
|
||||
$("#iet").val(tt);
|
||||
});
|
||||
|
||||
|
||||
</Script>
|
||||
<script src="https://dropload.io/js/tabber.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||
|
||||
|
||||
<Script type="45a2a97ee4145248610fba09-text/javascript">
|
||||
$(function() {
|
||||
|
||||
});
|
||||
</Script>
|
||||
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
|
||||
<script src='https://dropload.io/tag2.js' type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||
<script src='https://dropload.io/tag1.js' type="45a2a97ee4145248610fba09-text/javascript"></script>
|
||||
|
||||
|
||||
<!-- Javascript Alt Ads code start -->
|
||||
<!-- Javascript Alt Ads code end -->
|
||||
|
||||
|
||||
</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="45a2a97ee4145248610fba09-|49" defer></script></body>
|
||||
|
||||
</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 @@
|
|||
{}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue