feat: implement CF resolution server

This commit is contained in:
Moustapha Kodjo Amadou 2025-11-25 14:53:22 +01:00
parent 1d81906c4f
commit f06df9b3b1
3 changed files with 136 additions and 70 deletions

View file

@ -50,6 +50,7 @@ DiscordRPC? discordRpc;
WebViewEnvironment? webViewEnvironment; WebViewEnvironment? webViewEnvironment;
String? customDns; String? customDns;
void main(List<String> args) async { void main(List<String> args) async {
cfResolutionWebviewServer();
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
if (Platform.isLinux && runWebViewTitleBarWidget(args)) return; if (Platform.isLinux && runWebViewTitleBarWidget(args)) return;
MediaKit.ensureInitialized(); MediaKit.ensureInitialized();

View file

@ -1,4 +1,6 @@
import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:http_interceptor/http_interceptor.dart'; import 'package:http_interceptor/http_interceptor.dart';
import 'package:mangayomi/eval/model/m_bridge.dart'; import 'package:mangayomi/eval/model/m_bridge.dart';
import 'dart:async'; import 'dart:async';
@ -68,7 +70,7 @@ class MClient {
); );
return InterceptedClient.build( return InterceptedClient.build(
client: httpClient(settings: clientSettings, reqcopyWith: reqcopyWith), client: httpClient(settings: clientSettings, reqcopyWith: reqcopyWith),
retryPolicy: ResolveCloudFlareChallenge(showCloudFlareError),
interceptors: [ interceptors: [
MCookieManager(reqcopyWith), MCookieManager(reqcopyWith),
LoggerInterceptor(showCloudFlareError), LoggerInterceptor(showCloudFlareError),
@ -256,78 +258,138 @@ bool isCloudflare(BaseResponse response) {
["cloudflare-nginx", "cloudflare"].contains(response.headers["server"]); ["cloudflare-nginx", "cloudflare"].contains(response.headers["server"]);
} }
// class ResolveCloudFlareChallenge extends RetryPolicy { class ResolveCloudFlareChallenge extends RetryPolicy {
// bool showCloudFlareError; bool showCloudFlareError;
// ResolveCloudFlareChallenge(this.showCloudFlareError); ResolveCloudFlareChallenge(this.showCloudFlareError);
// @override @override
// int get maxRetryAttempts => 2; int get maxRetryAttempts => 2;
// @override @override
// Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async { Future<bool> shouldAttemptRetryOnResponse(BaseResponse response) async {
// if (!showCloudFlareError || Platform.isLinux) return false; if (!showCloudFlareError || Platform.isLinux) return false;
// flutter_inappwebview.HeadlessInAppWebView? headlessWebView; bool isCloudFlare = isCloudflare(response);
// int time = 0; if (isCloudFlare) {
// bool timeOut = false; try {
// bool isCloudFlare = isCloudflare(response); return http
// if (isCloudFlare) { .post(
// headlessWebView = flutter_inappwebview.HeadlessInAppWebView( Uri.parse('http://localhost:$cfPort/resolve_cf'),
// webViewEnvironment: webViewEnvironment, headers: {HttpHeaders.contentTypeHeader: 'application/json'},
// initialUrlRequest: flutter_inappwebview.URLRequest( body: jsonEncode({'url': response.request!.url.toString()}),
// url: flutter_inappwebview.WebUri(response.request!.url.toString()), )
// ), .then((res) {
// onLoadStop: (controller, url) async { if (res.statusCode == 200) {
// try { final data = jsonDecode(res.body) as Map<String, dynamic>;
// isCloudFlare = await controller.platform.evaluateJavascript( return data['result'] as bool;
// source: }
// "document.head.innerHTML.includes('#challenge-success-text')", return false;
// ); });
// } catch (_) { } catch (e) {
// isCloudFlare = false; return false;
// } }
}
// await Future.doWhile(() async { return false;
// if (!timeOut && isCloudFlare) { }
// try { }
// isCloudFlare = await controller.platform.evaluateJavascript(
// source:
// "document.head.innerHTML.includes('#challenge-success-text')",
// );
// } catch (_) {
// isCloudFlare = false;
// }
// }
// if (isCloudFlare) await Future.delayed(Duration(milliseconds: 300));
// return isCloudFlare; int cfPort = 0;
// }); void cfResolutionWebviewServer() async {
// if (!timeOut) { final server = await HttpServer.bind(InternetAddress.loopbackIPv4, cfPort);
// final ua = cfPort = server.port;
// await controller.evaluateJavascript(
// source: "navigator.userAgent",
// ) ??
// "";
// await MClient.setCookie(url.toString(), ua, controller);
// }
// },
// );
// headlessWebView.run(); server.listen((HttpRequest request) {
if (request.method == 'POST' && request.uri.path == '/resolve_cf') {
_handleResolveCf(request);
} else {
request.response
..statusCode = HttpStatus.notFound
..write('Not Found')
..close();
}
});
}
// await Future.doWhile(() async { void _handleResolveCf(HttpRequest request) async {
// timeOut = time == 15; int time = 0;
// if (!isCloudFlare || timeOut) { bool timeOut = false;
// return false; bool isCloudFlare = true;
// } try {
// await Future.delayed(const Duration(seconds: 1)); final body = await utf8.decoder.bind(request).join();
// time++; final data = jsonDecode(body) as Map<String, dynamic>;
// return true; final url = data['url'] as String?;
// });
// try {
// headlessWebView.dispose();
// } catch (_) {}
// return true; if (url == null) {
// } request.response
..statusCode = HttpStatus.badRequest
..write(jsonEncode({'error': 'Missing url parameter'}))
..close();
return;
}
// return false; flutter_inappwebview.HeadlessInAppWebView? headlessWebView;
// } headlessWebView = flutter_inappwebview.HeadlessInAppWebView(
// } webViewEnvironment: webViewEnvironment,
initialUrlRequest: flutter_inappwebview.URLRequest(
url: flutter_inappwebview.WebUri(url),
),
onLoadStop: (controller, url) async {
try {
isCloudFlare = await controller.platform.evaluateJavascript(
source:
"document.head.innerHTML.includes('#challenge-success-text')",
);
} catch (_) {
isCloudFlare = false;
}
await Future.doWhile(() async {
if (!timeOut && isCloudFlare) {
try {
isCloudFlare = await controller.platform.evaluateJavascript(
source:
"document.head.innerHTML.includes('#challenge-success-text')",
);
} catch (_) {
isCloudFlare = false;
}
}
if (isCloudFlare) await Future.delayed(Duration(milliseconds: 300));
return isCloudFlare;
});
if (!timeOut) {
final ua =
await controller.evaluateJavascript(
source: "navigator.userAgent",
) ??
"";
await MClient.setCookie(url.toString(), ua, controller);
}
},
);
headlessWebView.run();
await Future.doWhile(() async {
timeOut = time == 15;
if (!isCloudFlare || timeOut) {
return false;
}
await Future.delayed(const Duration(seconds: 1));
time++;
return true;
});
try {
headlessWebView.dispose();
} catch (_) {}
request.response
..headers.contentType = ContentType.json
..write(jsonEncode({'result': isCloudFlare}))
..close();
} catch (e) {
request.response
..statusCode = HttpStatus.badRequest
..write(jsonEncode({'error': 'Invalid JSON'}))
..close();
}
}

View file

@ -7,6 +7,7 @@ import 'package:mangayomi/eval/lib.dart';
import 'package:mangayomi/main.dart'; import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart'; import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/providers/storage_provider.dart'; import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/utils/log/log.dart'; import 'package:mangayomi/utils/log/log.dart';
class _IsolateData { class _IsolateData {
@ -103,6 +104,7 @@ class GetIsolateService {
final serviceType = message['serviceType'] as String?; final serviceType = message['serviceType'] as String?;
final useLoggerValue = message['useLogger'] as bool?; final useLoggerValue = message['useLogger'] as bool?;
final responsePort = message['responsePort'] as SendPort; final responsePort = message['responsePort'] as SendPort;
cfPort = message['cfPort'] as int;
if (useLoggerValue != null) { if (useLoggerValue != null) {
useLogger = useLoggerValue; useLogger = useLoggerValue;
} }
@ -195,6 +197,7 @@ class GetIsolateService {
'proxyServer': proxyServer, 'proxyServer': proxyServer,
'responsePort': responsePort.sendPort, 'responsePort': responsePort.sendPort,
'useLogger': useLogger, 'useLogger': useLogger,
'cfPort': cfPort,
}); });
return completer.future; return completer.future;