nekostream 2026.4.2 → 2026.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,7 +18,13 @@ exports.PROVIDER_LABELS = ['animevietsub', 'anime47', 'animehay'];
18
18
  exports.STREAM_HOSTS = [
19
19
  'abyss.to',
20
20
  'abysscdn.com',
21
+ 'abyssplayer.com',
22
+ 'abysshost.com',
23
+ 'abysscss.com',
21
24
  'hydrax.net',
25
+ 'playhydrax.com',
26
+ 'short.ink',
27
+ 'iamcdn.net',
22
28
  'googleapiscdn.com',
23
29
  'googleapis.com',
24
30
  'gstatic.com',
@@ -45,106 +45,37 @@ function extractFromAbyssServer(embedUrl, server, defaultBaseUrl) {
45
45
  }
46
46
  }
47
47
  /**
48
- * Extract video from DU server (storage.googleapiscdn.com)
48
+ * Extract video from DU server (stream.googleapiscdn.com / storage.googleapiscdn.com)
49
49
  */
50
- async function extractFromDUServer(embedUrl, server, baseUrl) {
51
- const crawler = await (0, crawler_1.getCrawler)();
52
- const page = await crawler.newPage();
50
+ function extractFromDUServer(embedUrl, server, defaultBaseUrl) {
53
51
  try {
54
- const interceptedUrls = [];
55
- page.on('response', async (response) => {
56
- const url = response.url();
57
- const contentType = response.headers()['content-type'] || '';
58
- if (url.includes('.m3u8') ||
59
- url.includes('.mp4') ||
60
- url.includes('/playlist/') ||
61
- url.includes('/hls/') ||
62
- url.includes('/video/') ||
63
- contentType.includes('mpegurl') ||
64
- contentType.includes('video/')) {
65
- interceptedUrls.push(url);
66
- }
67
- });
68
- await page.goto(embedUrl, {
69
- waitUntil: 'domcontentloaded',
70
- timeout: 30000
71
- });
72
- await page.waitForTimeout(3000);
73
- const jsUrls = await (0, interceptor_1.extractVideoFromJS)(page);
74
- interceptedUrls.push(...jsUrls);
52
+ const refererVal = server.referer || defaultBaseUrl;
53
+ let origin = defaultBaseUrl;
75
54
  try {
76
- await page.click('video, .play-button, [class*="play"]').catch(() => { });
77
- await page.waitForTimeout(2000);
55
+ origin = new URL(refererVal).origin;
78
56
  }
79
57
  catch { }
80
- const cleanUrls = interceptedUrls.filter(url => !(0, adblock_policy_1.isAdUrl)(url));
81
- const m3u8Url = cleanUrls.find(url => url.includes('.m3u8'));
82
- const playlistUrl = cleanUrls.find(url => url.includes('/playlist/'));
83
- const mp4Url = cleanUrls.find(url => url.includes('.mp4'));
84
- const streamUrl = m3u8Url || playlistUrl || mp4Url;
85
- if (streamUrl) {
86
- if (streamUrl.includes('/playlist/')) {
87
- return {
88
- url: embedUrl,
89
- type: 'iframe',
90
- quality: server.quality || 'FHD',
91
- headers: {
92
- 'Referer': baseUrl,
93
- 'Origin': baseUrl
94
- }
95
- };
96
- }
97
- if (streamUrl.includes('/chunks/')) {
98
- return {
99
- url: embedUrl,
100
- type: 'iframe',
101
- quality: server.quality || 'FHD',
102
- headers: {
103
- 'Referer': baseUrl,
104
- 'Origin': baseUrl
105
- }
106
- };
107
- }
108
- if (streamUrl.includes('.m3u8')) {
109
- const finalM3u8 = toMediaProxyUrl(streamUrl, embedUrl);
110
- const playlist = await (0, interceptor_1.fetchM3U8)(streamUrl, { Referer: embedUrl });
111
- if (playlist && playlist.qualities.length > 0) {
112
- return {
113
- url: finalM3u8,
114
- type: 'hls',
115
- quality: playlist.qualities[0].quality,
116
- headers: {
117
- 'Referer': embedUrl,
118
- 'Origin': new URL(embedUrl).origin
119
- }
120
- };
121
- }
58
+ return {
59
+ url: embedUrl,
60
+ type: 'iframe',
61
+ quality: server.quality || 'FHD',
62
+ headers: {
63
+ 'Referer': refererVal,
64
+ 'Origin': origin
122
65
  }
123
- return {
124
- url: streamUrl.includes('storage.googleapis') || streamUrl.includes('/chunks/')
125
- ? toMediaProxyUrl(streamUrl, embedUrl)
126
- : streamUrl,
127
- type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
128
- quality: server.quality || 'FHD',
129
- headers: {
130
- 'Referer': embedUrl,
131
- 'Origin': new URL(embedUrl).origin
132
- }
133
- };
134
- }
66
+ };
67
+ }
68
+ catch {
135
69
  return {
136
70
  url: embedUrl,
137
71
  type: 'iframe',
138
72
  quality: server.quality || 'HD',
139
73
  headers: {
140
- 'Referer': baseUrl,
141
- 'Origin': baseUrl
74
+ 'Referer': defaultBaseUrl,
75
+ 'Origin': defaultBaseUrl
142
76
  }
143
77
  };
144
78
  }
145
- finally {
146
- await page.close();
147
- }
148
79
  }
149
80
  /**
150
81
  * Generic Playwright-based extraction
@@ -246,9 +177,11 @@ async function extractFromAPIServer(hash, server, context) {
246
177
  const dummyServer = { ...server, embedUrl: link, referer: refererUrl };
247
178
  return extractFromAbyssServer(link, dummyServer, context.baseUrl);
248
179
  }
249
- if (link.includes('storage.googleapiscdn.com') || link.includes('storage.googleapis.com')) {
250
- const dummyServer = { ...server, embedUrl: link };
251
- return await extractFromDUServer(link, dummyServer, context.baseUrl);
180
+ if (link.includes('googleapiscdn.com') ||
181
+ link.includes('googleapis.com') ||
182
+ link.includes('storage.')) {
183
+ const dummyServer = { ...server, embedUrl: link, referer: refererUrl };
184
+ return extractFromDUServer(link, dummyServer, context.baseUrl);
252
185
  }
253
186
  return {
254
187
  url: link,
@@ -305,15 +238,19 @@ async function extractAnimeVietsubStream(server, context) {
305
238
  if (!embedUrl.startsWith('http')) {
306
239
  embedUrl = embedUrl.startsWith('//') ? `https:${embedUrl}` : `https://${embedUrl}`;
307
240
  }
308
- if (embedUrl.includes('storage.googleapiscdn.com') || server.type === 'iframe') {
309
- res = await extractFromDUServer(embedUrl, server, context.baseUrl);
310
- }
311
- else if (embedUrl.includes('abyss') ||
241
+ if (embedUrl.includes('abyss') ||
312
242
  embedUrl.includes('playhydrax') ||
313
243
  server.name.includes('HDX') ||
314
244
  server.name.includes('Hydrax')) {
315
245
  res = extractFromAbyssServer(embedUrl, server, context.baseUrl);
316
246
  }
247
+ else if (embedUrl.includes('googleapiscdn.com') ||
248
+ embedUrl.includes('googleapis.com') ||
249
+ embedUrl.includes('storage.') ||
250
+ server.name.includes('DU') ||
251
+ server.type === 'iframe') {
252
+ res = extractFromDUServer(embedUrl, server, context.baseUrl);
253
+ }
317
254
  else if (server.type === 'api') {
318
255
  res = await extractFromAPIServer(embedUrl, server, context);
319
256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nekostream",
3
- "version": "2026.4.2",
3
+ "version": "2026.4.3",
4
4
  "description": "A polished CLI for discovering and streaming anime from Vietnamese providers",
5
5
  "keywords": [
6
6
  "anime",
package/player-main.js CHANGED
@@ -65,7 +65,13 @@ const PROVIDER_LABELS = playerPolicy?.PROVIDER_LABELS || ['animevietsub', 'anime
65
65
  const STREAM_HOSTS = playerPolicy?.STREAM_HOSTS || [
66
66
  'abyss.to',
67
67
  'abysscdn.com',
68
+ 'abyssplayer.com',
69
+ 'abysshost.com',
70
+ 'abysscss.com',
68
71
  'hydrax.net',
72
+ 'playhydrax.com',
73
+ 'short.ink',
74
+ 'iamcdn.net',
69
75
  'googleapiscdn.com',
70
76
  'googleapis.com',
71
77
  'gstatic.com',
@@ -339,6 +345,10 @@ function createWindow() {
339
345
  }
340
346
 
341
347
  // ── Step 3: Block ad domains everywhere else (players/iframes) ──
348
+ if (details.url && details.url.includes('devtools-warning')) {
349
+ return callback({ cancel: true })
350
+ }
351
+
342
352
  if (hostMatches(hostname, AD_PROBE_HOSTS)) {
343
353
  return callback({ cancel: true })
344
354
  }
@@ -425,6 +435,11 @@ function createWindow() {
425
435
 
426
436
  // Block navigation away from the provider/stream hosts.
427
437
  guestWebContents.on('will-navigate', (e, url) => {
438
+ if (url && url.includes('devtools-warning')) {
439
+ console.log('[Webview Blocked devtools-warning redirect]:', url)
440
+ e.preventDefault()
441
+ return
442
+ }
428
443
  const scheme = (url.split(':')[0] || '').toLowerCase()
429
444
  const isAllowed =
430
445
  scheme === 'about' || scheme === 'chrome-extension' || isPlaybackHost(hostnameOf(url))
@@ -434,6 +449,13 @@ function createWindow() {
434
449
  }
435
450
  })
436
451
 
452
+ guestWebContents.on('will-frame-navigate', (e) => {
453
+ if (e.url && e.url.includes('devtools-warning')) {
454
+ console.log('[Webview Blocked frame devtools-warning redirect]:', e.url)
455
+ e.preventDefault()
456
+ }
457
+ })
458
+
437
459
  const injectIntoFrame = (frame) => {
438
460
  try {
439
461
  if (!isStreamHost(hostnameOf(frame.url))) return
@@ -461,7 +483,40 @@ function createWindow() {
461
483
  if (window.__neko_cleaner_active__) return;
462
484
  window.__neko_cleaner_active__ = true;
463
485
 
464
- // --- CSS block ---
486
+ // --- 1. Neutralize window.open to prevent popup blockers from throwing on .focus() ---
487
+ // Anti-adblock scripts on Abyss/Hydrax catch .focus() TypeError on blocked windows to trigger
488
+ // "Due to certain reasons (AdBlock/Sandbox), ads are not being displayed..."
489
+ try {
490
+ window.open = function(url) {
491
+ return {
492
+ closed: false,
493
+ focus: function() {},
494
+ blur: function() {},
495
+ close: function() {},
496
+ postMessage: function() {},
497
+ location: { href: url || '' }
498
+ };
499
+ };
500
+ } catch(e) {}
501
+
502
+ // --- 2. Prevent anti-adblock document.write from wiping out the player ---
503
+ try {
504
+ const _origDocWrite = document.write.bind(document);
505
+ document.write = function(content) {
506
+ if (
507
+ typeof content === 'string' &&
508
+ (content.includes('AdBlock/Sandbox') ||
509
+ content.includes('Due to certain reasons') ||
510
+ content.includes('disable AdBlock') ||
511
+ content.includes('removing the Sandbox'))
512
+ ) {
513
+ return;
514
+ }
515
+ return _origDocWrite(content);
516
+ };
517
+ } catch(e) {}
518
+
519
+ // --- 3. CSS block ---
465
520
  if (!document.getElementById('__neko_adblock__')) {
466
521
  const style = document.createElement('style');
467
522
  style.id = '__neko_adblock__';
@@ -478,33 +533,35 @@ function createWindow() {
478
533
  (document.head || document.documentElement).appendChild(style);
479
534
  }
480
535
 
481
- // --- DOM cleaner ---
536
+ // --- 4. DOM cleaner ---
482
537
  const AD_TEXTS = ['Đóng quảng cáo', 'Đóng và xem tiếp', 'Quảng cáo'];
483
- // Specific gambling-brand fragments only. Bare 'bet' and 'game'
484
- // used to be in this list and removed unrelated player controls.
485
538
  const AD_SRCS = ['in88', 'quayhu', 'nohu', '188bet', 'kubet', 'w88', 'fun88', 'sin88'];
486
539
 
487
540
  const clean = () => {
488
541
  try {
489
- // Bypass Abyss popup click-jacking by rewriting the overlay click event
490
- if (window.abyssConfig || document.onclick || document.ontouchend) {
491
- if (document.onclick !== null || document.ontouchend !== null) {
492
- document.onclick = null;
493
- document.ontouchend = null;
494
- const overlay = document.getElementById('overlay');
495
- if (overlay) {
496
- overlay.onclick = (e) => {
497
- e.stopPropagation();
498
- e.preventDefault();
499
- if (window.jwplayer && typeof window.jwplayer === 'function') {
500
- try {
501
- window.jwplayer().play();
502
- } catch(err) {}
503
- }
504
- overlay.remove();
505
- };
506
- overlay.ontouchend = overlay.onclick;
542
+ // Bypass Abyss/Hydrax popup click-jacking by rewriting the overlay click event
543
+ const overlay = document.getElementById('overlay');
544
+ if (overlay) {
545
+ overlay.onclick = (e) => {
546
+ if (e) {
547
+ e.stopPropagation();
548
+ e.preventDefault();
507
549
  }
550
+ if (window.jwplayer && typeof window.jwplayer === 'function') {
551
+ try {
552
+ window.jwplayer().play();
553
+ } catch(err) {}
554
+ }
555
+ overlay.remove();
556
+ };
557
+ overlay.ontouchend = overlay.onclick;
558
+
559
+ // Auto-remove overlay if player is active
560
+ if (document.querySelector('video') || (window.jwplayer && typeof window.jwplayer === 'function')) {
561
+ try {
562
+ window.jwplayer().play();
563
+ } catch(e) {}
564
+ overlay.remove();
508
565
  }
509
566
  }
510
567
 
@@ -559,9 +616,13 @@ function createWindow() {
559
616
 
560
617
  guestWebContents.on('frame-created', (e, details) => {
561
618
  const frame = details.frame
619
+ injectIntoFrame(frame)
620
+ setTimeout(() => {
621
+ injectIntoFrame(frame)
622
+ }, 300)
562
623
  setTimeout(() => {
563
624
  injectIntoFrame(frame)
564
- }, 500)
625
+ }, 1000)
565
626
  })
566
627
  })
567
628
 
package/player.html CHANGED
@@ -298,6 +298,7 @@
298
298
  f.id = '__neko_player__';
299
299
  f.src = ${streamUrl};
300
300
  f.setAttribute('allowfullscreen','true');
301
+ f.setAttribute('allow', 'autoplay; encrypted-media; fullscreen');
301
302
  document.body.appendChild(f);
302
303
  console.log('[Injected] Google Player iframe appended:', f.src);
303
304
  return true;
@@ -63,6 +63,62 @@ function nekoIsProviderHost(host, label) {
63
63
  if (nekoIsProviderHost(nekoHost, 'animevietsub')) {
64
64
  return;
65
65
  }
66
+
67
+ // Neutralize anti-devtools console serialization probe & devtools-warning redirect on DU/googleapiscdn
68
+ if (/(^|\.)googleapiscdn\.com$|(^|\.)googleapis\.com$/.test(nekoHost)) {
69
+ try {
70
+ var noop = function() {};
71
+ window.console.log = noop;
72
+ window.console.warn = noop;
73
+ window.console.error = noop;
74
+ window.console.info = noop;
75
+ window.console.clear = noop;
76
+ window.console.table = noop;
77
+
78
+ var _origReplace = window.location.replace ? window.location.replace.bind(window.location) : null;
79
+ var _origAssign = window.location.assign ? window.location.assign.bind(window.location) : null;
80
+ if (_origReplace) {
81
+ window.location.replace = function(u) {
82
+ if (typeof u === 'string' && u.includes('devtools-warning')) return;
83
+ return _origReplace(u);
84
+ };
85
+ }
86
+ if (_origAssign) {
87
+ window.location.assign = function(u) {
88
+ if (typeof u === 'string' && u.includes('devtools-warning')) return;
89
+ return _origAssign(u);
90
+ };
91
+ }
92
+ } catch (e) {}
93
+ }
94
+
95
+ // Neutralize anti-adblock click-jacking / popup triggers without throwing
96
+ window.open = function(url, target, features) {
97
+ return {
98
+ closed: false,
99
+ focus: function() {},
100
+ blur: function() {},
101
+ close: function() {},
102
+ postMessage: function() {},
103
+ location: { href: url || '' }
104
+ };
105
+ };
106
+
107
+ // Guard document.write against wiping out the player with anti-adblock / sandbox warnings
108
+ const _origDocWrite = document.write.bind(document);
109
+ document.write = function(content) {
110
+ if (
111
+ typeof content === 'string' &&
112
+ (content.includes('AdBlock/Sandbox') ||
113
+ content.includes('Due to certain reasons') ||
114
+ content.includes('disable AdBlock') ||
115
+ content.includes('removing the Sandbox'))
116
+ ) {
117
+ return;
118
+ }
119
+ return _origDocWrite(content);
120
+ };
121
+
66
122
  // ── 1. Fake Google Ad Manager (DFP / GPT) ──────────────────────────────
67
123
  if (!window.googletag) {
68
124
  const cmd = [];
@@ -178,6 +234,10 @@ function nekoIsProviderHost(host, label) {
178
234
  'please disable your ad blocker',
179
235
  'disable adblock',
180
236
  'turn off adblock',
237
+ 'Due to certain reasons',
238
+ 'AdBlock/Sandbox',
239
+ 'removing the Sandbox',
240
+ 'disable AdBlock'
181
241
  ];
182
242
  const ADBLOCK_CLASSES = /adblock|ad.block|adblocker|ad.detect|detect.ad/i;
183
243
 
@@ -415,11 +475,36 @@ var cleanDOM = function() {
415
475
  a.remove();
416
476
  }
417
477
  });
478
+
479
+ // Neutralize Abyss/Hydrax popup overlay
480
+ var overlay = doc.getElementById('overlay');
481
+ if (overlay) {
482
+ overlay.onclick = function(e) {
483
+ if (e) {
484
+ e.stopPropagation();
485
+ e.preventDefault();
486
+ }
487
+ try {
488
+ if (window.jwplayer && typeof window.jwplayer === 'function') {
489
+ window.jwplayer().play();
490
+ }
491
+ } catch(err) {}
492
+ overlay.remove();
493
+ };
494
+ overlay.ontouchend = overlay.onclick;
495
+
496
+ if (doc.querySelector('video') || (window.jwplayer && typeof window.jwplayer === 'function')) {
497
+ try {
498
+ window.jwplayer().play();
499
+ } catch(e) {}
500
+ overlay.remove();
501
+ }
502
+ }
418
503
 
419
504
  // Meta-CSP removal is limited to the stream player hosts, whose pages we
420
505
  // rewrite into a bare fullscreen iframe. Stripping it everywhere removed a
421
506
  // real defence from every other origin the webview happened to visit.
422
- if (/(^|\.)googleapiscdn\.com$|(^|\.)googleapis\.com$|(^|\.)abyss\.to$|(^|\.)hydrax\.net$/.test(nekoHost)) {
507
+ if (/(^|\.)googleapiscdn\.com$|(^|\.)googleapis\.com$|(^|\.)abyss\.to$|(^|\.)abyssplayer\.com$|(^|\.)abysshost\.com$|(^|\.)abysscss\.com$|(^|\.)hydrax\.net$|(^|\.)playhydrax\.com$/.test(nekoHost)) {
423
508
  doc.querySelectorAll('meta[http-equiv="content-security-policy" i]').forEach(function(m) { m.remove(); });
424
509
  }
425
510