Compare commits

...

1156 commits

Author SHA1 Message Date
WebStreamr
720c9bb651
docs: mention PixelServer / pixeldrain limits 2026-03-27 22:10:38 +00:00
webstreamr
f05cf5040c
chore(release): release v0.69.1 (#801) 2026-03-27 22:05:18 +00:00
WebStreamr
65191cfc8d
fix(source): exclude ad videos in HDHub4u 2026-03-27 22:03:14 +00:00
WebStreamr
bd2dec0392
fix(source): remove stray console.log 2026-03-27 21:59:07 +00:00
webstreamr
5f9fbbb30d
chore(release): release v0.69.0 (#799) 2026-03-27 22:48:26 +01:00
WebStreamr
43f04455f1
feat(source): add HDHub4u 2026-03-27 21:42:10 +00:00
WebStreamr
43637a2ff4
refactor(source): extract hd hub link redirect resolver 2026-03-27 20:17:03 +00:00
WebStreamr
b87bf62362
chore: remove duplicate URLs 2026-03-27 19:40:27 +00:00
WebStreamr
89e57d48d9
chore(extractor): detect languages from HubCloud title 2026-03-27 19:30:47 +00:00
renovate[bot]
ad47aecea3
chore(deps): update dependency axios to v1.14.0 (#800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-27 19:28:52 +00:00
WebStreamr
f192fd0b75
chore(extractor): detect resolution from HubCloud title 2026-03-27 19:26:31 +00:00
WebStreamr
557efd2079
fix(extractor): don't fail for HubDrive if HubCloud link is missing 2026-03-27 18:02:48 +00:00
webstreamr
373c6c1d4c
chore(release): release v0.68.0 (#798) 2026-03-26 15:57:28 +01:00
WebStreamr
4c7da5dc20
feat: exclude resolution config options 2026-03-26 14:46:07 +00:00
webstreamr
bed5b00300
chore(release): release v0.67.0 (#796) 2026-03-25 10:03:12 +01:00
WebStreamr
e36d460d57
feat(extractor): support FSLv2 server in HubCloud 2026-03-25 09:01:25 +00:00
renovate[bot]
5cffa112ba
chore(deps): update dependency typescript-eslint to v8.57.2 (#795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 17:59:06 +00:00
webstreamr
66ae60ca44
chore(release): release v0.66.16 (#794) 2026-03-23 18:57:05 +01:00
webstreamr
b60496bf22
docs: remove mention of XDMovies 2026-03-23 18:17:28 +01:00
WebStreamr
ff39c59475
fix(source): remove XDMovies
was fun while it lasted, I won't implement a 15s websocket wait
2026-03-23 16:55:48 +00:00
webstreamr
b6b67568b5
chore(release): release v0.66.15 (#790) 2026-03-23 15:07:39 +00:00
WebStreamr
eecf9c42c5
fix(extractor): hard-code referer for Dropload and SuperVideo for now 2026-03-23 15:06:05 +00:00
renovate[bot]
fc3d61b05a
chore(deps): lock file maintenance (#792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 00:42:51 +00:00
renovate[bot]
b2b9760445
chore(deps): update dependency cacheable to v2.3.4 (#789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 01:43:07 +00:00
webstreamr
7afff18629
chore(release): release v0.66.14 (#785) 2026-03-17 20:53:49 +00:00
WebStreamr
ee3effaf00
chore(extractor): add cacheVersion and bump it for HubCloud and HubDrive 2026-03-17 20:51:57 +00:00
WebStreamr
296f0478b0
fix(extractor): reduce HubCloud + HubDrive ttl to 12h 2026-03-17 20:46:17 +00:00
WebStreamr
693a77e73e
fix(extractor): add PixelDrain referer to HubCloud extractor 2026-03-17 20:34:26 +00:00
renovate[bot]
b769cd0aa1
chore(deps): update dependency typescript-eslint to v8.57.1 (#786)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 17:47:10 +00:00
renovate[bot]
9eca05efc5
chore(deps): lock file maintenance (#784)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 01:43:07 +00:00
webstreamr
ca0110cdbc
chore(release): release v0.66.13 (#781) 2026-03-14 12:56:14 +00:00
WebStreamr
66ee72baf0
fix: downgrade sqlite for now 2026-03-14 12:54:37 +00:00
webstreamr
3c08d69bc3
chore(release): release v0.66.12 (#778) 2026-03-14 08:33:01 +00:00
WebStreamr
0f004dc878
fix: require sqlite with alpine fix 2026-03-14 08:31:36 +00:00
renovate[bot]
b7093ed4ae
chore(deps): lock file maintenance (#779)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-14 08:29:13 +00:00
renovate[bot]
20289eaeaf
chore(deps): update dependency slugify to v1.6.8 (#777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-14 00:36:58 +00:00
webstreamr
3b16fe0390
chore(release): release v0.66.11 (#762) 2026-03-13 16:53:54 +01:00
WebStreamr
c78c1ad628
fix(extractor): add missing VOE domain 2026-03-13 15:52:08 +00:00
WebStreamr
8fe4cc5a9b
fix(source): switch to new Frembed URL and resolve link redirects 2026-03-13 15:50:08 +00:00
renovate[bot]
930e8191be
fix(deps): update dependency sqlite3 to v6 (#772)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-13 10:00:31 +01:00
renovate[bot]
2734fb4d0d
chore(deps): update dependency tough-cookie to v6.0.1 (#773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 21:54:29 +00:00
renovate[bot]
b5223148c7
chore(deps): update jest monorepo to v30.3.0 (#767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-10 02:42:27 +00:00
renovate[bot]
ad3d0b317e
chore(deps): update dependency typescript-eslint to v8.57.0 (#766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 17:24:18 +00:00
renovate[bot]
e90b5583a7
chore(deps): update dependency express-rate-limit to v8.3.1 (#765)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 15:15:19 +00:00
renovate[bot]
394dc91bfa
chore(deps): lock file maintenance (#764)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 02:14:06 +00:00
renovate[bot]
7de046838d
chore(deps): update dependency @types/node to v24.12.0 (#763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 02:09:48 +00:00
renovate[bot]
955872a825
chore(deps): update eslint monorepo to v9.39.4 (#761)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 22:42:15 +00:00
webstreamr
d17780f0a7
chore(release): release v0.66.10 (#751) 2026-03-06 14:05:01 +01:00
renovate[bot]
737a8063a8
chore(deps): update docker/build-push-action action to v7 (#759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 13:03:13 +00:00
WebStreamr
423abf2479
fix(extractor): use Dropload hosting URL origin as Referer 2026-03-06 13:01:24 +00:00
renovate[bot]
4e436509bf
chore(deps): update dependency @stylistic/eslint-plugin to v5.10.0 (#760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-06 05:58:20 +00:00
renovate[bot]
f58e0e1f4f
chore(deps): update dependency express-rate-limit to v8.3.0 (#758)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-05 19:17:41 +00:00
renovate[bot]
d2e72b0756
chore(deps): update docker/setup-buildx-action action to v4 (#757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-05 12:08:53 +01:00
renovate[bot]
edebd860aa
chore(deps): update dependency axios-cache-interceptor to v1.12.0 (#755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-05 01:18:54 +00:00
renovate[bot]
6b8363bd9c
chore(deps): update docker/login-action action to v4 (#750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 13:57:09 +01:00
webstreamr
d0c40ffa00
chore(release): release v0.66.9 (#747) 2026-03-04 12:05:03 +00:00
renovate[bot]
b36e1484eb
chore(deps): update docker/setup-qemu-action action to v4 (#749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-04 13:03:46 +01:00
WebStreamr
a5e429fea3
chore: add referral 2026-03-04 09:05:50 +00:00
WebStreamr
6d5870ccb2
docs: use default branch for tests badge 2026-03-02 13:28:27 +00:00
webstreamr
6cc82520a0
chore(release): release v0.66.8 (#746) 2026-03-02 13:14:43 +00:00
WebStreamr
7dfc599de9
fix(extractor): use Dropload playlist headers also internally 2026-03-02 13:02:34 +00:00
WebStreamr
aa820e1f64
docs: add more limitations 2026-03-02 12:59:17 +00:00
WebStreamr
38d0ac5305
docs: add more references 2026-03-02 12:54:32 +00:00
WebStreamr
438387faee
docs: add known issues / limitations section 2026-03-02 12:52:00 +00:00
webstreamr
5a1786d3e5
chore(release): release v0.66.7 (#738) 2026-03-02 11:42:22 +01:00
WebStreamr
bbb80f3cf0
fix(extractor): add referer as proxy header for Dropload 2026-03-02 10:26:46 +00:00
renovate[bot]
8ea6d4fe37
chore(deps): lock file maintenance (#745)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 01:36:34 +00:00
renovate[bot]
282b3d05b3
chore(deps): update dependency @types/node to v24.11.0 (#744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 01:33:17 +00:00
renovate[bot]
168d0280dc
chore(deps): update dependency cacheable to v2.3.3 (#737)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 18:17:33 +00:00
renovate[bot]
50e024a3ea
chore(deps): update dependency axios to v1.13.6 (#736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 18:13:55 +00:00
webstreamr
e1d582036a
chore(release): release v0.66.6 (#732) 2026-02-26 22:11:16 +01:00
renovate[bot]
354c2731d8
chore(deps): update dependency minimatch to v10.2.4 (#735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 19:50:19 +00:00
renovate[bot]
ce4637ea80
chore(deps): update dependency minimatch to v10.2.3 (#715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-25 05:50:31 +00:00
renovate[bot]
73ce5f372d
chore(deps): update dependency typescript-eslint to v8.56.1 (#734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 18:38:21 +00:00
WebStreamr
221fa989f4
chore(fetcher): cache whole flaresolverr solution and also use status from it 2026-02-23 12:08:01 +00:00
webstreamr
a4dfad6235
chore(release): release v0.66.5 (#731) 2026-02-23 13:01:01 +01:00
WebStreamr
ae10810caa
perf: cache all non-error requests for 15m, tune axios caching 2026-02-23 11:57:07 +00:00
WebStreamr
964e979469
fix(source): add support for old XDMovies uploads without URL protector 2026-02-23 11:14:53 +00:00
WebStreamr
556b20e669
fix(extractor): remove StreamUp 2026-02-23 10:58:55 +00:00
renovate[bot]
6c102362fd
chore(deps): lock file maintenance (#730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 01:12:44 +00:00
webstreamr
49faa5d116
chore(release): release v0.66.4 (#700) 2026-02-21 16:16:16 +01:00
WebStreamr
cf8fb4cfa4
fix(source): handle new XDMovies URL protection 2026-02-21 15:10:11 +00:00
renovate[bot]
3bb7a800e3
chore(deps): update dependency nodemon to v3.1.14 (#728)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-21 01:03:22 +00:00
renovate[bot]
26b6143152
chore(deps): update eslint monorepo to v9.39.3 (#727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 14:13:46 +00:00
renovate[bot]
728ed7ec06
chore(deps): update dependency nodemon to v3.1.13 (#726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-20 02:29:35 +00:00
renovate[bot]
5bfbd94e51
chore(deps): update dependency nodemon to v3.1.12 (#725)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-19 22:36:51 +00:00
renovate[bot]
23db19d073
chore(deps): update dependency glob to v13.0.6 (#724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-19 18:32:50 +00:00
renovate[bot]
bd160eb32c
chore(deps): update dependency @stylistic/eslint-plugin to v5.9.0 (#723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-19 14:16:22 +00:00
renovate[bot]
fb354b4746
chore(deps): update dependency glob to v13.0.5 (#721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-17 19:57:41 +00:00
renovate[bot]
245dcf6007
chore(deps): update dependency glob to v13.0.4 (#720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-17 05:37:00 +00:00
renovate[bot]
94f5c7438d
chore(deps): update dependency typescript-eslint to v8.56.0 (#719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 17:38:37 +00:00
renovate[bot]
d0f3e0f931
chore(deps): lock file maintenance (#718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 01:13:23 +00:00
renovate[bot]
524630d671
chore(deps): update dependency @types/node to v24.10.13 (#717)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 01:12:34 +00:00
renovate[bot]
65a0e57ab9
chore(deps): update dependency glob to v13.0.3 (#714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-12 23:33:37 +00:00
renovate[bot]
eccea520b7
chore(deps): update dependency glob to v13.0.2 (#711)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 17:53:29 +00:00
renovate[bot]
7ec9c5b147
chore(deps): update dependency typescript-eslint to v8.55.0 (#710)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-09 18:33:43 +00:00
renovate[bot]
1ae12fa0f7
chore(deps): update dependency @stylistic/eslint-plugin to v5.8.0 (#709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-09 11:04:50 +00:00
renovate[bot]
ccdd4600bb
chore(deps): lock file maintenance (#708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-09 01:13:41 +00:00
renovate[bot]
3f35ecf6f3
chore(deps): update dependency @types/node to v24.10.12 (#707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-09 01:12:39 +00:00
renovate[bot]
de13209709
chore(deps): update dependency axios to v1.13.5 (#705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-08 13:10:52 +00:00
WebStreamr
34500ea3fc
chore(extractor): adapt mixdrop host check 2026-02-06 14:04:40 +00:00
renovate[bot]
72c191fbc1
chore(deps): update dependency glob to v13.0.1 (#698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 18:54:11 +00:00
webstreamr
8c2897a0b1
chore(release): release v0.66.3 (#697) 2026-02-03 09:58:35 +00:00
WebStreamr
31da8aa92c
perf(fetcher): simplify FlareSolverr integration by just using a request per host 2026-02-03 09:57:09 +00:00
webstreamr
92c45d553e
chore(release): release v0.66.2 (#696) 2026-02-03 08:51:58 +01:00
WebStreamr
ae5829dcb0
perf(fetcher): improve FlareSolverr integration
- multiple sessions which are destroyed after being idle for 60 minutes
- fix timeout handling
- pass cookies to share between sessions
- disable media
2026-02-03 07:41:02 +00:00
webstreamr
3f5a82d663
chore(release): release v0.66.1 (#695) 2026-02-02 15:39:00 +00:00
WebStreamr
2612860f15
fix(source): make XDMovies a fallback source to avoid hubcloud and many duplicates 2026-02-02 15:37:15 +00:00
webstreamr
e6adf6549a
chore(release): release v0.66.0 (#693) 2026-02-02 14:44:23 +00:00
WebStreamr
c23ffa382d
fix(fetcher): never block flaresolverr timeouts 2026-02-02 14:42:53 +00:00
WebStreamr
1f4d81bfc9
feat(source): add XDMovies 2026-02-02 14:42:53 +00:00
renovate[bot]
455d84d5b1
chore(deps): lock file maintenance (#692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-02 01:14:33 +00:00
webstreamr
72c19e7f59
chore(release): release v0.65.10 (#684) 2026-01-28 18:13:53 +00:00
WebStreamr
e52cee2125
chore(extractor): increase StreamEmbed TTL to 3d 2026-01-28 18:12:03 +00:00
renovate[bot]
5c87c5d9e2
chore(deps): update dependency axios to v1.13.4 (#689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-27 19:35:09 +00:00
renovate[bot]
8f8ecb868e
chore(deps): update dependency typescript-eslint to v8.54.0 (#687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 19:36:45 +00:00
WebStreamr
a876c9b144
chore(extractor): increase Fsst TTL to 3 hours 2026-01-26 19:33:10 +00:00
WebStreamr
2ba6e4fa35
fix(extractor): avoid caching results with empty meta, add some self-healing 2026-01-26 14:12:10 +00:00
WebStreamr
50a7e5559b
refactor(extractor): introduce internal type to reduce common code 2026-01-26 13:47:55 +00:00
WebStreamr
db860c25c6
fix(extractor): ensure every result has a ttl 2026-01-26 13:37:26 +00:00
renovate[bot]
074311107a
chore(deps): lock file maintenance (#686)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 00:33:39 +00:00
renovate[bot]
19a73b3866
chore(deps): update dependency axios to v1.13.3 (#685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-25 18:30:04 +00:00
WebStreamr
75c9509793
chore(extractor): add new VOE domain 2026-01-25 07:02:47 +00:00
webstreamr
bc98d64969
chore(release): release v0.65.9 (#679) 2026-01-23 22:14:09 +00:00
WebStreamr
a90d7b1cad
fix(extractor): use full cached meta to avoid invalid cache states
The lazy extraction does not specify meta the same way the stream
resolver does. this leads to partial meta being inserted in both the
short-term and long-term cache and consecutive requests have then
missing meta info like e.g. countries
2026-01-23 22:12:50 +00:00
renovate[bot]
43e16f5bfa
chore(deps): update dependency cheerio to v1.2.0 (#683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 19:02:36 +00:00
renovate[bot]
622b1b3bcc
chore(deps): update dependency @stylistic/eslint-plugin to v5.7.1 (#682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-23 14:48:29 +00:00
WebStreamr
12ea135feb
refactor(cache): extract clearCache function 2026-01-22 15:16:03 +00:00
renovate[bot]
e5197fdb8b
chore(deps): update dependency @keyv/sqlite to v4.0.8 (#677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-22 15:03:48 +00:00
WebStreamr
a5539bcf59
refactor(cache): make code more dry and return in-memory cache for test env 2026-01-22 15:01:00 +00:00
renovate[bot]
d3db3fc512
chore(deps): update dependency axios-cache-interceptor to v1.11.4 (#678)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-21 10:13:10 +00:00
webstreamr
9945ed4615
chore(release): release v0.65.8 (#675) 2026-01-20 21:13:14 +00:00
WebStreamr
28a83e2f29
chore: npm audit fix 2026-01-20 21:11:27 +00:00
WebStreamr
93fe1ae871
chore: add env var for one-time cache file deletion
kind of a hack to be able to do that on environments without FS access
2026-01-20 21:10:29 +00:00
WebStreamr
1ddc8d99d6
fix(extractor): never use external URL results for lazy URL caching to avoid clashes 2026-01-20 20:57:01 +00:00
WebStreamr
3c979e77cc
chore(extractor): add lauradaydo.com to VOE 2026-01-20 20:52:19 +00:00
GSTAR
a00fa2c3d6
fix(extractor): use MediaFlow Proxy for DoodStream (#676)
Co-authored-by: WebStreamr <210764791+webstreamr@users.noreply.github.com>
2026-01-20 21:49:37 +01:00
renovate[bot]
7c886583c9
chore(deps): update dependency typescript-eslint to v8.53.1 (#674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 18:03:42 +00:00
webstreamr
92a4ed5578
chore(release): release v0.65.7 (#673) 2026-01-19 12:01:50 +01:00
WebStreamr
e8ed29eb9d
fix: pass empty array instead of undefined if meta has no countries 2026-01-19 10:58:49 +00:00
webstreamr
ef2cc7f16b
chore(release): release v0.65.6 (#670) 2026-01-19 09:42:17 +00:00
WebStreamr
646e75d5f8
fix: log all unhandled errors inside next handlers, terminate app properly for unhandled error outside 2026-01-19 09:40:28 +00:00
WebStreamr
d259587491
fix(extractor): use correct referer for rgshows, treat vidzee response as block 2026-01-19 09:12:45 +00:00
renovate[bot]
7c99e4d1c1
chore(deps): lock file maintenance (#669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 01:31:30 +00:00
renovate[bot]
aeb15d8640
chore(deps): update dependency @types/node to v24.10.9 (#668)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 01:30:43 +00:00
webstreamr
771d664e5a
chore(release): release v0.65.5 (#665) 2026-01-18 13:22:45 +00:00
WebStreamr
7b708e2385
chore: add priority for sorting, move VixSrc up and RgShows down 2026-01-18 13:21:17 +00:00
WebStreamr
a9c281c8c4
fix(extractor): only use bytes and height from meta cache to avoid clashes 2026-01-18 12:57:52 +00:00
renovate[bot]
8aa1344fa5
chore(deps): update dependency cacheable to v2.3.2 (#667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-17 20:33:42 +00:00
webstreamr
5fd62028ce
chore: add issue templates 2026-01-17 11:38:19 +01:00
WebStreamr
5b49dd0735
chore(fetcher): tune FlareSolverr timeouts and use other cookie variants too 2026-01-16 19:53:18 +00:00
webstreamr
7bb38fda3e
chore(release): release v0.65.4 (#664) 2026-01-16 14:23:59 +01:00
WebStreamr
507b96014c
chore(extractor): simplify cache expiration handling 2026-01-16 13:21:38 +00:00
WebStreamr
986db2cc67
chore: extract HubDrive handling into own extractor re-using HubCloud for better caching, increase both TTLs 2026-01-16 13:16:07 +00:00
webstreamr
546dee9799
chore(release): release v0.65.3 (#663) 2026-01-16 12:47:27 +01:00
WebStreamr
4cfbf731b8
fix(fetcher): more FlareSolverr related stats, fix clearance cookie expiration 2026-01-16 11:45:49 +00:00
WebStreamr
231a12f685
docs: add tmp mount for persistent cache to Docker run example 2026-01-16 10:09:29 +00:00
WebStreamr
e54b54bfb3
docs: mention FlareSolverr support / env var 2026-01-16 10:09:25 +00:00
webstreamr
58966ccff1
chore(release): release v0.65.2 (#659) 2026-01-16 10:36:11 +01:00
WebStreamr
59c84952ed
fix(fetcher): make FlareSolverr work again, use single session and pass proxy 2026-01-16 09:03:19 +00:00
renovate[bot]
90c56debfe
chore(deps): update dependency @tsconfig/node24 to v24.0.4 (#662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-16 06:16:54 +00:00
WebStreamr
9cf9c4498c
fix: remove leftover logging 2026-01-15 21:01:10 +00:00
webstreamr
31ca9597e5
chore(release): release v0.65.1 (#658) 2026-01-15 20:35:32 +00:00
WebStreamr
17e70c169b
fix(extractor): use milliseconds instead of incorrect seconds for ttl
🤦‍♂️
2026-01-15 20:33:45 +00:00
WebStreamr
79c8e7fc32
fix(source): be resilient against missing heights in 4KHDHub 2026-01-15 20:05:03 +00:00
WebStreamr
26278aadba
fix(extractor): use lazy url/meta cache also for extractors without a ttl 2026-01-15 19:48:34 +00:00
WebStreamr
5ef6119294
fix: calculate fallback count properly, only use VidSrc when no other normal source provided results 2026-01-15 19:43:55 +00:00
webstreamr
96c4b1f16b
chore(release): release v0.65.0 (#651) 2026-01-15 15:05:59 +01:00
WebStreamr
4cf08fc6d5
feat: add fetcher http stats 2026-01-15 14:03:51 +00:00
WebStreamr
bad1170421
feat: introduce fallback sources and mark VidSrc and RgShows as such
fallback sources are only considered when not enough results are found.
the idea is to use it for sources which are very useful as fallback but
not everytime either because the quality is not good or querying them is
resource intensive.
2026-01-15 14:03:03 +00:00
WebStreamr
7593d3d62e
chore: make cache immutable, add rate limit 2026-01-15 13:37:28 +00:00
WebStreamr
ce11a80ba9
fix(extractor): make VixSrc fall back to multi instead of it if language cannot be determined 2026-01-15 09:49:21 +00:00
webstreamr
5103be82a4
chore(release): release v0.64.0 (#649) 2026-01-14 14:52:35 +01:00
WebStreamr
9ee91dff31
feat(extractor): add back VidSrc
This reverts commit f5f47403cc.
2026-01-14 13:38:09 +00:00
WebStreamr
381fb89cfb
feat(source): bring back VidSrc as multi source
This reverts commit 1bd85fb4ad.
2026-01-14 13:11:40 +00:00
webstreamr
9354c0ee9b
chore(release): release v0.63.1 (#648) 2026-01-14 13:02:01 +00:00
WebStreamr
51aa127a33
chore: regularly clean-up expired items from sqlite cache 2026-01-14 13:00:42 +00:00
WebStreamr
9d6d138fff
chore(extractor): make lazyUrlResultCache non-blocking and add stats 2026-01-14 11:58:42 +00:00
WebStreamr
a189cddd37
chore: do not ignore errors on height guessing 2026-01-14 11:51:32 +00:00
WebStreamr
f7cf9da686
fix(source): make 4KHDHub title matching just a tiny bit less strict
To make the guy happy who wants to sleep with my mother
2026-01-14 11:15:40 +00:00
webstreamr
d00cc8a94a
chore(release): release v0.63.0 (#641) 2026-01-12 19:58:06 +00:00
renovate[bot]
20ce2ac831
chore(deps): lock file maintenance (#640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: WebStreamr <210764791+webstreamr@users.noreply.github.com>
2026-01-12 19:56:35 +00:00
WebStreamr
c6a6a70eaf
feat: introduce lazy extract for all non-HLS streams 2026-01-12 19:48:59 +00:00
renovate[bot]
9e256186aa
chore(deps): update dependency typescript-eslint to v8.53.0 (#645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 19:48:59 +00:00
WebStreamr
f047287fe9
feat: introduce generic meta cache 2026-01-12 19:48:54 +00:00
renovate[bot]
816123ab83
chore(deps): update dependency @types/node to v24.10.7 (#639)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 01:49:56 +00:00
webstreamr
815d3b6b1e
chore(release): release v0.62.3 (#638) 2026-01-11 20:35:58 +01:00
WebStreamr
05b80a2d63
chore: configure more Indian languages, add Tamil and Telugu to 4KHDHub 2026-01-11 19:29:55 +00:00
WebStreamr
de79dc014f
fix(source): RgShows is multi 2026-01-11 19:13:14 +00:00
WebStreamr
1a99a9e99b
chore: enable nonBlocking secondary cache writes 2026-01-11 19:12:09 +00:00
WebStreamr
f49769f3c4
chore(extractor): return only best Fsst result
it's too flaky to deal with all
2026-01-11 19:11:43 +00:00
webstreamr
77cf6767b1
chore(release): release v0.62.2 (#634) 2026-01-10 22:29:01 +00:00
WebStreamr
d1fcd671c9
fix(extractor): support non-HLS streams in RgShows 2026-01-10 22:27:44 +00:00
webstreamr
5cb4944413
chore(release): release v0.62.1 (#633) 2026-01-10 20:44:56 +00:00
WebStreamr
559ee6ed4e
chore(source): simplifly Frembed domain detection again
no clue why, but audin213.com is timing out on the official instance
2026-01-10 20:43:35 +00:00
webstreamr
cc1e96f3cf
chore(release): release v0.62.0 (#632) 2026-01-10 20:30:11 +00:00
WebStreamr
b273e3a71d
chore(source): improve 4KHDHub search matching
- search without year, otherwise incorrect results might be returned
  only
- do a less stricter levenshtein if name is fully included
2026-01-10 20:28:49 +00:00
WebStreamr
66fb0e2e6d
chore: remove all proxy black magic
can be configured per env via PROXY_CONFIG instead
2026-01-10 20:11:03 +00:00
GSTAR
baf5c9143d
feat(extractor): add StreamUP (#621)
Co-authored-by: WebStreamr <210764791+webstreamr@users.noreply.github.com>
2026-01-10 06:39:19 +00:00
WebStreamr
28a8a1554d
chore: do not fail resolution if height guessing fails 2026-01-09 20:00:46 +00:00
webstreamr
515ca0a98c
chore(release): release v0.61.0 (#625) 2026-01-09 19:50:01 +01:00
WebStreamr
dcad7db29c
feat: add RgShows 2026-01-09 15:34:04 +00:00
renovate[bot]
4bec9e556f
chore(deps): update dependency axios-cache-interceptor to v1.11.3 (#626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 15:04:15 +00:00
WebStreamr
da7397fe75
chore: enforce https:// prefix in mediaflow proxy url and support config without scheme to work aroud elfhosted weirdness 2026-01-09 15:00:29 +00:00
renovate[bot]
86eb3d498c
chore(deps): update dependency @stylistic/eslint-plugin to v5.7.0 (#624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 08:50:05 +00:00
webstreamr
ca8caa7795
chore(release): release v0.60.0 (#619) 2026-01-06 20:19:55 +01:00
WebStreamr
f6f71b043b
feat(extractor): switch KinoGer from cf to source, add kinoger.p2pplay.pro support 2026-01-06 19:10:15 +00:00
WebStreamr
281fe00a35
chore(fetcher): disable TLS verification
to better deal with hoster quirks
2026-01-06 19:08:16 +00:00
webstreamr
e5d68c5214
chore(release): release v0.59.1 (#618) 2026-01-06 15:41:23 +00:00
WebStreamr
04f3fc42e0
fix(source): adapt MegaKino domain
they came up with something new which might not work with redirects any
more. let's see..
2026-01-06 15:39:54 +00:00
webstreamr
ed50cb2f6a
chore(release): release v0.59.0 (#612) 2026-01-06 16:23:26 +01:00
WebStreamr
e1e8f37862
feat(extractor): bring back Fsst 2026-01-06 15:20:12 +00:00
WebStreamr
7146c6cf11
fix(source): handle MegaKino relative page links 2026-01-06 14:03:28 +00:00
WebStreamr
41bc53b9da
chore(source): do not filter out any KinoGer links 2026-01-06 13:49:46 +00:00
WebStreamr
bfe8c36e87
chore(extractor): add crystaltreatmenteast.com to VOE 2026-01-06 13:40:07 +00:00
renovate[bot]
430e5b7fce
chore(deps): update dependency typescript-eslint to v8.52.0 (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 19:01:18 +00:00
renovate[bot]
8d1991ed84
chore(deps): lock file maintenance (#615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 02:53:09 +00:00
WebStreamr
c075fe9481
fix(extractor): handle FileLions deleted by administration 2026-01-02 15:00:50 +00:00
webstreamr
7e4891a93f
chore(release): release v0.58.12 (#610) 2026-01-02 10:18:16 +01:00
WebStreamr
f8f62db174
chore(source): determine latest 4KHDHub domain more dynamically 2026-01-02 09:15:30 +00:00
WebStreamr
93a098e438
chore(source): add 4KHDHub test for stranger things s05e08 2026-01-02 08:58:11 +00:00
WebStreamr
1f4d7c752d
chore(source): update 4KHDHub TLD and re-generate fixtures 2026-01-02 08:47:12 +00:00
WebStreamr
9b3c7c76cf
fix: run npm audit fix to fix security issues 2026-01-02 08:38:55 +00:00
webstreamr
ac4cc8a346
chore(release): release v0.58.11 (#598) 2026-01-01 19:08:25 +00:00
WebStreamr
e7b1e2a4fc
fix(fetcher): increase queue limits 5 times 2026-01-01 19:06:25 +00:00
renovate[bot]
f8b63f4cce
chore(deps): update dependency typescript-eslint to v8.51.0 (#608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 21:45:33 +00:00
renovate[bot]
30c60261af
chore(deps): update dependency axios-cache-interceptor to v1.11.2 (#607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 05:08:12 +00:00
renovate[bot]
9079feebbd
chore(deps): lock file maintenance (#606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-29 02:13:17 +00:00
renovate[bot]
a7d93590c6
chore(deps): update dependency axios-cache-interceptor to v1.11.1 (#605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-27 16:36:28 +00:00
renovate[bot]
d20c568e85
chore(deps): update dependency typescript-eslint to v8.50.1 (#599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-22 20:43:02 +00:00
WebStreamr
c8e38d73ff
fix(extractor): add new FileLions domain 2025-12-22 13:35:44 +00:00
WebStreamr
135d90a233
fix(extractor): use last FileMoon iframe url matcher 2025-12-22 13:34:55 +00:00
WebStreamr
9f5ac02ec6
fix(extractor): deal better with cases where video ID URLs end with a slash 2025-12-22 13:32:33 +00:00
WebStreamr
6f24b7928d
fix(test): improve stability of MegaKino test by dirty mocking getFinalRedirectUrl 2025-12-22 13:31:30 +00:00
webstreamr
b5fc29f150
chore(release): release v0.58.10 (#575) 2025-12-22 10:38:44 +00:00
WebStreamr
6cc24c0332
fix(extractor): update FileLion domains, remove kinoger.be 2025-12-22 10:37:16 +00:00
WebStreamr
924265b964
fix(source): determine Frembed domain dynamically 2025-12-22 10:34:18 +00:00
WebStreamr
7264fd2ed4
chore(source): useless debug log 2025-12-22 10:29:14 +00:00
WebStreamr
0610b8556a
fix: return 400 for invalid ID instead of raising Error 2025-12-22 10:21:24 +00:00
renovate[bot]
9fd563450e
chore(deps): replace dependency @tsconfig/node22 with @tsconfig/node24 (#581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: WebStreamr <210764791+webstreamr@users.noreply.github.com>
2025-12-22 10:21:02 +01:00
WebStreamr
ddf4f227b3
fix: use "Mozilla" instead of "Mozilla/5.0" as default user agent 2025-12-22 09:04:36 +00:00
renovate[bot]
36a15f0091
chore(deps): lock file maintenance (#595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-22 00:38:29 +00:00
renovate[bot]
58bd6250c9
chore(deps): update dependency axios-cache-interceptor to v1.10.0 (#592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-20 01:16:25 +00:00
renovate[bot]
10081a2276
chore(deps): update dependency cacheable to v2.3.1 (#587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-17 02:04:17 +00:00
renovate[bot]
fa6bee4c83
chore(deps): update dependency axios-cache-interceptor to v1.9.1 (#584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-16 02:13:07 +00:00
renovate[bot]
40f9f9ec8c
chore(deps): update dependency typescript-eslint to v8.50.0 (#583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 21:52:15 +00:00
renovate[bot]
01649955b1
chore(deps): lock file maintenance (#580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 02:08:33 +00:00
renovate[bot]
5d412b759f
chore(deps): update dependency @types/node to v24.10.4 (#579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 02:07:35 +00:00
renovate[bot]
4f7a9d725e
chore(deps): update eslint monorepo to v9.39.2 (#574)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-13 03:07:21 +00:00
webstreamr
e3ea65d7f4
chore(release): release v0.58.9 (#572) 2025-12-12 17:58:18 +01:00
WebStreamr
d71aa240e7
fix(extractor): use extractorId per HubCloud sub hoster again for better bingeGroup 2025-12-12 16:56:10 +00:00
WebStreamr
e438f3cbbd
fix(extractor): fix HubCloud picking up incorrect FSL URLs 2025-12-12 16:48:46 +00:00
WebStreamr
d44e0a2588
fix(extractor): handle FileMoon with missing height info in packed 2025-12-12 16:26:43 +00:00
WebStreamr
258e35c94d
fix(source): resolve MegaKino dynamically and cache for an hour 2025-12-12 16:12:41 +00:00
webstreamr
d607b7fb6b
chore(release): release v0.58.8 (#560) 2025-12-11 16:21:18 +00:00
WebStreamr
42b9cbe38f
fix(source): update MegaKino domain 2025-12-11 16:19:26 +00:00
WebStreamr
d7edb51698
refactor: add generic source and extractor ids to meta and generalize bingeGroup generation 2025-12-11 16:02:13 +00:00
WebStreamr
1bd85fb4ad
chore(source): remove VidSrc which does not add value atm 2025-12-11 15:47:55 +00:00
renovate[bot]
580845ae77
chore(deps): update dependency typescript-eslint to v8.49.0 (#567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 20:16:18 +00:00
renovate[bot]
32b49d49a9
chore(deps): update dependency axios-cache-interceptor to v1.9.0 (#566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 20:12:24 +00:00
renovate[bot]
5ff8a49be5
chore(deps): lock file maintenance (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 01:53:09 +00:00
renovate[bot]
3955820eb9
chore(deps): update dependency winston to v3.19.0 (#559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-07 09:55:19 +00:00
webstreamr
609bd23cd8
chore(release): release v0.58.7 (#557) 2025-12-07 00:06:36 +01:00
WebStreamr
0f7bc7c07f
fix(source): update MegaKino domain 2025-12-06 23:01:39 +00:00
WebStreamr
cf0be4712e
fix(source): return all KinoGer domains, fixes VOE results 2025-12-06 23:01:39 +00:00
WebStreamr
50c5d3a1cd
chore(extractor): add kinoger.ru support to VOE 2025-12-06 23:01:39 +00:00
WebStreamr
8dae887eca
revert: fix(source): remove KinoGer
This reverts commit e5a5c62106.
2025-12-06 23:01:28 +00:00
webstreamr
3be37008b7
chore(release): release v0.58.6 (#548) 2025-12-04 21:31:29 +00:00
WebStreamr
ae00c43106
fix(extractor): handle DoodStream Not Found 2025-12-04 21:29:46 +00:00
renovate[bot]
d7392c09d2
chore(deps): update dependency typescript-eslint to v8.48.1 (#550)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-02 14:44:16 +00:00
renovate[bot]
f549a32dd2
chore(deps): update dependency express to v5.2.1 (#549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 22:05:45 +00:00
renovate[bot]
e092d7247d
chore(deps): update dependency express to v5.2.0 (#547)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 19:57:06 +00:00
renovate[bot]
d3dbedf18a
chore(deps): update dependency ts-jest to v29.4.6 (#546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 19:53:35 +00:00
webstreamr
90ab3d0a31
chore(release): release v0.58.5 (#543) 2025-12-01 12:24:06 +01:00
WebStreamr
cba3c8bbae
chore(extractor): add new VOE domains 2025-12-01 10:57:14 +00:00
WebStreamr
4a14ad7fb0
chore(extractor): match new DoodStream domain 2025-12-01 10:54:00 +00:00
WebStreamr
5c04104ad2
chore: introduce helper for better and central season/episode formatting 2025-12-01 10:51:44 +00:00
WebStreamr
ef2b6721c7
refactor(extractor): move label format handling into central place 2025-12-01 08:20:14 +00:00
renovate[bot]
d7e82ceff3
chore(deps): lock file maintenance (#542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 01:33:44 +00:00
webstreamr
973eb06584
chore(release): release v0.58.4 (#537) 2025-11-30 11:20:36 +01:00
WebStreamr
825d97adc4
chore: noop 2025-11-30 10:17:27 +00:00
webstreamr
8835219c26
chore(release): release v0.58.3 (#536) 2025-11-30 07:31:17 +00:00
WebStreamr
1dd9d324e6
chore: adapt default user agent slightly 2025-11-30 07:29:27 +00:00
WebStreamr
afbc16a3ec
revert: "chore: remove proxy headers in requests"
This reverts commit 6f6a3ae2fd.
2025-11-30 07:24:28 +00:00
webstreamr
98b9d5a81d
chore(release): release v0.58.2 (#533) 2025-11-29 18:31:14 +00:00
WebStreamr
6f6a3ae2fd
chore: remove proxy headers in requests 2025-11-29 18:29:51 +00:00
webstreamr
5300e6c6e7
chore(release): release v0.58.1 (#532) 2025-11-29 17:26:52 +01:00
WebStreamr
9f7155f42e
chore: switch to non-alpine based node image
to hopefully improve the tls fingerprint
2025-11-29 16:14:20 +00:00
webstreamr
362bef9f57
chore(release): release v0.58.0 (#480) 2025-11-29 13:23:10 +00:00
WebStreamr
4cd1e09466
chore(extractor): shorten mediaflow proxy label 2025-11-29 13:21:57 +00:00
WebStreamr
1765311586
chore(source): use more consistent meta title format 2025-11-29 12:54:34 +00:00
WebStreamr
68cfa65135
fix: use correct fetch for FlareSolverr 2025-11-29 12:37:35 +00:00
WebStreamr
b7930014ab
fix: use axios proxy config properly 2025-11-29 12:31:31 +00:00
WebStreamr
67d0508fd7
fix: ensure that VixSrc results have a title 2025-11-28 16:15:44 +00:00
WebStreamr
280abc4dea
chore: do not use proxy for MFP urls 2025-11-28 16:06:44 +00:00
GSTAR
e1c727b7de
Add all domains to streamtape extractoe (#489)
Co-authored-by: webstreamr <210764791+webstreamr@users.noreply.github.com>
2025-11-28 16:56:30 +01:00
WebStreamr
d07d31d94a
fix(source): update frembed domain and add referer for API requests 2025-11-28 15:51:48 +00:00
WebStreamr
09f795b862
feat: switch to axios to avoid Cloudflare TLS fingerprinting issues 2025-11-28 15:51:47 +00:00
WebStreamr
f5f47403cc
chore(extractor): remove non-working VidSrc 2025-11-28 15:27:51 +00:00
WebStreamr
31e46c5c56
fix: npm audit fix 2025-11-27 14:53:50 +00:00
renovate[bot]
219cb4b5f6
chore(deps): update dependency cacheable to v2.3.0 (#526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 02:56:56 +00:00
renovate[bot]
683e26e32a
chore(deps): update actions/checkout action to v6 (#515)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-25 10:12:43 +09:00
renovate[bot]
9bec5b5f3b
chore(deps): update dependency typescript-eslint to v8.48.0 (#525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 17:37:03 +00:00
renovate[bot]
4815054fed
chore(deps): lock file maintenance (#520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 01:06:04 +00:00
renovate[bot]
c50e095781
chore(deps): update dependency @stylistic/eslint-plugin to v5.6.1 (#513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 07:51:47 +00:00
renovate[bot]
b4ba098158
chore(deps): update dependency @stylistic/eslint-plugin to v5.6.0 (#512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 23:02:34 +00:00
renovate[bot]
836021c57e
chore(deps): update dependency @tsconfig/node22 to v22.0.5 (#511)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 06:17:39 +00:00
renovate[bot]
0a85b0dc1e
chore(deps): update dependency typescript-eslint to v8.47.0 (#510)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 23:34:19 +00:00
renovate[bot]
3b78186702
chore(deps): lock file maintenance (#508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 01:49:15 +00:00
renovate[bot]
dbbf310661
chore(deps): update dependency @types/node to v24.10.1 (#507)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 01:48:15 +00:00
renovate[bot]
7eb760b5d6
chore(deps): update dependency cacheable to v2.2.0 (#506)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-16 22:47:32 +00:00
renovate[bot]
e2268a325a
chore(deps): update dependency @tsconfig/node22 to v22.0.4 (#504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-16 06:11:43 +00:00
renovate[bot]
3f0c661dae
chore(deps): update dependency @tsconfig/node22 to v22.0.3 (#502)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 06:08:08 +00:00
renovate[bot]
7dca36fa83
chore(deps): update dependency nodemon to v3.1.11 (#499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 20:14:01 +00:00
renovate[bot]
159be1cac4
chore(deps): update dependency typescript-eslint to v8.46.4 (#497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 17:41:26 +00:00
renovate[bot]
e478a45bc0
chore(deps): lock file maintenance (#495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 01:17:33 +00:00
WebStreamr
d80ca63dad
ci: run build and test actions for PRs 2025-11-07 14:01:57 +00:00
renovate[bot]
57bc4640e5
chore(deps): lock file maintenance (#473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 00:40:06 +00:00
renovate[bot]
33dbeb9776
chore(deps): update eslint monorepo to v9.39.1 (#468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 00:37:04 +00:00
renovate[bot]
3c398d4a1d
chore(deps): update dependency typescript-eslint to v8.46.3 (#479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 20:30:05 +00:00
webstreamr
943f4c3453
chore(release): release v0.57.2 (#471) 2025-11-03 16:32:49 +01:00
WebStreamr
8e1ea10c5a
fix(extractor): fix KinoGer and add new domains 2025-11-03 11:12:14 +00:00
renovate[bot]
a7bc742035
chore(deps): update dependency @types/express to v5.0.5 (#472)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 01:15:06 +00:00
WebStreamr
82b810ed36
chore(extractor): register Vidora extractor 2025-11-02 21:57:50 +00:00
webstreamr
6eceaf474d
chore(release): release v0.57.1 (#470) 2025-11-02 22:56:23 +01:00
WebStreamr
88a1fe10a1
fix(source): switch to new Frembed domain 2025-11-02 21:54:42 +00:00
webstreamr
240504816c
chore(release): release v0.57.0 (#467) 2025-11-01 21:32:46 +01:00
WebStreamr
053dce0729
fix(extractor): remove flaky HubCloud head requests
potentially non-working URLs might be returned now from time to time, but that's still better than not returning working ones because of flakyness or blocking. should also improve performance slightly.

reverts changes from c819dd6
2025-11-01 20:29:47 +00:00
WebStreamr
a36d152b61
fix(extractor): support embed-only VOE urls 2025-11-01 20:22:00 +00:00
WebStreamr
e5a5c62106
fix(source): remove KinoGer
protected via CloudFlare challenge
2025-11-01 19:49:43 +00:00
webstreamr
db25f98251
feat(extractor): add Vidora extractor (#466) 2025-10-31 23:14:48 +01:00
webstreamr
ad7c9cc545
chore(release): release v0.56.8 (#462) 2025-10-29 16:07:14 +01:00
WebStreamr
331e5bd5d1
fix(source): add fallback search for english title to Kokoshka 2025-10-29 13:30:58 +00:00
WebStreamr
c4bf32f991
chore: allow Node 22 still 2025-10-29 13:30:58 +00:00
renovate[bot]
20c43a3ea1
chore(deps): update node.js to v24 2025-10-29 13:30:58 +00:00
WebStreamr
172edaf0fc
fix(source): fine-tune Kokoshka result matching 2025-10-29 13:30:53 +00:00
WebStreamr
4bc5e42e50
chore(extractor): cache not found results for 12 hours 2025-10-29 11:08:15 +00:00
WebStreamr
88b6a5ef45
fix(source): switch to working MegaKino domain 2025-10-29 11:04:08 +00:00
renovate[bot]
5078609c86
chore(deps): update dependency minimatch to v10.1.1 (#461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-29 03:41:36 +00:00
webstreamr
e6ef4b870a
chore(release): release v0.56.7 (#457) 2025-10-28 15:11:54 +00:00
WebStreamr
68dcab39fb
ci: build also for arm64
Co-authored-by: kapdon <94782486+kapdon@users.noreply.github.com>
2025-10-28 15:10:27 +00:00
WebStreamr
79c16d530d
ci: add workflow_dispatch trigger for build and tests 2025-10-28 15:03:35 +00:00
renovate[bot]
365c2de4e5
chore(deps): update dependency @types/node to v22.18.12 (#456)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 01:06:48 +00:00
renovate[bot]
7cabb4d4ab
chore(deps): update dependency @types/express to v5.0.4 (#455)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 01:04:00 +00:00
webstreamr
73476779ae
chore(release): release v0.56.6 (#454) 2025-10-24 14:57:43 +00:00
WebStreamr
bd1e184023
fix(extractor): handle Fastream deleted or expired file 2025-10-24 14:56:19 +00:00
webstreamr
cc7b0be150
chore(release): release v0.56.5 (#450) 2025-10-24 16:46:12 +02:00
WebStreamr
e2eaa9143b
chore(extractor): add VOE domain 2025-10-24 14:38:35 +00:00
WebStreamr
23da8dc637
chore(extractor): add KinoGer domains 2025-10-24 14:37:46 +00:00
WebStreamr
6203863523
chore: remove proxyConfig option 2025-10-24 14:33:39 +00:00
WebStreamr
e6d8d5076c
chore: remove noCache option 2025-10-24 14:30:57 +00:00
WebStreamr
cc3d30be2f
chore: add host info of relevant URL to all errors 2025-10-24 14:26:54 +00:00
WebStreamr
77164dfaa7
chore(extractor): do not make any requests for external URLs 2025-10-24 14:09:12 +00:00
WebStreamr
56e72b73fe
chore: cache playlist heights for a month 2025-10-24 14:07:04 +00:00
renovate[bot]
c8b6cf2732
chore(deps): update dependency typescript-eslint to v8.46.2 (#451)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 20:48:46 +00:00
renovate[bot]
a9b81d00c0
chore(deps): lock file maintenance (#449)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 01:05:31 +00:00
renovate[bot]
03bb9fea79
chore(deps): update dependency @types/node to v22.18.11 (#448)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 01:04:42 +00:00
webstreamr
bb733fca96
chore(release): release v0.56.4 (#443) 2025-10-18 20:10:42 +00:00
WebStreamr
ae7b784783
chore: mark VidSrc as being multi instead of English 2025-10-18 20:09:17 +00:00
WebStreamr
d59658fb7b
chore: switch to new VidSrc domains, pass-through TMDB IDs if given 2025-10-18 19:57:31 +00:00
webstreamr
20a4eb2f94
chore(release): release v0.56.3 (#440) 2025-10-18 18:53:31 +00:00
WebStreamr
ad61be8626
chore(extractor): add goofy-banana.com to Voe 2025-10-18 18:52:16 +00:00
renovate[bot]
209ec2673f
chore(deps): update dependency @stylistic/eslint-plugin to v5.5.0 (#441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 05:47:04 +00:00
renovate[bot]
6fffb57a8d
chore(deps): update dependency cacheable to v2.1.1 (#438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 01:56:36 +00:00
renovate[bot]
5d6a8145c3
chore(deps): update eslint monorepo to v9.38.0 (#439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 01:54:04 +00:00
webstreamr
00fcda4f12
chore(release): release v0.56.2 (#436) 2025-10-17 15:04:16 +00:00
WebStreamr
e989fc05d5
fix: only push lates tag, no need for invalid version tags 2025-10-17 15:02:53 +00:00
webstreamr
ea2c128937
chore(release): release v0.56.1 (#435) 2025-10-17 17:00:58 +02:00
WebStreamr
a93799036d
fix: push latest for release tags 2025-10-17 14:57:26 +00:00
webstreamr
ce2fac6c4c
chore(release): release v0.56.0 (#434) 2025-10-17 14:53:05 +00:00
WebStreamr
bcd868850d
chore: full remove Fsst 2025-10-17 14:51:43 +00:00
WebStreamr
eaa95e8cd9
docs: add more infos about proxy config 2025-10-17 14:50:01 +00:00
WebStreamr
a76e7254ff
docs: add info about MediaFlow Proxy 2025-10-17 14:48:03 +00:00
WebStreamr
14da572830
docs: default self hosting docs to official Docker image 2025-10-17 14:40:50 +00:00
WebStreamr
c7b507ca6b
docs: document environment variables 2025-10-17 14:27:19 +00:00
WebStreamr
6f60c49bd1
chore: throw explicit error if required TMDB_ACCESS_TOKEN env var is not set 2025-10-17 14:25:30 +00:00
WebStreamr
0018ccc929
feat: push docker latest build for tags 2025-10-17 14:02:14 +00:00
WebStreamr
177739e7cd
chore: tag 4KHDHub with Indian flag 2025-10-17 13:36:13 +00:00
webstreamr
5bf8096158
chore(release): release v0.55.1 (#433) 2025-10-16 21:01:00 +02:00
WebStreamr
066424e087
fix(source): query TMDB for Albanian info for Kokoshka 2025-10-16 18:57:14 +00:00
webstreamr
7e08a8090c
chore(release): release v0.55.0 (#431) 2025-10-16 14:58:21 +00:00
webstreamr
a4fb495e28
feat: add Albanian source Kokoshka (#430)
Co-authored-by: GSTAR <44748406+GLlgGL@users.noreply.github.com>
2025-10-16 16:57:07 +02:00
webstreamr
9ab69d8461
chore(release): release v0.54.4 (#421) 2025-10-15 20:27:38 +00:00
WebStreamr
1a56d7ef91
fix: add manual imdb -> tmdb mapping for "Monsters: The Lyle and Erik Menendez Story (2024)" 2025-10-15 20:26:16 +00:00
WebStreamr
c88b3798f0
fix(source): switch from imdb to tmdb search for CineHDPlus 2025-10-15 20:16:56 +00:00
WebStreamr
076f853a5b
fix(source): switch from imdb to tmdb search for StreamKiste 2025-10-15 20:16:32 +00:00
WebStreamr
405a8efad3
fix: add manual imdb -> tmdb mapping for "Monster: The Ed Gein Story(2025)" 2025-10-15 19:52:20 +00:00
WebStreamr
6f3233eefd
ci: remove explicit caching of node modules 2025-10-14 11:29:46 +00:00
renovate[bot]
3007698b1b
chore(deps): update actions/setup-node action to v6 (#426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 09:54:26 +00:00
WebStreamr
0830635947
chore: configure devEngines in package.json 2025-10-14 09:49:35 +00:00
renovate[bot]
c7040e9936
chore(deps): update dependency typescript-eslint to v8.46.1 (#425)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 20:31:06 +00:00
WebStreamr
da24e96fb4
chore: fully remove soaper 2025-10-13 10:36:15 +00:00
WebStreamr
a42edcd438
fix(source): remove PrimeWire
they started to limit redirect url resolve requests to one per second
2025-10-13 10:33:16 +00:00
renovate[bot]
cf1f3e9eed
chore(deps): lock file maintenance (#424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 01:55:30 +00:00
renovate[bot]
c5c4c1a41c
chore(deps): update dependency @types/node to v22.18.10 (#423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 01:54:28 +00:00
renovate[bot]
fbffe3aa67
chore(deps): update dependency ts-jest to v29.4.5 (#422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 14:15:02 +00:00
renovate[bot]
8761eee22f
chore(deps): update dependency @keyv/sqlite to v4.0.6 (#420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-10 10:01:45 +00:00
webstreamr
068017941b
chore(release): release v0.54.3 (#417) 2025-10-07 22:44:24 +02:00
WebStreamr
a3b90bf19a
chore: increase MFP related timeouts 2025-10-07 20:37:42 +00:00
WebStreamr
22c6754efc
refactor: use .at() instead of .slice() construct 2025-10-07 19:07:42 +00:00
WebStreamr
5e1c3eea24
perf(extractor): do not guess size from mp4 for Uqload 2025-10-07 19:01:41 +00:00
WebStreamr
e11cf8986e
perf(extractor): do not guess size from mp4 for Streamtape 2025-10-07 18:54:36 +00:00
WebStreamr
b8c5c1a4f3
perf(extractor): do not guess size from mp4 for DoodStream 2025-10-07 18:54:36 +00:00
WebStreamr
06e0ba23d7
fix(extractor): support Dropload with missing height/size info 2025-10-07 18:54:36 +00:00
WebStreamr
8fb3320868
perf(extractor): do not guess resolution via MFP playlist for Voe 2025-10-07 18:54:36 +00:00
WebStreamr
b3db546e00
perf(extractor): do not guess resolution via MFP playlist for LuluStream 2025-10-07 18:54:36 +00:00
WebStreamr
312f60660a
perf(extractor): do not guess resolution via MFP playlist for FileMoon 2025-10-07 18:54:36 +00:00
WebStreamr
bab0da2a8f
chore(extractor): use MEDIAFLOW_DEFAULT_INIT where it was forgotten 2025-10-07 18:54:36 +00:00
WebStreamr
a7360b68f0
perf(extractor): do not guess resolution via MFP playlist for FileLions 2025-10-07 18:54:36 +00:00
webstreamr
f1c00e2684
chore(release): release v0.54.2 (#416) 2025-10-07 11:41:49 +00:00
WebStreamr
298ac96558
fix(extractor): do not pass referer for Uqload MFP requests
deals with error_wrong_ip responses we got before
2025-10-07 11:39:47 +00:00
webstreamr
a54ae2b151
chore(release): release v0.54.1 (#415) 2025-10-06 18:56:39 +00:00
WebStreamr
d0958e8771
fix(extractor): handle another LuluStream file not found case 2025-10-06 18:55:08 +00:00
renovate[bot]
8b4aef94be
chore(deps): update dependency typescript-eslint to v8.46.0 (#414)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 18:46:53 +00:00
renovate[bot]
c19d46f406
chore(deps): update dependency cacheable to v2.1.0 (#413)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 18:44:13 +00:00
webstreamr
5170dde1d6
chore(release): release v0.54.0 (#410) 2025-10-06 13:17:43 +00:00
WebStreamr
c27c1f9541
chore(extractor): add KinoGer domain 2025-10-06 13:16:19 +00:00
WebStreamr
424c2fd1e0
fix(extractor): handle StreamEmbed video is not ready 2025-10-06 12:55:48 +00:00
WebStreamr
bb21bbc7fa
feat(extractor): add new MediaFlow Proxy extractors FileMoon, LuluStream and Voe 2025-10-06 12:52:59 +00:00
WebStreamr
d1493cbb57
fix(extractor): handle FileLions Not Found case 2025-10-06 09:43:42 +00:00
WebStreamr
30a709d619
fix(source): use PrimeWire page instead of API as Referer 2025-10-06 09:32:52 +00:00
WebStreamr
c610a995c2
chore(extractor): work around VidSrc blocking by generating random IPs 2025-10-06 08:23:04 +00:00
WebStreamr
78125c7f37
chore(extractor): add FileLions domain 2025-10-06 08:18:56 +00:00
WebStreamr
7191c59b8a
chore(extractor): re-use and optimise mediaflow default fetch init slightly 2025-10-06 08:00:43 +00:00
WebStreamr
eea8cac018
fix(extractor): remove referer header from Mixdrop 2025-10-06 07:48:55 +00:00
renovate[bot]
5a4f28e50a
chore(deps): lock file maintenance (#408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 01:51:33 +00:00
renovate[bot]
f7bb497432
chore(deps): update dependency @types/node to v22.18.8 (#407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 01:50:56 +00:00
renovate[bot]
b8360405dd
chore(deps): update eslint monorepo to v9.37.0 (#404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-03 20:36:35 +00:00
webstreamr
753e90bbe2
chore(release): release v0.53.7 (#402) 2025-10-02 15:40:42 +00:00
WebStreamr
ac7d2480ea
fix(extractor): bring back VixSrc language exclusion 2025-10-02 15:39:36 +00:00
webstreamr
26120d1662
chore(release): release v0.53.6 (#398) 2025-10-02 17:13:42 +02:00
WebStreamr
5ac359f1eb
chore(fetcher): retry unexpected exceptions 2025-10-02 15:07:57 +00:00
WebStreamr
34b7fddfc7
chore(extractor): configure new VidSrc TLDs 2025-10-02 15:05:01 +00:00
renovate[bot]
390adea9d4
chore(deps): update dependency cacheable to v2.0.3 (#400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 21:36:50 +00:00
renovate[bot]
7e4657c6ad
chore(deps): update dependency winston to v3.18.3 (#397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 03:07:10 +00:00
renovate[bot]
04069d83ea
chore(deps): update dependency typescript to v5.9.3 (#396)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 03:05:13 +00:00
webstreamr
0c0c2987e8
chore(release): release v0.53.5 (#395) 2025-09-30 22:17:35 +02:00
WebStreamr
012b1648e1
chore(extractor): make VixSrc work without MediaFlow proxy 2025-09-30 20:13:12 +00:00
WebStreamr
04b8987c30
chore: use concurrency of 4 for mediaflow proxy requests
by default mediaflow also runs with 4 workers, this should avoid
pressure
2025-09-30 19:29:11 +00:00
renovate[bot]
9f46b7fffa
chore(deps): update dependency winston to v3.18.2 (#394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 17:42:37 +00:00
webstreamr
b5d7e95a92
chore(release): release v0.53.4 (#393) 2025-09-30 14:53:57 +00:00
renovate[bot]
c3721c428e
chore(deps): update dependency winston to v3.18.1 (#392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 14:52:29 +00:00
WebStreamr
d6fb4c971b
fix(source): dynamically determine MegaKino base URL 2025-09-30 14:49:26 +00:00
WebStreamr
5466472b21
chore(fetcher): add generic fetch also returning response url 2025-09-30 14:45:47 +00:00
WebStreamr
f0a66e1984
fix(extractor): handle Uqload File Not Found 2025-09-30 08:10:30 +00:00
webstreamr
4598151034
chore(release): release v0.53.3 (#391) 2025-09-29 20:30:16 +00:00
WebStreamr
8f2e8e492f
chore(source): improve Eurostreaming search by trimming some special chars and supporting similarity search 2025-09-29 20:27:49 +00:00
WebStreamr
e327f3c734
chore(source): support similar match search in HomeCine 2025-09-29 20:15:23 +00:00
WebStreamr
20648ae4cd
chore(source): use original title as search fallback in HomeCine and don't enforce year check 2025-09-29 19:50:39 +00:00
renovate[bot]
4bf2cf632b
chore(deps): update dependency typescript-eslint to v8.45.0 (#389)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 19:49:12 +00:00
WebStreamr
0879531478
test: fix slugger length check in FetcherMock 2025-09-29 19:46:27 +00:00
webstreamr
4bb8e132f5
chore(release): release v0.53.2 (#386) 2025-09-29 10:51:23 +00:00
WebStreamr
fb689bf7da
fix(source): adapt MegaKino for new search 2025-09-29 10:50:04 +00:00
renovate[bot]
6c44fccf66
chore(deps): lock file maintenance (#385)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 01:09:41 +00:00
webstreamr
b670fc7109
chore(release): release v0.53.1 (#383) 2025-09-28 22:48:38 +02:00
WebStreamr
d8a4375783
chore: bring back mediaflow proxy redirect url generation
reverts parts of 25b326ee48
2025-09-28 20:47:01 +00:00
WebStreamr
60530343b4
fix: do not write out label twice if the same for source and extractor 2025-09-28 20:16:54 +00:00
WebStreamr
fcced2e122
chore(fetcher): add referer to fetch logging 2025-09-28 20:15:15 +00:00
WebStreamr
af5c3f2ae6
fix(source): improve Eurostream partial keyword matching 2025-09-28 20:04:38 +00:00
WebStreamr
be7c19a54f
chore: add referer explicitly to sources 2025-09-28 19:49:37 +00:00
renovate[bot]
708d3d785d
chore(deps): update jest monorepo to v30.2.0 (#384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-28 10:00:15 +00:00
WebStreamr
25b326ee48
chore: rework referer header passing and mediaflow proxy URL creation 2025-09-25 20:01:02 +00:00
webstreamr
447153ca3c
chore(release): release v0.53.0 (#381) 2025-09-25 16:52:50 +02:00
WebStreamr
1fb7d0c66b
feat: add source info 2025-09-25 14:51:26 +00:00
WebStreamr
ae342845f8
fix(source): use iframe src from API as referer for Movix 2025-09-25 14:32:58 +00:00
webstreamr
0ff0758b74
chore: lock to latest node major only (#382) 2025-09-25 09:11:35 +02:00
renovate[bot]
7760c13ea9
chore(deps): update node.js to v22.20.0 (#380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-24 18:57:42 +00:00
webstreamr
3757a3514b
chore(release): release v0.52.5 (#379) 2025-09-24 18:55:24 +00:00
WebStreamr
6214df7e8f
fix(source): set correct referer for Movix 2025-09-24 18:53:31 +00:00
WebStreamr
42d46accb6
fix: prefer using source referer for extractors if it exists 2025-09-24 18:53:07 +00:00
WebStreamr
ddc8133c5b
fix: always move errors up and external urls down 2025-09-24 18:34:53 +00:00
WebStreamr
a96d015a0c
refactor(fetcher): introduce json method 2025-09-24 18:34:53 +00:00
WebStreamr
b1244086cd
chore: add web emoji as icon 2025-09-24 18:15:51 +00:00
WebStreamr
da556d3523
chore(extractor): add DoodStream host 2025-09-24 18:06:51 +00:00
WebStreamr
6f412d736b
chore(extractor): add FileLions domain 2025-09-24 18:04:35 +00:00
WebStreamr
4697d150cf
chore(fetcher): double queue limit and timeouts 2025-09-24 18:04:00 +00:00
webstreamr
4c5f5a3514
chore(release): release v0.52.4 (#378) 2025-09-23 08:52:50 +00:00
WebStreamr
0a0bedacb4
chore(fetcher): complete proxy headers 2025-09-23 08:50:56 +00:00
WebStreamr
7e771c2644
revert: Revert "chore(fetcher): try to remove the proxy headers (again)"
This reverts commit 00bd11495f.
2025-09-23 08:32:59 +00:00
renovate[bot]
9709c31bcd
chore(deps): update dependency typescript-eslint to v8.44.1 (#376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 18:33:39 +00:00
renovate[bot]
2e3b6602af
fix(deps): update dependency cacheable to v2.0.2 (#377)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 18:31:07 +00:00
WebStreamr
6d5a511cb4
chore: remove XPrime
backend API responses are encrypted and use webassembly for decryption
2025-09-22 09:30:14 +00:00
renovate[bot]
1a2b40d6f2
chore(deps): lock file maintenance (#374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 02:38:48 +00:00
renovate[bot]
b7714d7d60
chore(deps): update dependency @types/node to v22.18.6 (#373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 02:38:02 +00:00
renovate[bot]
953a7914c9
chore(deps): update dependency @stylistic/eslint-plugin to v5.4.0 (#371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 17:59:00 +00:00
renovate[bot]
092d53ef9d
chore(deps): update eslint monorepo to v9.36.0 (#369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 14:44:16 +00:00
renovate[bot]
48b0924bd8
chore(deps): update dependency ts-jest to v29.4.4 (#368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-19 14:41:57 +00:00
webstreamr
a2af1ae21e
chore(release): release v0.52.3 (#363) 2025-09-18 08:54:27 +00:00
renovate[bot]
833c4870f8
chore(deps): update dependency ts-jest to v29.4.3 (#365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-18 08:52:59 +00:00
WebStreamr
a30b271a57
fix(source): improve 4KHDHub year and title matching 2025-09-18 08:50:44 +00:00
renovate[bot]
f9d8dc0b86
fix(deps): update dependency cacheable to v2.0.1 (#364)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 19:44:25 +00:00
WebStreamr
60f44c9603
revert: Revert "fix: remove turnstyle detection to check if it causes false-positives"
This reverts commit c10ca29fda.
2025-09-16 19:41:59 +00:00
webstreamr
1433d032f3
chore(release): release v0.52.2 (#360) 2025-09-16 14:50:54 +00:00
WebStreamr
d90cd8f984
fix(fetcher): only retry 503 and 504 2025-09-16 14:49:13 +00:00
WebStreamr
c10ca29fda
fix: remove turnstyle detection to check if it causes false-positives 2025-09-16 13:05:03 +00:00
WebStreamr
c819dd6bb7
fix(extractor): filter out non-accessible HubCloud streams 2025-09-16 12:40:01 +00:00
WebStreamr
d06c2934a8
fix: do not return cache headers when noCache is set 2025-09-16 12:09:42 +00:00
renovate[bot]
261897c699
chore(deps): update dependency typescript-eslint to v8.44.0 (#362)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 11:19:48 +00:00
renovate[bot]
6ef80d3166
chore(deps): update dependency ts-jest to v29.4.2 (#361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 10:43:52 +00:00
WebStreamr
aeb7ce5f54
fix(extractor): normalize Streamtape URLs 2025-09-15 14:02:37 +00:00
renovate[bot]
1028c663eb
chore(deps): lock file maintenance (#359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 00:26:42 +00:00
renovate[bot]
e33aa42b19
chore(deps): update dependency @types/node to v22.18.3 (#358)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 00:25:54 +00:00
webstreamr
48b6f9efb0
chore(release): release v0.52.1 (#357) 2025-09-14 23:12:05 +02:00
WebStreamr
f4d905833f
fix(source): re-implement PrimeWire in safer and more efficient way 2025-09-14 20:51:28 +00:00
webstreamr
65c73e8b6e
chore(release): release v0.52.0 (#355) 2025-09-14 21:31:32 +02:00
WebStreamr
737bf6aa94
feat: allow to disable extractors via env var 2025-09-14 19:30:32 +00:00
WebStreamr
f89d90619a
feat: allow to disable sources via env var 2025-09-14 19:29:22 +00:00
webstreamr
a3c25adc7a
chore(release): release v0.51.2 (#354) 2025-09-14 18:19:23 +00:00
WebStreamr
ae32376758
fix(fetcher): re-use proxy agents 2025-09-14 18:18:09 +00:00
WebStreamr
aded562304
fix(source): close jsdom window immediately after getting HTML 2025-09-14 18:12:41 +00:00
WebStreamr
8b479be6c5
fix: log more infos for uncaught errors 2025-09-14 18:12:28 +00:00
WebStreamr
8acbd37df3
revert: Revert recent reverts again
This reverts commit 78db179957.
2025-09-14 17:38:46 +00:00
webstreamr
cf6946bc4a
chore(release): release v0.51.1 (#351) 2025-09-14 15:40:25 +02:00
WebStreamr
78db179957
revert: Revert "chore(fetcher): log more infos about responses"
This reverts commit 731bd33470.
2025-09-14 13:37:17 +00:00
WebStreamr
9a279b53a4
revert: Revert "feat: allow to disable cache"
This reverts commit 4078f0c404.
2025-09-14 13:37:14 +00:00
WebStreamr
2aa9ad270e
revert: Revert "chore: log error count in final result log"
This reverts commit 2421913587.
2025-09-14 13:37:11 +00:00
WebStreamr
4ca87f0ff1
revert: Revert "chore(fetcher): log proxy usage, allow to disable proxy via config"
This reverts commit b67e1b85d6.
2025-09-14 13:37:08 +00:00
WebStreamr
dc610bfa64
revert: Revert "fix(deps): update dependency jsdom to v27 (#350)"
This reverts commit 58b50b49de.
2025-09-14 13:37:04 +00:00
renovate[bot]
58b50b49de
fix(deps): update dependency jsdom to v27 (#350)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-13 18:31:17 +00:00
webstreamr
a79b0bdfdd
chore(release): release v0.51.0 (#349) 2025-09-13 20:28:43 +02:00
WebStreamr
b67e1b85d6
chore(fetcher): log proxy usage, allow to disable proxy via config 2025-09-13 18:27:17 +00:00
WebStreamr
2421913587
chore: log error count in final result log 2025-09-13 17:59:21 +00:00
WebStreamr
4078f0c404
feat: allow to disable cache 2025-09-13 17:51:12 +00:00
WebStreamr
731bd33470
chore(fetcher): log more infos about responses 2025-09-13 17:41:28 +00:00
WebStreamr
4351ddc00d
revert: Revert "fix(extractor): use SuperVideo embed URls which are not triggering CF challenges"
This reverts commit 3a2178eb55.
2025-09-12 18:49:00 +00:00
webstreamr
941a61fb74
chore(release): release v0.50.4 (#348) 2025-09-12 20:36:18 +02:00
WebStreamr
a729dc85e0
chore: add url info to blocked error 2025-09-12 18:35:22 +00:00
webstreamr
491595934f
chore(release): release v0.50.3 (#347) 2025-09-12 20:27:19 +02:00
WebStreamr
d087318286
chore(fetcher): cache only 2xx and 404 responses 2025-09-12 18:24:19 +00:00
WebStreamr
5429b0cb7a
chore(extractor): set VidSrc referer again 2025-09-12 18:17:10 +00:00
WebStreamr
69da82b139
chore(fetcher): bring back error retry 2025-09-12 17:53:37 +00:00
WebStreamr
fdb9bff86b
chore: slightly clean-up extractor and source logging 2025-09-12 17:49:14 +00:00
WebStreamr
cb256de9f8
chore(source): disable Soaper 2025-09-12 17:41:18 +00:00
WebStreamr
b27126fd8e
fix(extractor): cache FileLions 2025-09-12 17:39:47 +00:00
webstreamr
b9c13b8a89
chore(release): release v0.50.2 (#346) 2025-09-12 13:22:31 +00:00
WebStreamr
2a47d97d5c
chore(fetcher): switch back to undici fetch 🤡 2025-09-12 13:20:59 +00:00
webstreamr
0f00aecde5
chore(release): release v0.50.1 (#345) 2025-09-12 11:54:32 +02:00
WebStreamr
7ee87ceb9e
chore(fetcher): remove interceptors and retries 2025-09-12 09:50:07 +00:00
webstreamr
81208ab99b
chore(release): release v0.50.0 (#344) 2025-09-12 10:15:35 +02:00
WebStreamr
b513a8926b
feat: add support for proxy per host using minimatch 2025-09-12 08:13:18 +00:00
WebStreamr
2f370d1b9c
refactor: create dispatcher per request 2025-09-12 07:30:27 +00:00
WebStreamr
ffd62a5d16
chore(extractor): remove Referer from follow-up VidSrc requests completely 2025-09-12 07:10:51 +00:00
webstreamr
7249c1fd96
chore(release): release v0.49.7 (#343) 2025-09-11 16:51:45 +02:00
WebStreamr
87893819b0
chore(fetcher): switch back to native fetch 🤡 2025-09-11 14:34:26 +00:00
webstreamr
c29ec82d6a
chore(release): release v0.49.6 (#342) 2025-09-11 15:33:53 +02:00
WebStreamr
98c53a15f2
fix(extractor): do not pass Referer for cloudnestra requests
seems to avoid some blocking interestingly 🙈
2025-09-11 12:42:57 +00:00
webstreamr
d190446187
chore(release): release v0.49.5 (#341) 2025-09-10 19:43:32 +00:00
WebStreamr
7f264b2503
chore: include imdb and tmdb IDs in XPrime requests 2025-09-10 19:42:18 +00:00
webstreamr
128c144ebf
chore(release): release v0.49.4 (#340) 2025-09-10 21:29:47 +02:00
WebStreamr
531ad7562a
fix(extractor): use correct xprime referer 2025-09-10 18:42:01 +00:00
webstreamr
c9e4dfce68
chore(release): release v0.49.3 (#339) 2025-09-10 15:25:51 +02:00
WebStreamr
057b2346dc
chore(extractor): clean-up header/referer passing 2025-09-10 13:21:36 +00:00
WebStreamr
7f2cf10021
fix(extractor): improve messy meta type 2025-09-10 13:21:02 +00:00
webstreamr
904f5de3e0
chore(release): release v0.49.2 (#338) 2025-09-10 11:26:52 +00:00
WebStreamr
00bd11495f
chore(fetcher): try to remove the proxy headers (again) 2025-09-10 11:25:16 +00:00
webstreamr
03b3512380
chore(release): release v0.49.1 (#337) 2025-09-10 10:13:59 +00:00
WebStreamr
c5da7c1997
fix(extractor): grab VixSrc height from playlist 2025-09-10 10:12:41 +00:00
renovate[bot]
8b5588936e
fix(deps): update dependency cacheable to v2 (#336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: WebStreamr <210764791+webstreamr@users.noreply.github.com>
2025-09-10 10:11:52 +00:00
WebStreamr
3a2178eb55
fix(extractor): use SuperVideo embed URls which are not triggering CF challenges
we unfortunately loose size and title info though
2025-09-10 10:01:33 +00:00
renovate[bot]
78a83c536e
fix(deps): update dependency undici to v7.16.0 (#335)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 01:31:56 +00:00
webstreamr
17e99101bc
chore(release): release v0.49.0 (#334) 2025-09-09 14:02:59 +02:00
WebStreamr
8430e5d0d1
feat(extractor): add FileLions extractor 2025-09-09 11:51:20 +00:00
renovate[bot]
0bfced503c
chore(deps): update dependency typescript-eslint to v8.43.0 (#333)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 18:12:47 +00:00
webstreamr
39681dc7f6
chore(release): release v0.48.0 (#332) 2025-09-08 09:34:47 +00:00
WebStreamr
39850dccf7
feat(extractor): support PixelServer in HubCloud
looks like it will limit download speed after a while, but some sources
only have that one..
2025-09-08 09:33:31 +00:00
webstreamr
327d297d58
chore(release): release v0.47.2 (#331) 2025-09-08 09:12:24 +00:00
WebStreamr
68232363ae
fix(extractor): find more FSL links 2025-09-08 09:11:07 +00:00
webstreamr
41f78b50e7
chore(release): release v0.47.1 (#330) 2025-09-08 09:03:57 +00:00
WebStreamr
c2983f0551
fix(source): handle missing 4KHDHub episodes more gracefully 2025-09-08 09:02:19 +00:00
WebStreamr
6577eda289
chore: add Norwegian, sort language list 2025-09-08 08:50:23 +00:00
webstreamr
ad4eea68ce
chore(release): release v0.47.0 (#325) 2025-09-08 08:33:35 +00:00
WebStreamr
e1b5e7bce1
chore(tmdb): use mutex to avoid double tmdb requests 2025-09-08 08:31:44 +00:00
WebStreamr
3b8481ed3f
chore: add infos about sources and extractors to description 2025-09-08 08:17:12 +00:00
WebStreamr
9f15a03a5a
chore: add more details to /live / ipcheck endpoint, add cloudnestra 2025-09-08 08:04:08 +00:00
WebStreamr
3380185449
chore: drop uuid in favor of node:crypto randomUUID 2025-09-08 07:38:24 +00:00
WebStreamr
b60f91a96a
chore: configure a bunch of more language mappings 2025-09-08 07:35:31 +00:00
WebStreamr
1ecb088b60
feat(extractor): add HubCloud with FSL 2025-09-08 07:35:31 +00:00
WebStreamr
8a1645fb81
feat(source): add 4KHDHub 2025-09-08 07:35:31 +00:00
renovate[bot]
224b241cd6
chore(deps): lock file maintenance (#329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 01:47:05 +00:00
renovate[bot]
907a150574
chore(deps): update dependency @types/node to v22.18.1 (#328)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 01:46:17 +00:00
WebStreamr
48c7735165
fix(extractor): gracefully handle deleted files for SaveFiles 2025-09-06 19:31:21 +00:00
renovate[bot]
55983990fb
chore(deps): update eslint monorepo to v9.35.0 (#324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 16:37:06 +00:00
webstreamr
120f10139c
chore(release): release v0.46.5 (#323) 2025-09-05 08:54:32 +00:00
WebStreamr
b589c25bb5
chore(extractor): do only one VidSrc request per domain at a time 2025-09-05 08:52:57 +00:00
WebStreamr
211babec37
chore(fetcher): wait out short rate limits and retry 2025-09-05 08:52:36 +00:00
WebStreamr
e846900384
fix(fetcher): fix TTL for rate limit cache 2025-09-05 07:35:06 +00:00
webstreamr
ad7444ed97
chore(release): release v0.46.4 (#322) 2025-09-04 11:37:50 +00:00
WebStreamr
af4d0010fb
chore(source): retry also blocking errors with different VidSrc TLD 2025-09-04 11:36:37 +00:00
webstreamr
fde6128757
chore(release): release v0.46.3 (#321) 2025-09-04 10:30:23 +02:00
WebStreamr
cf79473fad
chore: allow forcing the IP check in the /live endpoint 2025-09-04 08:07:55 +00:00
webstreamr
ff62ea0597
chore(release): release v0.46.2 (#319) 2025-09-04 07:43:24 +00:00
WebStreamr
ad7e480213
fix(extractor): remove non-working VidSrc pm TLD 2025-09-04 07:42:16 +00:00
renovate[bot]
139cb64ca5
chore(deps): update actions/setup-node action to v5 (#320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 06:40:25 +02:00
WebStreamr
cfcf81b665
refactor: simplify passing around meta 2025-09-03 19:55:58 +00:00
WebStreamr
4002301fa6
fix(source): VixSrc is not an English source 2025-09-03 17:44:07 +00:00
webstreamr
b0a251c21e
chore(release): release v0.46.1 (#318) 2025-09-03 16:49:34 +02:00
WebStreamr
756cd01ab9
chore: decrease fetcher and extractor memory cache sizes 2025-09-03 14:39:27 +00:00
webstreamr
c7e87e14b9
chore(release): release v0.46.0 (#316) 2025-09-03 15:02:24 +02:00
WebStreamr
fd0900e053
feat: add stats endpoint with cache stats 2025-09-03 13:00:07 +00:00
WebStreamr
5ea34f66dd
fix(source): make cache prop static 2025-09-03 12:48:21 +00:00
WebStreamr
5547fd5486
fix(source): move countryCode filtering into central place 2025-09-03 12:18:56 +00:00
WebStreamr
12ac42ef5f
chore: no special sorting for errors 2025-09-03 11:05:16 +00:00
WebStreamr
89790609db
feat(source): persist PrimeWire redirect URL cache 2025-09-03 11:03:03 +00:00
WebStreamr
2b7e9c403d
feat: configure sqlite as secondary source and extractor cache 2025-09-03 10:56:13 +00:00
WebStreamr
94abcd3f0a
chore: add ipStatus to /live endpoint 2025-09-03 10:29:00 +00:00
WebStreamr
83021fb773
chore(fetcher): decrease timeout to 5 seconds 2025-09-03 10:28:48 +00:00
webstreamr
63ada6099a
chore(release): release v0.45.3 (#315) 2025-09-02 15:08:17 +00:00
WebStreamr
833043143f
chore(extractor): cache YouTube for 6h 2025-09-02 15:06:55 +00:00
WebStreamr
41aacf84db
chore(extractor): cache ExternalUrl for 6h 2025-09-02 15:05:51 +00:00
WebStreamr
1b89cadab5
chore: always return integers for Cache-Control max-age 2025-09-02 14:51:31 +00:00
WebStreamr
969ab8d0d7
chore(extractor): start refreshing results randomly after at least 2/3 of the TTL passed 2025-09-02 14:41:55 +00:00
renovate[bot]
896ec74959
chore(deps): update dependency jest to v30.1.3 (#314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 14:30:31 +00:00
WebStreamr
316fd4621b
chore(extractor): cache KinoGer for 6h 2025-09-02 14:28:45 +00:00
WebStreamr
bb95118d2b
chore(extractor): cache SaveFiles for 6h 2025-09-02 14:28:04 +00:00
WebStreamr
fc17545860
chore(extractor): cache XPrime for 6h 2025-09-02 14:27:23 +00:00
WebStreamr
a250440670
chore(extractor): cache Doodstream for 6h 2025-09-02 14:26:25 +00:00
WebStreamr
1e100e6d2e
chore(extractor): cache Soaper for 12h 2025-09-02 13:48:01 +00:00
WebStreamr
ccf8006f13
chore(extractor): cache Dropload for 3h 2025-09-02 13:40:22 +00:00
WebStreamr
7ac71837b3
chore(extractor): cache SuperVideo for 3h 2025-09-02 13:39:39 +00:00
WebStreamr
c68570d2bc
chore(extractor): cache VidSrc for 3h 2025-09-02 13:38:51 +00:00
WebStreamr
bc06cb5750
chore(source): cache sources for 12h 2025-09-02 13:37:26 +00:00
WebStreamr
dc093f7109
refactor: consistently use cacheable for caching 2025-09-02 13:33:51 +00:00
renovate[bot]
50764b3602
chore(deps): update dependency typescript-eslint to v8.42.0 (#312)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 12:32:14 +00:00
webstreamr
bd38329fd8
chore(release): release v0.45.2 (#310) 2025-09-01 22:12:06 +02:00
WebStreamr
ceb520d49d
fix(source): consider season and episode for cache key 2025-09-01 20:10:30 +00:00
WebStreamr
b47a512587
fix: catch all timeouts 2025-09-01 18:33:42 +00:00
WebStreamr
2468fbde4e
chore(source): remove outdated comment 2025-09-01 18:33:42 +00:00
WebStreamr
e109637e48
refactor: move locks into StreamController 2025-09-01 10:58:05 +00:00
WebStreamr
4d05ac859f
chore: handle NotFoundError / 404 inside source and cache it also 2025-09-01 10:53:59 +00:00
renovate[bot]
285746a4a9
chore(deps): update dependency @stylistic/eslint-plugin to v5.3.1 (#309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 10:37:17 +00:00
webstreamr
8154527533
chore(release): release v0.45.1 (#307) 2025-09-01 12:35:45 +02:00
renovate[bot]
4414324a08
chore(deps): update jest monorepo to v30.1.2 (#308)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 08:20:06 +00:00
WebStreamr
c47f48c64b
chore(fetcher): log retries 2025-09-01 08:17:59 +00:00
WebStreamr
0fc7675560
chore(fetcher): retry timeouts once 2025-08-31 20:12:04 +00:00
WebStreamr
3c4507b198
test: disable console logging to reduce noise 2025-08-31 20:06:33 +00:00
WebStreamr
c556d9500b
chore(extractor): support caching via MediaFlow Proxy 2025-08-31 20:06:33 +00:00
WebStreamr
33ee150915
fix(extractor): set missing viaMediaFlowProxy in Streamtape 2025-08-31 19:22:48 +00:00
WebStreamr
c82a7ab756
chore(source): cache source results for 3 hours 2025-08-31 19:14:43 +00:00
WebStreamr
9da7c07dc6
refactor(source): introduce abstract parent class 2025-08-31 18:52:42 +00:00
renovate[bot]
3044e41d9b
chore(deps): update node.js to v22.19.0 (#306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 10:02:09 +00:00
webstreamr
75363c72cf
chore(release): release v0.45.0 (#293) 2025-08-28 21:21:55 +02:00
WebStreamr
18d8ee6b79
chore(extractor): use guessSizeFromMp4 in Streamtape, Uqload and XPrime 2025-08-28 19:17:21 +00:00
WebStreamr
4a1a3b19aa
refactor: introduce guessSizeFromMp4 2025-08-28 19:01:14 +00:00
WebStreamr
cefc80fa55
chore(extractor): move Fetcher into parent, guess missing heights for SuperVideo 2025-08-28 18:58:15 +00:00
renovate[bot]
ada3ff1926
chore(deps): update jest monorepo to v30.1.1 (#305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 19:42:12 +00:00
renovate[bot]
b4dca46e5e
chore(deps): update jest monorepo to v30.1.0 (#304)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 06:52:02 +00:00
renovate[bot]
0449150874
chore(deps): update dependency typescript-eslint to v8.41.0 (#302)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 19:07:18 +00:00
renovate[bot]
2381a2c2ad
chore(deps): lock file maintenance (#300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 14:30:00 +00:00
WebStreamr
c8cd2ec794
chore(extractor): add more KinoGer domains 2025-08-25 14:29:58 +00:00
renovate[bot]
b616fef018
chore(deps): update eslint monorepo to v9.34.0 (#298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 22:50:41 +00:00
renovate[bot]
41b44252b1
fix(deps): update dependency undici to v7.15.0 (#297)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 19:37:38 +00:00
WebStreamr
c7c2ebf353
feat: support TMDB IDs 2025-08-22 14:49:15 +00:00
WebStreamr
eecd3255f0
fix(extractor): handle SaveFiles /d/ links properly 2025-08-21 11:28:15 +00:00
WebStreamr
05d11e366b
chore: configure undici dispatcher allowing HTTP2, caching DNS and retrying connection errors 2025-08-21 09:11:58 +00:00
WebStreamr
795c033a82
revert: Revert "chore(source): disable Soaper"
This reverts commit 71979d82b6.
2025-08-20 21:24:21 +00:00
WebStreamr
b6a8e6f448
feat(source): add Einschalten 2025-08-19 09:50:04 +00:00
WebStreamr
2d9f9bf1af
chore(extractor): add more KinoGer domains and determine resolution 2025-08-19 09:35:35 +00:00
renovate[bot]
14da5da205
chore(deps): update dependency typescript-eslint to v8.40.0 (#292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 21:04:45 +00:00
webstreamr
c8afe6995d
chore(release): release v0.44.1 (#291) 2025-08-18 18:35:14 +02:00
WebStreamr
35acb49545
fix(extractor): remove unreliable title height guessing 2025-08-18 13:17:42 +00:00
WebStreamr
36f3d70467
chore(extractor): extract size and title for Fastream URLs 2025-08-18 12:30:28 +00:00
WebStreamr
bfdb212770
fix(source): do alternative dash search for HomeCine 2025-08-18 12:12:53 +00:00
renovate[bot]
747ea84880
chore(deps): lock file maintenance (#290)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 00:41:54 +00:00
renovate[bot]
8a24dbcd24
chore(deps): update dependency @types/node to v22.17.2 (#289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 00:41:15 +00:00
webstreamr
6b8e9d7ac6
chore(release): release v0.44.0 (#288) 2025-08-17 19:00:58 +00:00
WebStreamr
5e6c71f4ad
feat(extractor): add YouTube extractor 2025-08-17 18:59:29 +00:00
WebStreamr
f58c30299a
chore(source): bring back Frembed 2025-08-17 18:28:24 +00:00
WebStreamr
71979d82b6
chore(source): disable Soaper 2025-08-17 18:25:09 +00:00
WebStreamr
176c4c93e7
refactor: fully replace handler with source 2025-08-17 18:22:04 +00:00
WebStreamr
73627fbf0d
revert: "chore(extractor): remove HEAD request to XPrime hosters to determine size"
This reverts commit fdf1415f6d.
2025-08-17 18:17:07 +00:00
renovate[bot]
fe19a91cec
fix(deps): update dependency undici to v7.14.0 (#287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-17 13:31:22 +00:00
webstreamr
c34a1eafcc
chore(release): release v0.43.3 (#285) 2025-08-16 22:05:07 +00:00
WebStreamr
63e598a725
fix(extractor): use default TTL for XPrime 2025-08-16 22:03:34 +00:00
webstreamr
9897fa8225
chore(release): release v0.43.2 (#281) 2025-08-16 18:55:59 +02:00
WebStreamr
ef613f2742
chore: double HTTP cache, quadruple extractor cache 2025-08-16 16:40:15 +00:00
WebStreamr
5316199b8c
chore(extractor): increase XPrime cache to 12 hours 2025-08-16 16:24:49 +00:00
WebStreamr
fdf1415f6d
chore(extractor): remove HEAD request to XPrime hosters to determine size
they seem to be currently rate-limiting and the size info is not that
useful here
2025-08-16 16:24:43 +00:00
webstreamr
ac0e54f39f
chore(release): release v0.43.1 (#280) 2025-08-15 13:45:00 +00:00
WebStreamr
a65b8fb507
fix(extractor): use title from XPrime API 2025-08-15 13:43:30 +00:00
WebStreamr
be33b0b3b9
chore: use XPrime in live endpoint 2025-08-15 12:26:12 +00:00
renovate[bot]
7b7b0e1e7e
fix(deps): update dependency tough-cookie to v6 (#278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 13:11:32 +02:00
webstreamr
f15b00710b
chore(release): release v0.43.0 (#279) 2025-08-15 12:37:44 +02:00
WebStreamr
cfe38878d7
feat(source): add XPrime 2025-08-15 10:35:56 +00:00
WebStreamr
9854d3b5b6
test: use proxy in tests 2025-08-15 10:32:43 +00:00
WebStreamr
fe5b3af887
chore(fetcher): allow to customize minimum cache ttl 2025-08-15 08:54:14 +00:00
WebStreamr
2a5284d555
refactor(fetcher): sort custom request init props 2025-08-15 08:50:38 +00:00
webstreamr
b1a4b8017f
chore(release): release v0.42.1 (#277) 2025-08-14 10:24:15 +02:00
WebStreamr
c0797f3687
chore: use mutex per id when resolving streams 2025-08-14 08:20:23 +00:00
WebStreamr
6492e6be82
chore(extractor): use all VidSrc TLDs to handle rate-limit better 2025-08-14 07:35:03 +00:00
WebStreamr
285996cbf6
fix(extractor): handle SaveFiles locked file 2025-08-13 20:34:20 +00:00
webstreamr
c3fb400116
chore(release): release v0.42.0 (#276) 2025-08-13 12:51:17 +02:00
WebStreamr
a12ae7aaa5
chore(fetcher): retry 5xx errors up to 3 times 2025-08-13 10:49:36 +00:00
WebStreamr
17da20f6d0
fix: support stream resolving without config 2025-08-13 10:28:28 +00:00
WebStreamr
3e964f28ff
refactor(extractor): clean-up DoodStream sightly 2025-08-13 09:59:34 +00:00
WebStreamr
c9aaf8bb8f
feat(extractor): add SaveFiles support 2025-08-13 09:57:08 +00:00
webstreamr
7d85f37c21
chore(release): release v0.41.9 (#275) 2025-08-12 17:19:34 +02:00
WebStreamr
e28de7d4d9
revert: "fix(fetcher): remove weird proxy headers in request"
This reverts commit ee09e10e6e.
2025-08-12 15:15:03 +00:00
WebStreamr
8a81a5c14e
chore(extractor): add new DoodStream domains 2025-08-12 14:17:05 +00:00
WebStreamr
2d96e1db8f
fix(source): keep using jsdom for now because of performance issues 2025-08-12 13:57:48 +00:00
WebStreamr
1a5cad40a2
chore: clean-up critical error handling 2025-08-12 13:01:35 +00:00
webstreamr
c76ec91d15
chore(release): release v0.41.8 (#270) 2025-08-12 14:44:22 +02:00
WebStreamr
617ae77825
fix(source): bring back PrimeWire 🎉
- use happy-dom instead of jsdom
- grab link token from app JS to resolve links
2025-08-12 12:41:21 +00:00
renovate[bot]
ec8c35af18
chore(deps): update dependency typescript-eslint to v8.39.1 (#269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 18:26:26 +00:00
webstreamr
47dc0d57a8
chore(release): release v0.41.7 (#268) 2025-08-11 15:50:19 +00:00
renovate[bot]
ccbebe7516
chore(deps): update actions/checkout action to v5 (#267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 15:49:12 +00:00
WebStreamr
9a019396c8
fix(fetcher): simplify header and ttl determination, fix edge case bug 2025-08-11 15:47:04 +00:00
webstreamr
083bbb8e9b
chore(release): release v0.41.6 (#266) 2025-08-11 12:10:58 +00:00
WebStreamr
ee09e10e6e
fix(fetcher): remove weird proxy headers in request
looks like this is finally causing weird issues and leading to some
Cloudflare blocks. we might be better of without it, let's try..
2025-08-11 12:09:46 +00:00
WebStreamr
0efdfa4a6f
fix(fetcher): better detect inline Cloudflare Turnstile 2025-08-11 11:40:58 +00:00
webstreamr
24d2cff3f7
chore(release): release v0.41.5 (#262) 2025-08-11 08:46:06 +00:00
WebStreamr
6489b10527
chore(source): add English to VixSrc
It returns English links in most of the cases, this will unlock more
URLs for people only having English selected and MediaFlow Proxy configured
2025-08-11 08:44:33 +00:00
WebStreamr
491df54242
chore(manifest): sort sources for each language in configuration 2025-08-11 08:44:26 +00:00
WebStreamr
3aff950598
chore(manifest): add supported languages to description 2025-08-11 08:38:56 +00:00
WebStreamr
7d700103c4
chore(fetcher): decrease default timeout back to 10s 2025-08-11 08:38:56 +00:00
WebStreamr
c3d86fb47f
chore(source): disable PrimeWire for now 2025-08-11 08:38:56 +00:00
WebStreamr
b7ada8bbdf
chore: disable console methods only on prod 2025-08-11 08:38:56 +00:00
renovate[bot]
6d44e67a45
chore(deps): lock file maintenance (#265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 00:57:15 +00:00
renovate[bot]
1618fa3c32
chore(deps): update dependency @types/node to v22.17.1 (#264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 00:56:52 +00:00
renovate[bot]
f7b6ba435b
chore(deps): update eslint monorepo to v9.33.0 (#263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-08 21:47:51 +00:00
renovate[bot]
e365538699
chore(deps): update dependency @stylistic/eslint-plugin to v5.2.3 (#261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-08 13:08:36 +00:00
webstreamr
6285760bad
chore(release): release v0.41.4 (#260) 2025-08-08 08:28:48 +00:00
WebStreamr
78ced84774
chore(fetcher): block a host temporarily after 30 times timing out instead of 10 2025-08-08 08:27:40 +00:00
WebStreamr
785091f340
chore: remove health endpoint in favor of live 2025-08-08 08:27:37 +00:00
WebStreamr
8cd5c91aea
chore: add MostraGuarda to live/health check 2025-08-08 08:23:48 +00:00
webstreamr
c14e1c008a
chore(release): release v0.41.3 (#258) 2025-08-06 09:10:38 +00:00
WebStreamr
db21d78aff
chore: introduce dedicated startup/ready and live endpoints 2025-08-06 09:09:19 +00:00
webstreamr
ff538303c6
chore(release): release v0.41.2 (#257) 2025-08-05 15:04:30 +02:00
WebStreamr
dbe27c29da
fix: do not use special queue config for health check 2025-08-05 12:19:18 +00:00
webstreamr
2ab20dc9b8
chore(release): release v0.41.1 (#256) 2025-08-05 13:33:32 +02:00
WebStreamr
522034b61c
chore: use source instances instead of hard-coded URL for health check 2025-08-05 10:10:53 +00:00
WebStreamr
faa91ce238
fix(source): remove useless KinoGer p2pplay.pro URLs 2025-08-05 10:06:11 +00:00
WebStreamr
009bb930b5
chore(fetcher): increase default timeout to 15s
in most of the cases we're still very fast anyway
but in some of the small cases let's avoid timeout blocks by waiting
longer
2025-08-05 10:01:30 +00:00
WebStreamr
086c32f980
chore: health check improvements to avoid performance issues
- only once per minute
- concurrency 1
- use default timeout
2025-08-05 09:56:11 +00:00
renovate[bot]
115e2278d8
chore(deps): update dependency typescript to v5.9.2 (#255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 21:23:02 +00:00
renovate[bot]
d6fd34668d
chore(deps): update dependency typescript-eslint to v8.39.0 (#254)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 21:20:39 +00:00
renovate[bot]
8ed63213bb
chore(deps): update node.js to v22.18.0 (#252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 16:39:33 +00:00
webstreamr
6bf3dc3e2b
chore(release): release v0.41.0 (#244) 2025-08-04 11:55:24 +02:00
WebStreamr
854b211f2c
feat: do not show errors by default 2025-08-04 09:52:48 +00:00
WebStreamr
c81f17a19a
chore: use source baseUrl for source errors 2025-08-04 09:29:35 +00:00
WebStreamr
b94c539269
refactor(source): consistently define baseUrl 2025-08-04 09:27:25 +00:00
renovate[bot]
9f5652eb15
chore(deps): lock file maintenance (#251)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 02:56:26 +00:00
renovate[bot]
e270737882
chore(deps): update dependency @types/node to v22.17.0 (#250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 02:55:24 +00:00
renovate[bot]
9bf5011882
chore(deps): update dependency ts-jest to v29.4.1 (#249)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-03 14:03:47 +00:00
renovate[bot]
43d971a147
fix(deps): update dependency undici to v7.13.0 (#248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 09:48:18 +00:00
renovate[bot]
f5d3e95884
chore(deps): lock file maintenance (#246)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 03:05:40 +00:00
renovate[bot]
0dc9d4bf35
chore(deps): update eslint monorepo to v9.32.0 (#243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-25 15:15:37 +00:00
webstreamr
5728111fd2
chore(release): release v0.40.3 (#242) 2025-07-24 10:21:03 +02:00
WebStreamr
9a8e6f9c69
fix(health check): better logging, decrease request timeouts 2025-07-24 08:18:46 +00:00
WebStreamr
cc3dacdd76
refactor: extract error and logging helper 2025-07-24 08:16:40 +00:00
webstreamr
b15d93eca8
chore(release): release v0.40.2 (#241) 2025-07-23 07:45:03 +00:00
WebStreamr
ece3b45f59
fix: disable health check failing for blocked issues on non-hayd.uk instances
until there is a proper VPN/pod restart solution
2025-07-23 07:43:19 +00:00
WebStreamr
be8c664b47
refactor: extract helpers to figure out which instance we're running on 2025-07-23 07:40:41 +00:00
renovate[bot]
4ab0c88262
chore(deps): update dependency @stylistic/eslint-plugin to v5.2.2 (#240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 18:44:48 +00:00
webstreamr
337ced8467
chore(release): release v0.40.1 (#239) 2025-07-22 08:29:13 +00:00
WebStreamr
5803148837
fix(source): disable Frembed for now since it keeps timing-out 2025-07-22 08:28:05 +00:00
renovate[bot]
93194e37c9
chore(deps): update jest monorepo to v30.0.5 (#238)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-22 02:59:44 +00:00
webstreamr
ebcdc5ebd3
chore(release): release v0.40.0 (#237) 2025-07-21 20:05:12 +00:00
WebStreamr
951d442a7d
chore: mention languages and MediaFlow Proxy in description 2025-07-21 20:04:08 +00:00
WebStreamr
ef36f48e33
chore: try checking for blocks in health check endpoint 2025-07-21 20:04:08 +00:00
WebStreamr
51b9104f07
chore: handle cloudflare 451 censor block, show block reasons 2025-07-21 19:35:52 +00:00
WebStreamr
114d296fe3
feat: disable extractors via config 2025-07-21 19:18:20 +00:00
renovate[bot]
3c657680cb
chore(deps): update dependency typescript-eslint to v8.38.0 (#236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 19:10:52 +00:00
WebStreamr
6fe09fc459
refactor: improve manifest type 2025-07-21 19:08:21 +00:00
WebStreamr
453cc747a1
refactor: introduce createSources() analogue to createExtractors() 2025-07-21 14:39:13 +00:00
renovate[bot]
3d76b0a7b7
fix(deps): update dependency cheerio to v1.1.2 (#235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 11:09:34 +00:00
renovate[bot]
ff45f431ee
chore(deps): update dependency @stylistic/eslint-plugin to v5.2.1 (#234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 11:07:08 +00:00
renovate[bot]
b0dd5de51d
chore(deps): lock file maintenance (#231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 01:40:03 +00:00
renovate[bot]
ea7b39200f
chore(deps): update dependency @types/node to v22.16.5 (#230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 01:39:32 +00:00
renovate[bot]
3401525ab6
fix(deps): update dependency cheerio to v1.1.1 (#228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-20 21:34:46 +00:00
webstreamr
ee411709fc
chore(release): release v0.39.2 (#227) 2025-07-20 20:46:46 +02:00
WebStreamr
674eb7ca6d
fix(fetcher): support Basic Auth 2025-07-20 18:44:37 +00:00
WebStreamr
c62870a465
fix(source): use latest search params for Eurostreaming 2025-07-20 18:27:07 +00:00
webstreamr
75a5133004
chore(release): release v0.39.1 (#225) 2025-07-20 14:01:52 +02:00
WebStreamr
8ad5a8f8e6
chore: add explicit info that we can't fix 5xx errors 2025-07-20 11:45:01 +00:00
WebStreamr
d1e410ef33
chore: disable all console functions in favor of logger 2025-07-20 11:33:43 +00:00
WebStreamr
1a37662502
fix: use correct request ID header in context 2025-07-20 11:30:40 +00:00
webstreamr
70e6e670f3
chore(release): release v0.39.0 (#224) 2025-07-18 20:40:24 +02:00
WebStreamr
f3f61c85cc
feat(extractor): add Fastream via MediaFlow Proxy 2025-07-18 18:38:43 +00:00
WebStreamr
5759a311aa
feat(source): add HomeCine 2025-07-18 18:38:11 +00:00
WebStreamr
533470d080
fix(extractor): do not skip external blocked URLs 2025-07-18 17:47:28 +00:00
WebStreamr
f6dcd74dfb
refactor: make TMDB detail fetching more DRY 2025-07-18 16:42:46 +00:00
renovate[bot]
603bcff935
fix(deps): update dependency undici to v7.12.0 (#223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-18 15:38:10 +00:00
webstreamr
b31e029145
chore(release): release v0.38.1 (#222) 2025-07-18 09:24:52 +00:00
WebStreamr
de8e0bdd94
fix: do not use cache and proxy headers for health requests 2025-07-18 09:23:45 +00:00
webstreamr
258859462f
chore(release): release v0.38.0 (#221) 2025-07-18 08:20:32 +00:00
WebStreamr
cd19f1c999
feat: add health endpoint 2025-07-18 08:19:20 +00:00
WebStreamr
5555ba7040
refactor: extract context creation 2025-07-18 08:18:55 +00:00
webstreamr
21ad16861a
chore(release): release v0.37.2 (#220) 2025-07-17 10:02:00 +02:00
WebStreamr
1b9ee36cfd
revert: "chore: remove Soaper 😭" 🎉
This reverts commit df60013e16.
2025-07-17 07:56:54 +00:00
webstreamr
72e2f33074
chore(release): release v0.37.1 (#218) 2025-07-16 22:17:30 +02:00
renovate[bot]
1fda7f6c50
chore(deps): update dependency @stylistic/eslint-plugin to v5.2.0 (#219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 10:59:35 +00:00
WebStreamr
7cf313cedb
chore(extractor): normalize Mixdrop non-embed URLs 2025-07-16 10:56:51 +00:00
webstreamr
45febec802
chore(release): release v0.37.0 (#217) 2025-07-15 21:52:43 +00:00
WebStreamr
8f7039bbb3
feat(source): add PrimeWire 2025-07-15 21:46:54 +00:00
WebStreamr
69afcdcdb2
chore(extractor): normalize Dropload download URLs 2025-07-15 21:02:57 +00:00
WebStreamr
e03621db0b
fix(fetcher): import correct Headers 2025-07-15 20:08:45 +00:00
renovate[bot]
f701ed39ef
chore(deps): update dependency typescript-eslint to v8.37.0 (#215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 23:13:18 +00:00
webstreamr
d70013ee8e
chore(release): release v0.36.2 (#213) 2025-07-14 19:17:39 +02:00
WebStreamr
25d32117ed
fix(extractor): support DoodStream via cloudflarestorage 2025-07-14 15:58:26 +00:00
WebStreamr
a371f86933
fix(source): handle non-existent movies gracefully 2025-07-14 14:46:51 +00:00
WebStreamr
d6e29185e5
chore: sort errors down 2025-07-14 14:42:04 +00:00
WebStreamr
df60013e16
chore: remove Soaper 😭
Refs: https://www.reddit.com/r/Piracy/comments/1lux17l/soaperlive_down_again/
2025-07-14 13:53:50 +00:00
renovate[bot]
7e2858def3
chore(deps): lock file maintenance (#212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 01:38:16 +00:00
renovate[bot]
ae9c43edef
chore(deps): update dependency @types/node to v22.16.3 (#211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 01:37:36 +00:00
webstreamr
c5311e7c0b
chore(release): release v0.36.1 (#210) 2025-07-13 19:34:51 +00:00
WebStreamr
599e6eaf7e
fix(source): handle non-existent content gracefully via Movix 2025-07-13 19:33:54 +00:00
webstreamr
c60e958920
chore(release): release v0.36.0 (#209) 2025-07-13 19:22:58 +00:00
WebStreamr
ca0db32d19
feat(source): add Movix 2025-07-13 19:21:13 +00:00
webstreamr
5e13042c71
chore(release): release v0.35.0 (#207) 2025-07-13 20:54:01 +02:00
WebStreamr
a4e24722c2
feat(source): fix Frembed domain and enable it for movies 2025-07-13 18:43:49 +00:00
renovate[bot]
b4d556399a
chore(deps): update eslint monorepo to v9.31.0 (#206)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-11 23:13:51 +00:00
webstreamr
3136d94d69
chore(release): release v0.34.1 (#199) 2025-07-11 19:06:21 +02:00
WebStreamr
2c210941aa
refactor(fetcher): switch to undici fetch 2025-07-11 16:43:31 +00:00
WebStreamr
b11bba761a
fix(extractor): ignore processing SuperVideo files 2025-07-10 10:34:12 +00:00
renovate[bot]
7503503ccf
chore(deps): update dependency typescript-eslint to v8.36.0 (#203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 19:40:46 +00:00
renovate[bot]
32d125d7a7
chore(deps): lock file maintenance (#202)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 00:54:09 +00:00
renovate[bot]
3eae8f0d2a
chore(deps): update dependency @types/node to v22.16.0 (#201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 00:53:33 +00:00
renovate[bot]
1b40ecdd07
chore(deps): update jest monorepo to v30.0.4 (#198)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 00:13:01 +00:00
webstreamr
4b3829d782
chore(release): release v0.34.0 (#195) 2025-07-02 16:58:47 +00:00
WebStreamr
3d28b226e5
feat(extractor): add StreamEmbed support 2025-07-02 16:57:21 +00:00
WebStreamr
e73dba8445
feat(source): add MegaKino support 2025-07-02 16:56:54 +00:00
WebStreamr
ce4a00cfdd
chore(source): improve Soaper matching
There are cases where the upload year does not match, but we have a full
keywoard match and that should be enough.
2025-07-02 10:00:24 +00:00
WebStreamr
5c7b3fb7f2
refactor: more consistent import style 2025-07-02 09:28:10 +00:00
renovate[bot]
2515a34874
chore(deps): update eslint monorepo to v9.30.1 (#196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 22:45:11 +00:00
WebStreamr
1c986b0521
refactor(extractor): sort extractors 2025-07-01 18:30:49 +00:00
WebStreamr
5da52d648a
refactor(extractor): move title height guessing into central place 2025-07-01 18:23:27 +00:00
webstreamr
61f515e4d2
chore(release): release v0.33.0 (#191) 2025-07-01 18:08:31 +00:00
WebStreamr
0fe5659f08
fix(source): update Frembed domain 2025-07-01 18:07:06 +00:00
WebStreamr
a0ed13f18e
feat: make multi sources explicitly configurable 2025-07-01 18:02:21 +00:00
WebStreamr
81434fff0e
feat: add Japanese and Korean to languages for VixSrc 2025-07-01 15:46:30 +00:00
WebStreamr
bf2187bbbd
chore: remove warmup
not needed on public instances and on private ones potentially just
causes issues (e.g. supervideo rate-limiting)
2025-07-01 09:40:23 +00:00
WebStreamr
9d3ce24b90
feat(extractor): implement Streamtape via MediaFlow Proxy 2025-06-30 18:57:34 +00:00
WebStreamr
b88eba017d
chore(extractor): simplify type and passing of height 2025-06-30 18:57:34 +00:00
renovate[bot]
ac78e6831b
chore(deps): update dependency typescript-eslint to v8.35.1 (#194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 18:43:50 +00:00
WebStreamr
8c8bfd6051
chore(extractor): add viaMediaFlowProxy flag to extractors 2025-06-30 13:32:11 +00:00
renovate[bot]
947060b07e
chore(deps): update dependency @types/jest to v30 (#193)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 07:20:40 +02:00
renovate[bot]
091bb44ccd
chore(deps): update dependency @types/node to v22.15.34 (#192)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 03:16:25 +00:00
renovate[bot]
4213b1571d
chore(deps): update dependency @stylistic/eslint-plugin to v5.1.0 (#190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-29 09:57:59 +00:00
webstreamr
02aab959e0
chore(release): release v0.32.5 (#185) 2025-06-28 20:53:28 +02:00
WebStreamr
acaa7221dd
chore(fetcher): use gzip to compress HTTP cache 2025-06-28 18:23:21 +00:00
WebStreamr
f5121bb1fb
chore(fetcher): use LRUCache for HTTP responses 2025-06-28 17:18:10 +00:00
WebStreamr
881fed53a4
chore(fetcher): configure max for all TTLCaches 2025-06-28 17:14:51 +00:00
renovate[bot]
de3cc10478
chore(deps): update eslint monorepo to v9.30.0 (#187)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 22:41:36 +00:00
WebStreamr
aee0fd8373
revert: Revert "fix(fetcher): remove 200 minimum cache"
This reverts commit 80e4f4edc4.
2025-06-27 19:18:56 +00:00
renovate[bot]
70df4c2697
fix(deps): update dependency undici to v7.11.0 (#184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 21:55:48 +00:00
webstreamr
8bf34fc1f3
chore(release): release v0.32.4 (#183) 2025-06-26 17:56:46 +02:00
WebStreamr
d44da650bd
chore: keep showing VixSrc for Italian to avoid confusions 2025-06-26 12:31:12 +00:00
WebStreamr
adae573a0f
chore(extractor): handle Dropload queued files better 2025-06-26 12:21:38 +00:00
webstreamr
f7b22f61b3
chore(release): release v0.32.3 (#182) 2025-06-26 04:23:09 +00:00
WebStreamr
00681e1411
fix(extractor): disable ip/session locked Fsst
until a solution is found or MediaFlow Proxy is added
2025-06-26 04:21:20 +00:00
webstreamr
befd203feb
chore(release): release v0.32.2 (#180) 2025-06-25 22:14:22 +02:00
WebStreamr
b9d0fb3613
fix(extractor): filter by configured languages in VixSrc 2025-06-25 20:13:17 +00:00
webstreamr
c36d444657
chore(release): release v0.32.1 (#179) 2025-06-25 20:08:02 +00:00
WebStreamr
ec037d7c90
fix(extractor): do not cache session-scoped Fsst
should fix issues where results work only once
2025-06-25 20:06:18 +00:00
WebStreamr
80e4f4edc4
fix(fetcher): remove 200 minimum cache
causes problems with session-scoped responses which should not be cached
2025-06-25 20:05:31 +00:00
webstreamr
8ab564164b
chore(release): release v0.32.0 (#175) 2025-06-25 21:22:05 +02:00
renovate[bot]
28123cab00
chore(deps): update node.js to v22.17.0 (#178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 19:19:04 +00:00
WebStreamr
35e049f577
feat: make VixSrc a default multi-language source 2025-06-25 19:16:40 +00:00
WebStreamr
c0fc6ff7ea
feat(extractor): support multi-language in VixSrc 2025-06-25 19:06:13 +00:00
WebStreamr
91d5e9dd36
refactor: add support for multi-language extractor results 2025-06-25 18:27:44 +00:00
WebStreamr
e895077a7b
chore: handle invalid MediaFlow Proxy password better 2025-06-25 18:16:12 +00:00
WebStreamr
3ac1063c9b
fix: build MediaFlow Proxy URL without concatenation
Fixes issues with trailing slashes
2025-06-25 17:58:40 +00:00
renovate[bot]
2ed2ea372a
chore(deps): update dependency jest to v30.0.3 (#174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 02:00:52 +00:00
webstreamr
97e5f2595f
chore(release): release v0.31.2 (#173) 2025-06-24 23:33:52 +00:00
WebStreamr
5fffd0a2d3
chore: empty commit to trigger release 2025-06-24 23:32:17 +00:00
webstreamr
0bb6502e70
chore(release): release v0.31.1 (#172) 2025-06-25 01:23:47 +02:00
WebStreamr
287b031a12
fix(extractor): avoid cache clash between context dependent extractors 2025-06-24 23:22:47 +00:00
webstreamr
ac6eccc58a
chore(release): release v0.31.0 (#171) 2025-06-24 22:53:04 +00:00
WebStreamr
412e89b690
fix: typo/grammar mistake in includeExternalUrls config option 2025-06-24 22:51:12 +00:00
WebStreamr
9e2242093c
feat: pre-load ElfHosted MediaFlow Proxy config URL 2025-06-24 22:48:15 +00:00
WebStreamr
39dec26450
fix: support password-less MediaFlow Proxy 2025-06-24 22:47:50 +00:00
WebStreamr
f12e3c5bd8
feat(extractor): implement VixSrc via MediaFlow Proxy 2025-06-24 19:07:23 +00:00
WebStreamr
7ea57222d9
refactor: pass Context instead of Config to MFP helper 2025-06-24 11:33:58 +00:00
webstreamr
16870aa257
chore(release): release v0.30.2 (#165) 2025-06-24 11:24:12 +00:00
WebStreamr
24601fee3e
fix(source): update Frembed domain 2025-06-24 11:23:03 +00:00
WebStreamr
b2eb16fc82
refactor: extract Media Flow Proxy functions 2025-06-24 10:52:38 +00:00
webstreamr
64d1da9dc6
chore(release): release v0.30.1 (#163) 2025-06-23 21:09:59 +02:00
renovate[bot]
5656abb26c
chore(deps): update dependency typescript-eslint to v8.35.0 (#164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 18:14:25 +00:00
WebStreamr
bd67664827
fix(source): do not mix es/mx in Cuevana 2025-06-23 18:12:02 +00:00
webstreamr
731167efa8
chore(release): release v0.30.0 (#155) 2025-06-23 14:45:20 +02:00
renovate[bot]
74e965399a
chore(deps): update dependency @stylistic/eslint-plugin to v5 (#160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 12:38:23 +00:00
WebStreamr
ab588a1501
feat(extractor): implement Uqload via MediaFlow Proxy 2025-06-23 12:35:54 +00:00
renovate[bot]
fca2841ce0
chore(deps): update dependency @types/node to v22.15.32 (#159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 03:36:58 +00:00
WebStreamr
f90a53da68
feat(fetcher): add proxy support 2025-06-21 12:03:34 +00:00
WebStreamr
fd1145c162
fix(extractor): introduce format and set notWebReady accordingly
According to https://github.com/Stremio/stremio-addon-sdk/blob/master/docs/api/responses/stream.md
2025-06-20 18:40:59 +00:00
WebStreamr
d641fa1c2d
chore(extractor): add MediaFlow Proxy info to Mixdrop label 2025-06-20 08:46:46 +00:00
WebStreamr
7c600427e6
feat(extractor): implement Mixdrop via MediaFlow Proxy 2025-06-19 20:51:39 +00:00
webstreamr
0f415b7521
chore(release): release v0.29.3 (#153) 2025-06-19 14:28:37 +02:00
WebStreamr
552620a7eb
chore: use add-on URL as default externalUrl 2025-06-19 12:21:31 +00:00
WebStreamr
b5855aaab4
fix: config checkbox handling of includeExternalUrls 2025-06-19 12:13:36 +00:00
WebStreamr
138e92afc5
chore: make ip in Context optional 2025-06-19 11:54:13 +00:00
WebStreamr
2d4c5a8221
test: introduce helper function createTestContext 2025-06-19 11:32:59 +00:00
renovate[bot]
6f1f04681f
chore(deps): update jest monorepo to v30.0.2 (#154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 11:07:20 +00:00
WebStreamr
1d263b5540
chore(extractor): show label instead of host for errors 2025-06-19 11:04:44 +00:00
WebStreamr
d88cfd404c
chore(fetcher): remove unneeded istanbul 2025-06-19 11:04:41 +00:00
renovate[bot]
ac735976d1
chore(deps): update jest monorepo to v30.0.1 (#152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 23:48:07 +00:00
webstreamr
220fb7bdf4
chore(release): release v0.29.2 (#149) 2025-06-18 10:07:08 +00:00
WebStreamr
c1f9de58b0
chore: do not show external URLs by default 2025-06-18 10:06:13 +00:00
WebStreamr
deef8ab58b
chore: more minimal result format with less bloat and icons 2025-06-18 09:47:44 +00:00
WebStreamr
6c7993cdba
fix(extractor): handle Fsst with single file result 2025-06-18 09:33:40 +00:00
renovate[bot]
9fcc1c60d4
chore(deps): update dependency fetch-mock to v12.5.3 (#148)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 23:06:08 +00:00
webstreamr
161e78f95f
chore(release): release v0.29.1 (#146) 2025-06-17 19:11:37 +00:00
WebStreamr
f7042b94ef
chore(fetcher): remember recent timeouts and throw if count is too high
This should avoid issues with longer outages of hosters like we had with
DoodStream now twice recently.
2025-06-17 19:09:22 +00:00
WebStreamr
e1f5192f4b
refactor: avoid leaking fetch internals by introducing TimeoutError 2025-06-17 15:42:47 +00:00
renovate[bot]
aa9085c263
chore(deps): update dependency typescript-eslint to v8.34.1 (#145)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 19:45:27 +00:00
webstreamr
f0ebaa19a3
chore(release): release v0.29.0 (#142) 2025-06-16 17:32:16 +02:00
WebStreamr
d5ee89db9b
refactor(fetcher): use Semaphore with timeout for queuing 2025-06-16 09:03:35 +00:00
WebStreamr
2a8e67ca59
refactor(fetcher): introduce constants for defaults 2025-06-16 07:29:34 +00:00
renovate[bot]
b00ec559e0
chore(deps): lock file maintenance (#144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 00:56:45 +00:00
renovate[bot]
a5daea497d
chore(deps): update dependency @types/node to v22.15.31 (#143)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-16 00:56:11 +00:00
WebStreamr
952993a537
chore(extractor): set Referer to URL origin for external URLs 2025-06-15 20:41:02 +00:00
WebStreamr
f192051328
feat(source): add basic Cuevana support 2025-06-15 20:34:05 +00:00
WebStreamr
a4ceca57ae
chore(source): improve Eurostreaming (IT) search with multi-word keyword 2025-06-15 18:27:37 +00:00
webstreamr
21467ac670
chore(release): release v0.28.4 (#141) 2025-06-15 11:35:34 +00:00
WebStreamr
6cf31f60de
chore(fetcher): increase general timeouts 2025-06-15 11:34:09 +00:00
WebStreamr
e9ccee5621
chore: increase tmdb queuing limits 2025-06-15 11:31:23 +00:00
WebStreamr
3a28de46bd
fix(extractor): handle missing height infos in playlist properly 2025-06-15 11:27:53 +00:00
WebStreamr
dd121a068a
fix(extractor): only cache URL results if there was no error 2025-06-15 11:16:14 +00:00
webstreamr
af5bfadba3
chore(release): release v0.28.3 (#140) 2025-06-14 20:25:38 +00:00
WebStreamr
6541782541
fix(fetcher): consider request body for cache key 2025-06-14 20:24:02 +00:00
webstreamr
89af08b703
chore(release): release v0.28.2 (#137) 2025-06-14 19:45:35 +00:00
WebStreamr
6a773f7740
fix(source): adapt and enable Eurostreaming (IT) again 2025-06-14 19:43:34 +00:00
WebStreamr
5f34be12a9
chore: handle not found TMDB IDs better 2025-06-14 18:00:55 +00:00
WebStreamr
81d788ef84
chore(extractor): add various exotic DoodStream domains 2025-06-14 17:58:19 +00:00
WebStreamr
605e88fef4
chore: implement rate-limit detection 2025-06-14 19:51:00 +02:00
WebStreamr
f11c591dc5
chore: do not pass referer through context 2025-06-14 13:44:32 +02:00
WebStreamr
2853bff284
refactor: use context scoped fixtures 2025-06-13 22:37:50 +02:00
WebStreamr
3a4ff73953
test: hand-rolled, explicit Fetcher mock 2025-06-13 21:53:11 +02:00
WebStreamr
4bc25692a8
refactor: use proper prototype class methods 2025-06-13 21:26:26 +02:00
renovate[bot]
0e6558e0b1
chore(deps): update eslint monorepo to v9.29.0 (#138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-13 18:05:06 +00:00
WebStreamr
4caf62a367
test: instantiate mocked Fetcher with logger consistently 2025-06-13 13:20:13 +00:00
webstreamr
519e0d4156
chore(release): release v0.28.1 (#136) 2025-06-13 12:32:02 +02:00
WebStreamr
5aeab3fe23
chore(fetcher): decrease regular request timeout to 5s 2025-06-13 10:26:56 +00:00
WebStreamr
15099de798
refactor: introduce helper showExternalUrls() 2025-06-13 10:24:32 +00:00
WebStreamr
9b5943208e
refactor(extractor): introduce parent class for simplifications 2025-06-13 10:04:40 +00:00
WebStreamr
bdae1ff346
refactor: define member accessiblity explicitly 2025-06-13 09:42:06 +00:00
WebStreamr
b4b8581383
chore: rename handler to source 2025-06-12 21:05:24 +00:00
WebStreamr
013fb4084f
fix(extractor): handle missing VidSrc files 2025-06-12 20:58:25 +00:00
WebStreamr
d2d7e84d9b
refactor: separate handlers and extractors 2025-06-12 20:56:50 +00:00
WebStreamr
b5067aa0a9
refactor: make BlockedReason an enum 2025-06-12 16:00:34 +00:00
WebStreamr
250d6a1323
refactor: make CountryCode an enum 2025-06-12 15:56:33 +00:00
WebStreamr
9432ba5944
refactor: clean up handler import 2025-06-12 15:33:37 +00:00
WebStreamr
d1da91e030
fix(extractor): detect SuperVideo video deletion/expiration 2025-06-12 12:06:52 +00:00
WebStreamr
244778058c test: enable new globalsCleanup 2025-06-12 09:27:17 +02:00
renovate[bot]
c9d8a03156 chore(deps): update jest monorepo to v30 2025-06-12 09:27:17 +02:00
renovate[bot]
af8cadc143
chore(deps): update dependency ts-jest to v29.4.0 (#135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 05:46:08 +00:00
webstreamr
f1447e6975
chore(release): release v0.28.0 (#134) 2025-06-11 20:48:00 +00:00
WebStreamr
dd6d485340
feat(handler): add VidSrc with CloudStream Pro support 2025-06-11 20:46:28 +00:00
WebStreamr
d0efc0783f
fix(fetcher): correct noFlareSolverr logic 2025-06-11 20:41:11 +00:00
WebStreamr
4dc3c636a6
refactor: extract playlist height guessing 2025-06-11 20:18:49 +00:00
WebStreamr
2ea8c16d9e
chore(extractor): improve SuperVideo title 2025-06-11 10:08:13 +00:00
webstreamr
c05fe96059
chore(release): release v0.27.2 (#133) 2025-06-11 08:27:36 +00:00
WebStreamr
63079bf80a
chore(extractor): support dooood URLs 2025-06-11 08:25:58 +00:00
WebStreamr
9797957778
fix(handler): avoid creating invalid URLs in KinoGer 2025-06-11 08:24:19 +00:00
WebStreamr
19395f1921
chore(extractor): decrease timeout and ignore errors for external URLs 2025-06-11 08:17:58 +00:00
webstreamr
faad6a2562
chore(release): release v0.27.1 (#132) 2025-06-10 22:17:55 +02:00
WebStreamr
fcec03a6a4
fix(handler): do not share decipher instance in KinoGer 2025-06-10 20:16:48 +00:00
WebStreamr
695bf53262
chore: log error stack 2025-06-10 20:15:36 +00:00
webstreamr
c5ac27119e
chore(release): release v0.27.0 (#131) 2025-06-10 21:31:58 +02:00
WebStreamr
262abde2fc
feat(extractor): implement KinoGer extractor 2025-06-10 19:30:33 +00:00
WebStreamr
b940df5bc0
chore(extractor): add DoodStream height guessing based on title 2025-06-10 19:21:00 +00:00
WebStreamr
041c04e327
fix(extractor): prefer using extractor title 2025-06-10 19:13:04 +00:00
webstreamr
6af86ef6a6
chore(release): release v0.26.1 (#130) 2025-06-10 17:54:27 +02:00
WebStreamr
42950bc2a3
chore(handler): generalize KinoGer to support more (external) URLs 2025-06-10 15:48:31 +00:00
WebStreamr
141c737cf3
chore(extractor): support dooodster links via DoodStream 2025-06-10 15:48:11 +00:00
WebStreamr
73ea091b83
refactor(handler): improve KinoGer readability 2025-06-10 15:24:52 +00:00
WebStreamr
dd1b3a1f1d
chore(extractor): support embed-only SuperVideo URLs 2025-06-10 15:17:53 +00:00
WebStreamr
4da121d094
refactor(extractor): add return type to all extract methods 2025-06-10 15:15:52 +00:00
WebStreamr
2d615ce582
refactor(extractor): introduce URL normalization 2025-06-10 15:03:05 +00:00
WebStreamr
7b933bfbac
refactor: remove always true conditional 2025-06-10 09:30:03 +00:00
webstreamr
eec2e5030b
chore(release): release v0.26.0 (#128) 2025-06-09 22:46:15 +02:00
WebStreamr
16e1aab1c2
fix: avoid potential queuing dead lock 2025-06-09 20:42:10 +00:00
WebStreamr
bc9c41f005
test: remove usesless filter calls 2025-06-09 20:42:10 +00:00
WebStreamr
8382c009f4
feat(handler): add support for KinoGer Fsst 2025-06-09 20:42:10 +00:00
WebStreamr
7ad61df839
refactor: season instead of series ;) 2025-06-09 20:42:10 +00:00
renovate[bot]
b5a4de9c9e
chore(deps): update dependency typescript-eslint to v8.34.0 (#127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 20:42:10 +00:00
webstreamr
8f3500385c
chore(release): release v0.25.3 (#123) 2025-06-09 13:37:09 +02:00
WebStreamr
1ca4ea4abe
chore(extractor): don't use FlareSolverr for external URLs 2025-06-09 11:36:17 +00:00
WebStreamr
114c383c54
chore(fetcher): only call FlareSolverr for known challenge blocks 2025-06-09 11:28:20 +00:00
WebStreamr
cbec7c1a01
fix(handler): fix KinoKiste which is now StreamKiste 2025-06-09 11:27:05 +00:00
WebStreamr
6dff634299
test: use snapshots for manifest config tests 2025-06-09 11:24:38 +00:00
renovate[bot]
315588bc78
chore(deps): update dependency @types/node to v22.15.30 (#125)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 02:44:52 +00:00
renovate[bot]
ffeb135bcc
chore(deps): update dependency @types/express to v5.0.3 (#124)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 02:42:51 +00:00
renovate[bot]
6c5dcec71e
fix(deps): update dependency cheerio to v1.1.0 (#122)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 21:26:50 +00:00
webstreamr
20fa791632
chore(release): release v0.25.2 (#121) 2025-06-08 21:28:59 +02:00
WebStreamr
d1ff7cd732
chore(extractor): add bytes detection to DoodStream 2025-06-08 19:27:18 +00:00
WebStreamr
0446d52e78
refactor: UrlResult[] instead of UrlResult | undefined 2025-06-08 18:55:03 +00:00
WebStreamr
364bf166ae
test(handler): avoid side-effects by using a fresh handler for each test 2025-06-08 18:50:53 +00:00
WebStreamr
7d0544a590
chore(handler): add height detection to Soaper 2025-06-08 18:21:52 +00:00
webstreamr
2d9f602256
chore(release): release v0.25.1 (#120) 2025-06-08 17:12:21 +02:00
WebStreamr
48209ac876
chore: introduce config helper, enable English by default 2025-06-08 14:09:10 +00:00
WebStreamr
968b7d4bc0
chore(handler): add year to soaper result movie title 2025-06-08 14:01:43 +00:00
WebStreamr
fbae3924e5
refactor: simplify id usage and imdb <-> tmdb transformations 2025-06-08 13:50:53 +00:00
WebStreamr
8724e04860
refactor: move ids into dedicated folder 2025-06-08 13:26:58 +00:00
WebStreamr
374896caca
refactor: introduce ImdbId and TmdbId 2025-06-08 13:24:31 +00:00
WebStreamr
62f18ece85
refactor: strictly type ContentType 2025-06-08 07:54:17 +00:00
webstreamr
82c75b0654
chore(release): release v0.25.0 (#119) 2025-06-07 19:25:45 +00:00
WebStreamr
717c2afdc4
feat(handler): implement EN Soaper source 2025-06-07 19:24:50 +00:00
WebStreamr
5bda4c88f0
test: rename confusing tests 2025-06-07 19:10:06 +00:00
WebStreamr
a56aea56a8
refactor: add custom helper for flags instead of package 2025-06-07 18:59:23 +00:00
webstreamr
f08670bb21
chore(release): release v0.24.1 (#118) 2025-06-06 12:54:21 +00:00
WebStreamr
ab39636196
chore: warm up cache only with trending movies and tv shows 2025-06-06 12:53:07 +00:00
WebStreamr
99434aa49a
chore: only warm up cache in prod 2025-06-06 12:16:29 +00:00
webstreamr
802ec4275d
chore(release): release v0.24.0 (#117) 2025-06-06 14:12:23 +02:00
WebStreamr
9ba4e7374c
feat: implement hourly movie cache warmup 2025-06-06 12:08:53 +00:00
WebStreamr
01218c77a6
chore(handler): support TMDB ID in Frembed 2025-06-06 11:21:54 +00:00
WebStreamr
355663f036
chore(tmdb): add parseTmdbId 2025-06-06 11:12:03 +00:00
WebStreamr
860a6ae18c
chore(imdb): stricter parsing checks 2025-06-06 11:08:39 +00:00
WebStreamr
a0e83f287d
chore(tmdb): add getImdbIdFromTmdbId and cache both ID transformations 2025-06-06 08:03:16 +00:00
WebStreamr
876698825f
chore: add statusText to HttpError and show in result as well 2025-06-06 06:50:31 +00:00
webstreamr
db67a91413
chore(release): release v0.23.5 (#116) 2025-06-05 22:05:39 +02:00
WebStreamr
8e57400c58
fix: always sort external URLs down 2025-06-05 20:02:25 +00:00
WebStreamr
04ee59e9d3
fix(handler): skip invalid CSV URLs 2025-06-05 19:56:45 +00:00
WebStreamr
d8f0c4602c
chore: introduce HttpError and show status in result 2025-06-05 19:52:49 +00:00
webstreamr
119e338292
chore(release): release v0.23.4 (#113) 2025-06-05 17:21:35 +02:00
WebStreamr
be3464369b
chore(fetcher): simplify queuing and allow parallel requests
instead of only one per host we allow 5, which is more realistic
2025-06-05 15:19:30 +00:00
webstreamr
ad98dc8661
chore(release): release v0.23.3 (#112) 2025-06-05 12:31:36 +02:00
WebStreamr
65d2d0ac83
chore(fetcher): enable keepalive 2025-06-05 10:30:30 +00:00
WebStreamr
63a53a18cc
refactor(fetcher): introduce CustomRequestInit 2025-06-05 10:30:30 +00:00
WebStreamr
092ee4bfc2
refactor(fetcher): add full FlareSolverrResult type 2025-06-05 10:30:30 +00:00
webstreamr
523bbc0f41
fix(fetcher): use expires FlareSolverr cookie prop (#111) 2025-06-05 10:30:26 +00:00
webstreamr
d0802fea50
chore(release): release v0.23.2 (#110) 2025-06-04 18:38:45 +00:00
WebStreamr
efb0f96be2
chore(fetcher): use well-working node user agent by default again 2025-06-04 18:37:51 +00:00
WebStreamr
aa4c672594
chore(fetcher): persist and use FlareSolverr cookie and user agent
doesn't seem to work, but this is best practice anyway..
2025-06-04 18:29:02 +00:00
WebStreamr
0d3a5b8919
chore(fetcher): use queue limit of 10 again :)
queuing too long results in a bad experience. and often there are other
non-blocked sources too, so people can choose those. or self-host.
2025-06-04 16:18:03 +00:00
webstreamr
a9ebe9946a
chore(release): release v0.23.1 (#107) 2025-06-04 15:24:36 +00:00
WebStreamr
7387aef764
chore(fetcher): use FlareSolverr also for other 403 Cloudflare responses 2025-06-04 15:23:15 +00:00
WebStreamr
e6aa95f875
chore(fetcher): increase queue limit to 30 2025-06-04 15:22:59 +00:00
WebStreamr
2c9faebcdc
fix(fetcher): improve Flaresolverr fetching
- use queued fetching to not mix sessions
- log issue responses
2025-06-04 14:59:12 +00:00
webstreamr
7c45c78788
chore(release): release v0.23.0 (#105) 2025-06-04 16:20:24 +02:00
WebStreamr
af65ed0094
feat(fetcher): add FlareSolverr support 2025-06-04 14:18:50 +00:00
WebStreamr
1b8f75f00e
fix(extractor): do not mutate URLs to avoid cache misses 2025-06-04 13:23:54 +00:00
WebStreamr
fef59dd03b
fix: never cache result errors 2025-06-04 12:05:59 +00:00
WebStreamr
7ec7219ed4
chore: do not log headers for known blocking reasons 2025-06-04 12:05:09 +00:00
webstreamr
a156e98831
chore(release): release v0.22.10 (#104) 2025-06-04 12:59:20 +02:00
WebStreamr
422f7d014c
chore(fetcher): hard-code user-agent again for debugging purposes 2025-06-04 10:54:16 +00:00
WebStreamr
77fc20cfff
refactor: extract env access into helpers 2025-06-04 10:29:50 +00:00
webstreamr
64823685cc
chore(release): release v0.22.9 (#103) 2025-06-04 10:07:13 +00:00
WebStreamr
b69c9cde03
chore: cache successful results as long as we safely can 2025-06-04 10:06:24 +00:00
renovate[bot]
389461415d
chore(deps): update dependency @stylistic/eslint-plugin to v4.4.1 (#101)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 08:47:17 +00:00
WebStreamr
d78dca3345
chore: log response headers for blocks 2025-06-04 08:45:08 +00:00
webstreamr
c5cc955d91
chore(release): release v0.22.8 (#99) 2025-06-04 00:47:28 +02:00
WebStreamr
f0817a3dd6
chore: implement queued fetching 2025-06-03 22:44:57 +00:00
WebStreamr
ef2c45e2cb
chore: add more error infos to results 2025-06-03 21:56:19 +00:00
webstreamr
ed501a7ee9
chore(release): release v0.22.7 (#96) 2025-06-03 17:04:57 +02:00
WebStreamr
9d17f5340a
chore: catch handler blocked errors and report the and report themm 2025-06-03 15:02:56 +00:00
WebStreamr
f6967da5cc
chore: try to log error causes 2025-06-03 14:54:05 +00:00
WebStreamr
b94f9898b0
chore: use tsconfig base for node 22 2025-06-03 14:54:05 +00:00
WebStreamr
0b9cf21fae
fix(handler): use new frembed domain (again) 2025-06-03 14:21:18 +00:00
renovate[bot]
f0ffd9b024
chore(deps): update dependency typescript-eslint to v8.33.1 (#97)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 21:23:54 +00:00
WebStreamr
ae6fdfe726
chore: add language name to config in manifest as well 2025-06-02 15:45:16 +00:00
webstreamr
9291a377d3
chore(release): release v0.22.6 (#95) 2025-06-02 15:33:11 +00:00
WebStreamr
e4dd16e9d1
chore: improve results format and refactor name/title generation 2025-06-02 15:31:15 +00:00
webstreamr
120fb2896f
chore(release): release v0.22.5 (#94) 2025-06-02 11:43:57 +02:00
WebStreamr
e86b5c3454
fix(handler): use new frembed domain 2025-06-02 09:42:26 +00:00
WebStreamr
12d91ba0b4
test: fix mocked Fetcher real calls by using same headers 2025-06-02 09:42:26 +00:00
renovate[bot]
dbf1dd1fb9
chore(deps): lock file maintenance (#93)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 02:22:34 +00:00
renovate[bot]
d04c9280df
chore(deps): update dependency @types/node to v22.15.29 (#92)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 02:21:56 +00:00
webstreamr
d271b2c337
chore(release): release v0.22.4 (#91) 2025-06-01 19:38:06 +00:00
WebStreamr
cd8ff60508
fix(handler): disable Eurostreaming as long as it doesn't work 2025-06-01 19:37:03 +00:00
WebStreamr
c3a2f9fdc6
fix(fetcher): use "node" as user agent explicitly
no idea why, but this avoids challenges so far..
2025-06-01 19:31:25 +00:00
WebStreamr
2eb09fc65a
chore: generalize forbidden/blocked handling 2025-06-01 19:31:11 +00:00
webstreamr
f3cbc92286
chore(release): release v0.22.3 (#88) 2025-06-01 13:42:44 +00:00
WebStreamr
424ed78b93
chore(fetcher): hardcode user-agent for now to avoid using "node" 2025-06-01 13:40:59 +00:00
webstreamr
ba9f33230f
chore(release): release v0.22.2 (#87) 2025-06-01 13:21:25 +00:00
WebStreamr
717019e04e
chore(fetcher): add response header info to non-expected errors 2025-06-01 13:19:38 +00:00
WebStreamr
ca981220c9
chore(fetcher): remove Origin header
more is not always better I guess :)

not breaking anything, but that header is not used for normal HTTP GET
requests
2025-06-01 13:08:52 +00:00
webstreamr
a85a483732
chore(release): release v0.22.1 (#86) 2025-05-31 21:15:58 +00:00
WebStreamr
fbc1aa994c
fix: switch back to fetch and do not generate user agents
seems to be causing less cloudflare challenges
2025-05-31 21:13:52 +00:00
renovate[bot]
2f9a349d31
chore(deps): update eslint monorepo to v9.28.0 (#85)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 22:13:58 +00:00
webstreamr
0f19b4b7a7
chore(release): release v0.22.0 (#80) 2025-05-29 21:25:43 +02:00
WebStreamr
799ca975ac
feat: make external URL exclusion configurable 2025-05-29 19:17:21 +00:00
WebStreamr
5bfe2f800b
chore(fetcher): reduce timeout to 5s 2025-05-29 18:32:06 +00:00
WebStreamr
07ddaab7fb
chore(fetcher): set more headers to mimick browser requests 2025-05-29 17:55:20 +00:00
WebStreamr
f2a7da21e0
test: remove duplicated Fetcher test parts 2025-05-29 17:43:51 +00:00
WebStreamr
862d3bb5f2
refactor(handler): remove not needed 404 special handling 2025-05-29 17:40:48 +00:00
WebStreamr
02b73b41d7
chore(fetcher): use more realistic referer/origin header values 2025-05-29 17:36:43 +00:00
WebStreamr
0638ce0a8c
fix: detect and report Cloudflare challenge blocks 2025-05-29 17:17:17 +00:00
renovate[bot]
62098f8e84
chore(deps): update dependency typescript-eslint to v8.33.0 (#82)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 20:45:32 +00:00
renovate[bot]
17b6d79157
chore(deps): lock file maintenance (#79)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 03:21:34 +00:00
renovate[bot]
19d7a8b9dd
chore(deps): update dependency @types/node to v22.15.21 (#78)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 03:21:03 +00:00
webstreamr
fbc35550b9
chore(release): release v0.21.0 (#76) 2025-05-25 20:48:23 +02:00
WebStreamr
d4e402e5c9
feat: add title to meta, restructure result name 2025-05-25 18:46:55 +00:00
WebStreamr
f27fc6c38a
refactor: introduce type for CountryCode 2025-05-25 19:32:16 +02:00
WebStreamr
979dddd20c
refactor: move bytes, height and countryCode into meta object
and pass that to the extractors instead. this is a preparation to
support more meta like e.g. title
2025-05-25 19:25:39 +02:00
WebStreamr
faa6eb6ed7
test: update fetch fixtures only when requested
avoids automagic http requests in tests
2025-05-25 19:12:36 +02:00
WebStreamr
f3beb72f17
bug(extractor): handle not found case properly for DoodStream 2025-05-25 19:04:12 +02:00
WebStreamr
6b5c9a4ce7
feat(extractor): cache not found results too 2025-05-25 18:58:00 +02:00
renovate[bot]
9e9d4b2081
chore(deps): update dependency @stylistic/eslint-plugin to v4.4.0 (#77)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-24 06:05:05 +00:00
renovate[bot]
61a17b1d1c
chore(deps): update dependency @stylistic/eslint-plugin to v4.3.0 (#75)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-24 06:03:06 +00:00
webstreamr
9b0d416393
chore(release): release v0.20.1 (#74) 2025-05-23 13:47:32 +02:00
WebStreamr
5c51abbd46
chore: log IP in stream request 2025-05-23 10:53:09 +00:00
WebStreamr
b313c4eb8d
chore(fetcher): set Origin header and add back proxy headers 2025-05-23 10:52:49 +00:00
WebStreamr
8e791bbe24
chore: better handle not found streams/links 2025-05-23 10:40:59 +00:00
WebStreamr
1e9ebd5905
test: do not reset but only clear mocks 2025-05-23 10:37:13 +00:00
WebStreamr
f4a0b03246
fix(handler): do not ignore main link in Eurostreaming 2025-05-23 09:20:20 +00:00
WebStreamr
1447adad1b
chore: reduce axios retries to 1 again, avoid risky retries 2025-05-23 09:08:20 +00:00
WebStreamr
0a99f1e501
chore(extractor): use HEAD instead of GET to check external URLs 2025-05-22 16:36:28 +00:00
renovate[bot]
305ce3d873
chore(deps): update node.js to v22.16.0 (#72)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 04:47:48 +00:00
webstreamr
7be71c965f
chore(release): release v0.20.0 (#71) 2025-05-21 22:41:51 +02:00
WebStreamr
e90c89af89
test: use snapshots, also use fixtures for axios errors 2025-05-21 20:37:35 +00:00
WebStreamr
23445c143b
feat(extractor): add external URL support
This could still be a useful fallback on some devices since it indicates
that we found a potential embed URL but could not extract the video URL.
2025-05-21 19:22:32 +00:00
webstreamr
3f64ba0f9b
chore(release): release v0.19.4 (#69) 2025-05-21 15:46:15 +00:00
WebStreamr
ac8dd43662
fix(handler): return only results for configured country/language in CineHDPlus 2025-05-21 15:45:15 +00:00
webstreamr
8367b7d6e1
chore(release): release v0.19.3 (#65) 2025-05-20 19:23:17 +00:00
webstreamr
8daabf1f5a
chore(fetcher): increase timeout to 15s (#64) 2025-05-20 19:22:08 +00:00
webstreamr
e6980e5676
chore(release): release v0.19.2 (#63) 2025-05-19 12:06:03 +02:00
WebStreamr
c2c5331861
chore(fetcher): remove proxy headers, add default timeout
The proxy headers are not improving anything and only cause issues with
another extractor that using IP-locking.
2025-05-19 08:47:52 +00:00
renovate[bot]
1cc2a182a5
chore(deps): update dependency @types/node to v22.15.19 (#62)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 02:48:41 +00:00
renovate[bot]
ba57833fb0
chore(deps): update dependency @types/express to v5.0.2 (#61)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 02:46:50 +00:00
webstreamr
1197b9aba3
chore(release): release v0.19.1 (#60) 2025-05-18 23:04:59 +02:00
WebStreamr
57799a777a
revert: "feat(extractor): implement UQLoad"
This reverts commit fb61d39225.

UQLoad is IP-locked.
2025-05-18 22:50:31 +02:00
webstreamr
6a166b29a9
chore(release): release v0.19.0 (#59) 2025-05-18 22:26:30 +02:00
WebStreamr
b5c45d806e
refactor(extractor): manipulate URL instead of creating new one 2025-05-18 21:48:17 +02:00
WebStreamr
fb61d39225
feat(extractor): implement UQLoad 2025-05-18 21:20:48 +02:00
WebStreamr
d8a19d4916
refactor(handler): switch Frembed to TMDB search which is more reliable 2025-05-18 21:19:58 +02:00
WebStreamr
03cb05e2a3
feat: implement IMDb -> TMDB ID conversion helper 2025-05-18 21:16:47 +02:00
webstreamr
91eb1de1c4
chore(release): release v0.18.0 (#58) 2025-05-17 22:10:13 +00:00
WebStreamr
ac4ded4614
chore(manifest): add stremio-addons.net signature 2025-05-18 00:09:17 +02:00
WebStreamr
5b389ada9e
feat: configure languages instead of handlers
just feels more natural and helps maintainence. handlers should be an
internal thing.
2025-05-18 00:00:10 +02:00
webstreamr
d368b3a9de
chore(release): release v0.17.0 (#56) 2025-05-17 21:41:29 +02:00
WebStreamr
aac954cafc
docs: add some badges to the README 2025-05-17 21:36:05 +02:00
WebStreamr
f2fb4821e3
refactor: use template literal consistently 2025-05-17 21:10:40 +02:00
WebStreamr
f635d6c233
chore: create ID per request for logging and error response 2025-05-17 21:06:51 +02:00
WebStreamr
827fd88110
docs: add basic README 2025-05-17 20:43:27 +02:00
WebStreamr
4a705ce088
refactor: cleanup url and extractorRegistry variable/prop names 2025-05-17 20:25:51 +02:00
WebStreamr
8b82215c32
feat(handler): limited Frembed support 2025-05-17 20:20:19 +02:00
webstreamr
b331dc66ac
chore(release): release v0.16.1 (#54) 2025-05-17 01:06:50 +02:00
WebStreamr
bb395f0375
revert: "feat(extractor): implement Mixdrop"
This reverts commit 4c53161e98.

Mixdrop seems to be IP-locked.
2025-05-17 01:05:40 +02:00
webstreamr
2c4cccab66
chore(release): release v0.16.0 (#53) 2025-05-16 22:37:17 +02:00
WebStreamr
63a79f8758
fix(extractor): do not throw for broken/outdated link 2025-05-16 22:36:20 +02:00
WebStreamr
b790cc7954
refactor: use url.href instead of url.toString() 2025-05-16 22:34:19 +02:00
renovate[bot]
16b01a57fd
chore(deps): update dependency eslint to v9.27.0 (#52)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 22:34:19 +02:00
WebStreamr
4c53161e98
feat(extractor): implement Mixdrop 2025-05-16 22:34:19 +02:00
WebStreamr
24f1107df8
refactor(extractor): improve DoodStream comment about no match case 2025-05-16 18:51:32 +00:00
WebStreamr
07ffe444f3
chore: return stream.behaviorHints.videoSize when possible 2025-05-16 20:48:31 +02:00
renovate[bot]
8bc30b8234
chore(deps): update dependency @eslint/js to v9.27.0 (#51)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 18:41:48 +00:00
renovate[bot]
60273329ab
chore(deps): update dependency ts-jest to v29.3.4 (#50)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 18:39:45 +00:00
WebStreamr
e06b8a2d95
chore: rename behaviourHints to behaviorHints 🤦
This fixes DoodStream by fixing the Referer header usage hint.
2025-05-16 20:36:33 +02:00
webstreamr
7698757865
chore(release): release v0.15.2 (#48) 2025-05-16 15:33:12 +00:00
WebStreamr
e13ab0c70f
chore: compare by label if both height and bytes are the same 2025-05-16 15:31:54 +00:00
webstreamr
269a05499d
chore(release): release v0.15.1 (#47) 2025-05-16 17:24:05 +02:00
WebStreamr
241e6b0b77
fix: rename behaviourHints.group to behaviourHints.bingeGroup
as stated in 3950d57076/docs/api/responses/stream.md
TS types seem to be quite outdated...
2025-05-16 15:22:39 +00:00
webstreamr
4857bc0494
chore(release): release v0.15.0 (#46) 2025-05-16 17:12:11 +02:00
WebStreamr
87ec328a66
refactor(extractor): remove embed part from extractor 2025-05-16 15:07:03 +00:00
WebStreamr
8740979a80
feat(extractor): try to semi-blindly implement DoodStream support 2025-05-16 15:03:57 +00:00
WebStreamr
938f6cb2c3
refactor(fetcher): use origin for the referrer 2025-05-16 12:27:48 +00:00
webstreamr
7d465c0229
chore(release): release v0.14.1 (#45) 2025-05-15 16:55:00 +02:00
WebStreamr
c1da2d5552
fix: add back cache-control headers
but keep them super short, mostly to reduce traffic via external cache infra
2025-05-15 14:52:02 +00:00
WebStreamr
91a6935a91
chore: add missing dot after GitHub link 2025-05-15 14:37:24 +00:00
WebStreamr
e8ed101176
fix(fetcher): do not overwrite config 2025-05-15 14:25:34 +00:00
renovate[bot]
acd29d8f39
chore(deps): update dependency ts-jest to v29.3.3 (#44)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 20:22:07 +00:00
webstreamr
5206aa2c4a
chore(release): release v0.14.0 (#43) 2025-05-14 13:12:40 +00:00
WebStreamr
728bc7ecba
feat(fetcher): retry all 4xx requests too 2025-05-14 13:11:37 +00:00
WebStreamr
956ba68550
refactor(handler): also pass content type to handle() 2025-05-14 12:00:58 +00:00
WebStreamr
281cba8da1
chore(fetcher): always set same URL host as referer 2025-05-14 11:51:48 +00:00
714 changed files with 185553 additions and 12337 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View 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.

View 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
View 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

View file

@ -1,15 +1,18 @@
name: Build name: Build
on: push on:
push:
pull_request:
workflow_dispatch:
jobs: jobs:
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v4
- -
name: Build and push name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v7
with: with:
push: false push: false
tags: webstreamr/webstreamr:latest tags: webstreamr/webstreamr:latest

View file

@ -1,26 +1,17 @@
name: Tests name: Tests
on: push on:
push:
pull_request:
workflow_dispatch:
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: "22" node-version: "24"
- name: Cache node modules
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install Dependencies - name: Install Dependencies
run: npm install run: npm install

View file

@ -1,3 +1,3 @@
{ {
".": "0.13.0" ".": "0.69.1"
} }

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
FROM node:22.15-alpine AS builder FROM node:24 AS builder
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
@ -10,7 +10,7 @@ RUN npm run build
RUN npm ci --only=production RUN npm ci --only=production
FROM node:22.15-alpine FROM node:24
WORKDIR /app WORKDIR /app
COPY --from=builder /app/dist ./dist COPY --from=builder /app/dist ./dist

117
README.md Normal file
View file

@ -0,0 +1,117 @@
# WebStreamr
[![Tests](https://github.com/webstreamr/webstreamr/actions/workflows/tests.yml/badge.svg)](https://github.com/webstreamr/webstreamr/actions/workflows/tests.yml)
[![GitHub release](https://img.shields.io/github/v/release/webstreamr/webstreamr)](https://github.com/webstreamr/webstreamr/releases)
![GitHub License](https://img.shields.io/github/license/webstreamr/webstreamr)
[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).

View file

@ -1,9 +1,12 @@
import eslint from '@eslint/js'; import eslint from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin'; import stylistic from '@stylistic/eslint-plugin';
import importPlugin from 'eslint-plugin-import';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
export default tseslint.config( export default tseslint.config(
eslint.configs.recommended, eslint.configs.recommended,
importPlugin.flatConfigs.recommended,
importPlugin.flatConfigs.typescript,
tseslint.configs.strict, tseslint.configs.strict,
tseslint.configs.stylistic, tseslint.configs.stylistic,
stylistic.configs.customize({ stylistic.configs.customize({
@ -13,4 +16,11 @@ export default tseslint.config(
{ {
ignores: ['./dist'], ignores: ['./dist'],
}, },
{
rules: {
'@typescript-eslint/explicit-member-accessibility': 'error',
'import/no-unresolved': ['error', { ignore: ['typescript-eslint', 'stremio-addon-sdk'] }],
'import/order': ['error', { 'alphabetize': { order: 'asc', caseInsensitive: true }, 'named': { enabled: true }, 'newlines-between': 'never' }],
},
},
); );

View file

@ -1,6 +1,8 @@
import type { Config } from 'jest'; import type { Config } from 'jest';
const config: Config = { const config: Config = {
automock: false,
clearMocks: true,
collectCoverage: true, collectCoverage: true,
collectCoverageFrom: [ collectCoverageFrom: [
'<rootDir>/src/**/*.ts', '<rootDir>/src/**/*.ts',
@ -11,6 +13,7 @@ const config: Config = {
coverageDirectory: '<rootDir>/coverage', coverageDirectory: '<rootDir>/coverage',
coveragePathIgnorePatterns: [ coveragePathIgnorePatterns: [
'/src/controller/', '/src/controller/',
'/src/utils/dispatcher.ts',
], ],
coverageProvider: 'babel', coverageProvider: 'babel',
coverageThreshold: { coverageThreshold: {
@ -22,10 +25,15 @@ const config: Config = {
}, },
}, },
resetModules: true, resetModules: true,
restoreMocks: true, setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testEnvironment: 'node', testEnvironment: 'node',
testEnvironmentOptions: {
globalsCleanup: 'on',
},
transform: { transform: {
'^.+.tsx?$': ['ts-jest', {}], '^.+.tsx?$': ['ts-jest', {
tsconfig: 'tsconfig.dev.json',
}],
}, },
modulePathIgnorePatterns: [ modulePathIgnorePatterns: [
'<rootDir>/dist', '<rootDir>/dist',

17
jest.setup.ts Normal file
View file

@ -0,0 +1,17 @@
process.env['CACHE_DIR'] = '/dev/null';
process.env['HOST'] = 'example.test';
process.env['PROTOCOL'] = 'https';
if (!process.env['TMDB_ACCESS_TOKEN']) {
process.env['TMDB_ACCESS_TOKEN'] = 'some access token';
}
jest.mock('randomstring', () => ({
generate: jest.fn(() => 'mocked-random-string'),
}));
beforeEach(() => {
jest.spyOn(Date, 'now').mockImplementation(() => 639837296000);
});
console.log = console.warn = console.error = console.info = console.debug = () => { /* disable in favor of logger */ };

7773
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{ {
"name": "webstreamr", "name": "webstreamr",
"description": "Provides HTTP URLs from streaming websites.", "description": "Provides HTTP URLs from streaming websites.",
"version": "0.13.0", "version": "0.69.1",
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {
"analyse": "tsc --noEmit --project ./tsconfig.dev.json", "analyse": "tsc --noEmit --project ./tsconfig.dev.json",
@ -11,6 +11,7 @@
"lint:fix": "eslint --fix", "lint:fix": "eslint --fix",
"start": "node dist/index.js", "start": "node dist/index.js",
"test": "jest", "test": "jest",
"test:update-fixtures": "TEST_UPDATE_FIXTURES=1 npm run test",
"ci": "npm run lint && npm run analyse && npm run test" "ci": "npm run lint && npm run analyse && npm run test"
}, },
"repository": { "repository": {
@ -27,35 +28,61 @@
"url": "https://github.com/webstreamr/webstreamr/issues" "url": "https://github.com/webstreamr/webstreamr/issues"
}, },
"engines": { "engines": {
"node": "^22.0.0" "node": "^22.0.0 || ^24.0.0"
},
"devEngines": {
"runtime": {
"name": "node",
"onFail": "error"
},
"packageManager": {
"name": "npm",
"onFail": "error"
}
}, },
"dependencies": { "dependencies": {
"@isaacs/ttlcache": "^1.4.1", "@keyv/sqlite": "^4.0.5",
"axios-cache-interceptor": "^1.8.0", "async-mutex": "^0.5.0",
"axios": "^1.13.2",
"axios-cache-interceptor": "^1.8.3",
"axios-mock-adapter": "^2.1.0",
"axios-retry": "^4.5.0", "axios-retry": "^4.5.0",
"bytes": "^3.1.2", "bytes": "^3.1.2",
"cacheable": "^2.0.0",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"country-emoji": "^1.5.6",
"express": "^5.1.0", "express": "^5.1.0",
"express-rate-limit": "^8.2.1",
"fast-levenshtein": "^3.0.0",
"glob": "^13.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"memoizee": "^0.4.17",
"minimatch": "^10.0.3",
"randomstring": "^1.3.1",
"rot13-cipher": "^1.0.0",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"socks-proxy-agent": "^8.0.5",
"sqlite3": "^5.1.7",
"tough-cookie": "^6.0.0",
"unpacker": "^1.0.1", "unpacker": "^1.0.1",
"user-agents": "^1.1.536",
"winston": "^3.17.0" "winston": "^3.17.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.26.0", "@eslint/js": "^9.26.0",
"@stylistic/eslint-plugin": "^4.2.0", "@stylistic/eslint-plugin": "^5.0.0",
"@tsconfig/node24": "^24.0.0",
"@types/bytes": "^3.1.5", "@types/bytes": "^3.1.5",
"@types/express": "^5.0.1", "@types/express": "^5.0.1",
"@types/jest": "^29.5.14", "@types/fast-levenshtein": "^0.0.4",
"@types/make-fetch-happen": "^10.0.4", "@types/jest": "^30.0.0",
"@types/node": "^22.15.3", "@types/memoizee": "^0.4.12",
"@types/node": "^24.0.0",
"@types/randomstring": "^1.3.0",
"@types/stremio-addon-sdk": "^1.6.11", "@types/stremio-addon-sdk": "^1.6.11",
"@types/user-agents": "^1.0.4", "babel-jest": "^30.0.0",
"axios-mock-adapter": "^2.1.0",
"babel-jest": "^29.7.0",
"eslint": "^9.26.0", "eslint": "^9.26.0",
"jest": "^29.7.0", "eslint-plugin-import": "^2.32.0",
"jest": "^30.0.0",
"nodemon": "^3.1.10", "nodemon": "^3.1.10",
"ts-jest": "^29.3.2", "ts-jest": "^29.3.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",

View file

@ -1,27 +1,35 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { Extractor } from '../extractor';
import { landingTemplate } from '../landingTemplate'; import { landingTemplate } from '../landingTemplate';
import { buildManifest } from '../utils'; import { Source } from '../source';
import { Handler } from '../handler';
import { Config } from '../types'; import { Config } from '../types';
import { buildManifest, getDefaultConfig, isElfHostedInstance } from '../utils';
export class ConfigureController { export class ConfigureController {
public readonly router: Router; public readonly router: Router;
private readonly handlers: Handler[]; private readonly sources: Source[];
private readonly extractors: Extractor[];
constructor(handlers: Handler[]) { public constructor(sources: Source[], extractors: Extractor[]) {
this.router = Router(); this.router = Router();
this.handlers = handlers; this.sources = sources;
this.extractors = extractors;
this.router.get('/configure', this.getConfigure.bind(this)); this.router.get('/configure', this.getConfigure.bind(this));
this.router.get('/:config/configure', this.getConfigure.bind(this)); this.router.get('/:config/configure', this.getConfigure.bind(this));
} }
private readonly getConfigure = (req: Request, res: Response) => { private getConfigure(req: Request, res: Response) {
const config: Config = JSON.parse(req.params['config'] || '{}'); const config: Config = JSON.parse(req.params['config'] as string || JSON.stringify(getDefaultConfig()));
const manifest = buildManifest(this.handlers, config); // Convenience preset for ElfHosted WebStreamr bundle including Media Flow Proxy
if (!req.params['config'] && isElfHostedInstance(req)) {
config.mediaFlowProxyUrl = `${req.protocol}://${req.host.replace('webstreamr', 'mediaflow-proxy')}`;
}
const manifest = buildManifest(this.sources, this.extractors, config);
res.setHeader('content-type', 'text/html'); res.setHeader('content-type', 'text/html');
res.send(landingTemplate(manifest)); res.send(landingTemplate(manifest));

View 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);
}
};
}

View file

@ -1,26 +1,29 @@
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { buildManifest } from '../utils'; import { Extractor } from '../extractor';
import { Handler } from '../handler'; import { Source } from '../source';
import { Config } from '../types'; import { Config } from '../types';
import { buildManifest, getDefaultConfig } from '../utils';
export class ManifestController { export class ManifestController {
public readonly router: Router; public readonly router: Router;
private readonly handlers: Handler[]; private readonly sources: Source[];
private readonly extractors: Extractor[];
constructor(handlers: Handler[]) { public constructor(sources: Source[], extractors: Extractor[]) {
this.router = Router(); this.router = Router();
this.handlers = handlers; this.sources = sources;
this.extractors = extractors;
this.router.get('/manifest.json', this.getManifest.bind(this)); this.router.get('/manifest.json', this.getManifest.bind(this));
this.router.get('/:config/manifest.json', this.getManifest.bind(this)); this.router.get('/:config/manifest.json', this.getManifest.bind(this));
} }
private readonly getManifest = (req: Request, res: Response) => { private getManifest(req: Request, res: Response) {
const config: Config = JSON.parse(req.params['config'] || '{}'); const config: Config = JSON.parse(req.params['config'] as string || JSON.stringify(getDefaultConfig()));
const manifest = buildManifest(this.handlers, config); const manifest = buildManifest(this.sources, this.extractors, config);
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
res.send(manifest); res.send(manifest);

View file

@ -1,38 +1,70 @@
import { Mutex } from 'async-mutex';
import { Request, Response, Router } from 'express'; import { Request, Response, Router } from 'express';
import { ContentType } from 'stremio-addon-sdk';
import winston from 'winston'; import winston from 'winston';
import { Handler } from '../handler'; import { Source } from '../source';
import { Config } from '../types'; import { contextFromRequestAndResponse, envIsProd, Id, ImdbId, StreamResolver, TmdbId } from '../utils';
import { StreamResolver } from '../utils';
export class StreamController { export class StreamController {
public readonly router: Router; public readonly router: Router;
private readonly logger: winston.Logger; private readonly logger: winston.Logger;
private readonly handlers: Handler[]; private readonly sources: Source[];
private readonly streamResolver: StreamResolver; private readonly streamResolver: StreamResolver;
constructor(logger: winston.Logger, handlers: Handler[], streams: StreamResolver) { private readonly locks = new Map<string, Mutex>();
public constructor(logger: winston.Logger, sources: Source[], streams: StreamResolver) {
this.router = Router(); this.router = Router();
this.logger = logger; this.logger = logger;
this.handlers = handlers; this.sources = sources;
this.streamResolver = streams; this.streamResolver = streams;
this.router.get('/stream/:type/:id.json', this.getStream.bind(this));
this.router.get('/:config/stream/:type/:id.json', this.getStream.bind(this)); this.router.get('/:config/stream/:type/:id.json', this.getStream.bind(this));
} }
private readonly getStream = async (req: Request, res: Response) => { private async getStream(req: Request, res: Response) {
const config: Config = JSON.parse(req.params['config'] || '{}'); const type: ContentType = (req.params['type'] || '') as ContentType;
const type: string = req.params['type'] || ''; const rawId: string = req.params['id'] as string || '';
const id: string = req.params['id'] || '';
this.logger.info(`Search stream for type "${type}" and id "${id}"`); let id: Id;
if (rawId.startsWith('tmdb:')) {
id = TmdbId.fromString(rawId.replace('tmdb:', ''));
} else if (rawId.startsWith('tt')) {
id = ImdbId.fromString(rawId);
} else {
res.status(400).send(`Unsupported ID: ${rawId}`);
const selectedHandlers = this.handlers.filter(handler => handler.id in config); return;
}
const streams = await this.streamResolver.resolve({ ip: (req.ip as string) }, selectedHandlers, type, id); const ctx = contextFromRequestAndResponse(req, res);
res.setHeader('Content-Type', 'application/json'); this.logger.info(`Search stream for type "${type}" and id "${rawId}" for ip ${ctx.ip}`, ctx);
res.send(JSON.stringify({ streams }));
const sources = this.sources.filter(source => source.countryCodes.filter(countryCode => countryCode in ctx.config).length);
let mutex = this.locks.get(rawId);
if (!mutex) {
mutex = new Mutex();
this.locks.set(rawId, mutex);
}
await mutex.runExclusive(async () => {
const { streams, ttl } = await this.streamResolver.resolve(ctx, sources, type, id);
if (ttl && envIsProd()) {
res.setHeader('Cache-Control', `public, max-age=${Math.floor(ttl / 1000)}, immutable`);
}
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({ streams }));
});
if (!mutex.isLocked()) {
this.locks.delete(rawId);
}
}; };
} }

View file

@ -1,3 +1,4 @@
export * from './ExtractController';
export * from './ConfigureController'; export * from './ConfigureController';
export * from './ManifestController'; export * from './ManifestController';
export * from './StreamController'; export * from './StreamController';

View file

@ -1,38 +0,0 @@
import { EmbedExtractor } from './types';
import { extractUrlFromPacked, Fetcher } from '../utils';
import { Context } from '../types';
import bytes from 'bytes';
export class Dropload implements EmbedExtractor {
readonly id = 'dropload';
readonly label = 'Dropload';
readonly ttl = 900000; // 15m
private readonly fetcher: Fetcher;
constructor(fetcher: Fetcher) {
this.fetcher = fetcher;
}
readonly supports = (url: URL): boolean => null !== url.host.match(/dropload/);
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
const normalizedUrl = url.toString().replace('/e/', '').replace('/embed-', '/');
const html = await this.fetcher.text(ctx, new URL(normalizedUrl));
const heightMatch = html.match(/\d{3,}x(\d{3,}),/) as string[];
const sizeMatch = html.match(/([\d.]+ ?[GM]B)/) as string[];
return {
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
label: this.label,
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
height: parseInt(heightMatch[1] as string) as number,
bytes: bytes.parse(sizeMatch[1] as string) as number,
countryCode,
};
};
}

View file

@ -1,27 +0,0 @@
import winston from 'winston';
import { EmbedExtractorRegistry } from './EmbedExtractorRegistry';
import { Context } from '../types';
import { Fetcher } from '../utils';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
// @ts-expect-error No constructor args needed
const fetcher = new Fetcher();
const embedExtractors = new EmbedExtractorRegistry(logger, fetcher);
describe('EmbedExtractorRegistry', () => {
const ctx: Context = { ip: '127.0.0.1' };
test('returns undefined when no embed extractor can be found', async () => {
const urlResult = await embedExtractors.handle(ctx, new URL('https://some-url.test'), 'en');
expect(urlResult).toBeUndefined();
});
test('returns from memory cache if possible', async () => {
const urlResult1 = await embedExtractors.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
const urlResult2 = await embedExtractors.handle(ctx, new URL('https://dropload.io/lyo2h1snpe5c.html'), 'de');
expect(urlResult2).toBe(urlResult1);
});
});

View file

@ -1,41 +0,0 @@
import TTLCache from '@isaacs/ttlcache';
import winston from 'winston';
import { EmbedExtractor } from './types';
import { Context, UrlResult } from '../types';
import { Fetcher } from '../utils';
import { Dropload } from './Dropload';
import { SuperVideo } from './SuperVideo';
export class EmbedExtractorRegistry {
private readonly logger: winston.Logger;
private readonly embedExtractors: EmbedExtractor[];
private readonly urlResultCache: TTLCache<string, UrlResult>;
constructor(logger: winston.Logger, fetcher: Fetcher) {
this.logger = logger;
this.embedExtractors = [
new Dropload(fetcher),
new SuperVideo(fetcher),
];
this.urlResultCache = new TTLCache({ max: 1024 });
}
readonly handle = async (ctx: Context, url: URL, countryCode: string): Promise<UrlResult | undefined> => {
let urlResult = this.urlResultCache.get(url.href);
if (urlResult) {
return urlResult;
}
const embedExtractor = this.embedExtractors.find(embedExtractor => embedExtractor.supports(url));
if (undefined === embedExtractor) {
return undefined;
}
this.logger.info(`Extract stream URL using ${embedExtractor.id} extractor from ${url}`);
urlResult = await embedExtractor.extract(ctx, url, countryCode);
this.urlResultCache.set(url.href, urlResult, { ttl: embedExtractor.ttl });
return urlResult;
};
}

View file

@ -1,36 +0,0 @@
import bytes from 'bytes';
import { EmbedExtractor } from './types';
import { extractUrlFromPacked, Fetcher } from '../utils';
import { Context } from '../types';
export class SuperVideo implements EmbedExtractor {
readonly id = 'supervideo';
readonly label = 'SuperVideo';
readonly ttl = 900000; // 15m
private readonly fetcher: Fetcher;
constructor(fetcher: Fetcher) {
this.fetcher = fetcher;
}
readonly supports = (url: URL): boolean => null !== url.host.match(/supervideo/);
readonly extract = async (ctx: Context, url: URL, countryCode: string) => {
const normalizedUrl = url.toString().replace('/e/', '/').replace('/embed-', '/');
const html = await this.fetcher.text(ctx, new URL(normalizedUrl));
const heightAndSizeMatch = html.match(/\d{3,}x(\d{3,}), ([\d.]+ ?[GM]B)/) as string[];
return {
url: extractUrlFromPacked(html, [/sources:\[{file:"(.*?)"/]),
label: this.label,
sourceId: `${this.id}_${countryCode.toLowerCase()}`,
height: parseInt(heightAndSizeMatch[1] as string) as number,
bytes: bytes.parse(heightAndSizeMatch[2] as string) as number,
countryCode,
};
};
}

View file

@ -1 +0,0 @@
export * from './EmbedExtractorRegistry';

View file

@ -1,13 +0,0 @@
import { Context, UrlResult } from '../types';
export interface EmbedExtractor {
readonly id: string;
readonly label: string;
readonly ttl: number;
readonly supports: (url: URL) => boolean;
readonly extract: (ctx: Context, url: URL, countryCode: string) => Promise<UrlResult>;
}

16
src/error/BlockedError.ts Normal file
View 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
View 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;
}
}

View file

@ -0,0 +1 @@
export class NotFoundError extends Error {}

View file

@ -0,0 +1,9 @@
export class QueueIsFullError extends Error {
public readonly url: URL;
public constructor(url: URL) {
super();
this.url = url;
}
}

View file

@ -0,0 +1,9 @@
export class TimeoutError extends Error {
public readonly url: URL;
public constructor(url: URL) {
super();
this.url = url;
}
}

View 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;
}
}

View 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
View 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}.`;
};

View 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();
});
});

View 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 }),
},
},
];
};
}

View 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
View 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,
},
];
};
}

View 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();
});
});

View 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,
},
];
};
}

View 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;
}
}

View 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();
});
});

View 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}`;
}
}

View 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
View 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],
},
},
];
};
}

View 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();
});
});

View 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 }),
},
},
];
};
}

View 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
View 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) }),
},
},
];
};
}

View 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
View 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,
},
}];
};
}

View 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
View 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(),
]);
};
}

View 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
View 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) : [];
};
}

View 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
View 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,
},
];
};
}

View 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();
});
});

View 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),
},
},
];
};
}

View 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
View 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,
},
},
];
};
}

View 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
View 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,
},
];
};
}

View 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();
});
});

View 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),
},
},
];
};
}

View 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();
});
});

View 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),
},
},
];
};
}

View 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();
});
});

View 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,
},
},
];
};
}

View 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();
});
});

View 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,
},
];
};
}

View 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
View 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),
}),
},
},
];
};
}

View 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
View 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,
},
};
}),
);
}
}

View 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
View 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,
},
];
};
}

View 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
View 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
View 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
View 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 }),
},
},
];
};
}

View 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
View 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,
},
},
];
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -19,10 +19,10 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet"> <link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src="https://dropload.io/assets2/js/xupload.js?v=13" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
<script src="https://dropload.io/assets2/js/app.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src="https://dropload.io/assets2/js/app.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
</head> </head>
<body> <body>
@ -91,12 +91,12 @@
<main> <main>
<script src="https://dropload.io/js/jquery.cookie.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src="https://dropload.io/js/jquery.cookie.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
<script type="067d87f6f1b0d87bbe23bf48-text/javascript"> <script type="1198ec03089106bda1a7dcfc-text/javascript">
$.cookie('file_id', '376684', { expires: 10 }); $.cookie('file_id', '1062326', { expires: 10 });
$.cookie('aff', '1805', { expires: 10 }); $.cookie('aff', '1805', { expires: 10 });
</script> </script>
@ -119,54 +119,36 @@ $.cookie('aff', '1805', { expires: 10 });
<div id="pdiv" style="position: absolute;left:0;top:0;width: 100%;height:100%;">
<!-- Over player banner ADs code start here -->
<!-- Over player banner ADs code end here -->
<!--div id="adbd" class="overdiv">
<div>Disable ADBlock plugin and allow pop-ups in your browser to watch video</div>
</div-->
<!-- <div id="play_limit_box" class="d-flex flex-column justify-content-center p-3 text-center" style="position:absolute;left:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,.55);z-index:10;">
<div class="mb-2"> <img src="https://img.dropload.io/7xtxuac84xyh.jpg" alt="">
<a class="btn btn-gradient" href="/premium.html">Upgrade your account</a>
</div> <div class="encoding">
to watch videos with no limits! <div class="encoding-body">
</div> --> <div class="row align-items-center">
<div class="col-auto">
<div class="percent">
<script type="067d87f6f1b0d87bbe23bf48-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script> 75%
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">jwplayer.key="";</script> </div>
<style> </div>
.jw-text-track-cue { <div class="col-auto">
text-shadow: 1px 1px 2px transparent !important; <h6>Pending in queue</h6>
line-height: 1.53em; <small class="text-light">Video is processing now.<br>Conversion stage: <span id='enc_pp'>...</span></small>
padding-left: 0.3em !important; </div>
padding-right: 0.3em !important; </div>
padding-bottom: 0.2em !important; </div>
border-radius: 6px; </div>
}
</style><script src="/js/localstorage-slim.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script>
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
</div>
<button type="button" class="btn lightdown"></button>
</div> </div>
<div class="lightdown-backdrop"></div> <div class="lightdown-backdrop"></div>
<h1>Black Mirror 2x3 </h1> <h1>Bates Motel 2x3 </h1>
<div class="videoplayer-controlbar"> <div class="videoplayer-controlbar">
<div class="row g-1"> <div class="row g-1">
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0"> <div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
@ -178,7 +160,7 @@ $.cookie('aff', '1805', { expires: 10 });
<div class="col-auto d-flex align-items-center"> <div class="col-auto d-flex align-items-center">
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i> <i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
on Oct 12, 2023 on Aug 6, 2024
</div> </div>
<div class="col-auto d-flex align-items-center"> <div class="col-auto d-flex align-items-center">
@ -187,7 +169,7 @@ $.cookie('aff', '1805', { expires: 10 });
</div> </div>
<div class="col-auto d-flex align-items-center"> <div class="col-auto d-flex align-items-center">
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i> <i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
00:44:07 00:42:55
</div> </div>
</div> </div>
@ -205,13 +187,13 @@ $.cookie('aff', '1805', { expires: 10 });
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=t058ulwwkwn6&v=up')" title="Like" data-cf-modified-067d87f6f1b0d87bbe23bf48-=""> <button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=7xtxuac84xyh&v=up')" title="Like" data-cf-modified-1198ec03089106bda1a7dcfc-="">
<i class="icon icon-like icon-size-16 me-2"></i> <i class="icon icon-like icon-size-16 me-2"></i>
<span id="likes_num">0</span> <span id="likes_num">0</span>
</button> </button>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=t058ulwwkwn6&v=down')" title="Don&#39;t Like" data-cf-modified-067d87f6f1b0d87bbe23bf48-=""> <button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=7xtxuac84xyh&v=down')" title="Don&#39;t Like" data-cf-modified-1198ec03089106bda1a7dcfc-="">
<i class="icon icon-dislike icon-size-16 me-2"></i> <i class="icon icon-dislike icon-size-16 me-2"></i>
<span id="dislikes_num">0</span> <span id="dislikes_num">0</span>
</button> </button>
@ -224,66 +206,12 @@ $.cookie('aff', '1805', { expires: 10 });
<div class="row justify-content-center justify-content-lg-between"> <div class="row justify-content-center justify-content-lg-between">
<div class="col-auto order-lg-last mb-3">
<div class="dropdown">
<a class="btn btn-gradient videoplayer-download" data-bs-toggle="dropdown">
Download<i class="icon icon-download icon-size-20 ms-2"></i>
</a>
<div class="dropdown-menu w-100 py-0 shadow">
<a href="https://dropload.io/d/t058ulwwkwn6_h" class="dropdown-item d-flex align-items-center">
<span class="flex-grow-1 pe-3 text-white">
<b>HD quality</b> <br> <span class="xsmall text-light">640x360, <span class="download-size">140.0 MB</span></span>
</span>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="18"><defs><linearGradient id="a" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#3DB6D4"/><stop offset="100%" stop-color="#6BCFA4"/></linearGradient></defs><path fill-rule="evenodd" fill="#3DB6D4" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/><path fill="url(#a)" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/></svg>
</a>
</div>
</div>
</div>
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">
$(function(){
var a = $('.dropdown-menu a:first-child .download-size');
$('#size').append(a);
})
</script>
<div class="col-12 col-lg-7 mb-3"> <div class="col-12 col-lg-7 mb-3">
<div class="copybox"> <div class="copybox">
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/t058ulwwkwn6" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea> <textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/7xtxuac84xyh" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-067d87f6f1b0d87bbe23bf48-="">Copy</button> <button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-1198ec03089106bda1a7dcfc-="">Copy</button>
</div> </div>
</div> </div>
@ -305,9 +233,9 @@ $.cookie('aff', '1805', { expires: 10 });
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body pt-0"> <div class="modal-body pt-0">
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-067d87f6f1b0d87bbe23bf48-=""> <form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-1198ec03089106bda1a7dcfc-="">
<input type="hidden" name="op" value="report_file"> <input type="hidden" name="op" value="report_file">
<input type="hidden" name="file_code" value="t058ulwwkwn6"> <input type="hidden" name="file_code" value="7xtxuac84xyh">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Reason:</label> <label class="form-label">Reason:</label>
<select class="form-select" name="report_type"> <select class="form-select" name="report_type">
@ -370,8 +298,7 @@ $.cookie('aff', '1805', { expires: 10 });
<script type="067d87f6f1b0d87bbe23bf48-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1d("5v").5u({5t:{2i:"/5s/2m/2l-o.2m?v=3",q:"2l-o"},5r:[{2k:"13://5q.o.12/5p/5o/5n/5m/5l.5k?t=5j&s=1z&e=5i&f=22&i=0.0&5h=0&5g=21.20.5f.5e"}],5d:"1j%",5c:"1j%",5b:"5a",59:"2j.58",57:\'56\',55:"y",a:[{2k:"/26?23=54&k=2j&2i=13://2h.o.12/53.2g",52:"51"}],1s:{50:1,2f:\'#4z\',4y:\'#4x\',4w:"4v",4u:30,4t:\'1j\',},\'4s\':{"4r":"4q"},4p:"4o",4n:"13://o.12/",4m:{},4l:y,1r:[1,1.25,1.5,2],4k:\'13://2h.o.12/4j.2g\'}).g(\'1t\',8(){d n=2d.4i("11");n.4h="n";n.2e(\'4g\',\'4-1i 4-1i-4f 4-2c-2f 4-4e 4-1i-4d\');n.2e(\'4c\',\'6.1c(6.4b()+10)\');2d.4a(\'.4-2c-49\').48(n)});d 1g,1h;d 47=0,46=0;d 6=1d();d 2a=0,45=0,44=0,j=0;$.43({42:{\'41-40\':\'3z-3y\'}});6.g(\'3x\',8(x){9(5>0&&x.p>=5&&1h!=1){1h=1;$(\'11.3w\').3v(\'3u\')}9(x.p>=j+5||x.p<j){j=x.p;1f.3t(\'1e\',3s.3r(j),{3q:2b*2b*24*7})}});6.g(\'1c\',8(x){2a=x.p});6.g(\'3p\',8(x){29(x)});6.g(\'3o\',8(){$(\'11.28\').3n();1f.3m(\'1e\')});6.g(\'3l\',8(x){});8 29(x){$(\'11.28\').27();$(\'#3k\').27();9(1g)15;1g=1;z=0;9(3j.3i===3h){z=1}$.1x(\'/26?23=3g&3f=3e&3d=22-21-20-1z-3c&3b=&z=\'+z,8(1y){$(\'#3a\').39(1y)});d j=1f.1x(\'1e\');9(j>0){1d().1c(j)}}8 38(){d a=6.16(1w);1v.1u(a);9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1w){1v.1u(\'!!=\'+i);6.1k(i)}}}}6.g(\'1t\',8(){});6.g("h",8(r){d a=6.16();9(a.k<2)15;$(\'.4-c-37-36\').35(8(){$(\'#4-c-b-h\').19(\'4-c-b-u\');$(\'.4-b-h\').m(\'l-1a\',\'w\')});6.34("/33/32.31","2z 2y",8(){$(\'.4-1q\').2x(\'4-c-1p\');$(\'.4-c-1s, .4-c-1r\').m(\'l-1b\',\'w\');9($(\'.4-1q\').2w(\'4-c-1p\')){$(\'.4-b-h\').m(\'l-1b\',\'y\');$(\'.4-b-h\').m(\'l-1a\',\'y\');$(\'.4-c-b-2v\').19(\'4-c-b-u\');$(\'.4-c-b-h\').2u(\'4-c-b-u\')}2t{$(\'.4-b-h\').m(\'l-1b\',\'w\');$(\'.4-b-h\').m(\'l-1a\',\'w\');$(\'.4-c-b-h\').19(\'4-c-b-u\')}},"2s");6.g("2r",8(r){18.2q(\'17\',r.a[r.2p].q)});9(18.1o(\'17\')){2o("1n(18.1o(\'17\'));",2n)}});d 14;8 1n(1l){d a=6.16();9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1l){9(i==14){15}14=i;6.1k(i)}}}}',36,212,'||||jw||player||function|if|tracks|submenu|settings|var|||on|audioTracks||lastt|length|aria|attr|newButton|dropload|position|name|event|||active||false||true|adb||div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|seek|jwplayer|ttt058ulwwkwn6|ls|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|open|controls|playbackRates|captions|ready|log|console|track_name|get|data|1747213564|61|130|376684|op|||dl|hide|video_ad|doPlay|prevt|60|button|document|setAttribute|color|jpg|img|url|2647|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|quality|hasClass|toggleClass|Track|Audio||svg|dualy|images|addButton|mousedown|buttons|topbar|set_audio_track|html|fviews|embed|4020dbc00ffbffc6fb1a64b82baa1d45|hash|t058ulwwkwn6|file_code|view|undefined|cRAds|window|over_player_msg|pause|remove|show|complete|play|ttl|round|Math|set|slow|fadeIn|video_ad_fadein|time|cache|no|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|t058ulwwkwn6_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|436|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|t058ulwwkwn60000|get_slides|androidhls|metadata|preload|04|duration|uniform|stretching|height|width|204|236|ii|sp|14400|GkktV2WgIMrvrgNLyjn6Bxeo2fArEAz6y9nSi_6dVAs|m3u8|master|t058ulwwkwn6_h|00075|01|hls2|srv27|sources|assets2|skin|setup|vplayer'.split('|')))
</script>
<style media="screen"> <style media="screen">
.lightdown { .lightdown {
@ -411,13 +338,13 @@ $.cookie('aff', '1805', { expires: 10 });
} }
</style> </style>
<script type="067d87f6f1b0d87bbe23bf48-text/javascript"> <script type="1198ec03089106bda1a7dcfc-text/javascript">
$('.lightdown').on('click', function(){ $('.lightdown').on('click', function(){
$('body').toggleClass('body-lightdown'); $('body').toggleClass('body-lightdown');
}) })
</script> </script>
<Script type="067d87f6f1b0d87bbe23bf48-text/javascript"> <Script type="1198ec03089106bda1a7dcfc-text/javascript">
$("#embed_div textarea").focus(function (){ $(this).select(); }) $("#embed_div textarea").focus(function (){ $(this).select(); })
var tab_cookie='tab_down'; var tab_cookie='tab_down';
@ -439,18 +366,20 @@ $.cookie('aff', '1805', { expires: 10 });
}); });
encStatus('1062326',1);
</Script> </Script>
<script src="https://dropload.io/js/tabber.js" type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src="https://dropload.io/js/tabber.js" type="1198ec03089106bda1a7dcfc-text/javascript"></script>
<Script type="067d87f6f1b0d87bbe23bf48-text/javascript"> <Script type="1198ec03089106bda1a7dcfc-text/javascript">
$(function() { $(function() {
}); });
</Script> </Script>
<script data-cfasync="false" async type="text/javascript" src="//sx.impubicpuddly.com/rO7FQr7AANJdtg/111561"></script> <script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
<script src='https://dropload.io/tag2.js' type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src='https://dropload.io/tag2.js' type="1198ec03089106bda1a7dcfc-text/javascript"></script>
<script src='https://dropload.io/tag1.js' type="067d87f6f1b0d87bbe23bf48-text/javascript"></script> <script src='https://dropload.io/tag1.js' type="1198ec03089106bda1a7dcfc-text/javascript"></script>
<!-- Javascript Alt Ads code start --> <!-- Javascript Alt Ads code start -->
@ -495,7 +424,7 @@ $.cookie('aff', '1805', { expires: 10 });
</footer> </footer>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="067d87f6f1b0d87bbe23bf48-|49" defer></script></body> <script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="1198ec03089106bda1a7dcfc-|49" defer></script></body>
</html> </html>

View 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&#39;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>

File diff suppressed because one or more lines are too long

View file

@ -19,10 +19,10 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet"> <link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src="https://dropload.io/assets2/js/xupload.js?v=13" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<script src="https://dropload.io/assets2/js/app.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src="https://dropload.io/assets2/js/app.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
</head> </head>
<body> <body>
@ -91,11 +91,11 @@
<main> <main>
<script src="https://dropload.io/js/jquery.cookie.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src="https://dropload.io/js/jquery.cookie.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript"> <script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
$.cookie('file_id', '987607', { expires: 10 }); $.cookie('file_id', '987607', { expires: 10 });
$.cookie('aff', '4', { expires: 10 }); $.cookie('aff', '4', { expires: 10 });
@ -137,8 +137,21 @@ $.cookie('aff', '4', { expires: 10 });
</div> --> </div> -->
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script> <script type="ad8da47d2c4dcd2ad69dff49-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">jwplayer.key="";</script> <script type="ad8da47d2c4dcd2ad69dff49-text/javascript">jwplayer.key="";</script>
<div id="resume-modal" style="color:#fff; font-family:Roboto, Arial; display:none; padding:2rem; justify-content:center; align-items:center; position:absolute; top:0; left:0; right:0; bottom:0; background-color: rgba(0,0,0,.1); font-size: 12px; z-index:200;">
<div style="max-width:420px; background-color: rgba(28, 37, 52, .65); -webkit-backdrop-filter: blur(20px); backdrop-filter:blur(20px); text-align:center;padding:1.5rem; border-radius:8px;border: 1px solid rgba(255,255,255,.18);">
<h5 style="margin-bottom:10px;font-weight:bold; font-size:15px;" id="resume-title"></h5>
<p style="margin-bottom:10px;opacity:.65" id="resume-text"></p>
<div style="display:flex;gap:.4rem; align-items-center;justify-content:center">
<button onclick="if (!window.__cfRLUnblockHandlers) return false; resumeVideo()" id="resume-btn" style="border:0;cursor:pointer;border-radius:5px; padding:.5rem .75rem; color:#fff;background: linear-gradient(90deg, #3EB6D3 0%, #69CEA6 100%);color:#fff" data-cf-modified-ad8da47d2c4dcd2ad69dff49-=""></button>
<button onclick="if (!window.__cfRLUnblockHandlers) return false; cancelResume()" id="cancel-text" style="border:0;cursor:pointer;border-radius:5px;padding:.5rem .75rem; color:#fff;background:#476177;color:#fff" data-cf-modified-ad8da47d2c4dcd2ad69dff49-=""></button>
</div>
</div>
</div>
<style> <style>
.jw-text-track-cue { .jw-text-track-cue {
text-shadow: 1px 1px 2px transparent !important; text-shadow: 1px 1px 2px transparent !important;
@ -148,7 +161,7 @@ $.cookie('aff', '4', { expires: 10 });
padding-bottom: 0.2em !important; padding-bottom: 0.2em !important;
border-radius: 6px; border-radius: 6px;
} }
</style><script src="/js/localstorage-slim.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> </style><script src="/js/localstorage-slim.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div> <div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
</div> </div>
@ -205,13 +218,13 @@ $.cookie('aff', '4', { expires: 10 });
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=lyo2h1snpe5c&v=up')" title="Like" data-cf-modified-fb4dc13d1285d9e23d3f1de2-=""> <button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=lyo2h1snpe5c&v=up')" title="Like" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">
<i class="icon icon-like icon-size-16 me-2"></i> <i class="icon icon-like icon-size-16 me-2"></i>
<span id="likes_num">0</span> <span id="likes_num">0</span>
</button> </button>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=lyo2h1snpe5c&v=down')" title="Don&#39;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&#39;t Like" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">
<i class="icon icon-dislike icon-size-16 me-2"></i> <i class="icon icon-dislike icon-size-16 me-2"></i>
<span id="dislikes_num">0</span> <span id="dislikes_num">0</span>
</button> </button>
@ -270,7 +283,7 @@ $.cookie('aff', '4', { expires: 10 });
</div> </div>
</div> </div>
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript"> <script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
$(function(){ $(function(){
var a = $('.dropdown-menu a:first-child .download-size'); var a = $('.dropdown-menu a:first-child .download-size');
$('#size').append(a); $('#size').append(a);
@ -283,7 +296,7 @@ $.cookie('aff', '4', { expires: 10 });
<div class="col-12 col-lg-7 mb-3"> <div class="col-12 col-lg-7 mb-3">
<div class="copybox"> <div class="copybox">
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/lyo2h1snpe5c" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea> <textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/lyo2h1snpe5c" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-fb4dc13d1285d9e23d3f1de2-="">Copy</button> <button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">Copy</button>
</div> </div>
</div> </div>
@ -305,7 +318,7 @@ $.cookie('aff', '4', { expires: 10 });
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body pt-0"> <div class="modal-body pt-0">
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-fb4dc13d1285d9e23d3f1de2-=""> <form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-ad8da47d2c4dcd2ad69dff49-="">
<input type="hidden" name="op" value="report_file"> <input type="hidden" name="op" value="report_file">
<input type="hidden" name="file_code" value="lyo2h1snpe5c"> <input type="hidden" name="file_code" value="lyo2h1snpe5c">
<div class="mb-3"> <div class="mb-3">
@ -370,7 +383,7 @@ $.cookie('aff', '4', { expires: 10 });
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1d("5w").5v({5u:{2i:"/5t/2l/2k-o.2l?v=3",q:"2k-o"},5s:[{2j:"13://5r.o.12/5q/5p/5o/5n/5m.5l?t=5k&s=1z&e=5j&f=22&i=0.0&5i=0&5h=21.20.5g.5f"}],5e:"1j%",5d:"1j%",5c:"5b",5a:"59.56",58:\'57\',55:"y",a:[{2j:"/26?23=54&k=53&2i=13://2h.o.12/52.2g",51:"50"}],1s:{4z:1,2f:\'#4y\',4x:\'#4w\',4v:"4u",4t:30,4s:\'1j\',},\'4r\':{"4q":"4p"},4o:"4n",4m:"13://o.12/",4l:{},4k:y,1r:[1,1.25,1.5,2],4j:\'13://2h.o.12/4i.2g\'}).g(\'1t\',8(){d n=2d.4h("11");n.4g="n";n.2e(\'4f\',\'4-1i 4-1i-4e 4-2c-2f 4-4d 4-1i-4c\');n.2e(\'4b\',\'6.1c(6.4a()+10)\');2d.49(\'.4-2c-48\').47(n)});d 1g,1h;d 46=0,45=0;d 6=1d();d 2a=0,44=0,43=0,j=0;$.42({41:{\'40-3z\':\'3y-3x\'}});6.g(\'3w\',8(x){9(5>0&&x.p>=5&&1h!=1){1h=1;$(\'11.3v\').3u(\'3t\')}9(x.p>=j+5||x.p<j){j=x.p;1f.3s(\'1e\',3r.3q(j),{3p:2b*2b*24*7})}});6.g(\'1c\',8(x){2a=x.p});6.g(\'3o\',8(x){29(x)});6.g(\'3n\',8(){$(\'11.28\').3m();1f.3l(\'1e\')});6.g(\'3k\',8(x){});8 29(x){$(\'11.28\').27();$(\'#3j\').27();9(1g)15;1g=1;z=0;9(3i.3h===3g){z=1}$.1x(\'/26?23=3f&3e=3d&3c=22-21-20-1z-3b&3a=&z=\'+z,8(1y){$(\'#39\').38(1y)});d j=1f.1x(\'1e\');9(j>0){1d().1c(j)}}8 37(){d a=6.16(1w);1v.1u(a);9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1w){1v.1u(\'!!=\'+i);6.1k(i)}}}}6.g(\'1t\',8(){});6.g("h",8(r){d a=6.16();9(a.k<2)15;$(\'.4-c-36-35\').34(8(){$(\'#4-c-b-h\').19(\'4-c-b-u\');$(\'.4-b-h\').m(\'l-1a\',\'w\')});6.33("/32/31.2z","2y 2x",8(){$(\'.4-1q\').2w(\'4-c-1p\');$(\'.4-c-1s, .4-c-1r\').m(\'l-1b\',\'w\');9($(\'.4-1q\').2v(\'4-c-1p\')){$(\'.4-b-h\').m(\'l-1b\',\'y\');$(\'.4-b-h\').m(\'l-1a\',\'y\');$(\'.4-c-b-2u\').19(\'4-c-b-u\');$(\'.4-c-b-h\').2t(\'4-c-b-u\')}2s{$(\'.4-b-h\').m(\'l-1b\',\'w\');$(\'.4-b-h\').m(\'l-1a\',\'w\');$(\'.4-c-b-h\').19(\'4-c-b-u\')}},"2r");6.g("2q",8(r){18.2p(\'17\',r.a[r.2o].q)});9(18.1o(\'17\')){2n("1n(18.1o(\'17\'));",2m)}});d 14;8 1n(1l){d a=6.16();9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1l){9(i==14){15}14=i;6.1k(i)}}}}',36,213,'||||jw||player||function|if|tracks|submenu|settings|var|||on|audioTracks||lastt|length|aria|attr|newButton|dropload|position|name|event|||active||false||true|adb||div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|seek|jwplayer|ttlyo2h1snpe5c|ls|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|open|controls|playbackRates|captions|ready|log|console|track_name|get|data|1746903744|61|130|987607|op|||dl|hide|video_ad|doPlay|prevt|60|button|document|setAttribute|color|jpg|img|url|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|quality|hasClass|toggleClass|Track|Audio|svg||dualy|images|addButton|mousedown|buttons|topbar|set_audio_track|html|fviews|embed|4d087dfd416ea7ee8d382d34d700a2b9|hash|lyo2h1snpe5c|file_code|view|undefined|cRAds|window|over_player_msg|pause|remove|show|complete|play|ttl|round|Math|set|slow|fadeIn|video_ad_fadein|time|cache|no|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|lyo2h1snpe5c_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|1557|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|lyo2h1snpe5c0000|7259|get_slides|androidhls||metadata|preload|7258|duration|uniform|stretching|height|width|204|236|ii|sp|14400|qTZsRGIMnUjXsQ4o2LRLlsT4tGG1AdJrRBRDGttNLPM|m3u8|master|lyo2h1snpe5c_h|00197|01|hls2|srv27|sources|assets2|skin|setup|vplayer'.split('|'))) <script type="ad8da47d2c4dcd2ad69dff49-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1u("7y").7x({7w:{3i:"/7v/3l/3k-y.3l?v=3",11:"3k-y"},7u:[{3j:"1d://7t.y.1c/7s/2s/7r/7q/7p.7o?t=7n&s=34&e=7m&f=36&7l=1z&i=0.0&7k=0&7j=50.35.15.7i&7h=1z&7g=1z"}],7f:"1y%",7e:"1y%",7d:"7c",7b:"7a.56",79:\'78\',77:"16",b:[{3j:"/38?37=76&p=75&3i=1d://3h.y.1c/74.3g",73:"72"}],2a:{71:1,3f:\'#70\',6z:\'#6y\',6x:"6w",6v:30,6u:\'1y\',},\'6t\':{"6s":"6r"},6q:"6p",6o:"1d://y.1c/",6n:{},6m:16,29:[1,1.25,1.5,2],6l:\'1d://3h.y.1c/6k.3g\'}).j(\'1t\',6(){h w=g.6j("1b");w.6i="w";w.3e(\'6h\',\'4-1x 4-1x-6g 4-3d-3f 4-6f 4-1x-6e\');w.3e(\'6d\',\'8.1q(8.6c()+10)\');g.6b(\'.4-3d-6a\').69(w)});h 1v,1w;h 68=0,67=0;h 8=1u();h 3c=0,66=0,65=0,n=0;$.64({63:{\'62-61\':\'5z-5y\'}});8.j(\'u\',6(x){9(5>0&&x.z>=5&&1w!=1){1w=1;$(\'1b.5x\').5w(\'5v\')}9(x.z>=n+5||x.z<n){n=x.z;18.2d(\'17\',1r.5u(n),{5t:60*60*24*7})}});8.j(\'1q\',6(x){3c=x.z});8.j(\'2f\',6(x){3b(x)});8.j(\'5s\',6(){$(\'1b.3a\').5r();18.5q(\'17\')});8.j(\'5p\',6(x){});6 3b(x){$(\'1b.3a\').39();$(\'#5o\').39();9(1v)1f;1v=1;1a=0;9(2y.5n===5m){1a=1}$.2v(\'/38?37=5l&5k=2x&5j=36-50-35-34-5i&5h=&1a=\'+1a,6(33){$(\'#5g\').5f(33)})}6 5e(){h b=8.1g(32);31.2z(b);9(b.p>1){22(i=0;i<b.p;i++){9(b[i].11==32){31.2z(\'!!=\'+i);8.20(i)}}}}8.j(\'1t\',6(){});1u().2c("/2b/5d.5c","5b j 5a",6(){h 2w=2y.1m(\'/v/2x\',\'59\');2w.58()},"57");h n=18.2v(\'17\');8.j(\'1t\',6(){9(n>0){2k(n)}});6 2i(u){1s 2t=2u.55||2u.54;9(2t.53(\'52\')){g.k("l-2r").o=\'51 4z 4y?\';g.k("l-2q").o=\'4x! 4w 4v a 0:2s. 4u 4t 4s 4r 4q 4p?\';g.k("l-19").o=\'4o, 4n\';g.k("2m-19").o=\'2l, 4m\'}27{g.k("l-2r").o=\'4l 4k?\';g.k("l-2q").o=\'4j 4i! 4h 2o 2n 4g \'+u+\'. 4f 2p 4e 4d l 4c 4b 2p 2o 2n?\';g.k("l-19").o=\'4a, 49\';g.k("2m-19").o=\'2l, 48\'}}6 2k(u){1s 2h=1r.2j(u/60);1s 2g=1r.2j(u%60).47().46(2,\'0\');2i(2h+\':\'+2g);g.k("l-1p").1o.1n=\'45\'}6 44(){8.1q(n);8.2f();g.k("l-1p").1o.1n=\'2e\'}6 43(){g.k("l-1p").1o.1n=\'2e\';18.2d(\'17\',\'\')}8.j("m",6(12){h b=8.1g();9(b.p<2)1f;$(\'.4-d-42-41\').40(6(){$(\'#4-d-c-m\').1j(\'4-d-c-13\');$(\'.4-c-m\').r(\'q-1k\',\'14\')});8.2c("/2b/3z.3y","3x 3w",6(){$(\'.4-28\').3v(\'4-d-1m\');$(\'.4-d-2a, .4-d-29\').r(\'q-1l\',\'14\');9($(\'.4-28\').3u(\'4-d-1m\')){$(\'.4-c-m\').r(\'q-1l\',\'16\');$(\'.4-c-m\').r(\'q-1k\',\'16\');$(\'.4-d-c-3t\').1j(\'4-d-c-13\');$(\'.4-d-c-m\').3s(\'4-d-c-13\')}27{$(\'.4-c-m\').r(\'q-1l\',\'14\');$(\'.4-c-m\').r(\'q-1k\',\'14\');$(\'.4-d-c-m\').1j(\'4-d-c-13\')}},"3r");8.j("3q",6(12){1i.3p(\'1h\',12.b[12.3o].11)});9(1i.26(\'1h\')){3n("23(1i.26(\'1h\'));",3m)}});h 1e;6 23(21){h b=8.1g();9(b.p>1){22(i=0;i<b.p;i++){9(b[i].11==21){9(i==1e){1f}1e=i;8.20(i)}}}}',36,287,'||||jw||function||player|if||tracks|submenu|settings|||document|var||on|getElementById|resume|audioTracks|lastt|innerHTML|length|aria|attr|||time||newButton||dropload|position||name|event|active|false||true|ttlyo2h1snpe5c|ls|btn|adb|div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|open|display|style|modal|seek|Math|const|ready|jwplayer|vvplay|vvad|icon|100|srv27|setCurrentAudioTrack|audio_name|for|audio_set|||getItem|else|controls|playbackRates|captions|images|addButton|set|none|play|seconds|minutes|translateModal|floor|showResumeModal|No|cancel|off|left|you|text|title|01|userLang|navigator|get|win|lyo2h1snpe5c|window|log||console|track_name|data|1749845553|99|987607|op|dl|hide|video_ad|doPlay|prevt|button|setAttribute|color|jpg|img|url|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|addClass|quality|hasClass|toggleClass|Track|Audio|svg|dualy|mousedown|buttons|topbar|cancelResume|resumeVideo|flex|padStart|toString|thanks|please|Yes|where|watching|to|like|Would|at|You|back|Welcome|playing|Resume|grazie|certo|Si|lasciato|avevi|dove|da|riprendere|Vuoi|interrotto|Hai|Bentornato|visione|la||Riprendi|it|startsWith|userLanguage|language||download11|focus|_blank|site|Watch|png|download2|set_audio_track|html|fviews|embed|fc780ce094c01bfee287ea721706dbe2|hash|file_code|view|undefined|cRAds|over_player_msg|pause|remove|show|complete|ttl|round|slow|fadeIn|video_ad_fadein|cache|no||Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|lyo2h1snpe5c_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|1557|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|lyo2h1snpe5c0000|7259|get_slides|androidhls|metadata|preload|7258|duration|uniform|stretching|height|width|p2|p1|93|ii|sp|srv|14400|ESjD9Le53ttKN2JPyxrhLUqTebDZvx3vcQUlNgP6Vko|m3u8|master|lyo2h1snpe5c_h|00197|hls2|srv34|sources|assets2|skin|setup|vplayer'.split('|')))
</script> </script>
<style media="screen"> <style media="screen">
@ -411,13 +424,13 @@ $.cookie('aff', '4', { expires: 10 });
} }
</style> </style>
<script type="fb4dc13d1285d9e23d3f1de2-text/javascript"> <script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
$('.lightdown').on('click', function(){ $('.lightdown').on('click', function(){
$('body').toggleClass('body-lightdown'); $('body').toggleClass('body-lightdown');
}) })
</script> </script>
<Script type="fb4dc13d1285d9e23d3f1de2-text/javascript"> <Script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
$("#embed_div textarea").focus(function (){ $(this).select(); }) $("#embed_div textarea").focus(function (){ $(this).select(); })
var tab_cookie='tab_down'; var tab_cookie='tab_down';
@ -440,17 +453,17 @@ $.cookie('aff', '4', { expires: 10 });
</Script> </Script>
<script src="https://dropload.io/js/tabber.js" type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src="https://dropload.io/js/tabber.js" type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<Script type="fb4dc13d1285d9e23d3f1de2-text/javascript"> <Script type="ad8da47d2c4dcd2ad69dff49-text/javascript">
$(function() { $(function() {
}); });
</Script> </Script>
<script data-cfasync="false" async type="text/javascript" src="//sx.impubicpuddly.com/rO7FQr7AANJdtg/111561"></script> <script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
<script src='https://dropload.io/tag2.js' type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src='https://dropload.io/tag2.js' type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<script src='https://dropload.io/tag1.js' type="fb4dc13d1285d9e23d3f1de2-text/javascript"></script> <script src='https://dropload.io/tag1.js' type="ad8da47d2c4dcd2ad69dff49-text/javascript"></script>
<!-- Javascript Alt Ads code start --> <!-- Javascript Alt Ads code start -->
@ -495,7 +508,7 @@ $.cookie('aff', '4', { expires: 10 });
</footer> </footer>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="fb4dc13d1285d9e23d3f1de2-|49" defer></script></body> <script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="ad8da47d2c4dcd2ad69dff49-|49" defer></script></body>
</html> </html>

View file

@ -19,10 +19,10 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet"> <link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src="https://code.jquery.com/jquery-3.2.1.min.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src="https://dropload.io/assets2/js/xupload.js?v=13" type="45a2a97ee4145248610fba09-text/javascript"></script>
<script src="https://dropload.io/assets2/js/app.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src="https://dropload.io/assets2/js/app.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
</head> </head>
<body> <body>
@ -91,13 +91,13 @@
<main> <main>
<script src="https://dropload.io/js/jquery.cookie.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src="https://dropload.io/js/jquery.cookie.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
<script type="a9bf83b31be3a1c8064935cd-text/javascript"> <script type="45a2a97ee4145248610fba09-text/javascript">
$.cookie('file_id', '1448116', { expires: 10 }); $.cookie('file_id', '1526758', { expires: 10 });
$.cookie('aff', '5', { expires: 10 }); $.cookie('aff', '7208', { expires: 10 });
</script> </script>
@ -137,8 +137,21 @@ $.cookie('aff', '5', { expires: 10 });
</div> --> </div> -->
<script type="a9bf83b31be3a1c8064935cd-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script> <script type="45a2a97ee4145248610fba09-text/javascript" src='https://dropload.io/player/jw8/jwplayer.js'></script>
<script type="a9bf83b31be3a1c8064935cd-text/javascript">jwplayer.key="";</script> <script type="45a2a97ee4145248610fba09-text/javascript">jwplayer.key="";</script>
<div id="resume-modal" style="color:#fff; font-family:Roboto, Arial; display:none; padding:2rem; justify-content:center; align-items:center; position:absolute; top:0; left:0; right:0; bottom:0; background-color: rgba(0,0,0,.1); font-size: 12px; z-index:200;">
<div style="max-width:420px; background-color: rgba(28, 37, 52, .65); -webkit-backdrop-filter: blur(20px); backdrop-filter:blur(20px); text-align:center;padding:1.5rem; border-radius:8px;border: 1px solid rgba(255,255,255,.18);">
<h5 style="margin-bottom:10px;font-weight:bold; font-size:15px;" id="resume-title"></h5>
<p style="margin-bottom:10px;opacity:.65" id="resume-text"></p>
<div style="display:flex;gap:.4rem; align-items-center;justify-content:center">
<button onclick="if (!window.__cfRLUnblockHandlers) return false; resumeVideo()" id="resume-btn" style="border:0;cursor:pointer;border-radius:5px; padding:.5rem .75rem; color:#fff;background: linear-gradient(90deg, #3EB6D3 0%, #69CEA6 100%);color:#fff" data-cf-modified-45a2a97ee4145248610fba09-=""></button>
<button onclick="if (!window.__cfRLUnblockHandlers) return false; cancelResume()" id="cancel-text" style="border:0;cursor:pointer;border-radius:5px;padding:.5rem .75rem; color:#fff;background:#476177;color:#fff" data-cf-modified-45a2a97ee4145248610fba09-=""></button>
</div>
</div>
</div>
<style> <style>
.jw-text-track-cue { .jw-text-track-cue {
text-shadow: 1px 1px 2px transparent !important; text-shadow: 1px 1px 2px transparent !important;
@ -148,7 +161,7 @@ $.cookie('aff', '5', { expires: 10 });
padding-bottom: 0.2em !important; padding-bottom: 0.2em !important;
border-radius: 6px; border-radius: 6px;
} }
</style><script src="/js/localstorage-slim.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="a9bf83b31be3a1c8064935cd-text/javascript"></script> </style><script src="/js/localstorage-slim.js" type="45a2a97ee4145248610fba09-text/javascript"></script><script src="https://dropload.io/js/dnsads.js?dfp=1&ad_code=2&adsrc=3" type="45a2a97ee4145248610fba09-text/javascript"></script>
<div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div> <div id='vplayer' style="width:100%;height:100%;text-align:center;"><img src='/images/back2.jpg' style='width:100%;height:100%;'></div>
</div> </div>
@ -166,7 +179,7 @@ $.cookie('aff', '5', { expires: 10 });
</div> </div>
<div class="lightdown-backdrop"></div> <div class="lightdown-backdrop"></div>
<h1>Black Mirror S2 E4 1080p </h1> <h1>[~Squid~Game~EnG~]~S03E01~720p~WebRip ~❤GLD❤~ </h1>
<div class="videoplayer-controlbar"> <div class="videoplayer-controlbar">
<div class="row g-1"> <div class="row g-1">
<div class="col-auto flex-grow-1 flex-shrink-1 mw-0"> <div class="col-auto flex-grow-1 flex-shrink-1 mw-0">
@ -178,7 +191,7 @@ $.cookie('aff', '5', { expires: 10 });
<div class="col-auto d-flex align-items-center"> <div class="col-auto d-flex align-items-center">
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i> <i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
on Apr 10, 2025 on Jun 27, 2025
</div> </div>
<div class="col-auto d-flex align-items-center"> <div class="col-auto d-flex align-items-center">
@ -187,7 +200,7 @@ $.cookie('aff', '5', { expires: 10 });
</div> </div>
<div class="col-auto d-flex align-items-center"> <div class="col-auto d-flex align-items-center">
<i class="icon icon-clock icon-size-16 me-2 opacity-50"></i> <i class="icon icon-clock icon-size-16 me-2 opacity-50"></i>
01:14:05 00:57:13
</div> </div>
</div> </div>
@ -205,13 +218,13 @@ $.cookie('aff', '5', { expires: 10 });
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=978xyyxi3ldq&v=up')" title="Like" data-cf-modified-a9bf83b31be3a1c8064935cd-=""> <button class="btn btn-like border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=xhcmgcc2txnv&v=up')" title="Like" data-cf-modified-45a2a97ee4145248610fba09-="">
<i class="icon icon-like icon-size-16 me-2"></i> <i class="icon icon-like icon-size-16 me-2"></i>
<span id="likes_num">0</span> <span id="likes_num">0</span>
</button> </button>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=978xyyxi3ldq&v=down')" title="Don&#39;t Like" data-cf-modified-a9bf83b31be3a1c8064935cd-=""> <button class="btn btn-dislike border-0" onclick="if (!window.__cfRLUnblockHandlers) return false; return jah('/?op=vote&file_code=xhcmgcc2txnv&v=down')" title="Don&#39;t Like" data-cf-modified-45a2a97ee4145248610fba09-="">
<i class="icon icon-dislike icon-size-16 me-2"></i> <i class="icon icon-dislike icon-size-16 me-2"></i>
<span id="dislikes_num">0</span> <span id="dislikes_num">0</span>
</button> </button>
@ -243,14 +256,14 @@ $.cookie('aff', '5', { expires: 10 });
<a href="https://dropload.io/d/978xyyxi3ldq_h" class="dropdown-item d-flex align-items-center"> <a href="https://dropload.io/d/xhcmgcc2txnv_h" class="dropdown-item d-flex align-items-center">
<span class="flex-grow-1 pe-3 text-white"> <span class="flex-grow-1 pe-3 text-white">
<b>HD quality</b> <br> <span class="xsmall text-light">1920x1080, <span class="download-size">835.3 MB</span></span> <b>HD quality</b> <br> <span class="xsmall text-light">1280x720, <span class="download-size">317.6 MB</span></span>
</span> </span>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="18"><defs><linearGradient id="a" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#3DB6D4"/><stop offset="100%" stop-color="#6BCFA4"/></linearGradient></defs><path fill-rule="evenodd" fill="#3DB6D4" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/><path fill="url(#a)" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="18"><defs><linearGradient id="a" x1="0%" x2="100%" y1="0%" y2="0%"><stop offset="0%" stop-color="#3DB6D4"/><stop offset="100%" stop-color="#6BCFA4"/></linearGradient></defs><path fill-rule="evenodd" fill="#3DB6D4" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/><path fill="url(#a)" d="M13 6V0H7v6H2l8 8 8-8h-5ZM0 16h20v2H0v-2Z"/></svg>
@ -270,7 +283,7 @@ $.cookie('aff', '5', { expires: 10 });
</div> </div>
</div> </div>
<script type="a9bf83b31be3a1c8064935cd-text/javascript"> <script type="45a2a97ee4145248610fba09-text/javascript">
$(function(){ $(function(){
var a = $('.dropdown-menu a:first-child .download-size'); var a = $('.dropdown-menu a:first-child .download-size');
$('#size').append(a); $('#size').append(a);
@ -282,8 +295,8 @@ $.cookie('aff', '5', { expires: 10 });
<div class="col-12 col-lg-7 mb-3"> <div class="col-12 col-lg-7 mb-3">
<div class="copybox"> <div class="copybox">
<textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/978xyyxi3ldq" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea> <textarea class="form-control copy-source" rows="6"><IFRAME SRC="https://dropload.io/e/xhcmgcc2txnv" FRAMEBORDER=0 MARGINWIDTH=0 MARGINHEIGHT=0 SCROLLING=NO WIDTH=640 HEIGHT=360 allowfullscreen></IFRAME></textarea>
<button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-a9bf83b31be3a1c8064935cd-="">Copy</button> <button class="btn btn-primary" onclick="if (!window.__cfRLUnblockHandlers) return false; copy(this);" data-cf-modified-45a2a97ee4145248610fba09-="">Copy</button>
</div> </div>
</div> </div>
@ -305,9 +318,9 @@ $.cookie('aff', '5', { expires: 10 });
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body pt-0"> <div class="modal-body pt-0">
<form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-a9bf83b31be3a1c8064935cd-=""> <form method="POST" action="/" onsubmit="if (!window.__cfRLUnblockHandlers) return false; $.post('/',$(this).serialize(),function(code){ eval(code); } );return false;" data-cf-modified-45a2a97ee4145248610fba09-="">
<input type="hidden" name="op" value="report_file"> <input type="hidden" name="op" value="report_file">
<input type="hidden" name="file_code" value="978xyyxi3ldq"> <input type="hidden" name="file_code" value="xhcmgcc2txnv">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Reason:</label> <label class="form-label">Reason:</label>
<select class="form-select" name="report_type"> <select class="form-select" name="report_type">
@ -370,7 +383,7 @@ $.cookie('aff', '5', { expires: 10 });
<script type="a9bf83b31be3a1c8064935cd-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1d("5x").5w({5v:{2i:"/5u/2l/2k-o.2l?v=3",q:"2k-o"},5t:[{2j:"13://5s.o.12/5r/5q/5p/5o/5n.5m?t=5l-5k&s=1z&e=5j&f=22&i=0.0&5i=0&5h=21.20.5g.5f"}],5e:"1j%",5d:"1j%",5c:"5b",5a:"59.58",57:\'56\',55:"y",a:[{2j:"/26?23=54&k=53&2i=13://2h.o.12/52.2g",51:"50"}],1s:{4z:1,2f:\'#4y\',4x:\'#4w\',4v:"4u",4t:30,4s:\'1j\',},\'4r\':{"4q":"4p"},4o:"4n",4m:"13://o.12/",4l:{},4k:y,1r:[1,1.25,1.5,2],4j:\'13://2h.o.12/4i.2g\'}).g(\'1t\',8(){d n=2d.4h("11");n.4g="n";n.2e(\'4f\',\'4-1i 4-1i-4e 4-2c-2f 4-4d 4-1i-4c\');n.2e(\'4b\',\'6.1c(6.4a()+10)\');2d.49(\'.4-2c-48\').47(n)});d 1g,1h;d 46=0,45=0;d 6=1d();d 2a=0,44=0,43=0,j=0;$.42({41:{\'40-3z\':\'3y-3x\'}});6.g(\'3w\',8(x){9(5>0&&x.p>=5&&1h!=1){1h=1;$(\'11.3v\').3u(\'3t\')}9(x.p>=j+5||x.p<j){j=x.p;1f.3s(\'1e\',3r.3q(j),{3p:2b*2b*24*7})}});6.g(\'1c\',8(x){2a=x.p});6.g(\'3o\',8(x){29(x)});6.g(\'3n\',8(){$(\'11.28\').3m();1f.3l(\'1e\')});6.g(\'3k\',8(x){});8 29(x){$(\'11.28\').27();$(\'#3j\').27();9(1g)15;1g=1;z=0;9(3i.3h===3g){z=1}$.1x(\'/26?23=3f&3e=3d&3c=22-21-20-1z-3b&3a=&z=\'+z,8(1y){$(\'#39\').38(1y)});d j=1f.1x(\'1e\');9(j>0){1d().1c(j)}}8 37(){d a=6.16(1w);1v.1u(a);9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1w){1v.1u(\'!!=\'+i);6.1k(i)}}}}6.g(\'1t\',8(){});6.g("h",8(r){d a=6.16();9(a.k<2)15;$(\'.4-c-36-35\').34(8(){$(\'#4-c-b-h\').19(\'4-c-b-u\');$(\'.4-b-h\').m(\'l-1a\',\'w\')});6.33("/32/31.2z","2y 2x",8(){$(\'.4-1q\').2w(\'4-c-1p\');$(\'.4-c-1s, .4-c-1r\').m(\'l-1b\',\'w\');9($(\'.4-1q\').2v(\'4-c-1p\')){$(\'.4-b-h\').m(\'l-1b\',\'y\');$(\'.4-b-h\').m(\'l-1a\',\'y\');$(\'.4-c-b-2u\').19(\'4-c-b-u\');$(\'.4-c-b-h\').2t(\'4-c-b-u\')}2s{$(\'.4-b-h\').m(\'l-1b\',\'w\');$(\'.4-b-h\').m(\'l-1a\',\'w\');$(\'.4-c-b-h\').19(\'4-c-b-u\')}},"2r");6.g("2q",8(r){18.2p(\'17\',r.a[r.2o].q)});9(18.1o(\'17\')){2n("1n(18.1o(\'17\'));",2m)}});d 14;8 1n(1l){d a=6.16();9(a.k>1){1m(i=0;i<a.k;i++){9(a[i].q==1l){9(i==14){15}14=i;6.1k(i)}}}}',36,214,'||||jw||player||function|if|tracks|submenu|settings|var|||on|audioTracks||lastt|length|aria|attr|newButton|dropload|position|name|event|||active||false||true|adb||div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|seek|jwplayer|tt978xyyxi3ldq|ls|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|open|controls|playbackRates|captions|ready|log|console|track_name|get|data|1747217151|61|130|1448116|op|||dl|hide|video_ad|doPlay|prevt|60|button|document|setAttribute|color|jpg|img|url|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|else|addClass|quality|hasClass|toggleClass|Track|Audio|svg||dualy|images|addButton|mousedown|buttons|topbar|set_audio_track|html|fviews|embed|d4934d3a59c73c7efe2e9da5b2898e4a|hash|978xyyxi3ldq|file_code|view|undefined|cRAds|window|over_player_msg|pause|remove|show|complete|play|ttl|round|Math|set|slow|fadeIn|video_ad_fadein|time|cache|no|Cache|Content|headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|978xyyxi3ldq_xt|image|playbackRateControls|cast|aboutlink|DropLoad|abouttext|HD|1570|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|978xyyxi3ldq0000|4445|get_slides|androidhls|metadata|preload|63|4444|duration|uniform|stretching|height|width|204|236|ii|sp|14400|zgcmoXiSQ6rkMu7qHrSpN_pWOc95IPYwk|unq9qxjYC|m3u8|master|978xyyxi3ldq_h|00289|01|hls2|srv24|sources|assets2|skin|setup|vplayer'.split('|'))) <script type="45a2a97ee4145248610fba09-text/javascript">eval(function(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}('1t("7t").7s({7r:{3g:"/7q/3k/3j-y.3k?v=3",11:"3j-y"},7p:[{3i:"1c://7o.y.1b/7n/2q/7m/7l/7k.7j?t=7i&s=32&e=7h&f=34&i=0.0&7g=0&7f=33.61.7e.7d"}],7c:"1x%",7b:"1x%",7a:"79",78:"3h.43",77:\'76\',75:"15",b:[{3i:"/36?35=74&p=3h&3g=1c://3f.y.1b/73.3e",72:"71"}],28:{70:1,3d:\'#6z\',6y:\'#6x\',6w:"6v",6u:30,6t:\'1x\',},\'6s\':{"6r":"6q"},6p:"6o",6n:"1c://y.1b/",6m:{},6l:15,27:[1,1.25,1.5,2],6k:\'1c://3f.y.1b/6j.3e\'}).j(\'1s\',6(){h w=g.6i("1a");w.6h="w";w.3c(\'6g\',\'4-1w 4-1w-6f 4-3b-3d 4-6e 4-1w-6d\');w.3c(\'6c\',\'8.1p(8.6b()+10)\');g.6a(\'.4-3b-69\').68(w)});h 1u,1v;h 67=0,66=0;h 8=1t();h 3a=0,65=0,64=0,n=0;$.63({62:{\'5z-5y\':\'5x-5w\'}});8.j(\'u\',6(x){9(5>0&&x.z>=5&&1v!=1){1v=1;$(\'1a.5v\').5u(\'5t\')}9(x.z>=n+5||x.z<n){n=x.z;17.2b(\'16\',1q.5s(n),{5r:60*60*24*7})}});8.j(\'1p\',6(x){3a=x.z});8.j(\'2d\',6(x){39(x)});8.j(\'5q\',6(){$(\'1a.38\').5p();17.5o(\'16\')});8.j(\'5n\',6(x){});6 39(x){$(\'1a.38\').37();$(\'#5m\').37();9(1u)1e;1u=1;19=0;9(2w.5l===5k){19=1}$.2t(\'/36?35=5j&5i=2v&5h=34-33-61-32-5g&5f=&19=\'+19,6(31){$(\'#5e\').5d(31)})}6 5c(){h b=8.1f(2z);2y.2x(b);9(b.p>1){20(i=0;i<b.p;i++){9(b[i].11==2z){2y.2x(\'!!=\'+i);8.1y(i)}}}}8.j(\'1s\',6(){});1t().2a("/29/5b.5a","59 j 58",6(){h 2u=2w.1l(\'/v/2v\',\'57\');2u.56()},"55");h n=17.2t(\'16\');8.j(\'1s\',6(){9(n>0){2i(n)}});6 2g(u){1r 2r=2s.54||2s.53;9(2r.52(\'51\')){g.k("l-2p").o=\'50 4z 4y?\';g.k("l-2o").o=\'4x! 4w 4v a 0:2q. 4u 4t 4s 4r 4q 4p?\';g.k("l-18").o=\'4o, 4n\';g.k("2k-18").o=\'2j, 4m\'}23{g.k("l-2p").o=\'4l 4k?\';g.k("l-2o").o=\'4j 4i! 4h 2m 2l 4g \'+u+\'. 4f 2n 4e 4d l 4c 4b 2n 2m 2l?\';g.k("l-18").o=\'4a, 49\';g.k("2k-18").o=\'2j, 48\'}}6 2i(u){1r 2f=1q.2h(u/60);1r 2e=1q.2h(u%60).47().46(2,\'0\');2g(2f+\':\'+2e);g.k("l-1o").1n.1m=\'45\'}6 44(){8.1p(n);8.2d();g.k("l-1o").1n.1m=\'2c\'}6 42(){g.k("l-1o").1n.1m=\'2c\';17.2b(\'16\',\'\')}8.j("m",6(12){h b=8.1f();9(b.p<2)1e;$(\'.4-d-41-40\').3z(6(){$(\'#4-d-c-m\').1i(\'4-d-c-13\');$(\'.4-c-m\').r(\'q-1j\',\'14\')});8.2a("/29/3y.3x","3w 3v",6(){$(\'.4-26\').3u(\'4-d-1l\');$(\'.4-d-28, .4-d-27\').r(\'q-1k\',\'14\');9($(\'.4-26\').3t(\'4-d-1l\')){$(\'.4-c-m\').r(\'q-1k\',\'15\');$(\'.4-c-m\').r(\'q-1j\',\'15\');$(\'.4-d-c-3s\').1i(\'4-d-c-13\');$(\'.4-d-c-m\').3r(\'4-d-c-13\')}23{$(\'.4-c-m\').r(\'q-1k\',\'14\');$(\'.4-c-m\').r(\'q-1j\',\'14\');$(\'.4-d-c-m\').1i(\'4-d-c-13\')}},"3q");8.j("3p",6(12){1h.3o(\'1g\',12.b[12.3n].11)});9(1h.22(\'1g\')){3m("21(1h.22(\'1g\'));",3l)}});h 1d;6 21(1z){h b=8.1f();9(b.p>1){20(i=0;i<b.p;i++){9(b[i].11==1z){9(i==1d){1e}1d=i;8.1y(i)}}}}',36,282,'||||jw||function||player|if||tracks|submenu|settings|||document|var||on|getElementById|resume|audioTracks|lastt|innerHTML|length|aria|attr|||time||newButton||dropload|position||name|event|active|false|true|ttxhcmgcc2txnv|ls|btn|adb|div|io|https|current_audio|return|getAudioTracks|default_audio|localStorage|removeClass|expanded|checked|open|display|style|modal|seek|Math|const|ready|jwplayer|vvplay|vvad|icon|100|setCurrentAudioTrack|audio_name|for|audio_set|getItem|else|||controls|playbackRates|captions|images|addButton|set|none|play|seconds|minutes|translateModal|floor|showResumeModal|No|cancel|off|left|you|text|title|01|userLang|navigator|get|win|xhcmgcc2txnv|window|log|console|track_name||data|1752613129|130|1526758|op|dl|hide|video_ad|doPlay|prevt|button|setAttribute|color|jpg|img|url|3433|file|jw8|css|300|setTimeout|currentTrack|setItem|audioTrackChanged|dualSound|addClass|quality|hasClass|toggleClass|Track|Audio|svg|dualy|mousedown|buttons|topbar|cancelResume||resumeVideo|flex|padStart|toString|thanks|please|Yes|where|watching|to|like|Would|at|You|back|Welcome|playing|Resume|grazie|certo|Si|lasciato|avevi|dove|da|riprendere|Vuoi|interrotto|Hai|Bentornato|visione|la|Riprendi|it|startsWith|userLanguage|language|download11|focus|_blank|site|Watch|png|download2|set_audio_track|html|fviews|embed|436087fe7fbec4909d57378fd1de7255|hash|file_code|view|undefined|cRAds|over_player_msg|pause|remove|show|complete|ttl|round|slow|fadeIn|video_ad_fadein|cache|no|Cache|Content|||headers|ajaxSetup|v2done|tott|vastdone2|vastdone1|appendChild|container|querySelector|getPosition|onclick|forward|reset|inline|class|id|createElement|xhcmgcc2txnv_xt|image|playbackRateControls|cast|aboutlink|Dropload|abouttext|HD|768|qualityLabels|fontOpacity|backgroundOpacity|Tahoma|fontFamily|303030|backgroundColor|FFFFFF|userFontScale|thumbnails|kind|xhcmgcc2txnv0000|get_slides|androidhls|metadata|preload|duration|uniform|stretching|height|width|204|236|ii|sp|14400|MBKiIoVlA42YcZEixT7ZR1xJA5qKhcKgBi84GPmyT7U|m3u8|master|xhcmgcc2txnv_h|00305|hls2|srv29|sources|assets2|skin|setup|vplayer'.split('|')))
</script> </script>
<style media="screen"> <style media="screen">
@ -411,13 +424,13 @@ $.cookie('aff', '5', { expires: 10 });
} }
</style> </style>
<script type="a9bf83b31be3a1c8064935cd-text/javascript"> <script type="45a2a97ee4145248610fba09-text/javascript">
$('.lightdown').on('click', function(){ $('.lightdown').on('click', function(){
$('body').toggleClass('body-lightdown'); $('body').toggleClass('body-lightdown');
}) })
</script> </script>
<Script type="a9bf83b31be3a1c8064935cd-text/javascript"> <Script type="45a2a97ee4145248610fba09-text/javascript">
$("#embed_div textarea").focus(function (){ $(this).select(); }) $("#embed_div textarea").focus(function (){ $(this).select(); })
var tab_cookie='tab_down'; var tab_cookie='tab_down';
@ -440,17 +453,17 @@ $.cookie('aff', '5', { expires: 10 });
</Script> </Script>
<script src="https://dropload.io/js/tabber.js" type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src="https://dropload.io/js/tabber.js" type="45a2a97ee4145248610fba09-text/javascript"></script>
<Script type="a9bf83b31be3a1c8064935cd-text/javascript"> <Script type="45a2a97ee4145248610fba09-text/javascript">
$(function() { $(function() {
}); });
</Script> </Script>
<script data-cfasync="false" async type="text/javascript" src="//sx.impubicpuddly.com/rO7FQr7AANJdtg/111561"></script> <script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
<script src='https://dropload.io/tag2.js' type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src='https://dropload.io/tag2.js' type="45a2a97ee4145248610fba09-text/javascript"></script>
<script src='https://dropload.io/tag1.js' type="a9bf83b31be3a1c8064935cd-text/javascript"></script> <script src='https://dropload.io/tag1.js' type="45a2a97ee4145248610fba09-text/javascript"></script>
<!-- Javascript Alt Ads code start --> <!-- Javascript Alt Ads code start -->
@ -495,7 +508,7 @@ $.cookie('aff', '5', { expires: 10 });
</footer> </footer>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="a9bf83b31be3a1c8064935cd-|49" defer></script></body> <script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="45a2a97ee4145248610fba09-|49" defer></script></body>
</html> </html>

View file

@ -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

View file

@ -0,0 +1 @@
Fetcher error: 404: Not Found

View file

@ -0,0 +1 @@
TypeError: fetch failed

View file

@ -0,0 +1,160 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dropload - Revolution Video Hosting</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#FFF">
<meta name="format-detection" conten="telephone=no">
<link rel="apple-touch-icon" href="https://dropload.io/assets2/images/favicon/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon-32x32.png" sizes="32x32">
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="https://dropload.io/assets2/images/favicon/manifest.json">
<link rel="icon" href="https://dropload.io/assets2/images/favicon/favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap" rel="stylesheet">
<link href="https://dropload.io/assets2/css/style.css?v=2" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.2.1.min.js" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
<script src="https://dropload.io/assets2/js/bootstrap.bundle.min.js" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
<script src="https://dropload.io/assets2/js/xupload.js?v=13" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
<script src="https://dropload.io/assets2/js/app.js" type="67d4e7c92cd803f904f519b6-text/javascript"></script>
</head>
<body>
<header class="header">
<div class="container">
<div class="row align-items-center flex-nowrap">
<div class="col-auto d-lg-none me-auto">
<div class="dropdown">
<button class="btn icon-btn text-primary" data-bs-toggle="dropdown" data-bs-offset="0, 16">
<i class="icon icon-menu icon-size-18"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<a href="/?op=registration" class="dropdown-item">Upload</a>
<a href="/make_money.html" class="dropdown-item">Earn money</a>
<a href="/api.html" class="dropdown-item">Service API</a>
<a href="/tos" class="dropdown-item">Terms and Conditions</a>
<a href="/contact" class="dropdown-item">Contacts</a>
</ul>
</div>
</div>
<div class="col-auto me-lg-5">
<a href="/">
<img src="https://dropload.io/assets2/images/logo.svg" class="logo" alt="Dropload.io - ">
</a>
</div>
<div class="col-auto d-none d-lg-block">
<nav class="nav">
<a href="/?op=registration" class="nav-link">Upload</a>
<a href="/make_money.html" class="nav-link">Earn money</a>
</nav>
</div>
<div class="col-auto d-none d-lg-block ms-auto">
<a href="/login.html" class="btn login-btn me-2">
<i class="icon icon-account me-2"></i>Login</a>
<a href="/?op=registration" class="btn reg-btn">Create an Account</a>
</div>
<div class="col-auto d-lg-none ms-auto">
<div class="dropdown">
<button class="btn icon-btn text-primary" data-bs-toggle="dropdown" data-bs-offset="0, 16">
<i class="icon icon-account icon-size-18"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="/login.html">Login</a>
</li>
<li>
<a class="dropdown-item" href="/?op=registration">Create an Account</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</header>
<main>
<script data-cfasync="false" async type="text/javascript" src="//en.almanpureed.com/rWm0dvLNb29/111561"></script>
<script src='https://dropload.io/tag2.js' type="67d4e7c92cd803f904f519b6-text/javascript"></script>
<script src='https://dropload.io/tag1.js' type="67d4e7c92cd803f904f519b6-text/javascript"></script>
<section class="mb-5 mb-lg-7">
<div class="container">
<div class="message mb-6">
<div class="row align-items-center justify-content-center">
<div class="col-8 col-lg-6 pe-lg-5 text-lg-end mb-4 mb-lg-0">
<img src="https://dropload.io/assets2/images/nofile.svg" class="img-fluid" alt="">
</div>
<div class="col-lg-6">
<h4 class="text-uppercase">File Not Found</h4>
<p>The file you were looking for could not be found, sorry for any inconvenience.<br><br>
Possible causes of this error could be:</p>
<ul>
<li>The file expired
<li>The file was deleted by its owner
<li>The file was deleted by administration because it didn&#39;t comply with our Terms of Use
</ul>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="footer">
<div class="container">
<div class="row text-center text-lg-start flex-lg-nowrap">
<div class="col">
<div class="mb-3">
<a href="/">
<img src="https://dropload.io/assets2/images/logo.svg" alt="">
</a>
</div>
<div class="text-muted mb-4 small">© 2025 Dropload - Revolution Video Hosting. All rights reserved.</div>
</div>
<div class="col d-none d-lg-block">
<div class="nav flex-column mb-3">
<a href="/faq" class="nav-link">FAQ</a>
<a href="/contact" class="nav-link">Contact Us</a>
</div>
</div>
<div class="col d-none d-lg-block">
<div class="nav flex-column mb-3">
<a href="/tos" class="nav-link">Terms of service</a>
<a href="#" class="nav-link">Privacy Policy</a>
<a href="#" class="nav-link">Copyright Policy</a>
</div>
</div>
<div class="col d-none d-lg-block">
<div class="nav flex-column mb-3">
<a href="/api.html" class="nav-link">API</a>
<a href="/make_money.html" class="nav-link">Make Money</a>
</div>
</div>
</div>
</div>
</footer>
<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="67d4e7c92cd803f904f519b6-|49" defer></script></body>
</html>

Some files were not shown because too many files have changed in this diff Show more