// lib/core/api_client.dart import 'dart:convert'; import 'dart:math'; import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; import 'package:encrypt/encrypt.dart' as encrypt; import 'package:swiftcat_downloader/models/book.dart'; // 确认路径正确 class ApiClient { static const _signKey = 'd3dGiJc651gSQ8w1'; static const _aesKeyHex = '32343263636238323330643730396531'; static const _baseUrlBc = "https://api-bc.wtzw.com"; static const _baseUrlKs = "https://api-ks.wtzw.com"; static const _versionList = [ '73720', '73700', '73620', '73600', '73500', '73420', '73400', '73328', '73325', '73320', '73300', '73220', '73200', '73100', '73000', '72900', '72820', '72800', '70720', '62010', '62112', ]; final Dio _dio = Dio(); String _generateSignature(Map params, String key) { var sortedKeys = params.keys.toList()..sort(); var signStr = sortedKeys.map((k) => '$k=${params[k]}').join('') + key; return md5.convert(utf8.encode(signStr)).toString(); } Map _getHeaders(String bookId) { final random = Random(bookId.hashCode); final version = _versionList[random.nextInt(_versionList.length)]; final headers = { "AUTHORIZATION": "", "app-version": version, "application-id": "com.****.reader", "channel": "unknown", "net-env": "1", "platform": "android", "qm-params": "", "reg": "0", }; headers['sign'] = _generateSignature(headers, _signKey); return headers; } Future> searchBooks(String keyword) async { final params = { 'extend': '', 'tab': '0', 'gender': '0', 'refresh_state': '8', 'page': '1', 'wd': keyword, 'is_short_story_user': '0' }; params['sign'] = _generateSignature(params, _signKey); final response = await _dio.get( "$_baseUrlBc/search/v1/words", queryParameters: params, options: Options(headers: _getHeaders('00000000')), ); if (response.statusCode == 200 && response.data['data'] != null) { List books = response.data['data']['books'] ?? []; return books .where((json) => json['id'] != null && json['id'].toString().isNotEmpty) .map((json) => SearchResultBook.fromSearchJson(json)) .toList(); } else { throw Exception('搜索失败: ${response.data['message']}'); } } Future fetchBookInfo(String bookId) async { final params = {'id': bookId, 'imei_ip': '2937357107', 'teeny_mode': '0'}; params['sign'] = _generateSignature(params, _signKey); final response = await _dio.get( "$_baseUrlBc/api/v4/book/detail", queryParameters: params, options: Options(headers: _getHeaders(bookId)), ); if (response.statusCode == 200 && response.data['data'] != null) { return Book.fromJson(response.data); } else { throw Exception('获取书籍信息失败: ${response.data['message']}'); } } Future> fetchChapterList(String bookId) async { final params = {'chapter_ver': '0', 'id': bookId}; params['sign'] = _generateSignature(params, _signKey); final response = await _dio.get( "$_baseUrlKs/api/v1/chapter/chapter-list", queryParameters: params, options: Options(headers: _getHeaders(bookId)), ); if (response.statusCode == 200 && response.data['data']['chapter_lists'] != null) { List chaptersJson = response.data['data']['chapter_lists']; chaptersJson.sort((a, b) => (a['chapter_sort'] as int).compareTo(b['chapter_sort'])); return chaptersJson.map((json) => BookChapter.fromJson(json)).toList(); } else { throw Exception('获取目录失败: ${response.data['message']}'); } } Future getCacheZipLink(String bookId) async { final params = {'id': bookId, 'source': 1, 'type': 2, 'is_vip': 1}; params['sign'] = _generateSignature(params, _signKey); final response = await _dio.get( "$_baseUrlBc/api/v1/book/download", queryParameters: params, options: Options(headers: _getHeaders(bookId)), ); if (response.statusCode == 200 && response.data['data']['link'] != null) { return response.data['data']['link']; } else { throw Exception('获取下载链接失败: ${response.data['message']}'); } } String decryptChapterContent(String encryptedContent) { final key = encrypt.Key.fromBase16(_aesKeyHex); final encryptedBytes = base64.decode(encryptedContent); final iv = encrypt.IV(encryptedBytes.sublist(0, 16)); final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc)); final decrypted = encrypter.decrypt( encrypt.Encrypted(encryptedBytes.sublist(16)), iv: iv, ); return decrypted; } }