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 'base_provider.dart'; /// Port of g4f.Provider.Liaobots — guest auth code + chat. class Liaobots extends BaseProvider { @override String get name => 'Liaobots'; @override bool get working => true; @override List get models => ['gpt-3.5-turbo', 'gpt-4']; @override String get defaultModel => 'gpt-3.5-turbo'; static const _hosts = [ 'https://liaobots.work', 'https://liaobots.site', 'https://liaobots.com', ]; @override Stream createAsyncGenerator( String model, List messages, { bool stream = false, }) async* { Object? last; for (final host in _hosts) { try { yield await _chat(host, model, messages); return; } catch (e) { last = e; } } throw Exception('Liaobots failed: $last'); } Future _chat(String host, String model, List messages) async { final headers = browserHeaders( origin: host, referer: '$host/', contentType: 'application/json', ); final login = await g4fHttp .post( Uri.parse('$host/api/user'), headers: headers, body: jsonEncode({'authcode': const Uuid().v4()}), ) .timeout(const Duration(seconds: 12)); String? auth; if (login.statusCode == 200) { try { final data = jsonDecode(login.body); auth = (data is Map ? (data['authCode'] ?? data['authcode']) : null) ?.toString(); } catch (_) {} } final req = http.Request('POST', Uri.parse('$host/api/chat')); req.headers.addAll({ ...headers, if (auth != null) 'x-auth-code': auth, if (auth != null) 'authorization': auth, }); req.body = jsonEncode({ 'conversationId': const Uuid().v4(), 'model': { 'id': models.contains(model) ? model : defaultModel, 'name': model, 'maxLength': 24000, 'tokenLimit': 8000, }, 'messages': toOpenAiMessages(messages), 'key': '', 'prompt': extractSystemMessage(messages), }); final res = await g4fHttp.send(req).timeout(requestTimeout); if (res.statusCode != 200) { throw Exception('Liaobots ${res.statusCode} @ $host'); } final raw = utf8.decode(await res.stream.toBytes().timeout(requestTimeout)); final sse = extractAllSseContent(raw).join(); final text = looksLikeModelOutput(sse) ? sse : raw.trim(); if (!looksLikeModelOutput(text)) throw Exception('Liaobots empty @ $host'); return text; } }