import 'dart:convert'; import 'package:http/http.dart' as http; import '../http_client.dart'; import 'package:uuid/uuid.dart'; import '../http_util.dart'; import '../models.dart'; import '../sse.dart'; import 'webview_base_provider.dart'; /// Blackbox web chat. Tries REST after scraping a `validated` token, then WebView. class Blackbox extends WebViewBaseProvider { @override String get name => 'Blackbox'; @override bool get working => true; @override List get models => ['blackboxai', 'gpt-4o']; @override String get defaultModel => 'blackboxai'; @override String get baseUrl => 'https://www.blackbox.ai/'; @override String get apiEndpoint => 'https://www.blackbox.ai/api/chat'; static const _uuid = Uuid(); @override Stream createAsyncGenerator( String model, List messages, { bool stream = false, }) async* { try { await for (final chunk in _tryRest(messages, stream: stream)) { yield chunk; } return; } catch (_) {} await for (final chunk in super.createAsyncGenerator(model, messages, stream: stream)) { yield chunk; } } Stream _tryRest(List messages, {required bool stream}) async* { final validated = await _scrapeValidated(); final payload = { 'messages': [ for (final m in messages) { 'id': _uuid.v4(), 'content': m.content, 'role': m.role.name, } ], 'id': _uuid.v4(), 'previewToken': null, 'userId': null, 'codeModelMode': true, 'agentMode': {}, 'trendingAgentMode': {}, 'isMicMode': false, 'maxTokens': 1024, 'isChromeExt': false, 'clickedAnswer2': false, 'clickedAnswer3': false, 'clickedForceWebSearch': false, 'visitFromDelta': false, 'mobileClient': false, 'userSelectedModel': null, if (validated != null) 'validated': validated, 'imageGenerationMode': false, 'webSearchMode': false, }; final urls = [ 'https://www.blackbox.ai/api/chat', 'https://api.blackbox.ai/api/chat', ]; Object? lastError; for (final url in urls) { try { final req = http.Request('POST', Uri.parse(url)); req.headers.addAll(browserHeaders( origin: 'https://www.blackbox.ai', referer: 'https://www.blackbox.ai/', contentType: 'application/json', )); req.body = jsonEncode(payload); final res = await g4fHttp.send(req).timeout(const Duration(seconds: 20)); if (res.statusCode != 200) { lastError = 'HTTP ${res.statusCode}'; continue; } final collected = StringBuffer(); await for (final chunk in res.stream.transform(utf8.decoder)) { if (chunk.isEmpty) continue; // Blackbox sometimes streams raw text, sometimes SSE. if (chunk.trimLeft().startsWith('data:')) { for (final piece in extractAllSseContent(chunk)) { collected.write(piece); if (stream) yield piece; } } else { collected.write(chunk); if (stream) yield chunk; } } final text = collected.toString().trim(); if (!looksLikeModelOutput(text)) { lastError = 'unusable body'; continue; } if (!stream) yield text; return; } catch (e) { lastError = e; } } throw Exception('Blackbox REST failed: $lastError'); } Future _scrapeValidated() async { try { final page = await g4fHttp.get( Uri.parse('https://www.blackbox.ai/'), headers: browserHeaders(referer: 'https://www.blackbox.ai/'), ).timeout(const Duration(seconds: 12)); if (page.statusCode != 200) return null; final chunks = RegExp(r'/_next/static/[^"\x27]+\.js').allMatches(page.body); final uuidRe = RegExp( r'[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}', caseSensitive: false, ); for (final m in chunks.take(20)) { final src = m.group(0)!; final js = await g4fHttp.get( Uri.parse('https://www.blackbox.ai$src'), headers: browserHeaders(), ).timeout(const Duration(seconds: 8)); if (js.statusCode != 200) continue; if (!js.body.contains('validated') && !js.body.contains('api/chat')) { continue; } final match = uuidRe.firstMatch(js.body); if (match != null) return match.group(0); } } catch (_) {} return null; } @override String buildJsFetchCode(String model, List messages, bool stream) { final messagesJson = jsonEncode([ for (final m in messages) {'id': _uuid.v4(), 'content': m.content, 'role': m.role.name} ]); return ''' (async function() { const payload = { messages: $messagesJson, id: crypto.randomUUID(), previewToken: null, userId: null, codeModelMode: true, agentMode: {}, trendingAgentMode: {}, isMicMode: false, maxTokens: 1024 }; const urls = ['/api/chat', 'https://www.blackbox.ai/api/chat']; for (const url of urls) { try { const resp = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }); if (resp.status === 200) { return await resp.text(); } } catch (e) {} } return JSON.stringify({error: 'Blackbox chat endpoint not available'}); })(); '''; } @override String parseResponse(String rawResponse) { if (looksLikeModelOutput(rawResponse) && !rawResponse.trim().startsWith('{')) { return rawResponse.trim(); } try { final json = jsonDecode(rawResponse); if (json is Map && json['error'] != null) { throw Exception(json['error']); } } catch (e) { if (e.toString().contains('Blackbox') || e.toString().contains('error')) { rethrow; } } final sse = extractAllSseContent(rawResponse).join(); if (looksLikeModelOutput(sse)) return sse; if (looksLikeModelOutput(rawResponse)) return rawResponse.trim(); throw Exception('Failed to parse Blackbox: ${rawResponse.substring(0, rawResponse.length.clamp(0, 180))}'); } }