672 lines
24 KiB
Dart
672 lines
24 KiB
Dart
import 'dart:async';
|
||
import 'dart:collection';
|
||
import 'dart:convert';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||
|
||
abstract class ILinkExtractionService {
|
||
Future<String?> extractVideoUrl(String pageUrl);
|
||
}
|
||
|
||
class LinkExtractionService implements ILinkExtractionService {
|
||
|
||
@override
|
||
Future<String?> extractVideoUrl(String pageUrl) async {
|
||
// 1. Zaten direkt bir video linkiyse hemen döndür
|
||
if (_isVideoUrl(pageUrl)) return pageUrl;
|
||
|
||
// 2. hdfilmizle.so gibi bilinen siteleri özel olarak işle
|
||
if (_isHdFilmIzle(pageUrl)) {
|
||
debugPrint("EXTRACTION: Detected hdfilmizle.so — using specialized extractor");
|
||
return _extractFromHdFilmIzle(pageUrl);
|
||
}
|
||
|
||
// 3. Genel amaçlı sniffer (diğer siteler için)
|
||
return _extractGeneric(pageUrl);
|
||
}
|
||
|
||
// ============================================================
|
||
// hdfilmizle.so Özel Extractor
|
||
// ============================================================
|
||
|
||
bool _isHdFilmIzle(String url) {
|
||
final lower = url.toLowerCase();
|
||
return lower.contains('hdfilmizle.so') ||
|
||
lower.contains('hdfilmizle.co') ||
|
||
lower.contains('hdfilmizle.org') ||
|
||
lower.contains('hdfilmizle.net');
|
||
}
|
||
|
||
/// hdfilmizle.so için 2 aşamalı extraction:
|
||
/// Aşama 1: Ana sayfadan "parts" array'ini çıkar → iframe URL al
|
||
/// Aşama 2: iframe (vidrame.pro) sayfasından EE.dd() ile m3u8 URL decode et
|
||
Future<String?> _extractFromHdFilmIzle(String pageUrl) async {
|
||
// AŞAMA 1: Ana sayfadan iframe URL'lerini çıkar
|
||
debugPrint("EXTRACTION [PHASE 1]: Loading main page to extract iframe URLs...");
|
||
final iframeUrl = await _extractIframeUrl(pageUrl);
|
||
|
||
if (iframeUrl == null) {
|
||
debugPrint("EXTRACTION [PHASE 1]: No iframe URL found, falling back to generic extractor");
|
||
return _extractGeneric(pageUrl);
|
||
}
|
||
|
||
debugPrint("EXTRACTION [PHASE 1]: Found iframe URL: $iframeUrl");
|
||
|
||
// AŞAMA 2: iframe sayfasından video URL'yi decode et
|
||
debugPrint("EXTRACTION [PHASE 2]: Loading iframe to decode video URL...");
|
||
final videoUrl = await _extractVideoFromIframe(iframeUrl);
|
||
|
||
if (videoUrl != null) {
|
||
debugPrint("EXTRACTION [PHASE 2]: SUCCESS! Video URL: $videoUrl");
|
||
return videoUrl;
|
||
}
|
||
|
||
debugPrint("EXTRACTION [PHASE 2]: Failed, falling back to generic extractor");
|
||
return _extractGeneric(pageUrl);
|
||
}
|
||
|
||
/// Aşama 1: hdfilmizle.so ana sayfasından "parts" JavaScript değişkenini parse eder
|
||
/// ve ilk iframe URL'sini döndürür.
|
||
Future<String?> _extractIframeUrl(String pageUrl) async {
|
||
final completer = Completer<String?>();
|
||
HeadlessInAppWebView? webView;
|
||
Timer? timeout;
|
||
|
||
void cleanup() {
|
||
timeout?.cancel();
|
||
webView?.dispose();
|
||
webView = null;
|
||
}
|
||
|
||
timeout = Timer(const Duration(seconds: 12), () {
|
||
if (!completer.isCompleted) {
|
||
debugPrint("EXTRACTION [PHASE 1]: Timeout");
|
||
cleanup();
|
||
completer.complete(null);
|
||
}
|
||
});
|
||
|
||
// JS: "parts" array'ini bul ve iframe src'lerini çıkar
|
||
final extractScript = UserScript(
|
||
source: '''
|
||
(function() {
|
||
if (window.__phase1Injected) return;
|
||
window.__phase1Injected = true;
|
||
|
||
// parts array'inden iframe URL çıkar
|
||
window.__extractIframeUrls = function() {
|
||
try {
|
||
// 1. Sayfadaki tüm script taglarını tara
|
||
var scripts = document.querySelectorAll('script');
|
||
for (var i = 0; i < scripts.length; i++) {
|
||
var text = scripts[i].textContent || scripts[i].innerText || '';
|
||
|
||
// "let parts" veya "var parts" değişkenini bul
|
||
var partsMatch = text.match(/(?:let|var|const)\\s+parts\\s*=\\s*(\\[.*?\\]);/s);
|
||
if (partsMatch) {
|
||
try {
|
||
var partsData = eval('(' + partsMatch[1] + ')');
|
||
var iframeUrls = [];
|
||
|
||
for (var j = 0; j < partsData.length; j++) {
|
||
var part = partsData[j];
|
||
var data = part.data || part.html || '';
|
||
|
||
// iframe src'yi çıkar
|
||
var iframeMatch = data.match(/src=["']([^"']+)["']/i);
|
||
if (iframeMatch) {
|
||
iframeUrls.push({
|
||
title: part.title || part.name || ('Part ' + j),
|
||
url: iframeMatch[1]
|
||
});
|
||
}
|
||
}
|
||
|
||
if (iframeUrls.length > 0) {
|
||
window.flutter_inappwebview.callHandler('iframeUrlsFound', JSON.stringify(iframeUrls));
|
||
return;
|
||
}
|
||
} catch(e) {
|
||
console.log('PHASE1: Error parsing parts: ' + e);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. Fallback: Sayfadaki mevcut iframe'leri doğrudan kontrol et
|
||
var iframes = document.querySelectorAll('iframe');
|
||
var directUrls = [];
|
||
for (var k = 0; k < iframes.length; k++) {
|
||
var src = iframes[k].src || iframes[k].getAttribute('data-src') || '';
|
||
if (src && src.indexOf('vidrame') > -1 || src.indexOf('vidmoly') > -1 ||
|
||
src.indexOf('closeload') > -1 || src.indexOf('rapidezvous') > -1 ||
|
||
src.indexOf('player') > -1) {
|
||
directUrls.push({ title: 'iframe-' + k, url: src });
|
||
}
|
||
}
|
||
if (directUrls.length > 0) {
|
||
window.flutter_inappwebview.callHandler('iframeUrlsFound', JSON.stringify(directUrls));
|
||
return;
|
||
}
|
||
|
||
// 3. Fallback: data-url veya benzeri attribute'lerden
|
||
var playerElements = document.querySelectorAll('[data-url], [data-src], [data-iframe]');
|
||
var attrUrls = [];
|
||
for (var m = 0; m < playerElements.length; m++) {
|
||
var u = playerElements[m].getAttribute('data-url') ||
|
||
playerElements[m].getAttribute('data-src') ||
|
||
playerElements[m].getAttribute('data-iframe') || '';
|
||
if (u && u.startsWith('http')) {
|
||
attrUrls.push({ title: 'data-' + m, url: u });
|
||
}
|
||
}
|
||
if (attrUrls.length > 0) {
|
||
window.flutter_inappwebview.callHandler('iframeUrlsFound', JSON.stringify(attrUrls));
|
||
}
|
||
|
||
} catch(e) {
|
||
console.log('PHASE1: Top-level error: ' + e);
|
||
}
|
||
};
|
||
})();
|
||
''',
|
||
injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
|
||
forMainFrameOnly: true,
|
||
);
|
||
|
||
webView = HeadlessInAppWebView(
|
||
initialUrlRequest: URLRequest(url: WebUri(pageUrl)),
|
||
initialUserScripts: UnmodifiableListView<UserScript>([extractScript]),
|
||
initialSettings: InAppWebViewSettings(
|
||
javaScriptEnabled: true,
|
||
mediaPlaybackRequiresUserGesture: true,
|
||
mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW,
|
||
supportMultipleWindows: false,
|
||
javaScriptCanOpenWindowsAutomatically: false,
|
||
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
),
|
||
onWebViewCreated: (controller) {
|
||
controller.addJavaScriptHandler(
|
||
handlerName: 'iframeUrlsFound',
|
||
callback: (args) {
|
||
if (args.isNotEmpty && !completer.isCompleted) {
|
||
try {
|
||
final List<dynamic> urls = jsonDecode(args[0].toString());
|
||
if (urls.isNotEmpty) {
|
||
final firstUrl = urls[0]['url']?.toString();
|
||
debugPrint("EXTRACTION [PHASE 1]: Found ${urls.length} iframe(s), first: $firstUrl");
|
||
cleanup();
|
||
completer.complete(firstUrl);
|
||
}
|
||
} catch (e) {
|
||
debugPrint("EXTRACTION [PHASE 1]: JSON parse error: $e");
|
||
}
|
||
}
|
||
},
|
||
);
|
||
},
|
||
onReceivedServerTrustAuthRequest: (controller, challenge) async {
|
||
return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
|
||
},
|
||
onLoadStop: (controller, url) async {
|
||
debugPrint("EXTRACTION [PHASE 1]: Page loaded: $url");
|
||
|
||
// Sayfa yüklendikten sonra extraction fonksiyonunu çalıştır
|
||
for (int i = 0; i < 5; i++) {
|
||
if (completer.isCompleted) break;
|
||
await Future.delayed(const Duration(milliseconds: 800));
|
||
if (completer.isCompleted) break;
|
||
|
||
try {
|
||
await controller.evaluateJavascript(
|
||
source: "if(window.__extractIframeUrls) window.__extractIframeUrls();",
|
||
);
|
||
} catch (e) {
|
||
debugPrint("EXTRACTION [PHASE 1]: JS execution error: $e");
|
||
}
|
||
}
|
||
},
|
||
);
|
||
|
||
await webView?.run();
|
||
return completer.future;
|
||
}
|
||
|
||
/// Aşama 2: vidrame.pro iframe sayfasından video URL'yi decode eder.
|
||
/// Sayfa JWPlayer kullanır ve video kaynağı EE.dd() fonksiyonu ile şifrelenir.
|
||
/// Strateji: Sayfanın kendi JS'ini çalıştırarak decode edilmiş URL'yi alıyoruz.
|
||
Future<String?> _extractVideoFromIframe(String iframeUrl) async {
|
||
final completer = Completer<String?>();
|
||
HeadlessInAppWebView? webView;
|
||
Timer? timeout;
|
||
|
||
void cleanup() {
|
||
timeout?.cancel();
|
||
webView?.dispose();
|
||
webView = null;
|
||
}
|
||
|
||
void handleFoundUrl(String url) {
|
||
if (!completer.isCompleted) {
|
||
cleanup();
|
||
completer.complete(url);
|
||
}
|
||
}
|
||
|
||
timeout = Timer(const Duration(seconds: 15), () {
|
||
if (!completer.isCompleted) {
|
||
debugPrint("EXTRACTION [PHASE 2]: Timeout");
|
||
cleanup();
|
||
completer.complete(null);
|
||
}
|
||
});
|
||
|
||
// JS: vidrame.pro sayfasından video URL'yi çıkar
|
||
// EE.dd() fonksiyonu sayfada zaten tanımlı, biz sadece configs.sources[0].file değerini alıyoruz
|
||
final decodeScript = UserScript(
|
||
source: '''
|
||
(function() {
|
||
if (window.__phase2Injected) return;
|
||
window.__phase2Injected = true;
|
||
|
||
console.log("PHASE2: Injected into: " + window.location.href);
|
||
|
||
// Yöntem 1: Network request'leri yakala (en güvenilir)
|
||
function reportUrl(url) {
|
||
if (url && (url.includes('.m3u8') || url.includes('.mp4'))) {
|
||
console.log("PHASE2: Found video URL via intercept: " + url);
|
||
window.flutter_inappwebview.callHandler('videoUrlFound', url);
|
||
}
|
||
}
|
||
|
||
var oldOpen = XMLHttpRequest.prototype.open;
|
||
XMLHttpRequest.prototype.open = function(method, url) {
|
||
reportUrl(url);
|
||
return oldOpen.apply(this, arguments);
|
||
};
|
||
|
||
var oldFetch = window.fetch;
|
||
window.fetch = function() {
|
||
var url = (arguments[0] instanceof Request) ? arguments[0].url : arguments[0];
|
||
if (url) reportUrl(url.toString());
|
||
return oldFetch.apply(this, arguments);
|
||
};
|
||
|
||
// Yöntem 2: JWPlayer configs'ten doğrudan çıkar
|
||
window.__extractFromJwPlayer = function() {
|
||
try {
|
||
// jwplayer instance'ını kontrol et
|
||
if (typeof jwplayer !== 'undefined') {
|
||
var pl = jwplayer();
|
||
if (pl && pl.getPlaylistItem) {
|
||
var item = pl.getPlaylistItem();
|
||
if (item && item.file) {
|
||
reportUrl(item.file);
|
||
return true;
|
||
}
|
||
if (item && item.sources) {
|
||
for (var i = 0; i < item.sources.length; i++) {
|
||
if (item.sources[i].file) {
|
||
reportUrl(item.sources[i].file);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch(e) {
|
||
console.log("PHASE2: JWPlayer check error: " + e);
|
||
}
|
||
|
||
try {
|
||
// configs variable'ını doğrudan kontrol et
|
||
if (typeof configs !== 'undefined' && configs.sources) {
|
||
for (var j = 0; j < configs.sources.length; j++) {
|
||
if (configs.sources[j].file) {
|
||
reportUrl(configs.sources[j].file);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
} catch(e) {
|
||
console.log("PHASE2: configs check error: " + e);
|
||
}
|
||
|
||
try {
|
||
// EE.dd decode fonksiyonunu doğrudan dene
|
||
if (typeof EE !== 'undefined' && EE.dd) {
|
||
// Sayfadaki tüm scriptleri tara ve EE.dd(...) çağrılarını bul
|
||
var scripts = document.querySelectorAll('script');
|
||
for (var k = 0; k < scripts.length; k++) {
|
||
var text = scripts[k].textContent || '';
|
||
var matches = text.match(/EE\\.dd\\(["']([^"']+)["']\\)/g);
|
||
if (matches) {
|
||
for (var m = 0; m < matches.length; m++) {
|
||
var encoded = matches[m].match(/EE\\.dd\\(["']([^"']+)["']\\)/);
|
||
if (encoded && encoded[1]) {
|
||
try {
|
||
var decoded = EE.dd(encoded[1]);
|
||
if (decoded) reportUrl(decoded);
|
||
} catch(e2) {}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch(e) {
|
||
console.log("PHASE2: EE.dd scan error: " + e);
|
||
}
|
||
|
||
try {
|
||
// HTML içinde doğrudan m3u8/mp4 regex tara
|
||
var html = document.documentElement.innerHTML;
|
||
var match = html.match(/https?:\\/\\/[^"'\\s<>]+\\.(m3u8|mp4)[^"'\\s<>]*/i);
|
||
if (match) {
|
||
reportUrl(match[0]);
|
||
return true;
|
||
}
|
||
} catch(e) {}
|
||
|
||
try {
|
||
// Video elementlerini kontrol et
|
||
document.querySelectorAll('video').forEach(function(v) {
|
||
if (v.src && v.src.length > 5) reportUrl(v.src);
|
||
v.querySelectorAll('source').forEach(function(s) {
|
||
if (s.src) reportUrl(s.src);
|
||
});
|
||
});
|
||
} catch(e) {}
|
||
|
||
return false;
|
||
};
|
||
})();
|
||
''',
|
||
injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
|
||
forMainFrameOnly: false,
|
||
);
|
||
|
||
webView = HeadlessInAppWebView(
|
||
initialUrlRequest: URLRequest(url: WebUri(iframeUrl)),
|
||
initialUserScripts: UnmodifiableListView<UserScript>([decodeScript]),
|
||
initialSettings: InAppWebViewSettings(
|
||
javaScriptEnabled: true,
|
||
mediaPlaybackRequiresUserGesture: false,
|
||
mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW,
|
||
supportMultipleWindows: false,
|
||
javaScriptCanOpenWindowsAutomatically: false,
|
||
useShouldInterceptRequest: true,
|
||
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
),
|
||
onWebViewCreated: (controller) {
|
||
controller.addJavaScriptHandler(
|
||
handlerName: 'videoUrlFound',
|
||
callback: (args) {
|
||
if (args.isNotEmpty) {
|
||
handleFoundUrl(args[0].toString());
|
||
}
|
||
},
|
||
);
|
||
},
|
||
onReceivedServerTrustAuthRequest: (controller, challenge) async {
|
||
return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
|
||
},
|
||
shouldInterceptRequest: (controller, request) async {
|
||
final url = request.url.toString();
|
||
if (_isVideoUrl(url)) {
|
||
handleFoundUrl(url);
|
||
}
|
||
return null;
|
||
},
|
||
onLoadStop: (controller, url) async {
|
||
debugPrint("EXTRACTION [PHASE 2]: iframe loaded: $url");
|
||
|
||
// JWPlayer'ın yüklenmesini bekle ve video URL'yi çıkar
|
||
for (int i = 0; i < 10; i++) {
|
||
if (completer.isCompleted) break;
|
||
await Future.delayed(const Duration(seconds: 1));
|
||
if (completer.isCompleted) break;
|
||
|
||
try {
|
||
await controller.evaluateJavascript(
|
||
source: "if(window.__extractFromJwPlayer) window.__extractFromJwPlayer();",
|
||
);
|
||
} catch (e) {
|
||
debugPrint("EXTRACTION [PHASE 2]: JS execution error: $e");
|
||
}
|
||
}
|
||
},
|
||
);
|
||
|
||
await webView?.run();
|
||
return completer.future;
|
||
}
|
||
|
||
// ============================================================
|
||
// Dart tarafında EE.dd() decode (fallback olarak)
|
||
// ============================================================
|
||
|
||
/// vidrame.pro'nun EE.dd() şifre çözme algoritması:
|
||
/// 1. URL-safe base64 → standart base64 dönüştür
|
||
/// 2. Base64 decode
|
||
/// 3. ROT13 uygula
|
||
/// 4. String reverse
|
||
static String? decodEEdd(String encoded) {
|
||
try {
|
||
// URL-safe base64 → standart base64
|
||
String s = encoded.replaceAll('-', '+').replaceAll('_', '/');
|
||
while (s.length % 4 != 0) {
|
||
s += '=';
|
||
}
|
||
|
||
// Base64 decode
|
||
final decoded = utf8.decode(base64Decode(s));
|
||
|
||
// ROT13
|
||
final rot13 = _rot13(decoded);
|
||
|
||
// String reverse
|
||
return rot13.split('').reversed.join('');
|
||
} catch (e) {
|
||
debugPrint("decodEEdd error: $e");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// ROT13 dönüşümü (harfler ve rakamlar dahil, vidrame'nin özel versiyonu)
|
||
static String _rot13(String input) {
|
||
final buffer = StringBuffer();
|
||
for (int i = 0; i < input.length; i++) {
|
||
final c = input.codeUnitAt(i);
|
||
|
||
if (c >= 65 && c <= 90) {
|
||
// A-Z
|
||
buffer.writeCharCode(((c - 65 + 13) % 26) + 65);
|
||
} else if (c >= 97 && c <= 122) {
|
||
// a-z
|
||
buffer.writeCharCode(((c - 97 + 13) % 26) + 97);
|
||
} else if (c >= 48 && c <= 57) {
|
||
// 0-9
|
||
buffer.writeCharCode(((c - 48 + 13) % 10) + 48);
|
||
} else {
|
||
buffer.writeCharCode(c);
|
||
}
|
||
}
|
||
return buffer.toString();
|
||
}
|
||
|
||
// ============================================================
|
||
// Genel Amaçlı Sniffer (Fallback)
|
||
// ============================================================
|
||
|
||
Future<String?> _extractGeneric(String pageUrl) async {
|
||
final completer = Completer<String?>();
|
||
Timer? timeoutTimer;
|
||
HeadlessInAppWebView? headlessWebView;
|
||
|
||
void cleanup() {
|
||
timeoutTimer?.cancel();
|
||
headlessWebView?.dispose();
|
||
headlessWebView = null;
|
||
}
|
||
|
||
void handleFoundUrl(String url) {
|
||
if (!completer.isCompleted) {
|
||
debugPrint("EXTRACTION: SUCCESS! Found: $url");
|
||
cleanup();
|
||
completer.complete(url);
|
||
}
|
||
}
|
||
|
||
// Zaman aşımı (15 saniye)
|
||
timeoutTimer = Timer(const Duration(seconds: 15), () {
|
||
if (!completer.isCompleted) {
|
||
debugPrint("EXTRACTION: Timeout - no video URL found");
|
||
cleanup();
|
||
completer.complete(null);
|
||
}
|
||
});
|
||
|
||
// UserScript: JS Kodunu Iframe'ler dahil tüm sayfalara DOM yüklenmeden ENJEKTE EDİYORUZ
|
||
final snifferScript = UserScript(
|
||
source: '''
|
||
(function() {
|
||
if (window.snifferInjected) return;
|
||
window.snifferInjected = true;
|
||
|
||
console.log("SNIFFER: Injected into frame: " + window.location.href);
|
||
|
||
function reportUrl(url) {
|
||
if (url && (url.includes('.m3u8') || url.includes('.mp4'))) {
|
||
window.flutter_inappwebview.callHandler('foundVideoUrl', url);
|
||
}
|
||
}
|
||
|
||
// 1. Override XMLHttpRequest
|
||
var oldOpen = XMLHttpRequest.prototype.open;
|
||
XMLHttpRequest.prototype.open = function(method, url) {
|
||
reportUrl(url);
|
||
return oldOpen.apply(this, arguments);
|
||
};
|
||
|
||
// 2. Override Fetch
|
||
var oldFetch = window.fetch;
|
||
window.fetch = function() {
|
||
var url = (arguments[0] instanceof Request) ? arguments[0].url : arguments[0];
|
||
if (url) reportUrl(url.toString());
|
||
return oldFetch.apply(this, arguments);
|
||
};
|
||
|
||
// 3. Oynatma Butonlarına Tıklama (Tüm iframe'lerde çalışacak)
|
||
window.clickPlayButtons = function() {
|
||
var selectors = [
|
||
'.play-that-video', '.play-button', '.vjs-big-play-button',
|
||
'[aria-label="Play"]', '.ytp-large-play-button', '.play-icon',
|
||
'#play', '.player-play', '.jw-display-icon-container',
|
||
'div[class*="play"]', 'button[class*="play"]', 'svg[class*="play"]'
|
||
];
|
||
|
||
selectors.forEach(function(sel) {
|
||
try {
|
||
document.querySelectorAll(sel).forEach(function(el) {
|
||
if (el.offsetWidth > 0 || el.offsetHeight > 0) {
|
||
el.click();
|
||
}
|
||
});
|
||
} catch(e) {}
|
||
});
|
||
|
||
// Video etiketlerini direkt kontrol et
|
||
document.querySelectorAll('video').forEach(function(v) {
|
||
if (v.src && v.src.length > 5) reportUrl(v.src);
|
||
v.querySelectorAll('source').forEach(function(s) {
|
||
if (s.src) reportUrl(s.src);
|
||
});
|
||
});
|
||
};
|
||
|
||
// HTML içindeki linkleri Regex ile tara
|
||
var scanInterval = setInterval(function() {
|
||
var html = document.documentElement.innerHTML;
|
||
var match = html.match(/https?:\\/\\/[^"'\\s<>]+\\.(m3u8|mp4)[^"'\\s<>]*/i);
|
||
if (match) reportUrl(match[0]);
|
||
}, 1500);
|
||
|
||
})();
|
||
''',
|
||
injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START, // Sayfa açılmadan hemen önce
|
||
forMainFrameOnly: false, // ÇOK ÖNEMLİ: Iframe'lerin içine girmesi için false olmalı!
|
||
);
|
||
|
||
headlessWebView = HeadlessInAppWebView(
|
||
initialUrlRequest: URLRequest(url: WebUri(pageUrl)),
|
||
initialUserScripts: UnmodifiableListView<UserScript>([snifferScript]),
|
||
initialSettings: InAppWebViewSettings(
|
||
javaScriptEnabled: true,
|
||
isInspectable: true,
|
||
mediaPlaybackRequiresUserGesture: false, // ÇOK ÖNEMLİ: Videoların tıklanmadan yüklenmesine izin ver
|
||
mixedContentMode: MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW,
|
||
supportMultipleWindows: false,
|
||
javaScriptCanOpenWindowsAutomatically: false,
|
||
useShouldInterceptRequest: true,
|
||
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
),
|
||
onWebViewCreated: (controller) {
|
||
// JS'den gelen sinyali dinle
|
||
controller.addJavaScriptHandler(handlerName: 'foundVideoUrl', callback: (args) {
|
||
if (args.isNotEmpty) {
|
||
handleFoundUrl(args[0].toString());
|
||
}
|
||
});
|
||
},
|
||
onReceivedServerTrustAuthRequest: (controller, challenge) async {
|
||
return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
|
||
},
|
||
shouldInterceptRequest: (controller, request) async {
|
||
final url = request.url.toString();
|
||
if (_isVideoUrl(url)) {
|
||
handleFoundUrl(url);
|
||
}
|
||
return null;
|
||
},
|
||
onLoadStop: (controller, url) async {
|
||
debugPrint("EXTRACTION: Page loaded: $url");
|
||
|
||
// JS yüklenmiş mi diye kontrol et ve butonlara tıkla
|
||
for (int i = 0; i < 8; i++) {
|
||
if (completer.isCompleted) break;
|
||
await Future.delayed(const Duration(seconds: 1));
|
||
if (completer.isCompleted) break;
|
||
|
||
try {
|
||
// WebView kapanmışsa hata fırlatmasını engellemek için try-catch
|
||
await controller.evaluateJavascript(source: "if(window.clickPlayButtons) window.clickPlayButtons();");
|
||
} catch (e) {
|
||
debugPrint("WebView disposed before javascript execution.");
|
||
}
|
||
}
|
||
},
|
||
);
|
||
|
||
await headlessWebView?.run();
|
||
return completer.future;
|
||
}
|
||
|
||
// ============================================================
|
||
// Yardımcı Fonksiyonlar
|
||
// ============================================================
|
||
|
||
bool _isVideoUrl(String url) {
|
||
final lower = url.toLowerCase();
|
||
if (lower.startsWith('data:') || lower.length < 10) return false;
|
||
|
||
// Analitik veya reklam linklerini es geç
|
||
if (lower.contains('google.com') || lower.contains('gstatic.com') || lower.contains('analytics')) return false;
|
||
|
||
return lower.contains('.m3u8') ||
|
||
lower.contains('.mp4') ||
|
||
lower.contains('.mkv') ||
|
||
lower.contains('/hls/') ||
|
||
lower.contains('master.m3u8') ||
|
||
lower.contains('index.m3u8') ||
|
||
lower.contains('playlist.m3u8');
|
||
}
|
||
} |