Files
999/lib/g4f/http_util.dart
Alma Alexyz fbf04d90db feat: embed a Flutter G4F client with Cronet, cookie jar, and origin WebView
Port the Oracle off the Python g4f container onto on-device providers: Chromium HTTP via Cronet, a shared cookie session, IterList retry, hosted OpenAI-compat aggregators, and same-origin WebView fallbacks instead of CORS fetch.
2026-09-01 22:05:38 +05:00

76 lines
2.5 KiB
Dart

const kBrowserUserAgent =
'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36';
const kDesktopUserAgent =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
Map<String, String> browserHeaders({
String? origin,
String? referer,
String? accept,
String? contentType,
bool desktop = false,
Map<String, String>? extra,
}) {
return {
'User-Agent': desktop ? kDesktopUserAgent : kBrowserUserAgent,
'Accept': accept ?? '*/*',
'Accept-Language': 'en-US,en;q=0.9',
if (origin != null) 'Origin': origin,
if (referer != null) 'Referer': referer,
if (contentType != null) 'Content-Type': contentType,
...?extra,
};
}
/// True when a provider dumped an error page / WAF / paywall instead of model text.
bool looksLikeGarbage(String text) {
final t = text.trimLeft();
if (t.isEmpty) return true;
final lower = t.toLowerCase();
if (t.startsWith('<!DOCTYPE') || t.startsWith('<html') || t.startsWith('<HTML')) {
return true;
}
if (lower.contains('just a moment') ||
lower.contains('cf-browser-verification') ||
lower.contains('attention required') ||
lower.contains('payment required') ||
lower.contains('frontend_captcha') ||
lower.contains('err_challenge') ||
lower.contains('error code:') ||
lower.contains('missing api key') ||
lower.contains('invalid api key') ||
lower.contains('you have to be logged in') ||
lower.contains('no cake credits') ||
lower.contains('insufficient_credits')) {
return true;
}
return false;
}
bool looksLikeModelOutput(String text) {
final trimmed = text.trim();
if (trimmed.length < 8) return false;
final lower = trimmed.toLowerCase();
if (lower == ': ping' || lower == 'ping' || lower == 'pong' || lower == '{}') {
return false;
}
if (RegExp(r'^[\{\}\[\]":\s]+$').hasMatch(trimmed)) return false;
return !looksLikeGarbage(trimmed);
}
String? cookieHeaderFromSetCookie(String? setCookie, {String? name}) {
if (setCookie == null || setCookie.isEmpty) return null;
// http package may join multiple Set-Cookie values with ", ".
final parts = setCookie.split(RegExp(r',(?=\s*[^;=]+=)'));
final pairs = <String>[];
for (final part in parts) {
final nv = part.split(';').first.trim();
if (nv.isEmpty || !nv.contains('=')) continue;
if (name != null && !nv.startsWith('$name=')) continue;
pairs.add(nv);
}
if (pairs.isEmpty) return null;
return pairs.join('; ');
}