From c0858d22cb5ed5afd1b8519439a7b1acd9387350 Mon Sep 17 00:00:00 2001 From: ngfchl Date: Sat, 18 Jul 2026 18:33:39 +0800 Subject: [PATCH] use server side global file search --- lib/api/guangya_api.dart | 564 ++++++++++++++++++----------- lib/pages/search_results_page.dart | 24 +- 2 files changed, 364 insertions(+), 224 deletions(-) diff --git a/lib/api/guangya_api.dart b/lib/api/guangya_api.dart index e8b49be..281407d 100644 --- a/lib/api/guangya_api.dart +++ b/lib/api/guangya_api.dart @@ -18,11 +18,8 @@ class GuangyaAPI { DateTime? tokenExpiresAt; final String deviceID; - GuangyaAPI({ - this.accessToken = '', - String? refreshToken, - String? deviceID, - }) : deviceID = deviceID ?? _generateDeviceID(); + GuangyaAPI({this.accessToken = '', String? refreshToken, String? deviceID}) + : deviceID = deviceID ?? _generateDeviceID(); static String _generateDeviceID() { return 'flutter-${DateTime.now().millisecondsSinceEpoch}'; @@ -34,7 +31,8 @@ class GuangyaAPI { if (access == null) return null; accessToken = access; refreshTokenValue = - _findStringDeep(value, ['refresh_token', 'refreshToken']) ?? refreshTokenValue; + _findStringDeep(value, ['refresh_token', 'refreshToken']) ?? + refreshTokenValue; final expires = _findIntDeep(value, ['expires_in', 'expiresIn']); if (expires != null) { tokenExpiresAt = DateTime.now().add(Duration(seconds: expires)); @@ -54,8 +52,10 @@ class GuangyaAPI { // ── Authentication ────────────────────────────────────────────────── - Future> loginSMSInit(String phoneNumber, - {String? captchaToken}) async { + Future> loginSMSInit( + String phoneNumber, { + String? captchaToken, + }) async { final body = { 'client_id': AppConfig.clientID, 'action': 'POST:/v1/auth/verification', @@ -66,24 +66,34 @@ class GuangyaAPI { return Http.accountRequest('/v1/shield/captcha/init', body: body); } - Future> loginSMSSend(String phoneNumber, - {String captchaToken = '', String target = 'ANY'}) async { - return Http.accountRequest('/v1/auth/verification', - body: { - 'phone_number': phoneNumber, - 'target': target, - 'client_id': AppConfig.clientID, - }, - extraHeaders: {'x-captcha-token': captchaToken}); + Future> loginSMSSend( + String phoneNumber, { + String captchaToken = '', + String target = 'ANY', + }) async { + return Http.accountRequest( + '/v1/auth/verification', + body: { + 'phone_number': phoneNumber, + 'target': target, + 'client_id': AppConfig.clientID, + }, + extraHeaders: {'x-captcha-token': captchaToken}, + ); } Future> loginSMSVerify( - String verificationID, String verificationCode) async { - return Http.accountRequest('/v1/auth/verification/verify', body: { - 'verification_id': verificationID, - 'verification_code': verificationCode, - 'client_id': AppConfig.clientID, - }); + String verificationID, + String verificationCode, + ) async { + return Http.accountRequest( + '/v1/auth/verification/verify', + body: { + 'verification_id': verificationID, + 'verification_code': verificationCode, + 'client_id': AppConfig.clientID, + }, + ); } Future> loginSMSSignIn({ @@ -92,31 +102,36 @@ class GuangyaAPI { required String username, required String captchaToken, }) async { - final result = await Http.accountRequest('/v1/auth/signin', - body: { - 'verification_code': code, - 'verification_token': verificationToken, - 'username': username, - 'client_id': AppConfig.clientID, - }, - extraHeaders: {'x-captcha-token': captchaToken}); + final result = await Http.accountRequest( + '/v1/auth/signin', + body: { + 'verification_code': code, + 'verification_token': verificationToken, + 'username': username, + 'client_id': AppConfig.clientID, + }, + extraHeaders: {'x-captcha-token': captchaToken}, + ); updateTokens(result); return result; } Future> loginQRInit() async { - return Http.accountRequest('/v1/auth/device/code', body: { - 'scope': 'user', - 'client_id': AppConfig.clientID, - }); + return Http.accountRequest( + '/v1/auth/device/code', + body: {'scope': 'user', 'client_id': AppConfig.clientID}, + ); } Future> loginQRPoll(String token) async { - final result = await Http.accountRequest('/v1/auth/token', body: { - 'grant_type': 'urn:ietf:params:oauth:grant-type:device_code', - 'device_code': token, - 'client_id': AppConfig.clientID, - }); + final result = await Http.accountRequest( + '/v1/auth/token', + body: { + 'grant_type': 'urn:ietf:params:oauth:grant-type:device_code', + 'device_code': token, + 'client_id': AppConfig.clientID, + }, + ); updateTokens(result); return result; } @@ -124,13 +139,15 @@ class GuangyaAPI { Future> refreshAccessToken([String? token]) async { final t = token ?? refreshTokenValue; if (t == null) throw Exception('没有可用的刷新令牌'); - final result = await Http.accountRequest('/v1/auth/token', - body: { - 'client_id': AppConfig.clientID, - 'grant_type': 'refresh_token', - 'refresh_token': t, - }, - extraHeaders: {'x-action': '401'}); + final result = await Http.accountRequest( + '/v1/auth/token', + body: { + 'client_id': AppConfig.clientID, + 'grant_type': 'refresh_token', + 'refresh_token': t, + }, + extraHeaders: {'x-action': '401'}, + ); updateTokens(result); return result; } @@ -146,16 +163,17 @@ class GuangyaAPI { int pageSize = 50, List status = const [0, 1, 3, 4], }) async { - return Http.apiRequest('/nd.bizcloudcollection.s/v1/list_task', body: { - 'page': page, - 'pageSize': pageSize, - 'status': status, - }); + return Http.apiRequest( + '/nd.bizcloudcollection.s/v1/list_task', + body: {'page': page, 'pageSize': pageSize, 'status': status}, + ); } Future> cloudResolveURL(String url) async { - return Http.apiRequest('/nd.bizcloudcollection.s/v1/resolve_res', - body: {'url': url}); + return Http.apiRequest( + '/nd.bizcloudcollection.s/v1/resolve_res', + body: {'url': url}, + ); } Future> cloudResolveTorrent(File torrentFile) async { @@ -163,21 +181,27 @@ class GuangyaAPI { final formData = FormData.fromMap({ 'torrent': MultipartFile.fromBytes(bytes, filename: 'file.torrent'), }); - return Http.apiRequest('/nd.bizcloudcollection.s/v1/resolve_torrent', - body: formData); + return Http.apiRequest( + '/nd.bizcloudcollection.s/v1/resolve_torrent', + body: formData, + ); } - Future> cloudCreateTask(String url, - {String? parentID}) async { - return Http.apiRequest('/nd.bizcloudcollection.s/v1/create_task', body: { - 'url': url, - 'parentId': parentID ?? '', - }); + Future> cloudCreateTask( + String url, { + String? parentID, + }) async { + return Http.apiRequest( + '/nd.bizcloudcollection.s/v1/create_task', + body: {'url': url, 'parentId': parentID ?? ''}, + ); } Future> taskStatus(String taskID) async { - return Http.apiRequest('/nd.bizuserres.s/v1/get_task_status', - body: {'taskId': taskID}); + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_task_status', + body: {'taskId': taskID}, + ); } // ── File system ──────────────────────────────────────────────────── @@ -207,42 +231,64 @@ class GuangyaAPI { return Http.apiRequest('/userres/v1/file/get_file_list', body: body); } - Future> fsCreateDir(String name, - {String? parentID, bool failIfNameExist = false}) async { - final body = { - 'dirName': name, - 'parentId': parentID ?? '', - }; + /// Searches the full cloud drive server-side instead of walking every folder. + Future> searchFiles( + String name, { + int page = 0, + int pageSize = 100, + }) { + return Http.apiRequest( + '/userres/v1/file/search_files', + body: {'page': page, 'pageSize': pageSize, 'name': name}, + ); + } + + Future> fsCreateDir( + String name, { + String? parentID, + bool failIfNameExist = false, + }) async { + final body = {'dirName': name, 'parentId': parentID ?? ''}; if (failIfNameExist) body['failIfNameExist'] = true; - return Http.apiRequest('/nd.bizuserres.s/v1/file/create_dir', - body: body, - allowedCodes: failIfNameExist ? [159] : []); + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/create_dir', + body: body, + allowedCodes: failIfNameExist ? [159] : [], + ); } - Future> fsCopy(List fileIDs, - {String? parentID}) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/copy_file', body: { - 'fileIds': fileIDs, - 'parentId': parentID ?? '', - }); + Future> fsCopy( + List fileIDs, { + String? parentID, + }) async { + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/copy_file', + body: {'fileIds': fileIDs, 'parentId': parentID ?? ''}, + ); } - Future> fsMove(List fileIDs, - {String? parentID}) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/move_file', body: { - 'fileIds': fileIDs, - 'parentId': parentID ?? '', - }); + Future> fsMove( + List fileIDs, { + String? parentID, + }) async { + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/move_file', + body: {'fileIds': fileIDs, 'parentId': parentID ?? ''}, + ); } Future> fsDelete(List fileIDs) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/delete_file', - body: {'fileIds': fileIDs}); + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/delete_file', + body: {'fileIds': fileIDs}, + ); } Future> fsRecycle(List fileIDs) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/recycle_file', - body: {'fileIds': fileIDs}); + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/recycle_file', + body: {'fileIds': fileIDs}, + ); } Future> fsClearRecycleBin() async { @@ -250,31 +296,58 @@ class GuangyaAPI { } Future> fsRename(String fileID, String newName) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/rename', body: { - 'fileId': fileID, - 'newName': newName, - }); + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/rename', + body: {'fileId': fileID, 'newName': newName}, + ); } Future> fsDetail(String fileID) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/get_file_detail', - body: {'fileId': fileID}); + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/get_file_detail', + body: {'fileId': fileID}, + ); } Future> fsImageList() async { - return fsFiles(parentID: '*', orderBy: 3, sortType: 1, fileTypes: [1], resType: 1); + return fsFiles( + parentID: '*', + orderBy: 3, + sortType: 1, + fileTypes: [1], + resType: 1, + ); } Future> fsVideoList() async { - return fsFiles(parentID: '*', orderBy: 3, sortType: 1, fileTypes: [2], resType: 1); + return fsFiles( + parentID: '*', + orderBy: 3, + sortType: 1, + fileTypes: [2], + resType: 1, + ); } Future> fsAudioList() async { - return fsFiles(parentID: '*', orderBy: 3, sortType: 1, fileTypes: [3], resType: 1, needPlayRecord: true); + return fsFiles( + parentID: '*', + orderBy: 3, + sortType: 1, + fileTypes: [3], + resType: 1, + needPlayRecord: true, + ); } Future> fsDocumentList() async { - return fsFiles(parentID: '*', orderBy: 3, sortType: 1, fileTypes: [4], resType: 1); + return fsFiles( + parentID: '*', + orderBy: 3, + sortType: 1, + fileTypes: [4], + resType: 1, + ); } Future> fsRecycleFiles() async { @@ -282,29 +355,37 @@ class GuangyaAPI { } Future> downloadURL(String fileID) async { - return Http.apiRequest('/nd.bizuserres.s/v1/get_res_download_url', - body: {'fileId': fileID}); + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_res_download_url', + body: {'fileId': fileID}, + ); } - Future> vodDownloadURL(String fileID, String gcid) async { - return Http.apiRequest('/userres/v1/file/get_vod_download_url', - body: {'fileId': fileID, 'gcid': gcid}); + Future> vodDownloadURL( + String fileID, + String gcid, + ) async { + return Http.apiRequest( + '/userres/v1/file/get_vod_download_url', + body: {'fileId': fileID, 'gcid': gcid}, + ); } Future> recentViewed({ int pageSize = 100, String cursor = '', }) async { - return Http.apiRequest('/userres/v1/get_user_action', - body: {'cursor': cursor, 'pageSize': pageSize}); + return Http.apiRequest( + '/userres/v1/get_user_action', + body: {'cursor': cursor, 'pageSize': pageSize}, + ); } Future> recentRestored({int pageSize = 100}) async { - return Http.apiRequest('/userres/v1/get_restore_list', body: { - 'pageSize': pageSize, - 'orderBy': 2, - 'sortType': 1, - }); + return Http.apiRequest( + '/userres/v1/get_restore_list', + body: {'pageSize': pageSize, 'orderBy': 2, 'sortType': 1}, + ); } // ── Shares ───────────────────────────────────────────────────────── @@ -320,34 +401,37 @@ class GuangyaAPI { int maxRestoreCount = 0, int downloadType = 1, }) async { - return Http.apiRequest('/nd.bizuserres.s/v1/share_file', body: { - 'fileIds': fileIDs, - 'title': title, - 'validateDuration': validateDuration, - 'shareType': shareType, - 'code': code, - 'autoFillCode': autoFillCode, - 'trafficLimit': trafficLimit, - 'maxRestoreCount': maxRestoreCount, - 'downloadType': downloadType, - }); + return Http.apiRequest( + '/nd.bizuserres.s/v1/share_file', + body: { + 'fileIds': fileIDs, + 'title': title, + 'validateDuration': validateDuration, + 'shareType': shareType, + 'code': code, + 'autoFillCode': autoFillCode, + 'trafficLimit': trafficLimit, + 'maxRestoreCount': maxRestoreCount, + 'downloadType': downloadType, + }, + ); } Future> shareUserList({ int page = 0, int pageSize = 50, }) async { - return Http.apiRequest('/nd.bizuserres.s/v1/get_share_list', body: { - 'page': page, - 'pageSize': pageSize, - 'orderType': 1, - 'sortType': 1, - }); + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_share_list', + body: {'page': page, 'pageSize': pageSize, 'orderType': 1, 'sortType': 1}, + ); } Future> shareDelete(List ids) async { - return Http.apiRequest('/nd.bizuserres.s/v1/delete_share', - body: {'ids': ids}); + return Http.apiRequest( + '/nd.bizuserres.s/v1/delete_share', + body: {'ids': ids}, + ); } Future> shareUpdate( @@ -361,17 +445,20 @@ class GuangyaAPI { int maxRestoreCount = 0, int downloadType = 1, }) async { - return Http.apiRequest('/nd.bizuserres.s/v1/update_share', body: { - 'id': shareID, - 'title': title, - 'validateDuration': validateDuration, - 'shareType': shareType, - 'code': code, - 'autoFillCode': autoFillCode, - 'trafficLimit': trafficLimit, - 'maxRestoreCount': maxRestoreCount, - 'downloadType': downloadType, - }); + return Http.apiRequest( + '/nd.bizuserres.s/v1/update_share', + body: { + 'id': shareID, + 'title': title, + 'validateDuration': validateDuration, + 'shareType': shareType, + 'code': code, + 'autoFillCode': autoFillCode, + 'trafficLimit': trafficLimit, + 'maxRestoreCount': maxRestoreCount, + 'downloadType': downloadType, + }, + ); } Future> shareRestore( @@ -379,17 +466,24 @@ class GuangyaAPI { List fileIDs, { String parentID = '', }) async { - return Http.apiRequest('/nd.bizuserres.s/v1/restore_share', body: { - 'accessToken': accessToken, - 'fileIds': fileIDs, - 'parentId': parentID, - }); + return Http.apiRequest( + '/nd.bizuserres.s/v1/restore_share', + body: { + 'accessToken': accessToken, + 'fileIds': fileIDs, + 'parentId': parentID, + }, + ); } Future> shareDownloadURL( - String fileID, String accessToken) async { - return Http.apiRequest('/nd.bizuserres.s/v1/get_share_download_url', - body: {'fileId': fileID, 'accessToken': accessToken}); + String fileID, + String accessToken, + ) async { + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_share_download_url', + body: {'fileId': fileID, 'accessToken': accessToken}, + ); } Future> shareFilesSize( @@ -397,12 +491,14 @@ class GuangyaAPI { List fileIDs, { bool download = true, }) async { - return Http.apiRequest('/nd.bizuserres.s/v1/get_share_files_size', - body: { - 'accessToken': accessToken, - 'fileIds': fileIDs, - 'download': download, - }); + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_share_files_size', + body: { + 'accessToken': accessToken, + 'fileIds': fileIDs, + 'download': download, + }, + ); } Future> shareFilesList( @@ -414,26 +510,33 @@ class GuangyaAPI { int sortType = 0, }) async { return Http.publicRequest( - '/nd.bizuserres.s/v1/get_share_page_files_list', - body: { - 'accessToken': accessToken, - 'parentId': parentID, - 'page': page, - 'pageSize': pageSize, - 'orderBy': orderBy, - 'sortType': sortType, - }); + '/nd.bizuserres.s/v1/get_share_page_files_list', + body: { + 'accessToken': accessToken, + 'parentId': parentID, + 'page': page, + 'pageSize': pageSize, + 'orderBy': orderBy, + 'sortType': sortType, + }, + ); } Future> shareSummary(String shareID) async { - return Http.publicRequest('/nd.bizuserres.s/v1/get_share_summary', - body: {'shareId': shareID}); + return Http.publicRequest( + '/nd.bizuserres.s/v1/get_share_summary', + body: {'shareId': shareID}, + ); } Future> shareAccessToken( - String shareID, String code) async { - return Http.publicRequest('/nd.bizuserres.s/v1/get_share_access_token', - body: {'shareId': shareID, 'code': code}); + String shareID, + String code, + ) async { + return Http.publicRequest( + '/nd.bizuserres.s/v1/get_share_access_token', + body: {'shareId': shareID, 'code': code}, + ); } // ── Upload ───────────────────────────────────────────────────────── @@ -446,12 +549,16 @@ class GuangyaAPI { }) async { final res = {'fileSize': fileSize}; if (md5 != null) res['md5'] = _base64MD5(md5); - return Http.apiRequest('/nd.bizuserres.s/v1/get_res_center_token', body: { - 'capacity': 2, - 'name': name, - 'res': res, - 'parentId': parentID ?? '', - }, allowedCodes: const [156]); + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_res_center_token', + body: { + 'capacity': 2, + 'name': name, + 'res': res, + 'parentId': parentID ?? '', + }, + allowedCodes: const [156], + ); } Future> flashTransferToken({ @@ -461,12 +568,16 @@ class GuangyaAPI { required String md5, }) async { final normalizedMD5 = md5.trim().toLowerCase(); - return Http.apiRequest('/nd.bizuserres.s/v1/get_res_center_token', body: { - 'capacity': 1, - 'name': name, - 'res': {'md5': normalizedMD5, 'fileSize': fileSize}, - 'parentId': parentID ?? '', - }, allowedCodes: const [156]); + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_res_center_token', + body: { + 'capacity': 1, + 'name': name, + 'res': {'md5': normalizedMD5, 'fileSize': fileSize}, + 'parentId': parentID ?? '', + }, + allowedCodes: const [156], + ); } Future> flashTransferGCIDToken({ @@ -476,29 +587,41 @@ class GuangyaAPI { required String gcid, }) async { final normalizedGCID = gcid.trim().toUpperCase(); - return Http.apiRequest('/nd.bizuserres.s/v1/get_res_center_token', body: { - 'capacity': 1, - 'name': name, - 'res': {'gcid': normalizedGCID, 'fileSize': fileSize}, - 'parentId': parentID ?? '', - }, allowedCodes: const [156]); + return Http.apiRequest( + '/nd.bizuserres.s/v1/get_res_center_token', + body: { + 'capacity': 1, + 'name': name, + 'res': {'gcid': normalizedGCID, 'fileSize': fileSize}, + 'parentId': parentID ?? '', + }, + allowedCodes: const [156], + ); } Future> deleteUploadTask(List taskIDs) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/delete_upload_task', - body: {'taskIds': taskIDs}); + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/delete_upload_task', + body: {'taskIds': taskIDs}, + ); } Future> checkCanFlashUpload( - String taskID, String gcid) async { - return Http.apiRequest('/nd.bizuserres.s/v1/check_can_flash_upload', - body: {'taskId': taskID, 'gcid': gcid}); + String taskID, + String gcid, + ) async { + return Http.apiRequest( + '/nd.bizuserres.s/v1/check_can_flash_upload', + body: {'taskId': taskID, 'gcid': gcid}, + ); } Future> uploadInfo(String taskID) async { - return Http.apiRequest('/nd.bizuserres.s/v1/file/get_info_by_task_id', - body: {'taskId': taskID}, - allowedCodes: const [145, 146, 155, 163]); + return Http.apiRequest( + '/nd.bizuserres.s/v1/file/get_info_by_task_id', + body: {'taskId': taskID}, + allowedCodes: const [145, 146, 155, 163], + ); } Future> fileUpload( @@ -533,7 +656,8 @@ class GuangyaAPI { final gcid = _calculateGCID(bytes); final canFlash = await checkCanFlashUpload(taskID, gcid); - if (_findBoolDeep(canFlash, ['canFlashUpload', 'can_flash_upload']) == true) { + if (_findBoolDeep(canFlash, ['canFlashUpload', 'can_flash_upload']) == + true) { return uploadInfo(taskID); } @@ -553,11 +677,15 @@ class GuangyaAPI { required String contentType, required int chunkSize, }) async { - final creds = _findMapDeep(tokenData, ['creds']) ?? const {}; + final creds = + _findMapDeep(tokenData, ['creds']) ?? const {}; final accessKeyID = _findStringDeep(creds, ['accessKeyID', 'accessKeyId']); final secret = _findStringDeep(creds, ['secretAccessKey']); final sessionToken = _findStringDeep(creds, ['sessionToken']); - final endpoint = _findStringDeep(tokenData, ['fullEndPoint', 'fullEndpoint']); + final endpoint = _findStringDeep(tokenData, [ + 'fullEndPoint', + 'fullEndpoint', + ]); final bucket = _findStringDeep(tokenData, ['bucketName']); final objectPath = _findStringDeep(tokenData, ['objectPath']); if (accessKeyID == null || @@ -603,7 +731,9 @@ class GuangyaAPI { contentMD5: contentMD5, subResources: {'partNumber': '$number', 'uploadId': uploadID}, ); - parts.add(MapEntry(number, response.headers['etag']?.replaceAll('"', '') ?? '')); + parts.add( + MapEntry(number, response.headers['etag']?.replaceAll('"', '') ?? ''), + ); offset = end; number += 1; } @@ -641,7 +771,8 @@ class GuangyaAPI { Map subResources = const {}, }) async { final date = HttpDate.format(DateTime.now().toUtc()); - final canonicalResource = '/$bucket/$objectKey${_subResourceQuery(subResources)}'; + final canonicalResource = + '/$bucket/$objectKey${_subResourceQuery(subResources)}'; final canonical = [ method.toUpperCase(), contentMD5, @@ -745,8 +876,11 @@ class GuangyaAPI { 'append_to_response': 'credits,images,translations', 'include_image_language': 'zh-CN,zh,null,en', }; - final uri = Uri.https('api.themoviedb.org', - '/3/tv/$seriesID/season/$season/episode/$episode', params); + final uri = Uri.https( + 'api.themoviedb.org', + '/3/tv/$seriesID/season/$season/episode/$episode', + params, + ); return _tmdbRequest(uri, proxyHost: proxyHost, proxyPort: proxyPort); } @@ -758,9 +892,10 @@ class GuangyaAPI { String proxyPort = '', }) async { final dio = DioClient.dio; - final response = await dio.getUri(url, options: Options( - headers: {'Accept': 'application/json'}, - )); + final response = await dio.getUri( + url, + options: Options(headers: {'Accept': 'application/json'}), + ); if (response.statusCode! < 200 || response.statusCode! >= 300) { throw Exception('TMDB 请求失败'); } @@ -769,7 +904,10 @@ class GuangyaAPI { // ── Helpers ─────────────────────────────────────────────────────── - static String? _findStringDeep(Map? json, List keys) { + static String? _findStringDeep( + Map? json, + List keys, + ) { if (json == null) return null; for (final key in keys) { final v = json[key]; @@ -777,8 +915,10 @@ class GuangyaAPI { } for (final entry in json.entries) { if (entry.value is Map) { - final found = - _findStringDeep(entry.value as Map, keys); + final found = _findStringDeep( + entry.value as Map, + keys, + ); if (found != null) return found; } } @@ -869,10 +1009,10 @@ class GuangyaAPI { final chunkSize = length <= 0x8000000 ? 262144 : length <= 0x10000000 - ? 524288 - : length <= 0x20000000 - ? 1048576 - : 2097152; + ? 524288 + : length <= 0x20000000 + ? 1048576 + : 2097152; final hashes = []; for (var offset = 0; offset < bytes.length; offset += chunkSize) { final end = min(offset + chunkSize, bytes.length); diff --git a/lib/pages/search_results_page.dart b/lib/pages/search_results_page.dart index 5bc297b..31dc50c 100644 --- a/lib/pages/search_results_page.dart +++ b/lib/pages/search_results_page.dart @@ -35,27 +35,21 @@ class _FileSearchResultsPageState extends ConsumerState { Future> _search() async { final api = ref.read(authProvider.notifier).api; - final query = widget.query.trim().toLowerCase(); + final query = widget.query.trim(); + if (query.isEmpty) return const []; final results = []; final ids = {}; var page = 0; while (page < 100) { - final response = await api.fsFiles( - parentID: '*', - page: page, - pageSize: 1000, - orderBy: 0, - sortType: 0, - ); + final response = await api.searchFiles(query, page: page, pageSize: 100); final batch = _extractFiles(response); for (final file in batch) { - if ((file.name.toLowerCase().contains(query) || - file.cloudPath.toLowerCase().contains(query)) && - ids.add(file.id)) { + if (ids.add(file.id)) { results.add(file); } } - if (batch.length < 1000) break; + final total = _extractTotal(response); + if (batch.isEmpty || results.length >= total || batch.length < 100) break; page += 1; } results.sort( @@ -64,6 +58,12 @@ class _FileSearchResultsPageState extends ConsumerState { return results; } + int _extractTotal(Map json) { + final data = json['data']; + if (data is Map) return int.tryParse(data['total']?.toString() ?? '') ?? 0; + return 0; + } + List _extractFiles(Map json) { final values = []; final ids = {};