import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:fpdart/fpdart.dart'; import 'package:hiddify/domain/clash/clash.dart'; import 'package:hiddify/domain/constants.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; class ClashApi with InfraLogger { ClashApi(int port) : address = "${Constants.localHost}:$port"; final String address; late final _clashDio = Dio( BaseOptions( baseUrl: "http://$address", connectTimeout: const Duration(seconds: 3), receiveTimeout: const Duration(seconds: 10), sendTimeout: const Duration(seconds: 3), ), ); TaskEither> getProxies() { return TaskEither( () async { final response = await _clashDio.get("/proxies"); if (response.statusCode != 200 || response.data == null) { return left(response.statusMessage ?? ""); } final proxies = (jsonDecode(response.data! as String)["proxies"] as Map) .entries .map( (e) { final proxyMap = (e.value as Map) ..putIfAbsent('name', () => e.key); return ClashProxy.fromJson(proxyMap); }, ); return right(proxies.toList()); }, ); } TaskEither changeProxy(String selectorName, String proxyName) { return TaskEither( () async { final response = await _clashDio.put( "/proxies/$selectorName", data: {"name": proxyName}, ); if (response.statusCode != HttpStatus.noContent) { return left(response.statusMessage ?? ""); } return right(unit); }, ); } TaskEither getProxyDelay( String name, String url, { Duration timeout = const Duration(seconds: 10), }) { return TaskEither( () async { final response = await _clashDio.get( "/proxies/$name/delay", queryParameters: { "timeout": timeout.inMilliseconds, "url": url, }, ); if (response.statusCode != 200 || response.data == null) { return left(response.statusMessage ?? ""); } return right(response.data!["delay"] as int); }, ); } TaskEither getConfigs() { return TaskEither( () async { final response = await _clashDio.get("/configs"); if (response.statusCode != 200 || response.data == null) { return left(response.statusMessage ?? ""); } final config = ClashConfig.fromJson(response.data as Map); return right(config); }, ); } TaskEither updateConfigs(String path) { return TaskEither.of(unit); } TaskEither patchConfigs(ClashConfig config) { return TaskEither( () async { final response = await _clashDio.patch( "/configs", data: config.toJson(), ); if (response.statusCode != HttpStatus.noContent) { return left(response.statusMessage ?? ""); } return right(unit); }, ); } Stream watchLogs(LogLevel level) { return const Stream.empty(); } Stream watchTraffic() { final channel = WebSocketChannel.connect( Uri.parse("ws://$address/traffic"), ); return channel.stream.map( (event) { return ClashTraffic.fromJson( jsonDecode(event as String) as Map, ); }, ); } TaskEither getTraffic() { return TaskEither( () async { final response = await _clashDio.get>("/traffic"); if (response.statusCode != 200 || response.data == null) { return left(response.statusMessage ?? ""); } return right(ClashTraffic.fromJson(response.data!)); }, ); } }