diff --git a/common/xunlei_pan.go b/common/xunlei_pan.go index 4a9ea60..629130c 100644 --- a/common/xunlei_pan.go +++ b/common/xunlei_pan.go @@ -344,36 +344,37 @@ func (x *XunleiPanService) Transfer(shareID string) (*TransferResult, error) { if err != nil { return ErrorResult(fmt.Sprintf("获取分享详情失败: %v", err)), nil } + if shareDetail["share_status"].(string) != "OK" { + return ErrorResult(fmt.Sprintf("获取分享详情失败: %v", "分享状态异常")), nil + } + if shareDetail["file_num"].(string) == "0" { + return ErrorResult(fmt.Sprintf("获取分享详情失败: %v", "文件列表为空")), nil + } + + parent_id := "" // 默认存储路径 // 检查是否为检验模式 if config.IsType == 1 { // 检验模式:直接获取分享信息 urls := map[string]interface{}{ - "title": "", + "title": shareDetail["title"], "share_url": config.URL, "stoken": "", } return SuccessResult("检验成功", urls), nil } - files := shareDetail["files"].([]interface{}) - // 转存到网盘 - parent_id := "0" // 默认存储路径 - if config.ExpiredType == 2 { - parent_id = "指定路径" // 这里可能需要从配置获取 - } + // files := shareDetail["files"].([]interface{}) + // fileIDs := make([]string, 0) + // for _, file := range files { + // fileMap := file.(map[string]interface{}) + // if fid, ok := fileMap["id"].(string); ok { + // fileIDs = append(fileIDs, fid) + // } + // } - fileIDs := make([]string, 0) - for _, file := range files { - fileMap := file.(map[string]interface{}) - if fid, ok := fileMap["id"].(string); ok { - fileIDs = append(fileIDs, fid) - } - } - - if len(fileIDs) == 0 { - return ErrorResult("分享中没有可转存的文件"), nil - } + // 处理广告过滤(这里简化处理) + // TODO: 添加广告文件过滤逻辑 // 转存资源 restoreResult, err := x.getRestore(shareID, shareDetail, accessToken, captchaToken, parent_id) @@ -381,43 +382,27 @@ func (x *XunleiPanService) Transfer(shareID string) (*TransferResult, error) { return ErrorResult(fmt.Sprintf("转存失败: %v", err)), nil } - if restoreResult["code"].(int) != 200 { - return ErrorResult("转存失败"), nil - } - // 获取转存任务信息 - restoreData := restoreResult["data"].(map[string]interface{}) - taskID := restoreData["restore_task_id"].(string) + taskID := restoreResult["restore_task_id"].(string) // 等待转存完成 - _, err = x.waitForTask(taskID, accessToken, captchaToken) + taskResp, err := x.waitForTask(taskID, accessToken, captchaToken) if err != nil { return ErrorResult(fmt.Sprintf("等待转存完成失败: %v", err)), nil } - // 获取任务结果并解析文件ID // 获取任务结果以获取文件ID existingFileIds := make([]string, 0) - taskResp, err := x.getTasks(taskID, accessToken, captchaToken) - if err != nil { - return ErrorResult(fmt.Sprintf("获取任务结果失败: %v", err)), nil - } - - if taskData, ok := taskResp["data"].(map[string]interface{}); ok { - if params, ok2 := taskData["params"].(map[string]interface{}); ok2 { - if traceIds, ok3 := params["trace_file_ids"].(string); ok3 { - traceData := make(map[string]interface{}) - json.Unmarshal([]byte(traceIds), &traceData) - for _, fid := range traceData { - existingFileIds = append(existingFileIds, fid.(string)) - } + if params, ok2 := taskResp["params"].(map[string]interface{}); ok2 { + if traceIds, ok3 := params["trace_file_ids"].(string); ok3 { + traceData := make(map[string]interface{}) + json.Unmarshal([]byte(traceIds), &traceData) + for _, fid := range traceData { + existingFileIds = append(existingFileIds, fid.(string)) } } } - // 处理广告过滤(这里简化处理) - // TODO: 添加广告文件过滤逻辑 - // 创建分享链接 expirationDays := "-1" if config.ExpiredType == 2 { @@ -425,36 +410,30 @@ func (x *XunleiPanService) Transfer(shareID string) (*TransferResult, error) { } // 根据share_id获取到分享链接 - passwordResult, err := x.getSharePassword(existingFileIds, accessToken, captchaToken, expirationDays) + shareResult, err := x.getSharePassword(existingFileIds, accessToken, captchaToken, expirationDays) if err != nil { return ErrorResult(fmt.Sprintf("创建分享链接失败: %v", err)), nil } - if passwordResult["code"].(int) != 200 { - return ErrorResult("创建分享链接失败"), nil - } - - passwordData := passwordResult["data"].(map[string]interface{}) - shareTitle := "" - if len(files) > 0 { - file0 := files[0].(map[string]interface{}) - if name, ok := file0["name"].(string); ok { - shareTitle = name - } + var fid string + if len(existingFileIds) > 1 { + fid = strings.Join(existingFileIds, ",") + } else { + fid = existingFileIds[0] } result := map[string]interface{}{ - "title": shareTitle, - "share_url": passwordData["share_url"].(string) + "?pwd=" + passwordData["pass_code"].(string), - "code": passwordData["pass_code"].(string), - "fid": existingFileIds, + "title": "", + "shareUrl": shareResult["share_url"].(string) + "?pwd=" + shareResult["pass_code"].(string), + "code": shareResult["pass_code"].(string), + "fid": fid, } return SuccessResult("转存成功", result), nil } // waitForTask 等待任务完成 - 使用 HTTPGet 方法 -func (x *XunleiPanService) waitForTask(taskID string, accessToken, captchaToken string) (*XLTaskResult, error) { +func (x *XunleiPanService) waitForTask(taskID string, accessToken, captchaToken string) (map[string]interface{}, error) { maxRetries := 50 retryDelay := 2 * time.Second @@ -464,7 +443,7 @@ func (x *XunleiPanService) waitForTask(taskID string, accessToken, captchaToken return nil, err } - if result.Status == 2 { // 任务完成 + if int64(result["progress"].(float64)) == 100 { // 任务完成 return result, nil } @@ -475,12 +454,9 @@ func (x *XunleiPanService) waitForTask(taskID string, accessToken, captchaToken } // getTaskStatus 获取任务状态 - 使用 HTTPGet 方法 -func (x *XunleiPanService) getTaskStatus(taskID string, retryIndex int, accessToken, captchaToken string) (*XLTaskResult, error) { - apiURL := x.apiHost("") + "/drive/v1/task" - queryParams := map[string]string{ - "task_id": taskID, - "retry_index": fmt.Sprintf("%d", retryIndex), - } +func (x *XunleiPanService) getTaskStatus(taskID string, retryIndex int, accessToken, captchaToken string) (map[string]interface{}, error) { + apiURL := x.apiHost("") + "/drive/v1/tasks/" + taskID + queryParams := map[string]string{} // 设置 request 所需的 headers headers := map[string]string{ @@ -492,17 +468,7 @@ func (x *XunleiPanService) getTaskStatus(taskID string, retryIndex int, accessTo if err != nil { return nil, err } - - if code, ok := resp["code"].(float64); ok && code != 200 { - return nil, fmt.Errorf("获取任务状态失败") - } - - var data XLTaskResult - resultBytes, _ := json.Marshal(resp) - if err := json.Unmarshal(resultBytes, &data); err != nil { - return nil, err - } - return &data, nil + return resp, nil } // GetUserInfoByEntity 根据 entity.Cks 获取用户信息(待实现) diff --git a/demo/pan/QuarkPan.php b/demo/pan/QuarkPan.php deleted file mode 100644 index bf7be79..0000000 --- a/demo/pan/QuarkPan.php +++ /dev/null @@ -1,449 +0,0 @@ -urlHeader = [ - 'Accept: application/json, text/plain, */*', - 'Accept-Language: zh-CN,zh;q=0.9', - 'content-type: application/json;charset=UTF-8', - 'sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"', - 'sec-ch-ua-mobile: ?0', - 'sec-ch-ua-platform: "Windows"', - 'sec-fetch-dest: empty', - 'sec-fetch-mode: cors', - 'sec-fetch-site: same-site', - 'Referer: https://pan.quark.cn/', - 'Referrer-Policy: strict-origin-when-cross-origin', - 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'cookie: ' . Config('qfshop.quark_cookie') - ]; - } - - public function getFiles($pdir_fid=0) - { - // 原 getFiles 方法内容 - $urlData = []; - $queryParams = [ - 'pr' => 'ucpro', - 'fr' => 'pc', - 'uc_param_str' => '', - 'pdir_fid' => $pdir_fid, - '_page' => 1, - '_size' => 50, - '_fetch_total' => 1, - '_fetch_sub_dirs' => 0, - '_sort' => 'file_type:asc,updated_at:desc', - ]; - - $res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body']; - $res = json_decode($res, true); - if($res['status'] !== 200){ - return jerr2($res['message']=='require login [guest]'?'夸克未登录,请检查cookie':$res['message']); - } - - return jok2('获取成功',$res['data']['list']); - } - - public function transfer($pwd_id) - { - if(empty($this->stoken)){ - //获取要转存夸克资源的stoken - $res = $this->getStoken($pwd_id); - if($res['status'] !== 200) return jerr2($res['message']); - $infoData = $res['data']; - - if($this->isType == 1){ - $urls['title'] = $infoData['title']; - $urls['share_url'] = $this->url; - $urls['stoken'] = $infoData['stoken']; - return jok2('检验成功', $urls); - } - $stoken = $infoData['stoken']; - $stoken = str_replace(' ', '+', $stoken); - }else{ - $stoken = str_replace(' ', '+', $this->stoken); - } - - //获取要转存夸克资源的详细内容 - $res = $this->getShare($pwd_id,$stoken); - if($res['status']!== 200) return jerr2($res['message']); - $detail = $res['data']; - - $fid_list = []; - $fid_token_list = []; - $title = $detail['share']['title']; //资源名称 - foreach ($detail['list'] as $key => $value) { - $fid_list[] = $value['fid']; - $fid_token_list[] = $value['share_fid_token']; - } - - //转存资源到指定文件夹 - $res = $this->getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list); - if($res['status']!== 200) return jerr2($res['message']); - $task_id = $res['data']['task_id']; - - //转存后根据task_id获取转存到自己网盘后的信息 - $retry_index = 0; - $myData = ''; - while ($myData=='' || $myData['status'] != 2) { - $res = $this->getShareTask($task_id, $retry_index); - if($res['message']== 'capacity limit[{0}]'){ - return jerr2('容量不足'); - } - if($res['status']!== 200) { - return jerr2($res['message']); - } - $myData = $res['data']; - $retry_index++; - // 可以添加一个最大重试次数的限制,防止无限循环 - if ($retry_index > 50) { - break; - } - } - - try { - //删除转存后可能有的广告 - $banned = Config('qfshop.quark_banned')??''; //如果出现这些字样就删除 - if(!empty($banned)){ - $bannedList = explode(',', $banned); - $pdir_fid = $myData['save_as']['save_as_top_fids'][0]; - $dellist = []; - $plist = $this->getPdirFid($pdir_fid); - if(!empty($plist)){ - foreach ($plist as $key => $value) { - // 检查$value['file_name']是否包含$bannedList中的任何一项 - $contains = false; - foreach ($bannedList as $item) { - if (strpos($value['file_name'], $item) !== false) { - $contains = true; - break; - } - } - if ($contains) { - $dellist[] = $value['fid']; - } - } - if(count($plist) === count($dellist)){ - //要删除的资源数如果和原数据资源数一样 就全部删除并终止下面的分享 - $this->deletepdirFid([$pdir_fid]); - return jerr2("资源内容为空"); - }else{ - if (!empty($dellist)) { - $this->deletepdirFid($dellist); - } - } - - } - } - } catch (Exception $e) { - } - - $shareFid = $myData['save_as']['save_as_top_fids']; - //分享资源并拿到更新后的task_id - $res = $this->getShareBtn($myData['save_as']['save_as_top_fids'],$title); - if($res['status']!== 200) return jerr2($res['message']); - $task_id = $res['data']['task_id']; - - //根据task_id拿到share_id - $retry_index = 0; - $myData = ''; - while ($myData=='' || $myData['status'] != 2) { - $res = $this->getShareTask($task_id, $retry_index); - if($res['status']!== 200) continue; - $myData = $res['data']; - $retry_index++; - // 可以添加一个最大重试次数的限制,防止无限循环 - if ($retry_index > 50) { - break; - } - } - - - //根据share_id 获取到分享链接 - $res = $this->getSharePassword($myData['share_id']); - if($res['status']!== 200) return jerr2($res['message']); - $share = $res['data']; - // $share['fid'] = $share['first_file']['fid']; - $share['fid'] = (is_array($shareFid) && count($shareFid) > 1) ? $shareFid : $share['first_file']['fid']; - - return jok2('转存成功', $share); - } - - /** - * 获取要转存资源的stoken - * - * @return void - */ - public function getStoken($pwd_id) - { - $urlData = array( - 'passcode' => '', - 'pwd_id' => $pwd_id, - ); - $queryParams = [ - 'pr' => 'ucpro', - 'fr' => 'pc', - 'uc_param_str' => '', - ]; - return $this->executeApiRequest( - "https://drive-pc.quark.cn/1/clouddrive/share/sharepage/token", - "POST", - $urlData, - $queryParams - ); - } - - - /** - * 获取要转存资源的详细内容 - * - * @return void - */ - public function getShare($pwd_id,$stoken) - { - $urlData = array(); - $queryParams = [ - "pr" => "ucpro", - "fr" => "pc", - "uc_param_str" => "", - "pwd_id" => $pwd_id, - "stoken" => $stoken, - "pdir_fid" => "0", - "force" => "0", - "_page" => "1", - "_size" => "100", - "_fetch_banner" => "1", - "_fetch_share" => "1", - "_fetch_total" => "1", - "_sort" => "file_type:asc,updated_at:desc" - ]; - return $this->executeApiRequest( - "https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", - "GET", - $urlData, - $queryParams - ); - } - - - /** - * 转存资源到指定文件夹 - * - * @return void - */ - public function getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list) - { - if(!empty($this->to_pdir_fid)){ - $to_pdir_fid = $this->to_pdir_fid; - }else{ - $to_pdir_fid = Config('qfshop.quark_file'); //默认存储路径 - if($this->expired_type == 2){ - $to_pdir_fid = Config('qfshop.quark_file_time'); //临时资源路径 - } - } - - $urlData = array( - 'fid_list' => $fid_list, - 'fid_token_list' => $fid_token_list, - 'to_pdir_fid' => $to_pdir_fid, - 'pwd_id' => $pwd_id, - 'stoken' => $stoken, - 'pdir_fid' => "0", - 'scene' => "link", - ); - $queryParams = [ - "entry" => "update_share", - "pr" => "ucpro", - "fr" => "pc", - "uc_param_str" => "" - ]; - - return $this->executeApiRequest( - "https://drive-pc.quark.cn/1/clouddrive/share/sharepage/save", - "POST", - $urlData, - $queryParams - ); - } - - /** - * 分享资源拿到task_id - * - * @return void - */ - public function getShareBtn($fid_list,$title) - { - if(!empty($this->ad_fid)){ - $fid_list[] = $this->ad_fid; - } - $urlData = array( - 'fid_list' => $fid_list, - 'expired_type' => $this->expired_type, - 'title' => $title, - 'url_type' => 1, - ); - $queryParams = [ - "pr" => "ucpro", - "fr" => "pc", - "uc_param_str" => "" - ]; - - return $this->executeApiRequest( - "https://drive-pc.quark.cn/1/clouddrive/share", - "POST", - $urlData, - $queryParams - ); - } - - - /** - * 根据task_id拿到自己的资源信息 - * - * @return void - */ - public function getShareTask($task_id,$retry_index) - { - $urlData = array(); - $queryParams = [ - "pr" => "ucpro", - "fr" => "pc", - "uc_param_str" => "", - "task_id" => $task_id, - "retry_index" => $retry_index - ]; - return $this->executeApiRequest( - "https://drive-pc.quark.cn/1/clouddrive/task", - "GET", - $urlData, - $queryParams - ); - } - - /** - * 根据share_id 获取到分享链接 - * - * @return void - */ - public function getSharePassword($share_id) - { - $urlData = array( - 'share_id' => $share_id, - ); - $queryParams = [ - "pr" => "ucpro", - "fr" => "pc", - "uc_param_str" => "" - ]; - return $this->executeApiRequest( - "https://drive-pc.quark.cn/1/clouddrive/share/password", - "POST", - $urlData, - $queryParams - ); - } - - - /** - * 删除指定资源 - * - * @return void - */ - public function deletepdirFid($filelist) - { - $urlData = array( - 'action_type' => 2, - 'exclude_fids' => [], - 'filelist' => $filelist, - ); - $queryParams = [ - "pr" => "ucpro", - "fr" => "pc", - "uc_param_str" => "" - ]; - return $this->executeApiRequest( - "https://drive-pc.quark.cn/1/clouddrive/file/delete", - "POST", - $urlData, - $queryParams - ); - } - - /** - * 获取夸克网盘指定文件夹内容 - * - * @return void - */ - public function getPdirFid($pdir_fid) - { - $urlData = []; - $queryParams = [ - 'pr' => 'ucpro', - 'fr' => 'pc', - 'uc_param_str' => '', - 'pdir_fid' => $pdir_fid, - '_page' => 1, - '_size' => 200, - '_fetch_total' => 1, - '_fetch_sub_dirs' => 0, - '_sort' => 'file_type:asc,updated_at:desc', - ]; - try { - $res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body']; - $res = json_decode($res, true); - if($res['status'] !== 200){ - return []; - } - return $res['data']['list']; - } catch (\Throwable $e) { - return []; - } - } - - /** - * 执行API请求并处理重试逻辑 - * - * @param string $url 请求URL - * @param string $method 请求方法(GET/POST) - * @param array $data 请求数据 - * @param array $queryParams 查询参数 - * @param int $maxRetries 最大重试次数 - * @param int $retryDelay 重试延迟(秒) - * @return array 响应结果 - */ - protected function executeApiRequest($url, $method, $data = [], $queryParams = [], $maxRetries = 3, $retryDelay = 2) - { - $attempt = 0; - while ($attempt < $maxRetries) { - $attempt++; - try { - $res = curlHelper($url, $method, json_encode($data), $this->urlHeader, $queryParams)['body']; - return json_decode($res, true); - } catch (\Throwable $e) { - $this->logApiError($url, $attempt, $e->getMessage()); - if ($attempt < $maxRetries) { - sleep($retryDelay); - } - } - } - - return ['status' => 500, 'message' => '接口请求异常']; - } - /** - * 记录API错误日志 - * - * @param string $prefix 日志前缀 - * @param int $attempt 尝试次数 - * @param mixed $error 错误信息 - */ - protected function logApiError($prefix, $attempt, $error) - { - $errorMsg = is_scalar($error) ? $error : json_encode($error); - $logMessage = date('Y-m-d H:i:s') . ' ' . $prefix . '请求失败(尝试次数: ' . $attempt . ') 错误: ' . $errorMsg . "\n"; - file_put_contents('error.log', $logMessage, FILE_APPEND); - } -} \ No newline at end of file diff --git a/handlers/resource_handler.go b/handlers/resource_handler.go index 70ff88d..2f6619c 100644 --- a/handlers/resource_handler.go +++ b/handlers/resource_handler.go @@ -464,6 +464,7 @@ func GetResourceLink(c *gin.Context) { // TransferResult 转存结果 type TransferResult struct { Success bool `json:"success"` + Fid string `json:"fid"` SaveURL string `json:"save_url"` ErrorMsg string `json:"error_msg"` } @@ -525,6 +526,8 @@ func performAutoTransfer(resource *entity.Resource) TransferResult { if result.Success { // 更新资源的转存信息 resource.SaveURL = result.SaveURL + resource.Fid = result.Fid + resource.CkID = &account.ID resource.ErrorMsg = "" if err := repoManager.ResourceRepository.Update(resource); err != nil { utils.Error("更新资源转存信息失败: %v", err) @@ -594,10 +597,15 @@ func transferSingleResource(resource *entity.Resource, account entity.Cks, facto // 提取转存链接 var saveURL string + var fid string + if data, ok := transferResult.Data.(map[string]interface{}); ok { if v, ok := data["shareUrl"]; ok { saveURL, _ = v.(string) } + if v, ok := data["fid"]; ok { + fid, _ = v.(string) + } } if saveURL == "" { saveURL = transferResult.ShareURL @@ -615,6 +623,7 @@ func transferSingleResource(resource *entity.Resource, account entity.Cks, facto return TransferResult{ Success: true, SaveURL: saveURL, + Fid: fid, } }