feat: add LinkExtractionService with specialized support for hdfilmizle video extraction
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
|
||||||
@@ -14,20 +15,499 @@ class LinkExtractionService implements ILinkExtractionService {
|
|||||||
// 1. Zaten direkt bir video linkiyse hemen döndür
|
// 1. Zaten direkt bir video linkiyse hemen döndür
|
||||||
if (_isVideoUrl(pageUrl)) return pageUrl;
|
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?>();
|
final completer = Completer<String?>();
|
||||||
Timer? timeoutTimer;
|
Timer? timeoutTimer;
|
||||||
|
|
||||||
// HeadlessWebView'i yerel değişkene alıyoruz ki paralel çalışmalarda çakışmasın
|
|
||||||
HeadlessInAppWebView? headlessWebView;
|
HeadlessInAppWebView? headlessWebView;
|
||||||
|
|
||||||
// Temizlik ve Bellek Yönetimi
|
|
||||||
void cleanup() {
|
void cleanup() {
|
||||||
timeoutTimer?.cancel();
|
timeoutTimer?.cancel();
|
||||||
headlessWebView?.dispose();
|
headlessWebView?.dispose();
|
||||||
headlessWebView = null;
|
headlessWebView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bulunan URL'yi işleme
|
|
||||||
void handleFoundUrl(String url) {
|
void handleFoundUrl(String url) {
|
||||||
if (!completer.isCompleted) {
|
if (!completer.isCompleted) {
|
||||||
debugPrint("EXTRACTION: SUCCESS! Found: $url");
|
debugPrint("EXTRACTION: SUCCESS! Found: $url");
|
||||||
@@ -45,7 +525,7 @@ class LinkExtractionService implements ILinkExtractionService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. UserScript: JS Kodunu Iframe'ler dahil tüm sayfalara DOM yüklenmeden ENJEKTE EDİYORUZ
|
// UserScript: JS Kodunu Iframe'ler dahil tüm sayfalara DOM yüklenmeden ENJEKTE EDİYORUZ
|
||||||
final snifferScript = UserScript(
|
final snifferScript = UserScript(
|
||||||
source: '''
|
source: '''
|
||||||
(function() {
|
(function() {
|
||||||
@@ -118,7 +598,7 @@ class LinkExtractionService implements ILinkExtractionService {
|
|||||||
|
|
||||||
headlessWebView = HeadlessInAppWebView(
|
headlessWebView = HeadlessInAppWebView(
|
||||||
initialUrlRequest: URLRequest(url: WebUri(pageUrl)),
|
initialUrlRequest: URLRequest(url: WebUri(pageUrl)),
|
||||||
initialUserScripts: UnmodifiableListView<UserScript>([snifferScript]), // Scripti buradan veriyoruz
|
initialUserScripts: UnmodifiableListView<UserScript>([snifferScript]),
|
||||||
initialSettings: InAppWebViewSettings(
|
initialSettings: InAppWebViewSettings(
|
||||||
javaScriptEnabled: true,
|
javaScriptEnabled: true,
|
||||||
isInspectable: true,
|
isInspectable: true,
|
||||||
@@ -170,6 +650,10 @@ class LinkExtractionService implements ILinkExtractionService {
|
|||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Yardımcı Fonksiyonlar
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
bool _isVideoUrl(String url) {
|
bool _isVideoUrl(String url) {
|
||||||
final lower = url.toLowerCase();
|
final lower = url.toLowerCase();
|
||||||
if (lower.startsWith('data:') || lower.length < 10) return false;
|
if (lower.startsWith('data:') || lower.length < 10) return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user