izen bypass ( get url final )

JavaScriptPublic
by zx-apiAug 27, 2026, 12:49 PMExpires: Never177 views
izen bypass ( get url final )
Raw
1// izen-bypass.js
2'use strict';
3
4/**
5 * IzenBypass
6 * Description: Bypass Mengambil Url Final Dari Berbagai platform Pastebin.com/sub4unlock dll
7 * Credt By Zx?
8 * Sumber Saluran: https://whatsapp.com/channel/0029VbDLqe7EquiSF4STU13o
9 * Note: Kalau Mau Sher Lagi Minimal jangan hapus credit atau sumber ya kampung
10 */
11
12const DEFAULT_USER_AGENT =
13  'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Mobile Safari/537.36';
14
15const DEFAULTS = Object.freeze({
16  baseUrl: 'https://izen.lol',
17  bypassPath: '/api/bypass',
18  healthPath: '/api/health',
19  turnstilePageUrl: 'https://izen.lol/bypass/sub2unlock',
20  turnstileSiteKey: '0x4AAAAAADNEi_2N24gpQqY0',
21  turnstileMode: 'min',
22  referer: 'https://izen.lol/bypass/sub2unlock',
23  concurrency: 1,
24  maxQueue: 50,
25  rateLimitMax: 5,
26  rateLimitWindowMs: 60_000,
27  rateLimitJitterMs: 150,
28  autoSolveTurnstile: true,
29  useWafSession: false,
30  useBycf: true,
31});
32
33function sleep(ms) {
34  return new Promise((resolve) => setTimeout(resolve, ms));
35}
36
37class ScraperError extends Error {
38  constructor(message, { cause = null, status = null, body = null, url = null } = {}) {
39    super(message);
40    this.name = 'ScraperError';
41    this.cause = cause;
42    this.status = status;
43    this.body = body;
44    this.url = url;
45  }
46}
47
48class RateLimiter {
49  constructor({ max = 20, windowMs = 60_000, jitterMs = 150 } = {}) {
50    this.max = Math.max(1, Number(max) || 1);
51    this.windowMs = Math.max(1_000, Number(windowMs) || 60_000);
52    this.jitterMs = Math.max(0, Number(jitterMs) || 0);
53    this.timestamps = [];
54  }
55
56  async acquire() {
57    for (;;) {
58      const now = Date.now();
59      this.timestamps = this.timestamps.filter((ts) => now - ts < this.windowMs);
60
61      if (this.timestamps.length < this.max) {
62        this.timestamps.push(now);
63        return;
64      }
65
66      const oldest = this.timestamps[0];
67      const wait = oldest + this.windowMs - now + Math.floor(Math.random() * this.jitterMs);
68      await sleep(Math.max(wait, 10));
69    }
70  }
71
72  stats() {
73    return {
74      max: this.max,
75      windowMs: this.windowMs,
76      used: this.timestamps.length,
77    };
78  }
79}
80
81class PromiseQueue {
82  constructor({ concurrency = 2, maxQueue = 100 } = {}) {
83    this.concurrency = Math.max(1, Number(concurrency) || 1);
84    this.maxQueue = Math.max(0, Number(maxQueue) || 0);
85    this.running = 0;
86    this.queue = [];
87  }
88
89  get size() {
90    return this.queue.length;
91  }
92
93  add(task, { signal } = {}) {
94    return new Promise((resolve, reject) => {
95      if (signal?.aborted) {
96        reject(new ScraperError('Task dibatalkan sebelum masuk queue.', { status: 499 }));
97        return;
98      }
99
100      if (this.queue.length >= this.maxQueue) {
101        reject(
102          new ScraperError(
103            'Queue penuh. Server/client sedang terlalu sibuk, coba lagi nanti.',
104            { status: 429 }
105          )
106        );
107        return;
108      }
109
110      const item = { task, resolve, reject, signal };
111
112      const onAbort = () => {
113        const idx = this.queue.indexOf(item);
114        if (idx >= 0) {
115          this.queue.splice(idx, 1);
116          reject(new ScraperError('Task dibatalkan.', { status: 499 }));
117        }
118      };
119
120      signal?.addEventListener('abort', onAbort, { once: true });
121      this.queue.push(item);
122      this._next();
123    });
124  }
125
126  _next() {
127    if (this.running >= this.concurrency || this.queue.length === 0) return;
128
129    const item = this.queue.shift();
130    if (!item) return this._next();
131
132    if (item.signal?.aborted) {
133      item.reject(new ScraperError('Task dibatalkan.', { status: 499 }));
134      return this._next();
135    }
136
137    this.running += 1;
138
139    Promise.resolve()
140      .then(() => item.task())
141      .then(
142        (value) => item.resolve(value),
143        (error) => item.reject(error)
144      )
145      .finally(() => {
146        this.running -= 1;
147        this._next();
148      });
149  }
150
151  stats() {
152    return {
153      running: this.running,
154      queued: this.queue.length,
155      concurrency: this.concurrency,
156      maxQueue: this.maxQueue,
157    };
158  }
159}
160
161class BycfHelper {
162  constructor({ enabled = true, proxy = null, logger = console } = {}) {
163    this.enabled = Boolean(enabled);
164    this.proxy = proxy;
165    this.logger = logger;
166    this._sdk = null;
167    this._loading = null;
168  }
169
170  async _load() {
171    if (!this.enabled) {
172      throw new ScraperError('BYCF helper dinonaktifkan.', { status: 400 });
173    }
174
175    if (this._sdk) return this._sdk;
176
177    if (!this._loading) {
178      this._loading = (async () => {
179        let mod;
180
181        try {
182          mod = await import('bycf');
183        } catch (error) {
184          throw new ScraperError(
185            'Package "bycf" belum terpasang. Jalankan: npm install bycf',
186            { cause: error, status: 500 }
187          );
188        }
189
190        const sdk = mod?.shz ?? mod?.default?.shz ?? mod?.default ?? mod;
191
192        if (!sdk || (typeof sdk !== 'object' && typeof sdk !== 'function')) {
193          throw new ScraperError('Format package bycf tidak dikenali.', { status: 500 });
194        }
195
196        this._sdk = sdk;
197        return this._sdk;
198      })();
199    }
200
201    return this._loading;
202  }
203
204  async _call(methodName, ...args) {
205    const sdk = await this._load();
206    const fn = sdk?.[methodName];
207
208    if (typeof fn !== 'function') {
209      throw new ScraperError(`bycf.${methodName} tidak tersedia.`, { status: 500 });
210    }
211
212    return fn.apply(sdk, args);
213  }
214
215  async stats() {
216    return this._call('stats');
217  }
218
219  async turnstileMin(pageUrl, siteKey, proxy = null) {
220    return this._call('turnstileMin', pageUrl, siteKey, proxy ?? this.proxy ?? null);
221  }
222
223  async turnstileMax(pageUrl, proxy = null) {
224    return this._call('turnstileMax', pageUrl, proxy ?? this.proxy ?? null);
225  }
226
227  async wafSession(url, options = {}) {
228    return this._call('wafSession', url, options);
229  }
230
231  async source(url) {
232    return this._call('source', url);
233  }
234}
235
236class IzenBypass {
237  constructor(options = {}) {
238    if (typeof fetch !== 'function') {
239      throw new ScraperError(
240        'Node.js versi 18+ dibutuhkan (global fetch tidak tersedia).',
241        { status: 500 }
242      );
243    }
244
245    this.baseUrl = options.baseUrl ?? DEFAULTS.baseUrl;
246    this.bypassPath = options.bypassPath ?? DEFAULTS.bypassPath;
247    this.healthPath = options.healthPath ?? DEFAULTS.healthPath;
248
249    try {
250      this.baseOrigin = new URL(this.baseUrl).origin;
251    } catch {
252      throw new ScraperError(`baseUrl tidak valid: ${this.baseUrl}`, { status: 400 });
253    }
254
255    this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
256    this.referer = options.referer ?? DEFAULTS.referer;
257
258    this.timeoutMs = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : 30_000;
259    this.retries = Math.max(1, Number(options.retries) || 3);
260    this.retryDelayMs = Number(options.retryDelayMs) > 0 ? Number(options.retryDelayMs) : 750;
261
262    this.cache = new Map();
263    this.cacheTtlMs = Number(options.cacheTtlMs) >= 0 ? Number(options.cacheTtlMs) : 5 * 60_000;
264    this.maxCacheSize = Number(options.maxCacheSize) > 0 ? Number(options.maxCacheSize) : 200;
265
266    this.queue = new PromiseQueue({
267      concurrency: options.concurrency ?? DEFAULTS.concurrency,
268      maxQueue: options.maxQueue ?? DEFAULTS.maxQueue,
269    });
270
271    this.rateLimiter = new RateLimiter({
272      max: options.rateLimit?.max ?? DEFAULTS.rateLimitMax,
273      windowMs: options.rateLimit?.windowMs ?? DEFAULTS.rateLimitWindowMs,
274      jitterMs: options.rateLimit?.jitterMs ?? DEFAULTS.rateLimitJitterMs,
275    });
276
277    this.logger = options.logger === null ? null : options.logger ?? console;
278
279    this.turnstile = {
280      pageUrl: options.turnstilePageUrl ?? DEFAULTS.turnstilePageUrl,
281      siteKey: options.turnstileSiteKey ?? DEFAULTS.turnstileSiteKey,
282      mode: options.turnstileMode ?? DEFAULTS.turnstileMode,
283    };
284
285    this.proxy = options.proxy ?? null;
286    this.wafSessionOptions = options.wafSessionOptions ?? {};
287
288    this.useBycf = options.useBycf ?? DEFAULTS.useBycf;
289    this.useWafSession = options.useWafSession ?? DEFAULTS.useWafSession;
290    this.autoSolveTurnstile = options.autoSolveTurnstile ?? DEFAULTS.autoSolveTurnstile;
291
292    this.captchaTokenProvider =
293      typeof options.captchaTokenProvider === 'function' ? options.captchaTokenProvider : null;
294
295    this.bycf = new BycfHelper({
296      enabled: this.useBycf,
297      proxy: this.proxy,
298      logger: this.logger ?? console,
299    });
300
301    this.session = null;
302    this.cookie = '';
303  }
304
305  async health() {
306    return this._request('GET', this.healthPath, {
307      parse: 'json',
308      retries: Math.max(1, this.retries),
309    });
310  }
311
312  async bypass(targetUrl, opts = {}) {
313    this._assertUrl(targetUrl);
314
315    const {
316      captchaToken = null,
317      force = false,
318      referer = null,
319      timeoutMs = null,
320      solveMode = null,
321    } = opts;
322
323    const cacheKey = `bypass:${targetUrl}`;
324
325    if (!force) {
326      const cached = this._cacheGet(cacheKey);
327      if (cached) {
328        return { ...cached, fromCache: true };
329      }
330    }
331
332    return this.queue.add(async () => {
333      await this._ensureSessionIfEnabled();
334
335      const token = captchaToken || (await this._getCaptchaToken({ referer, mode: solveMode }));
336
337      const payload = {
338        url: targetUrl,
339        captchaToken: token,
340      };
341
342      const data = await this._request('POST', this.bypassPath, {
343        body: payload,
344        referer: referer || this.turnstile.pageUrl || this.referer,
345        timeoutMs,
346        parse: 'json',
347      });
348
349      const normalized = this._normalizeBypassResponse(data, targetUrl);
350
351      if (!normalized.success) {
352        throw new ScraperError(data?.message || 'Respons API bypass tidak sukses.', {
353          status: 502,
354          body: data,
355          url: targetUrl,
356        });
357      }
358
359      this._cacheSet(cacheKey, normalized);
360      return normalized;
361    });
362  }
363
364  async bypassSafe(targetUrl, opts = {}) {
365    try {
366      const data = await this.bypass(targetUrl, opts);
367      return { ok: true, url: targetUrl, data };
368    } catch (error) {
369      return {
370        ok: false,
371        url: targetUrl,
372        error: error?.message ?? String(error),
373        status: error?.status ?? null,
374        body: error?.body ?? null,
375      };
376    }
377  }
378
379  /**
380   * Bypass banyak URL sekaligus, tetap mengikuti queue & rate limiter.
381   *
382   * @param {string[]} urls
383   * @param {object|Function} [opts]
384   */
385  async bulkBypass(urls, opts = {}) {
386    if (!Array.isArray(urls)) {
387      throw new ScraperError('urls harus berupa array.', { status: 400 });
388    }
389
390    return Promise.all(
391      urls.map(async (url, index) => {
392        const itemOpts = typeof opts === 'function' ? opts(url, index) : opts;
393
394        try {
395          const data = await this.bypass(url, itemOpts);
396          return { url, ok: true, data };
397        } catch (error) {
398          return {
399            url,
400            ok: false,
401            error: error?.message ?? String(error),
402            status: error?.status ?? null,
403            body: error?.body ?? null,
404          };
405        }
406      })
407    );
408  }
409
410  async solveTurnstile({
411    pageUrl = this.turnstile.pageUrl,
412    siteKey = this.turnstile.siteKey,
413    mode = this.turnstile.mode,
414  } = {}) {
415    if (!siteKey) {
416      throw new ScraperError('Turnstile siteKey kosong.', { status: 400 });
417    }
418
419    if (!pageUrl) {
420      throw new ScraperError('Turnstile pageUrl kosong.', { status: 400 });
421    }
422
423    if (mode === 'max') {
424      return this.bycf.turnstileMax(pageUrl, this.proxy);
425    }
426
427    try {
428      const token = await this.bycf.turnstileMin(pageUrl, siteKey, this.proxy);
429      if (token) return token;
430    } catch (error) {
431      this._warn('turnstileMin gagal, fallback ke turnstileMax.', error);
432    }
433
434    return this.bycf.turnstileMax(pageUrl, this.proxy);
435  }
436
437  async prepareSession({ force = false } = {}) {
438    if (!this.useWafSession) return null;
439
440    if (this.session && !force) return this.session;
441
442    const session = await this.bycf.wafSession(this.baseUrl, this.wafSessionOptions);
443    this.session = session;
444    this.cookie = this._normalizeCookies(session?.cookies);
445
446    return session;
447  }
448
449  async scrapeSource(url, { useBycf = true, timeoutMs = null } = {}) {
450    this._assertUrl(url);
451
452    if (useBycf && this.bycf.enabled) {
453      return this.bycf.source(url);
454    }
455
456    return this._request('GET', url, {
457      parse: 'text',
458      timeoutMs,
459    });
460  }
461
462  async scrapeJson(url, { timeoutMs = null, headers = {} } = {}) {
463    this._assertUrl(url);
464
465    return this._request('GET', url, {
466      parse: 'json',
467      timeoutMs,
468      headers,
469    });
470  }
471
472  async stats() {
473    let bycfStats = null;
474
475    try {
476      bycfStats = await this.bycf.stats();
477    } catch (error) {
478      bycfStats = {
479        error: error?.message ?? String(error),
480      };
481    }
482
483    return {
484      internal: {
485        queue: this.queue.stats(),
486        rateLimiter: this.rateLimiter.stats(),
487        cacheSize: this.cache.size,
488        sessionReady: Boolean(this.session || this.cookie),
489        autoSolveTurnstile: this.autoSolveTurnstile,
490        useWafSession: this.useWafSession,
491      },
492      bycf: bycfStats,
493    };
494  }
495
496  setCaptchaTokenProvider(fn) {
497    if (typeof fn !== 'function') {
498      throw new ScraperError('captchaTokenProvider harus function.', { status: 400 });
499    }
500
501    this.captchaTokenProvider = fn;
502    return this;
503  }
504
505  setProxy(proxy) {
506    this.proxy = proxy;
507    this.bycf.proxy = proxy;
508    return this;
509  }
510
511  clearCache() {
512    this.cache.clear();
513    return this;
514  }
515
516  async _ensureSessionIfEnabled() {
517    if (this.useWafSession) {
518      await this.prepareSession();
519    }
520  }
521
522  async _getCaptchaToken({ referer = null, mode = null } = {}) {
523    if (this.captchaTokenProvider) {
524      const token = await this.captchaTokenProvider({ scraper: this, referer });
525      if (token) return token;
526    }
527
528    if (!this.autoSolveTurnstile) {
529      throw new ScraperError(
530        'captchaToken kosong. Kirim captchaToken, set captchaTokenProvider, atau aktifkan autoSolveTurnstile jika punya izin.',
531        { status: 400 }
532      );
533    }
534
535    return this.solveTurnstile({
536      pageUrl: referer || this.turnstile.pageUrl,
537      mode: mode || this.turnstile.mode,
538    });
539  }
540
541  _normalizeBypassResponse(data, requestedUrl) {
542    const status = data?.status;
543    const result = data?.result;
544    const time = data?.time != null ? Number(data.time) : null;
545
546    if (status === 'success' && result) {
547      return {
548        success: true,
549        result,
550        time: Number.isFinite(time) ? time : null,
551        requestedUrl,
552        raw: data,
553        fromCache: false,
554      };
555    }
556
557    if (status === undefined && result) {
558      return {
559        success: true,
560        result,
561        time: Number.isFinite(time) ? time : null,
562        requestedUrl,
563        raw: data,
564        fromCache: false,
565      };
566    }
567
568    return {
569      success: false,
570      result: null,
571      time: null,
572      requestedUrl,
573      raw: data,
574      fromCache: false,
575    };
576  }
577
578  _normalizeCookies(cookies) {
579    if (!cookies) return '';
580
581    if (typeof cookies === 'string') return cookies;
582
583    if (Array.isArray(cookies)) {
584      return cookies.filter(Boolean).join('; ');
585    }
586
587    if (typeof cookies === 'object') {
588      return Object.entries(cookies)
589        .map(([key, value]) => `${key}=${value}`)
590        .join('; ');
591    }
592
593    return '';
594  }
595
596  async _request(method, path, opts = {}) {
597    const {
598      body,
599      referer,
600      headers = {},
601      timeoutMs = null,
602      retries = this.retries,
603      parse = 'json',
604    } = opts;
605
606    const targetUrl = new URL(path, this.baseUrl);
607    const url = targetUrl.toString();
608
609    let lastError;
610
611    for (let attempt = 1; attempt <= retries; attempt += 1) {
612      try {
613        await this.rateLimiter.acquire();
614
615        const requestHeaders = {
616          'User-Agent': this.userAgent,
617          Accept:
618            parse === 'json'
619              ? 'application/json'
620              : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
621          ...headers,
622        };
623
624        if (body !== undefined) {
625          requestHeaders['Content-Type'] = 'application/json';
626        }
627
628        if (referer) {
629          requestHeaders.Referer = referer;
630        } else if (this.referer) {
631          requestHeaders.Referer = this.referer;
632        }
633
634        if (this.cookie && targetUrl.origin === this.baseOrigin) {
635          requestHeaders.Cookie = this.cookie;
636        }
637
638        const response = await this._fetchWithTimeout(
639          url,
640          {
641            method,
642            headers: requestHeaders,
643            body: body === undefined ? undefined : JSON.stringify(body),
644          },
645          timeoutMs ?? this.timeoutMs
646        );
647
648        const text = await response.text();
649
650        let json = null;
651        if (parse === 'json') {
652          try {
653            json = text ? JSON.parse(text) : null;
654          } catch {
655            json = { raw: text };
656          }
657        }
658
659        if (response.ok) {
660          return parse === 'json' ? json : text;
661        }
662
663        const error = new ScraperError(`HTTP ${response.status} dari ${method} ${url}`, {
664          status: response.status,
665          body: parse === 'json' ? json : { raw: text },
666          url,
667        });
668
669        if (attempt < retries && this._isRetryableHttpStatus(response.status)) {
670          const retryAfterHeader = response.headers?.get?.('retry-after');
671          const retryAfter = Number(retryAfterHeader);
672          const wait = Number.isFinite(retryAfter)
673            ? retryAfter * 1000
674            : this._backoffDelay(attempt);
675
676          this._warn(`HTTP ${response.status}, retry ${attempt}/${retries} dalam ${wait}ms.`);
677          await sleep(wait);
678          continue;
679        }
680
681        throw error;
682      } catch (error) {
683        lastError = error;
684
685        const canRetry = attempt < retries && this._isRetryableError(error);
686
687        if (canRetry) {
688          const wait = this._backoffDelay(attempt);
689          this._warn(`Request gagal: ${error?.message}. Retry ${attempt}/${retries} dalam ${wait}ms.`);
690          await sleep(wait);
691          continue;
692        }
693
694        throw error;
695      }
696    }
697
698    throw lastError ?? new ScraperError('Request gagal setelah beberapa percobaan.', { url });
699  }
700
701  async _fetchWithTimeout(url, options = {}, timeoutMs = 30_000) {
702    const controller = new AbortController();
703    const timer = setTimeout(() => controller.abort(), timeoutMs);
704
705    try {
706      return await fetch(url, {
707        ...options,
708        signal: controller.signal,
709      });
710    } catch (error) {
711      if (error?.name === 'AbortError') {
712        throw new ScraperError(`Timeout setelah ${timeoutMs} ms saat mengakses ${url}`, {
713          cause: error,
714          status: 408,
715          url,
716        });
717      }
718
719      throw error;
720    } finally {
721      clearTimeout(timer);
722    }
723  }
724
725  _isRetryableHttpStatus(status) {
726    return [408, 429, 500, 502, 503, 504].includes(status);
727  }
728
729  _isRetryableError(error) {
730    if (error instanceof ScraperError && error.status) {
731      return this._isRetryableHttpStatus(error.status);
732    }
733
734    if (error?.cause?.name === 'AbortError') return true;
735
736    const message = String(error?.message || '').toLowerCase();
737    return [
738      'fetch failed',
739      'network',
740      'econnreset',
741      'etimedout',
742      'socket',
743      'und_err',
744      'eai_again',
745    ].some((keyword) => message.includes(keyword));
746  }
747
748  _backoffDelay(attempt) {
749    const base = this.retryDelayMs * 2 ** (attempt - 1);
750    const jitter = Math.floor(Math.random() * 250);
751    return Math.min(15_000, base + jitter);
752  }
753
754  _cacheGet(key) {
755    if (this.cacheTtlMs <= 0) return null;
756
757    const entry = this.cache.get(key);
758    if (!entry) return null;
759
760    if (Date.now() > entry.expiresAt) {
761      this.cache.delete(key);
762      return null;
763    }
764
765    return entry.value;
766  }
767
768  _cacheSet(key, value) {
769    if (this.cacheTtlMs <= 0) return;
770
771    this.cache.set(key, {
772      value,
773      expiresAt: Date.now() + this.cacheTtlMs,
774    });
775
776    if (this.cache.size > this.maxCacheSize) {
777      const firstKey = this.cache.keys().next().value;
778      if (firstKey) this.cache.delete(firstKey);
779    }
780  }
781
782  _assertUrl(url) {
783    if (typeof url !== 'string' || !url.trim()) {
784      throw new ScraperError('URL wajib diisi.', { status: 400 });
785    }
786
787    try {
788      const parsed = new URL(url);
789      if (!['http:', 'https:'].includes(parsed.protocol)) {
790        throw new Error('invalid protocol');
791      }
792    } catch {
793      throw new ScraperError(`URL tidak valid: ${url}`, { status: 400 });
794    }
795  }
796
797  _warn(message, error = null) {
798    if (!this.logger?.warn) return;
799
800    if (error) {
801      this.logger.warn(`[IzenBypass] ${message}`, error?.message ?? error);
802    } else {
803      this.logger.warn(`[IzenBypass] ${message}`);
804    }
805  }
806}
807
808module.exports = {
809  IzenBypass,
810  ScraperError,
811  RateLimiter,
812  PromiseQueue,
813  BycfHelper,
814  DEFAULTS,
815};
816
817// Contoh Penggunaan 
818
819(async () => {
820  const scraper = new IzenBypass();
821
822  try {
823    const result = await scraper.bypass('https://sfl.gl/7UE0TtK');
824    console.log('Final URL:', result.result);
825  } catch (error) {
826    console.error('Gagal:', error.message);
827  }
828})();
828 lines·21,301 chars·20.8 KB