izen bypass ( get url final )
JavaScriptPublicby zx-apiAug 27, 2026, 12:49 PMExpires: Never177 views
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
Wwrap·Ffullscreen