diff --git a/app/api/controller/Index.php b/app/api/controller/Index.php index 64af936..76c8ddf 100644 --- a/app/api/controller/Index.php +++ b/app/api/controller/Index.php @@ -12,244 +12,7 @@ class Index extends QfShop { public function index() { - // return jok("Hello World!"); - - $cookie = 'csrfToken=gKesaboeWtJkukG4VNGh5Ljk; BAIDUID=757FBF0C282638B49A59DC7E37C7702B:FG=1; BAIDUID_BFESS=757FBF0C282638B49A59DC7E37C7702B:FG=1; Hm_lvt_7a3960b6f067eb0085b7f96ff5e660b0=1742787964; RT="z=1&dm=baidu.com&si=86a18d7d-4537-4b9b-ac36-06a756df3179&ss=m8mj80dn&sl=0&tt=0&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ul=261&hd=26a"; Hm_lpvt_7a3960b6f067eb0085b7f96ff5e660b0=1742794455; newlogin=1; ppfuid=FOCoIC3q5fKa8fgJnwzbE67EJ49BGJeplOzf+4l4EOvDuu2RXBRv6R3A1AZMa49I27C0gDDLrJyxcIIeAeEhD8JYsoLTpBiaCXhLqvzbzmvy3SeAW17tKgNq/Xx+RgOdb8TWCFe62MVrDTY6lMf2GrfqL8c87KLF2qFER3obJGmxOaJD7Qr04D9rET96PX99GEimjy3MrXEpSuItnI4KD2P5vWa8VVdqKPLBckQ0WyruzFB5pZ7L1GIDHy291nRZSc37WI7hn7N5DEkitWgHVHqxGUGRl1qke9+4QxQVI1jGgLbz7OSojK1zRbqBESR5Pdk2R9IA3lxxOVzA+Iw1TWLSgWjlFVG9Xmh1+20oPSbrzvDjYtVPmZ+9/6evcXmhcO1Y58MgLozKnaQIaLfWRIM4pp9u1B7t2Y8SxQH/XnrgZXt2Kg4R5SS0He7SlWGt42bJBOW2wZKr1YF6Z6VWTM5FjnYxYstXg/9EfB3EVmLB2thKqX6G/zUWgdr9REaklV1Uhhp5FAe6gNJIUptp7EMAaXYKm11G+JVPszQFdp9AJLcm4YSsYUXkaPI2Tl66J246cmjWQDTahAOINR5rXR5r/7VVI1RMZ8gb40q7az7vCK56XLooKT5a+rsFrf5Zu0yyCiiagElhrTEOtNdBJJq8eHwEHuFBni9ahSwpC7lbKkUwaKH69tf0DFV7hJROiLETSFloIVkHdy3+I2JUr1LsplAz0hMkWt/tE4tXVUV7QcTDTZWS/2mCoS/GV3N9awQ6iM6hs/BWjlgnEa1+5hYkkYOBBLZqDwzG9FQyZZJmSCAynpprqnVbhrjcNYdk5FKazXcZ/j40FJv+iLGBn3nkkgHlne61I8I7KhtQgInXjsNS9+mteGCT/vj/pEvkm+ZitcePbf21QMldRrlvw4uFnWmLU84Th29MqUuVR0ujl0ys3zTnxv/D47Q2VFyeFv12nptBFm2PISy0WmJ/Sy3ktTWpm7mcOABi5Gey1FBzkrq/nzqRQID/LRsJJW24e+1N4wlJzhNmFXMQ0YiYVdq7aHN89Oybut+xsc38J6I4Wd2rVi5xrH8hGqvd77OJuzH8X+tb0PV+Xnu3NL1fmqLIh+XcF6fnyPFPUxteQtbtLyi+gq5zowg1oFj8O/L9oVsoK22a9qUmM/HrJMRsLi1+J9aSd42+X78fDIZgkPh3epzLLvwRmnAbs5z/V+jl3P3gVnlwm9bfwhaFtnhFN2dHYAw7i4QhrXdzc77isXhbvkM5DsEwz5RTVSb4C+N+kIl81Iase/C16XVPKOj9XA==; BDUSS=jYwbDRJZG0yTi1VbDgtMWF6M1BGZ0k0ZG96dm9iOHJGczFMOUYtMFhwYlJmd2hvRVFBQUFBJCQAAAAAAAAAAAEAAAA63UdY2K~Yr8Dksru5~cjL0MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHy4GfR8uBnT; BDUSS_BFESS=jYwbDRJZG0yTi1VbDgtMWF6M1BGZ0k0ZG96dm9iOHJGczFMOUYtMFhwYlJmd2hvRVFBQUFBJCQAAAAAAAAAAAEAAAA63UdY2K~Yr8Dksru5~cjL0MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHy4GfR8uBnT; STOKEN=608a270497995724e6ed69015178372081331f404747f0a8492ee31de1184c41; PANPSC=6332961014215215224%3AAuI6D6iGxsvXU%2FMlrspYA1cS2d9ns3O5g0mIZdLHpdQGbqupDlB1gtH1%2BL8BUDk1coDRJu%2FbTp%2FiLaLUl0KD5De4ZdaW7CoOlL98c8Ccr9ch6uZoP3DwQ9YfJggg9xZJa6gjWHNi7jj6tFSlPcYsyyZWmuM7JILl4szUrnVKA5MNNiO7%2B4TS%2B1QskbZnP5EyWagh%2FyTVtumv9EBTj3cIccvsqRsDSpjMTItbmssa8FDJel1POICqciYu0WtaRbrkbSBvzd4TSG3wk8UVEJrJjetuQFScyIV%2FkrFWWqsgPEw%3D; Hm_lvt_182d6d59474cf78db37e0b2248640ea5=1742788120; ndut_fmt=6F438330EB0B4C594C515F276A3E73E3F2D3A8BB5A3C26DD1E721E98B58ED17B; Hm_lpvt_182d6d59474cf78db37e0b2248640ea5=1742795476; ab_sr=1.0.1_YTI3MjJmMjkwY2Y5MTJhMjc4MGJhNWU2ZGMwNTFiZWZiNmZhMTZkMWRlNWYyMzAxNTA1OTU2YTU4ZjU5M2ZiNWI5NDY2YjYwZWY3Yjc4YTY3MmNiMWVlNWNkNzE2ZGI2YzJmYTY1YTgwM2M1ZDJlYTk1YjAxZTcyMmNkNWRkNzliM2RiOGMzYzUxYmMwZTU4OTJiZmRmNjhiMDIzZDc1OWRhMGIyNGNkMDJhOTMzZjllYmQxMDUzY2ZjYzU4MjVk'; - // $cookie = ''; - $network = new \netdisk\pan\BaiduWork($cookie); - $bdstoken = $network->getBdstoken(); - $network->setBdstoken($bdstoken); - - // 验证提取码 - // $linkUrl = "https://pan.baidu.com/s/11ioAjAwdwzI3BEvP5c62bw"; // 分享链接 - // $passCode = "him8"; // 4位提取码 - - $linkUrl = "https://pan.baidu.com/s/1HU99KPtdJEpKnYnYLnQ37Q"; // 分享链接 - $passCode = "ruwf"; // 4位提取码 - - // $linkUrl = "https://pan.baidu.com/s/1f03pM6dvtZPGA1bA0DH9AQ"; // 分享链接 - // $passCode = "a9oa"; // 4位提取码 - - // 先判断是否有提取码 - if (!empty($passCode)) { - // 验证提取码 - $randsk = $network->verifyPassCode($linkUrl, $passCode); - if (is_numeric($randsk)) { - return jerr($network->getErrorMessage($randsk)); - } - // 验证成功,更新 cookie - $network->updateBdclnd($randsk); - } - - // 获取转存参数 - $transferParams = $network->getTransferParams($linkUrl); - if (is_numeric($transferParams)) { - return jerr($network->getErrorMessage($transferParams)); - } - // 解析返回的参数 - list($shareId, $userId, $fsIds, $fileNames, $isDirs) = $transferParams; - - $folderName = ''; // 自定义保存目录名 - // 检查目录名是否包含非法字符 - $invalidChars = ['<', '>', '|', '*', '?', '\\', ':']; - foreach ($invalidChars as $char) { - if (strpos($folderName, $char) !== false) { - return jerr('转存目录名有非法字符,不能包含:< > | * ? \\ :'); - } - } - - // 先检查目录是否存在,不存在再创建 - $dirList = $network->getDirList('/' . $folderName); - // 如果返回的是错误码,说明目录不存在,需要创建 - if (is_numeric($dirList)) { - $createResult = $network->createDir($folderName); - if ($createResult !== 0) { - return jerr($network->getErrorMessage($createResult)); - } - } - - // 执行文件转存 - $transferResult = $network->transferFile([$shareId, $userId, $fsIds], $folderName); - if ($transferResult !== 0) { - return jerr($network->getErrorMessage($transferResult)); - } - - // 转存成功后,获取目录中的文件列表 - $dirList = $network->getDirList('/' . $folderName); - if (is_numeric($dirList)) { - return jerr($network->getErrorMessage($dirList)); - } - - // 找到刚刚转存的所有文件 - $targetFiles = []; - $fsIdList = []; - $filePaths = []; - $adFilePaths = []; // 用于存储包含广告的文件路径 - $allFilesAreAds = true; // 假设所有文件都是广告 - - foreach ($dirList as $file) { - if (in_array($file['server_filename'], $fileNames)) { - $targetFiles[] = $file; - $fsIdList[] = $file['fs_id']; - $filePath = '/' . $folderName . '/' . $file['server_filename']; - $filePaths[] = $filePath; - - // 检查文件名是否包含广告内容 - $containsAd = $this->containsAdKeywords($file['server_filename']); - - // 如果是目录,需要检查目录内的文件 - if ($file['isdir'] == 1) { - // 获取子目录内容 - $subDirList = $network->getDirList($filePath); - if (!is_numeric($subDirList)) { - foreach ($subDirList as $subFile) { - // 检查子文件是否包含广告关键词 - if ($this->containsAdKeywords($subFile['server_filename'])) { - // 将包含广告的子文件添加到待删除列表 - $adFilePaths[] = $filePath . '/' . $subFile['server_filename']; - } else { - // 只要有一个文件不是广告,就将标志设为false - $allFilesAreAds = false; - } - } - } - } else { - // 如果是文件,直接判断 - if ($containsAd) { - $adFilePaths[] = $filePath; - } else { - $allFilesAreAds = false; - } - } - } - } - - if (empty($targetFiles)) { - return jerr('找不到刚转存的文件'); - } - - // 如果所有文件都是广告,删除整个转存的内容 - if ($allFilesAreAds && !empty($targetFiles)) { - // 删除所有转存的文件 - $deleteResult = $network->batchDeleteFiles($filePaths); - if ($deleteResult['errno'] === 0) { - return jok('所有转存的文件都包含广告内容,已全部删除', [ - 'deletedCount' => $deleteResult['deletedCount'], - 'paths' => $deleteResult['paths'] - ]); - } else { - return jerr('删除广告文件失败: ' . $deleteResult['message']); - } - } - - // 如果只有部分文件是广告,只删除广告文件 - if (!empty($adFilePaths)) { - $deleteResult = $network->batchDeleteFiles($adFilePaths); - // 删除后更新文件列表 - if ($deleteResult['errno'] === 0) { - // 从fsIdList和filePaths中移除已删除的广告文件 - foreach ($adFilePaths as $adPath) { - $key = array_search($adPath, $filePaths); - if ($key !== false) { - unset($filePaths[$key]); - unset($fsIdList[$key]); - } - } - // 重新索引数组 - $filePaths = array_values($filePaths); - $fsIdList = array_values($fsIdList); - } - } - - // 如果删除广告后没有文件了,返回提示 - if (empty($fsIdList)) { - return jok('所有有效文件都包含广告内容,已全部删除'); - } - - // 创建分享 - $expiry = 0; // 0为永久 - $password = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 4); // 随机4位提取码 - $shareLink = $network->createShare(implode(',', $fsIdList), $expiry, $password); - - if (is_numeric($shareLink)) { - return jerr($network->getErrorMessage($shareLink)); - } - - // 转存成功 - return jok("文件转存成功", [ - 'fileNames' => $fileNames, - 'fileCount' => count($fsIdList), - 'folderName' => $folderName, - 'shareLink' => $shareLink, - 'password' => $password, - 'filePaths' => $filePaths, - 'removedAdFiles' => $adFilePaths - ]); - } - - /** - * 检查文件名是否包含广告关键词 - * @param string $filename 文件名 - * @return bool 是否包含广告关键词 - */ - private function containsAdKeywords($filename) - { - $banned = Config('qfshop.quark_banned') ?? ''; // 如果出现这些字样就删除 - - // 广告关键词列表 - $adKeywords = []; - if (!empty($banned)) { - $adKeywords = array_map('trim', explode(',', $banned)); - } - - // 转为小写进行比较 - $lowercaseFilename = mb_strtolower($filename); - - foreach ($adKeywords as $keyword) { - if (mb_strpos($lowercaseFilename, mb_strtolower($keyword)) !== false) { - return true; - } - } - - return false; - } - - public function getFiles() - { - $cookie = 'csrfToken=gKesaboeWtJkukG4VNGh5Ljk; BAIDUID=757FBF0C282638B49A59DC7E37C7702B:FG=1; BAIDUID_BFESS=757FBF0C282638B49A59DC7E37C7702B:FG=1; Hm_lvt_7a3960b6f067eb0085b7f96ff5e660b0=1742787964; RT="z=1&dm=baidu.com&si=86a18d7d-4537-4b9b-ac36-06a756df3179&ss=m8mj80dn&sl=0&tt=0&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ul=261&hd=26a"; Hm_lpvt_7a3960b6f067eb0085b7f96ff5e660b0=1742794455; newlogin=1; ppfuid=FOCoIC3q5fKa8fgJnwzbE67EJ49BGJeplOzf+4l4EOvDuu2RXBRv6R3A1AZMa49I27C0gDDLrJyxcIIeAeEhD8JYsoLTpBiaCXhLqvzbzmvy3SeAW17tKgNq/Xx+RgOdb8TWCFe62MVrDTY6lMf2GrfqL8c87KLF2qFER3obJGmxOaJD7Qr04D9rET96PX99GEimjy3MrXEpSuItnI4KD2P5vWa8VVdqKPLBckQ0WyruzFB5pZ7L1GIDHy291nRZSc37WI7hn7N5DEkitWgHVHqxGUGRl1qke9+4QxQVI1jGgLbz7OSojK1zRbqBESR5Pdk2R9IA3lxxOVzA+Iw1TWLSgWjlFVG9Xmh1+20oPSbrzvDjYtVPmZ+9/6evcXmhcO1Y58MgLozKnaQIaLfWRIM4pp9u1B7t2Y8SxQH/XnrgZXt2Kg4R5SS0He7SlWGt42bJBOW2wZKr1YF6Z6VWTM5FjnYxYstXg/9EfB3EVmLB2thKqX6G/zUWgdr9REaklV1Uhhp5FAe6gNJIUptp7EMAaXYKm11G+JVPszQFdp9AJLcm4YSsYUXkaPI2Tl66J246cmjWQDTahAOINR5rXR5r/7VVI1RMZ8gb40q7az7vCK56XLooKT5a+rsFrf5Zu0yyCiiagElhrTEOtNdBJJq8eHwEHuFBni9ahSwpC7lbKkUwaKH69tf0DFV7hJROiLETSFloIVkHdy3+I2JUr1LsplAz0hMkWt/tE4tXVUV7QcTDTZWS/2mCoS/GV3N9awQ6iM6hs/BWjlgnEa1+5hYkkYOBBLZqDwzG9FQyZZJmSCAynpprqnVbhrjcNYdk5FKazXcZ/j40FJv+iLGBn3nkkgHlne61I8I7KhtQgInXjsNS9+mteGCT/vj/pEvkm+ZitcePbf21QMldRrlvw4uFnWmLU84Th29MqUuVR0ujl0ys3zTnxv/D47Q2VFyeFv12nptBFm2PISy0WmJ/Sy3ktTWpm7mcOABi5Gey1FBzkrq/nzqRQID/LRsJJW24e+1N4wlJzhNmFXMQ0YiYVdq7aHN89Oybut+xsc38J6I4Wd2rVi5xrH8hGqvd77OJuzH8X+tb0PV+Xnu3NL1fmqLIh+XcF6fnyPFPUxteQtbtLyi+gq5zowg1oFj8O/L9oVsoK22a9qUmM/HrJMRsLi1+J9aSd42+X78fDIZgkPh3epzLLvwRmnAbs5z/V+jl3P3gVnlwm9bfwhaFtnhFN2dHYAw7i4QhrXdzc77isXhbvkM5DsEwz5RTVSb4C+N+kIl81Iase/C16XVPKOj9XA==; BDUSS=jYwbDRJZG0yTi1VbDgtMWF6M1BGZ0k0ZG96dm9iOHJGczFMOUYtMFhwYlJmd2hvRVFBQUFBJCQAAAAAAAAAAAEAAAA63UdY2K~Yr8Dksru5~cjL0MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHy4GfR8uBnT; BDUSS_BFESS=jYwbDRJZG0yTi1VbDgtMWF6M1BGZ0k0ZG96dm9iOHJGczFMOUYtMFhwYlJmd2hvRVFBQUFBJCQAAAAAAAAAAAEAAAA63UdY2K~Yr8Dksru5~cjL0MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHy4GfR8uBnT; STOKEN=608a270497995724e6ed69015178372081331f404747f0a8492ee31de1184c41; PANPSC=6332961014215215224%3AAuI6D6iGxsvXU%2FMlrspYA1cS2d9ns3O5g0mIZdLHpdQGbqupDlB1gtH1%2BL8BUDk1coDRJu%2FbTp%2FiLaLUl0KD5De4ZdaW7CoOlL98c8Ccr9ch6uZoP3DwQ9YfJggg9xZJa6gjWHNi7jj6tFSlPcYsyyZWmuM7JILl4szUrnVKA5MNNiO7%2B4TS%2B1QskbZnP5EyWagh%2FyTVtumv9EBTj3cIccvsqRsDSpjMTItbmssa8FDJel1POICqciYu0WtaRbrkbSBvzd4TSG3wk8UVEJrJjetuQFScyIV%2FkrFWWqsgPEw%3D; Hm_lvt_182d6d59474cf78db37e0b2248640ea5=1742788120; ndut_fmt=6F438330EB0B4C594C515F276A3E73E3F2D3A8BB5A3C26DD1E721E98B58ED17B; Hm_lpvt_182d6d59474cf78db37e0b2248640ea5=1742795476; ab_sr=1.0.1_YTI3MjJmMjkwY2Y5MTJhMjc4MGJhNWU2ZGMwNTFiZWZiNmZhMTZkMWRlNWYyMzAxNTA1OTU2YTU4ZjU5M2ZiNWI5NDY2YjYwZWY3Yjc4YTY3MmNiMWVlNWNkNzE2ZGI2YzJmYTY1YTgwM2M1ZDJlYTk1YjAxZTcyMmNkNWRkNzliM2RiOGMzYzUxYmMwZTU4OTJiZmRmNjhiMDIzZDc1OWRhMGIyNGNkMDJhOTMzZjllYmQxMDUzY2ZjYzU4MjVk'; - // $cookie = ''; - $network = new \netdisk\pan\BaiduWork($cookie); - - $res = $network->getDirList('/'); - - return jok('获取成功',$res); - } - - public function delete() - { - $cookie = 'csrfToken=gKesaboeWtJkukG4VNGh5Ljk; BAIDUID=757FBF0C282638B49A59DC7E37C7702B:FG=1; BAIDUID_BFESS=757FBF0C282638B49A59DC7E37C7702B:FG=1; Hm_lvt_7a3960b6f067eb0085b7f96ff5e660b0=1742787964; RT="z=1&dm=baidu.com&si=86a18d7d-4537-4b9b-ac36-06a756df3179&ss=m8mj80dn&sl=0&tt=0&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ul=261&hd=26a"; Hm_lpvt_7a3960b6f067eb0085b7f96ff5e660b0=1742794455; newlogin=1; ppfuid=FOCoIC3q5fKa8fgJnwzbE67EJ49BGJeplOzf+4l4EOvDuu2RXBRv6R3A1AZMa49I27C0gDDLrJyxcIIeAeEhD8JYsoLTpBiaCXhLqvzbzmvy3SeAW17tKgNq/Xx+RgOdb8TWCFe62MVrDTY6lMf2GrfqL8c87KLF2qFER3obJGmxOaJD7Qr04D9rET96PX99GEimjy3MrXEpSuItnI4KD2P5vWa8VVdqKPLBckQ0WyruzFB5pZ7L1GIDHy291nRZSc37WI7hn7N5DEkitWgHVHqxGUGRl1qke9+4QxQVI1jGgLbz7OSojK1zRbqBESR5Pdk2R9IA3lxxOVzA+Iw1TWLSgWjlFVG9Xmh1+20oPSbrzvDjYtVPmZ+9/6evcXmhcO1Y58MgLozKnaQIaLfWRIM4pp9u1B7t2Y8SxQH/XnrgZXt2Kg4R5SS0He7SlWGt42bJBOW2wZKr1YF6Z6VWTM5FjnYxYstXg/9EfB3EVmLB2thKqX6G/zUWgdr9REaklV1Uhhp5FAe6gNJIUptp7EMAaXYKm11G+JVPszQFdp9AJLcm4YSsYUXkaPI2Tl66J246cmjWQDTahAOINR5rXR5r/7VVI1RMZ8gb40q7az7vCK56XLooKT5a+rsFrf5Zu0yyCiiagElhrTEOtNdBJJq8eHwEHuFBni9ahSwpC7lbKkUwaKH69tf0DFV7hJROiLETSFloIVkHdy3+I2JUr1LsplAz0hMkWt/tE4tXVUV7QcTDTZWS/2mCoS/GV3N9awQ6iM6hs/BWjlgnEa1+5hYkkYOBBLZqDwzG9FQyZZJmSCAynpprqnVbhrjcNYdk5FKazXcZ/j40FJv+iLGBn3nkkgHlne61I8I7KhtQgInXjsNS9+mteGCT/vj/pEvkm+ZitcePbf21QMldRrlvw4uFnWmLU84Th29MqUuVR0ujl0ys3zTnxv/D47Q2VFyeFv12nptBFm2PISy0WmJ/Sy3ktTWpm7mcOABi5Gey1FBzkrq/nzqRQID/LRsJJW24e+1N4wlJzhNmFXMQ0YiYVdq7aHN89Oybut+xsc38J6I4Wd2rVi5xrH8hGqvd77OJuzH8X+tb0PV+Xnu3NL1fmqLIh+XcF6fnyPFPUxteQtbtLyi+gq5zowg1oFj8O/L9oVsoK22a9qUmM/HrJMRsLi1+J9aSd42+X78fDIZgkPh3epzLLvwRmnAbs5z/V+jl3P3gVnlwm9bfwhaFtnhFN2dHYAw7i4QhrXdzc77isXhbvkM5DsEwz5RTVSb4C+N+kIl81Iase/C16XVPKOj9XA==; BDUSS=jYwbDRJZG0yTi1VbDgtMWF6M1BGZ0k0ZG96dm9iOHJGczFMOUYtMFhwYlJmd2hvRVFBQUFBJCQAAAAAAAAAAAEAAAA63UdY2K~Yr8Dksru5~cjL0MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHy4GfR8uBnT; BDUSS_BFESS=jYwbDRJZG0yTi1VbDgtMWF6M1BGZ0k0ZG96dm9iOHJGczFMOUYtMFhwYlJmd2hvRVFBQUFBJCQAAAAAAAAAAAEAAAA63UdY2K~Yr8Dksru5~cjL0MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHy4GfR8uBnT; STOKEN=608a270497995724e6ed69015178372081331f404747f0a8492ee31de1184c41; PANPSC=6332961014215215224%3AAuI6D6iGxsvXU%2FMlrspYA1cS2d9ns3O5g0mIZdLHpdQGbqupDlB1gtH1%2BL8BUDk1coDRJu%2FbTp%2FiLaLUl0KD5De4ZdaW7CoOlL98c8Ccr9ch6uZoP3DwQ9YfJggg9xZJa6gjWHNi7jj6tFSlPcYsyyZWmuM7JILl4szUrnVKA5MNNiO7%2B4TS%2B1QskbZnP5EyWagh%2FyTVtumv9EBTj3cIccvsqRsDSpjMTItbmssa8FDJel1POICqciYu0WtaRbrkbSBvzd4TSG3wk8UVEJrJjetuQFScyIV%2FkrFWWqsgPEw%3D; Hm_lvt_182d6d59474cf78db37e0b2248640ea5=1742788120; ndut_fmt=6F438330EB0B4C594C515F276A3E73E3F2D3A8BB5A3C26DD1E721E98B58ED17B; Hm_lpvt_182d6d59474cf78db37e0b2248640ea5=1742795476; ab_sr=1.0.1_YTI3MjJmMjkwY2Y5MTJhMjc4MGJhNWU2ZGMwNTFiZWZiNmZhMTZkMWRlNWYyMzAxNTA1OTU2YTU4ZjU5M2ZiNWI5NDY2YjYwZWY3Yjc4YTY3MmNiMWVlNWNkNzE2ZGI2YzJmYTY1YTgwM2M1ZDJlYTk1YjAxZTcyMmNkNWRkNzliM2RiOGMzYzUxYmMwZTU4OTJiZmRmNjhiMDIzZDc1OWRhMGIyNGNkMDJhOTMzZjllYmQxMDUzY2ZjYzU4MjVk'; - // $cookie = ''; - $network = new \netdisk\pan\BaiduWork($cookie); - $bdstoken = $network->getBdstoken(); - $network->setBdstoken($bdstoken); - - // 使用固定的文件路径数组 - $filePaths = [ - "//1", - "//2" - ]; - - // 调用批量删除方法 - $result = $network->batchDeleteFiles($filePaths); - - if ($result['errno'] === 0) { - return jok('文件删除成功', [ - 'deletedCount' => $result['deletedCount'], - 'paths' => $result['paths'] - ]); - } else { - return jerr($result['message']); - } + return jok("Hello World!"); } public function search() { diff --git a/extend/quarkPlugin/QuarkPlugin.php b/extend/quarkPlugin/QuarkPlugin.php index a9c2269..442f7e6 100644 --- a/extend/quarkPlugin/QuarkPlugin.php +++ b/extend/quarkPlugin/QuarkPlugin.php @@ -110,7 +110,7 @@ class QuarkPlugin function processSingleData($value, $logId = 0, $total_result = 0, $isType = 0) { - $detail = $this->model->where('title', $value['title'])->find(); + $detail = $this->model->where('title', $value['title'])>where('is_type', determineIsType($value['url']))->find(); if (!empty($detail)) { if (!empty($logId)) { $this->SourceLogModel->editLog($logId, $total_result, 'skip_num', '重复跳过转存'); diff --git a/extend/util/Network.php b/extend/util/Network.php deleted file mode 100644 index e502ab1..0000000 --- a/extend/util/Network.php +++ /dev/null @@ -1,412 +0,0 @@ - '链接错误,链接失效或缺少提取码', - -4 => '转存失败,无效登录。请退出账号在其他地方的登录', - -6 => '转存失败,请用浏览器无痕模式获取 Cookie 后再试', - -7 => '转存失败,转存文件夹名有非法字符,不能包含 < > | * ? \\ :,请改正目录名后重试', - -8 => '转存失败,目录中已有同名文件或文件夹存在', - -9 => '链接错误,提取码错误', - -10 => '转存失败,容量不足', - -12 => '链接错误,提取码错误', - -62 => '转存失败,链接访问次数过多,请手动转存或稍后再试', - 0 => '转存成功', - 2 => '转存失败,目标目录不存在', - 4 => '转存失败,目录中存在同名文件', - 12 => '转存失败,转存文件数超过限制', - 20 => '转存失败,容量不足', - 105 => '链接错误,所访问的页面不存在' - ]; - - public function getErrorMessage($code) - { - return $this->errorCodes[$code] ?? "未知错误(错误码:{$code})"; - } - - protected $session; - protected $headers; - protected $bdstoken; - protected $baseUrl; - protected $cookie; - - public function __construct($cookie = '') - { - $this->cookie = $cookie??''; - $this->headers = [ - 'Host: pan.baidu.com', - 'Connection: keep-alive', - 'Upgrade-Insecure-Requests: 1', - 'Sec-Fetch-Dest: document', - 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', - 'Sec-Fetch-Site: same-site', - 'Sec-Fetch-Mode: navigate', - 'Referer: https://pan.baidu.com', - 'Accept-Encoding: gzip, deflate, br', - 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7,en-GB;q=0.6,ru;q=0.5', - 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', - 'Cookie: ' . $cookie - ]; - $this->bdstoken = ''; - $this->baseUrl = 'https://pan.baidu.com'; - } - - public function getBdstoken() - { - $url = $this->baseUrl . '/api/gettemplatevariable'; - $params = [ - 'clienttype' => '0', - 'app_id' => '38824127', - 'web' => '1', - 'fields' => '["bdstoken","token","uk","isdocuser","servertime"]' - ]; - - $res = $this->request('GET', $url, $params); - if ($res['errno'] != 0) { - return $res['errno']; - } - - return $res['result']['bdstoken']; - } - - public function getDirList($folderName) - { - $url = $this->baseUrl . '/api/list'; - $params = [ - 'order' => 'time', - 'desc' => '1', - 'showempty' => '0', - 'web' => '1', - 'page' => '1', - 'num' => '1000', - 'dir' => $folderName, - 'bdstoken' => $this->bdstoken - ]; - - $res = $this->request('GET', $url, $params); - - if ($res['errno'] != 0) { - return $res['errno']; - } - - return $res['list']; - } - - public function createDir($folderName) - { - $url = $this->baseUrl . '/api/create'; - $params = [ - 'a' => 'commit', - 'bdstoken' => $this->bdstoken - ]; - $data = [ - 'path' => $folderName, - 'isdir' => '1', - 'block_list' => '[]' - ]; - - $res = $this->request('POST', $url, $params, $data); - return $res['errno']; - } - - public function verifyPassCode($linkUrl, $passCode) - { - $url = $this->baseUrl . '/share/verify'; - $params = [ - 'surl' => substr($linkUrl, 25, 23), - 'bdstoken' => $this->bdstoken, - 't' => round(microtime(true) * 1000), - 'channel' => 'chunlei', - 'web' => '1', - 'clienttype' => '0' - ]; - $data = [ - 'pwd' => $passCode, - 'vcode' => '', - 'vcode_str' => '' - ]; - - $res = $this->request('POST', $url, $params, $data); - if ($res['errno'] != 0) { - return $res['errno']; - } - - return $res['randsk']; - } - - public function updateBdclnd($randsk) - { - $this->cookie = $this->updateCookie($randsk, $this->cookie); - - // 更新 headers 中的 cookie - foreach ($this->headers as &$header) { - if (strpos($header, 'Cookie:') === 0) { - $header = 'Cookie: ' . $this->cookie; - break; - } - } - } - - public function getTransferParams($url) - { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers); - curl_setopt($ch, CURLOPT_TIMEOUT, 15); - curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate'); - // 允许跳转 - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_MAXREDIRS, 3); - - $response = curl_exec($ch); - curl_close($ch); - - if ($response === false) { - throw new \Exception(curl_error($ch)); - } - - // 直接返回原始响应内容,不做 json 解析 - return $this->parseResponse($response); - } - - public function transferFile($paramsList, $folderName) - { - $url = $this->baseUrl . '/share/transfer'; - $params = [ - 'shareid' => $paramsList[0], - 'from' => $paramsList[1], - 'bdstoken' => $this->bdstoken, - 'channel' => 'chunlei', - 'web' => '1', - 'clienttype' => '0' - ]; - $data = [ - 'fsidlist' => '[' . implode(',', $paramsList[2]) . ']', - 'path' => '/' . $folderName - ]; - - $res = $this->request('POST', $url, $params, $data); - return $res['errno']; - } - - public function createShare($fsId, $expiry, $password) - { - $url = $this->baseUrl . '/share/set'; - $params = [ - 'channel' => 'chunlei', - 'bdstoken' => $this->bdstoken, - 'clienttype' => '0', - 'app_id' => '250528', - 'web' => '1' - ]; - $data = [ - 'period' => $expiry, - 'pwd' => $password, - 'eflag_disable' => 'true', - 'channel_list' => '[]', - 'schannel' => '4', - 'fid_list' => '[' . $fsId . ']' - ]; - - $res = $this->request('POST', $url, $params, $data); - if ($res['errno'] != 0) { - return $res['errno']; - } - - return $res['link']; - } - - public function setBdstoken($token) - { - $this->bdstoken = $token; - } - - protected function request($method, $url, $params = [], $data = null, $retry = 3) - { - while ($retry > 0) { - try { - $ch = curl_init(); - if ($method === 'GET' && !empty($params)) { - $url .= '?' . http_build_query($params); - } - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate'); - - if ($method === 'POST') { - curl_setopt($ch, CURLOPT_POST, true); - if (!empty($params)) { - $url .= '?' . http_build_query($params); - curl_setopt($ch, CURLOPT_URL, $url); - } - if (!empty($data)) { - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); - } - } - - $response = curl_exec($ch); - curl_close($ch); - - if ($response === false) { - throw new \Exception(curl_error($ch)); - } - - return json_decode($response, true); - } catch (\Exception $e) { - $retry--; - if ($retry <= 0) { - throw $e; - } - usleep(rand(1000000, 2000000)); // 1-2秒随机延迟 - } - } - } - - protected function updateCookie($bdclnd, $cookie) - { - // 拆分 cookie 字符串到数组 - $cookiePairs = array_filter(explode(';', $cookie)); - $cookiesDict = []; - - // 将 cookie 键值对转换为关联数组 - foreach ($cookiePairs as $pair) { - $parts = explode('=', trim($pair), 2); - if (count($parts) == 2) { - $cookiesDict[$parts[0]] = $parts[1]; - } - } - - // 更新或添加 BDCLND 值 - $cookiesDict['BDCLND'] = $bdclnd; - - // 重新构建 cookie 字符串 - $cookieParts = []; - foreach ($cookiesDict as $key => $value) { - $cookieParts[] = $key . '=' . $value; - } - - return implode('; ', $cookieParts); - } - - protected function parseResponse($response) - { - // 预定义正则表达式 - $patterns = [ - 'shareid' => '/"shareid":(\d+?),"/', - 'user_id' => '/"share_uk":"(\d+?)","/', - 'fs_id' => '/"fs_id":(\d+?),"/', - 'server_filename' => '/"server_filename":"(.+?)","/', - 'isdir' => '/"isdir":(\d+?),"/' - ]; - - // 提取所有需要的参数 - $results = []; - foreach ($patterns as $key => $pattern) { - preg_match_all($pattern, $response, $matches); - $results[$key] = $matches[1] ?? []; - } - - // 验证是否获取到所有必要参数 - if (empty($results['shareid']) || empty($results['user_id']) || - empty($results['fs_id']) || empty($results['server_filename']) || - empty($results['isdir'])) { - return -1; - } - - // 返回格式化的结果 - return [ - $results['shareid'][0], // shareid - $results['user_id'][0], // user_id - $results['fs_id'], // fs_id 列表 - array_unique($results['server_filename']), // 文件名列表(去重) - $results['isdir'] // 是否为目录 - ]; - } - - public function deleteFile($filePath) - { - $url = $this->baseUrl . '/api/filemanager'; - $params = [ - 'async' => '2', - 'onnest' => 'fail', - 'opera' => 'delete', - 'bdstoken' => $this->bdstoken, - 'newVerify' => '1', - 'clienttype' => '0', - 'app_id' => '250528', - 'web' => '1' - ]; - - // 支持单个路径字符串或路径数组 - if (!is_array($filePath)) { - $filePath = [$filePath]; - } - - $data = [ - 'filelist' => json_encode($filePath) - ]; - - $res = $this->request('POST', $url, $params, $data); - return $res['errno']; - } - - /** - * 批量删除文件 - * @param array|string $filePaths 文件路径数组或单个文件路径 - * @return array 删除结果 - */ - public function batchDeleteFiles($filePaths) - { - // 如果是字符串,转换为数组处理 - if (!is_array($filePaths)) { - $filePaths = [$filePaths]; - } - - // 处理每个路径 - $processedPaths = []; - foreach ($filePaths as $path) { - if (empty($path)) { - continue; - } - - // 防止删除根目录 - if ($path === '/' || $path === '') { - continue; - } - - // 确保路径以斜杠开头 - if (substr($path, 0, 1) !== '/') { - $path = '/' . $path; - } - - $processedPaths[] = $path; - } - - if (empty($processedPaths)) { - return [ - 'errno' => -100, - 'message' => '没有有效的文件路径可删除' - ]; - } - - // 调用删除方法 - $result = $this->deleteFile($processedPaths); - - return [ - 'errno' => $result, - 'message' => $this->getErrorMessage($result), - 'deletedCount' => count($processedPaths), - 'paths' => $processedPaths - ]; - } -} \ No newline at end of file diff --git a/extend/util/Operations.php b/extend/util/Operations.php deleted file mode 100644 index 8b274db..0000000 --- a/extend/util/Operations.php +++ /dev/null @@ -1,211 +0,0 @@ -', '|', '*', '?', '\\', ':']; - - public function __construct($cookie = '') - { - $this->network = new Network($cookie); - $this->cookie = $cookie; - $this->completedTaskCount = 0; - $this->running = true; - } - - public function save($links, $folderName = '', $customMode = false, $checkMode = false) - { - try { - $this->prepareRun($folderName, $customMode, $checkMode); - $this->setupSave($links); - $this->handleInput(); - $this->handleBdstoken(); - $this->handleCreateDir($this->folderName); - $this->handleProcessSave(); - return ['code' => 0, 'msg' => '转存完成']; - } catch (\Exception $e) { - return ['code' => -1, 'msg' => '程序出现未预料错误: ' . $e->getMessage()]; - } - } - - protected function prepareRun($folderName, $customMode, $checkMode) - { - $this->folderName = trim($folderName); - $this->customMode = $customMode; - $this->checkMode = $checkMode; - $this->completedTaskCount = 0; - } - - protected function setupSave($links) - { - $this->linkList = array_filter(array_map(function($link) { - return $this->normalizeLink($link . ' '); - }, explode("\n", $links))); - - $this->linkListOrg = array_unique(array_filter(explode("\n", $links))); - $this->totalTaskCount = count($this->linkList); - } - - protected function handleInput() - { - if ($this->totalTaskCount == 0) { - throw new \Exception('无有效链接。'); - } - if ($this->totalTaskCount > self::SAVE_LIMIT) { - throw new \Exception("批量转存一次不能超过 " . self::SAVE_LIMIT . ",当前链接数:" . $this->totalTaskCount); - } - if (!preg_match('/^[\x00-\x7F]+$/', $this->cookie) || strpos($this->cookie, 'BAIDUID') === false) { - throw new \Exception('百度网盘 cookie 输入不正确。'); - } - foreach (self::INVALID_CHARS as $char) { - if (strpos($this->folderName, $char) !== false) { - throw new \Exception('转存目录名有非法字符,不能包含:< > | * ? \ :'); - } - } - } - - protected function handleBdstoken() - { - $bdstoken = $this->network->getBdstoken(); - if (is_numeric($bdstoken)) { - throw new \Exception("没获取到 bdstoken 参数,错误代码:" . $bdstoken); - } - $this->network->setBdstoken($bdstoken); - } - - protected function handleCreateDir($folderName) - { - if (!empty($folderName)) { - $result = $this->network->getDirList("/{$folderName}"); - if (is_numeric($result)) { - $returnCode = $this->network->createDir($folderName); - if ($returnCode !== 0) { - throw new \Exception("创建目录失败,错误代码:" . $returnCode); - } - } - } - } - - protected function handleProcessSave() - { - $results = []; - foreach ($this->linkList as $urlCode) { - $result = $this->processSave($urlCode); - $results[] = $result; - usleep(self::DELAY_SECONDS * 1000000); - } - return $results; - } - - protected function processSave($urlCode) - { - if (strpos($urlCode, 'https://pan.baidu.com/') === false) { - return ['status' => 'error', 'message' => "不支持的链接:{$urlCode}"]; - } - - list($url, $code) = $this->parseUrlAndCode($urlCode); - $result = $this->verifyLink($url, $code); - - if ($this->checkMode) { - return $this->checkOnly($result, $urlCode); - } - - return $this->saveFile($result, $urlCode, $this->folderName); - } - - protected function normalizeLink($link) - { - if (empty($link)) return ''; - - $link = trim($link); - if (preg_match('/https:\/\/pan\.baidu\.com\/s\/[a-zA-Z0-9_-]+/', $link, $matches)) { - return $matches[0]; - } - return ''; - } - - protected function parseUrlAndCode($urlCode) - { - $parts = explode(' ', trim($urlCode)); - $url = $parts[0]; - $code = isset($parts[1]) ? $parts[1] : ''; - return [$url, $code]; - } - - protected function verifyLink($url, $password) - { - if ($password) { - $bdclnd = $this->network->verifyPassCode($url, $password); - if (is_numeric($bdclnd)) { - return $bdclnd; - } - } - - $response = $this->network->getTransferParams($url); - return $this->parseResponse($response); - } - - protected function parseResponse($html) - { - // 实现解析响应的逻辑 - // 返回解析后的参数数组或错误代码 - return []; - } - - protected function checkOnly($result, $urlCode) - { - if (is_array($result)) { - return ['status' => 'success', 'message' => "链接有效:{$urlCode}"]; - } - return ['status' => 'error', 'message' => "链接无效:{$urlCode}"]; - } - - protected function saveFile($result, $urlCode, $folderName) - { - if (!is_array($result)) { - return ['status' => 'error', 'message' => "转存失败:{$urlCode}"]; - } - - if ($this->customMode) { - $folderName = $this->createUserDir($folderName); - } - - $transferResult = $this->network->transferFile($result, $folderName); - - if ($transferResult === 0) { - return ['status' => 'success', 'message' => "转存成功:{$urlCode} 到 {$folderName}"]; - } - - return ['status' => 'error', 'message' => "转存失败,错误代码({$transferResult}):{$urlCode}"]; - } - - protected function createUserDir($baseFolderName) - { - if (empty($baseFolderName)) { - throw new \Exception('必须输入转存目录'); - } - - $customFolder = $this->completedTaskCount + 1; - $folderName = $baseFolderName . '/' . $customFolder; - - // 替换非法字符 - $folderName = str_replace(self::INVALID_CHARS, '_', $folderName); - - $this->handleCreateDir($folderName); - return $folderName; - } -} \ No newline at end of file diff --git a/extend/util/network.py b/extend/util/network.py deleted file mode 100644 index d03133e..0000000 --- a/extend/util/network.py +++ /dev/null @@ -1,219 +0,0 @@ -""" -所有和网络请求相关函数。 - -:author: assassing -:contact: https://github.com/hxz393 -:copyright: Copyright 2024, hxz393. 保留所有权利。 -""" -import time -from typing import Union, List, Any - -import requests -from retrying import retry - -from src.constants import HEADERS, BASE_URL - - -class Network: - """ - 网络请求相关类。 - 注意,此类函数用 retry 来处理网络问题,达到重试次数则由主函数来捕获异常,中断运行。只要请求能返回数据,都可以正确处理。 - 请求地址没有取全参数(params),只留关键参数。 - """ - - def __init__(self): - """ - 初始化会话、请求头和 bdstoken。 - """ - self.s = requests.Session() - self.headers = HEADERS - self.bdstoken = '' - # 忽略证书验证警告 - requests.packages.urllib3.disable_warnings() - - @retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=2000) - def get_bdstoken(self) -> Union[str, int]: - """ - 获取 bdstoken,用于创建、转存等操作,是所有其他请求的先决条件。 - 获取到的 token 在整个会话中通用。 - - :return: 获取成功返回 bdstoken,获取失败返回错误代码 - """ - url = f'{BASE_URL}/api/gettemplatevariable' - params = { - 'clienttype': '0', - 'app_id': '38824127', - 'web': '1', - 'fields': '["bdstoken","token","uk","isdocuser","servertime"]' - } - - r = self.s.get(url=url, params=params, headers=self.headers, timeout=10, allow_redirects=False, verify=False) - if r.json()['errno'] != 0: - return r.json()['errno'] - - return r.json()['result']['bdstoken'] - - @retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=2000) - def get_dir_list(self, folder_name: str) -> Union[List[Any], int]: - """ - 获取指定目录下的文件或目录列表。 - 用于创建目录前,检查目录是否已存在; - 用于批量分享时,生成任务列表。 - - :param folder_name: 指定要获取列表的目录名 - :return: 获取成功时返回文件列表,获取失败时返回错误代码 - """ - url = f'{BASE_URL}/api/list' - params = { - 'order': 'time', - 'desc': '1', - 'showempty': '0', - 'web': '1', - 'page': '1', - 'num': '1000', - 'dir': folder_name, - 'bdstoken': self.bdstoken - } - - r = self.s.get(url=url, params=params, headers=self.headers, timeout=15, allow_redirects=False, verify=False) - if r.json()['errno'] != 0: - return r.json()['errno'] - - return r.json()['list'] - - @retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=2000) - def create_dir(self, folder_name: str) -> int: - """ - 新建指定目录。 - 用于批量转存前,建立缺失的目标目录。 - - :param folder_name: 指定要建立的目录名 - :return: 获取请求返回的代码,成功时返回 0 - """ - url = f'{BASE_URL}/api/create' - params = { - 'a': 'commit', - 'bdstoken': self.bdstoken - } - data = { - 'path': folder_name, - 'isdir': '1', - 'block_list': '[]', - } - - r = self.s.post(url=url, params=params, headers=self.headers, data=data, timeout=15, allow_redirects=False, verify=False) - - return r.json()['errno'] - - @retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=2000) - def verify_pass_code(self, link_url: str, pass_code: str) -> Union[str, int]: - """ - 验证提取码是否正确。 - 如果正确,则会返回转存所必须的 randsk,也就是 bdclnd 参数。 - - :param link_url: 网盘地址 - :param pass_code: 提取码 - :return: 成功时返回 randsk 字符串,失败时返回错误代码 - """ - url = f'{BASE_URL}/share/verify' - params = { - # 可放心用暴力切片 - 'surl': link_url[25:48], - 'bdstoken': self.bdstoken, - # 当前时间的毫秒级时间戳 - 't': str(int(round(time.time() * 1000))), - # 下面是不明所以的固定参数 - 'channel': 'chunlei', - 'web': '1', - 'clienttype': '0' - } - data = { - 'pwd': pass_code, - # 并没有发现下面两个参数的用途 - 'vcode': '', - 'vcode_str': '' - } - - r = self.s.post(url=url, params=params, headers=self.headers, data=data, timeout=10, allow_redirects=False, verify=False) - if r.json()['errno'] != 0: - return r.json()['errno'] - - return r.json()['randsk'] - - @retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=2000) - def get_transfer_params(self, url: str) -> str: - """ - 更新 bdclnd 到 cookie 后,再次请求网盘链接,获取响应内容。 - 请求需要允许跳转。 - 请求不再需要提取码。 - - :param url: 网盘地址 - :return: 返回原始请求内容,丢给 parse_response 函数取处理 - """ - r = self.s.get(url=url, headers=self.headers, timeout=15, verify=False) - - return r.content.decode("utf-8") - - @retry(stop_max_attempt_number=5, wait_random_min=1000, wait_random_max=2000) - def transfer_file(self, params_list: List[str], folder_name: str) -> int: - """ - 转存百度网盘文件。 - - :param params_list: 带有 shareid、share_uk 和 fs_id 的列表 - :param folder_name: 转存目标目录 - :return: 返回转存请求结果代码 - """ - url = f'{BASE_URL}/share/transfer' - params = { - # shareid 是文件 id - 'shareid': params_list[0], - # share_uk 猜是分享者的 id - 'from': params_list[1], - 'bdstoken': self.bdstoken, - 'channel': 'chunlei', - 'web': '1', - 'clienttype': '0' - } - data = { - # 针对一个分享链接带有多个分享文件的情况,转换一下列表格式 - 'fsidlist': f"[{','.join(params_list[2])}]", - # 目标目录为空,则直接等于根目录 '/' - 'path': f'/{folder_name}' - } - - r = self.s.post(url=url, params=params, headers=self.headers, data=data, timeout=30, allow_redirects=False, verify=False) - - return r.json()['errno'] - - @retry(stop_max_attempt_number=3, wait_random_min=1000, wait_random_max=2000) - def create_share(self, fs_id: int, expiry: str, password: str) -> Union[str, int]: - """ - 生成百度网盘分享链接。 - - :param fs_id: 文件或目录独一无二的 id - :param expiry: 自定义失效时长 - :param password: 自定义提取码 - :return: 成功时返回生成的分享链接,失败时返回错误代码 - """ - url = f'{BASE_URL}/share/set' - params = { - 'channel': 'chunlei', - 'bdstoken': self.bdstoken, - 'clienttype': '0', - 'app_id': '250528', - 'web': '1' - } - data = { - 'period': expiry, - 'pwd': password, - 'eflag_disable': 'true', - 'channel_list': '[]', - 'schannel': '4', - 'fid_list': f'[{fs_id}]' - } - - r = self.s.post(url=url, params=params, headers=self.headers, data=data, timeout=15, allow_redirects=False, verify=False) - if r.json()['errno'] != 0: - return r.json()['errno'] - - return r.json()['link'] \ No newline at end of file diff --git a/extend/util/operations.py b/extend/util/operations.py deleted file mode 100644 index cdfd1bd..0000000 --- a/extend/util/operations.py +++ /dev/null @@ -1,327 +0,0 @@ -""" -主要功能实现逻辑,包括批量转存和分享。 - -:author: assassing -:contact: https://github.com/hxz393 -:copyright: Copyright 2024, hxz393. 保留所有权利。 -""" - -import sys -import time -import traceback -from typing import Union, List, Dict, Any - -import ttkbootstrap as ttk - -from src.constants import EXP_MAP, DELAY_SECONDS, INVALID_CHARS, ERROR_CODES, SAVE_LIMIT, COLOR_MAP -from src.network import Network -from src.ui import CustomDialog -from src.utils import thread_it, write_config, parse_response, normalize_link, parse_url_and_code, update_cookie, generate_code - - -class Operations: - """ - 包括主功能实现流程,涉及部分对 UI 的操作。 - 日志文字不额外提取到映射字典,方便定位错误。 - - :param root: 主窗口 - """ - - def __init__(self, root): - self.root = root - self.network = Network() - - def save(self) -> None: - """ - 转存主函数,流程为: - 1.获取用户输入,更新按钮和标签状态; - 2.检查用户输入,有问题则终止并发送日志; - 3.获取 bdstoken,如果获取失败则终止; - 4.获取根目录文件列表,建立转存目录; - 5.批量转存链接。先验证链接有效性,再转存文件,插入结果到日志框。 - """ - try: - self.prepare_run() - self.setup_save() - self.handle_input() - self.handle_bdstoken() - self.handle_create_dir(folder_name=self.folder_name) - self.handle_process_save() - except Exception as e: - self.insert_logs(f'程序出现未预料错误,信息如下:\n{e}\n{traceback.format_exc()}') - finally: - self.network.s.close() - self.change_status('stopped') - - def share(self) -> None: - """ - 分享主函数,流程为: - 0.弹出设置窗口,设置分享参数; - 1.获取用户输入,更新按钮和标签状态; - 2.检查用户输入,有问题则终止并发送日志; - 3.获取 bdstoken,如果获取失败则终止; - 4.获取目标目录文件列表,如果为空则终止; - 5.批量分享文件。先向链接框插入文件名,再创建分享,插入结果到日志框。 - """ - try: - # 创建对话框实例,如果对话框没有返回输入值,则忽略 - self.dialog_result = CustomDialog(self.root) - if self.dialog_result.result: - self.prepare_run() - self.handle_input(task_count_check=False) - self.handle_bdstoken() - self.handle_list_dir() - self.setup_share() - self.handle_process_share() - except Exception as e: - self.insert_logs(f'程序出现未预料错误,信息如下:\n{e}\n{traceback.format_exc()}') - finally: - self.network.s.close() - self.change_status('stopped') - - def prepare_run(self) -> None: - """获取变量,准备运行。转存和分享共用逻辑""" - # 从用户输入获取变量 - self.cookie = "".join(self.root.entry_cookie.get().split()) - self.folder_name = "".join(self.root.entry_folder_name.get().split()) - self.network.s.trust_env = self.root.var_trust_env.get() - self.custom_mode = self.root.var_custom_mode.get() - self.check_mode = self.root.var_check_mode.get() - - # 更新 cookie、初始化任务总数、更改状态、写入配置文件 - self.completed_task_count = 0 - self.network.headers['Cookie'] = self.cookie - self.change_status('init') - write_config(f'{self.cookie}\n{self.folder_name}') - - def setup_save(self) -> None: - """准备链接,更新状态""" - # 从文本链接控件获取全部链接,清洗并标准化链接。注意链接后拼接一个空格,是为了后面能统一处理带与不带提取码的链接 - raw_links = self.root.text_links.get(1.0, ttk.END).splitlines() - self.link_list = [normalize_link(f'{link} ') for link in raw_links if link] - self.link_list_org = list(dict.fromkeys(link for link in raw_links if link)) - # 更新任务总数和状态 - self.total_task_count = len(self.link_list) - self.change_status('running') - - def setup_share(self) -> None: - """准备参数,更新状态""" - # 从设置对话框获取参数变量 - self.expiry, self.password, self.random_password = self.dialog_result.result - # 更新任务总数和状态 - self.total_task_count = len(self.dir_list_all) - self.change_status('sharing') - - def handle_input(self, task_count_check: bool = True) -> None: - """输入检查,如链接数限制和 cookie 格式""" - # 转存时才检查输入链接数量 - if task_count_check: - self.check_condition(self.total_task_count == 0, - message='无有效链接。') - self.check_condition(self.total_task_count > SAVE_LIMIT, - message=f'批量转存一次不能超过 {SAVE_LIMIT},当前链接数:{self.total_task_count}') - - # cookie 带非 ascii 字符,或不包含 BAIDUID 时,铁定不对 - self.check_condition(not self.cookie.isascii() or self.cookie.find('BAIDUID') == -1, - message='百度网盘 cookie 输入不正确,请查看使用帮助。') - # 非法字符由官方规定的。符号 / 可以使用,作为子目录的分隔符 - self.check_condition(any(char in self.folder_name for char in INVALID_CHARS), - message=r'转存目录名有非法字符,不能包含:< > | * ? \ :') - - def handle_bdstoken(self) -> None: - """获取 bdstoken 相关逻辑""" - self.network.bdstoken = self.network.get_bdstoken() - self.check_condition(isinstance(self.network.bdstoken, int), - message=f'没获取到 bdstoken 参数,错误代码:{self.network.bdstoken}') - - def handle_list_dir(self) -> None: - """获取目标目录下的文件和目录列表""" - self.dir_list_all = self.network.get_dir_list(f'/{self.folder_name}') - self.check_condition(isinstance(self.dir_list_all, int) or not self.dir_list_all, - message=f'目录 {self.folder_name} 中没获取到任何内容,请求返回:{self.dir_list_all}') - - def handle_create_dir(self, folder_name: str) -> None: - """新建目录。如果目录已存在则不新建,否则会建立一个带时间戳的空目录""" - result = self.network.get_dir_list(f'/{folder_name}') - # 如果 result 为错误代码数字,代表目标目录不存在 - if self.folder_name and isinstance(result, int): - return_code = self.network.create_dir(folder_name) - self.check_condition(return_code != 0, - message=f'创建目录失败,错误代码:{return_code}') - - def handle_process_save(self) -> None: - """执行批量转存""" - for url_code in self.link_list: - self.process_save(url_code) - - def handle_process_share(self) -> None: - """执行批量分享""" - for info in self.dir_list_all: - self.process_share(info) - - def process_save(self, url_code: str) -> None: - """执行转存操作并记录结果""" - # 跳过非网盘链接 - if 'https://pan.baidu.com/' not in url_code: - self.insert_logs(f'不支持的链接:{url_code}') - else: - # 执行转存过程,通过简单的循环判断是否要暂停 - self.pause_detection(url_code) - - # 处理完毕一个链接,更新状态栏任务计数 - self.change_status('update') - - def process_share(self, info: Dict[str, Any]) -> None: - """执行分享操作并记录结果""" - # 插入要分享的文件或文件夹到链接输入框,对文件夹加入 "/" 标记来区别 - is_dir = "/" if info["isdir"] == 1 else "" - filename = f"{info['server_filename']}{is_dir}" - msg = f'目录:{filename}' if is_dir else f'文件:{filename}' - self.insert_logs(msg, alt=True) - - # 处理提取码 - password = self.password if not self.random_password else generate_code() - - # 发送创建分享请求 - r = self.network.create_share(info['fs_id'], EXP_MAP[self.expiry], password) - if isinstance(r, str): - result = f'分享成功:{r}?pwd={password} {msg}' - else: - result = f'分享失败:错误代码({r}) {msg}' - - # 记录日志,更改状态 - self.insert_logs(result) - self.change_status('update') - - # noinspection PyArgumentList - def change_status(self, status: str) -> None: - """运行状态变化更新函数""" - if status == 'init': - self.root.label_status.config(text='准备就绪!', font=('', 10), bootstyle='primary', cursor="arrow") - self.root.label_status.unbind("") - self.root.text_logs.config(fg=COLOR_MAP['text']) - self.root.text_logs.delete(1.0, ttk.END) - elif status == 'running': - self.running = True - self.root.bottom_share.config(state="disabled") - self.root.bottom_save.config(text='点击暂停', bootstyle='danger', command=lambda: self.change_status('paused')) - elif status == 'paused': - self.running = False - self.root.bottom_save.config(text='点击继续', bootstyle='success', command=lambda: self.change_status('running')) - elif status == 'update': - self.completed_task_count += 1 - self.root.label_status.config(text=f'总进度:{self.completed_task_count}/{self.total_task_count}', bootstyle='success') - elif status == 'sharing': - self.root.text_links.delete(1.0, ttk.END) - self.root.bottom_share.config(state="disabled") - self.root.bottom_save.config(state="disabled") - elif status == 'error': - self.root.label_status.config(text='发生错误:', bootstyle='danger') - else: - self.running = False - self.root.bottom_save.config(text='批量转存', state="normal", bootstyle='primary', command=lambda: thread_it(self.save, )) - self.root.bottom_share.config(state="normal") - - def check_condition(self, condition: bool, message: str) -> None: - """ - 用户输入或函数返回检查。 - 如果条件 condition 为 True,表示错误不可恢复,中断当前任务流程。 - 单个链接处理出错,应该直接调用 insert_logs 函数记录错误,不中断任务。 - - :param condition: 条件表达式 - :param message: 插入到日志框的内容 - """ - if condition: - self.change_status('error') - self.insert_logs(message) - sys.exit() - - def insert_logs(self, message: str, alt: bool = False) -> None: - """ - 在文本框末尾插入内容 - - :param message: 插入到日志框的内容 - :param alt: 如果为 True,插入到链接输入框,用于批量分享时记录文件名 - """ - text_box = self.root.text_links if alt else self.root.text_logs - text_box.insert('end', f'{message}\n') - text_box.see(ttk.END) - - def pause_detection(self, url_code: str) -> None: - """循环检测暂停逻辑,每个转存任务开始时都会检测""" - # 只要 self.running 状态没变,会一直等待。而状态变化由用户点击按钮控制 - while not self.running: - time.sleep(DELAY_SECONDS) - - # 执行验证链接和转存文件 - self.verify_and_save(url_code) - # 转存完毕等待一小段时间。如果要转存超过 1000 个链接,可以增加 DELAY_SECONDS 到 3 以上。 - time.sleep(DELAY_SECONDS) - - def verify_and_save(self, url_code: str) -> None: - """验证链接和转存文件""" - # 验证链接是否有效,返回的数字为错误代码,反之返回参数列表 - result = self.verify_link(*parse_url_and_code(url_code)) - # 如果开启检查模式,插入检查结果,然后结束 - if self.check_mode: - self.check_only(result, url_code) - else: - self.save_file(result, url_code, self.folder_name) - - def verify_link(self, url: str, password: str) -> Union[List[str], int]: - """验证链接有效性,验证通过返回转存所需参数列表""" - # 对于有提取码的链接先验证提取码,试图获取更新 bdclnd - if password: - bdclnd = self.network.verify_pass_code(url, password) - # 如果 bdclnd 是错误代码,直接返回 - if isinstance(bdclnd, int): - return bdclnd - - # 更新 bdclnd 到 cookie - self.network.headers['Cookie'] = update_cookie(bdclnd, self.network.headers['Cookie']) - - # 直接访问没有提取码的链接,或更新 bdclnd 后再次访问,获取转存必须的 3 个参数 - response = self.network.get_transfer_params(url) - # 这里不考虑网络异常了,假设请求一定会返回页面内容,对其进行解析 - return parse_response(response) - - def check_only(self, result: Union[List[str], int], url_code: str) -> None: - """开启检查模式时,只管判断返回值类型,并输出结果到日志""" - if isinstance(result, list): - self.insert_logs(f'链接有效:{url_code} {"目录" if result[4] == ["1"] else "文件"}:{result[3]}') - else: - self.insert_logs(f'链接无效:{url_code} 原因:{ERROR_CODES.get(result, f"错误代码({result})")}') - - def creat_user_dir(self, folder_name: str) -> str: - """建立用户指定目录,返回完整路径。目录名从原始输入取,函数为 custom_mode 专用""" - self.check_condition(not folder_name, message='必须输入转存目录') - # 对原始输入进行分割 - link_org_sep = self.link_list_org[self.completed_task_count].split() - # 建立自定义目录,如果没有指定则用行数代替 - custom_folder = link_org_sep[0] - folder_name = f'{folder_name}/{custom_folder}' if 'pan.baidu.com' not in custom_folder else f'{folder_name}/{self.completed_task_count + 1}' - # 此处用替换处理目标目录名非法字符,不报错了 - folder_name = folder_name.translate(str.maketrans({char: '_' for char in INVALID_CHARS})) - self.handle_create_dir(folder_name) - - return folder_name - - def save_file(self, result: Union[List[str], int], url_code: str, folder_name: str) -> None: - """转存文件。返回结果为列表时,执行转存文件,否则跳过转存""" - file_info = "" - if isinstance(result, list): - file_info = f'{"目录" if result[4] == ["1"] else "文件"}:{result[3]}' - # 如果开启安全转存模式,对每个转存链接建立目录 - if self.custom_mode: - folder_name = self.creat_user_dir(folder_name) - result = self.network.transfer_file(result, folder_name) - file_info = f'{file_info} 转存到:{folder_name}' - # 改在这里检查链接重复,在日志中查找。链接有出现过不转存,直接赋值结果为错误代码 4 - elif url_code in self.root.text_logs.get('1.0', 'end'): - result = 4 - # 正常转存 - else: - result = self.network.transfer_file(result, folder_name) - - # 最后插入转存结果到日志框 - self.insert_logs(f'{ERROR_CODES.get(result, f"转存失败,错误代码({result})")}:{url_code} {file_info}') diff --git a/extend/util/utils.py b/extend/util/utils.py deleted file mode 100644 index c163bc5..0000000 --- a/extend/util/utils.py +++ /dev/null @@ -1,161 +0,0 @@ -""" -通用的工具函数,主要是一些静态函数。 - -:author: assassing -:contact: https://github.com/hxz393 -:copyright: Copyright 2024, hxz393. 保留所有权利。 -""" -import atexit -import base64 -import os -import random -import re -import string -import tempfile -import threading -import zlib -from typing import Any, Union, Tuple, Callable, List, Optional - -from src.constants import CONFIG_PATH, ICON_BASE64 - -# 预编译正则表达式 -SHARE_ID_REGEX = re.compile(r'"shareid":(\d+?),"') -USER_ID_REGEX = re.compile(r'"share_uk":"(\d+?)","') -FS_ID_REGEX = re.compile(r'"fs_id":(\d+?),"') -SERVER_FILENAME_REGEX = re.compile(r'"server_filename":"(.+?)","') -ISDIR_REGEX = re.compile(r'"isdir":(\d+?),"') - - -def thread_it(func: Callable, *args: Tuple[Any, ...]) -> None: - """ - 多线程防止转存时主界面卡死。 - - :param func: 要调用的函数 - :param args: 函数参数 - :return: 无返回值 - """ - t = threading.Thread(target=func, args=args) - t.start() - - -def write_config(config: str) -> None: - """ - 写入配置文件,点击批量转存或分享按钮时才运行。 - - :param config: 配置文件内容,以换行转义字符 '\n' 拼接多个配置到一行字符串 - :return: 无返回值 - """ - with open(CONFIG_PATH, 'w') as f: - f.write(config) - - -def read_config() -> Optional[List[str]]: - """ - 读取配置文件,在 UI 初始化完毕后执行。 - - :return: 读取成功时返回配置列表,一个元素一个配置。配置文件不存在时返回 None,什么也不会发生 - """ - if os.path.exists(CONFIG_PATH): - with open(CONFIG_PATH) as f: - config = f.read().splitlines() - return config - - -def create_icon() -> str: - """ - 从 base64 编码中生成临时图标,在程序结束时自动删除。 - 好处是打包时不用导入资源文件。 - - :return: 返回图标文件路径 - """ - with tempfile.NamedTemporaryFile(delete=False, suffix='.ico') as temp_file: - temp_file.write(zlib.decompress(base64.b64decode(ICON_BASE64))) - - ico_path = temp_file.name - # 显式声明程序退出时,删除临时图标文件,避免在个别系统平台自动删除失败 - atexit.register(os.remove, ico_path) - return ico_path - - -def normalize_link(url_code: str) -> str: - """ - 预处理链接至标准格式。 - - :param url_code: 需要处理的的原始链接格式 - :return: 返回标准格式:链接+空格+提取码 - """ - # 升级旧链接格式 - normalized = url_code.replace("share/init?surl=", "s/1") - # 替换掉 ?pwd= 或 &pwd= 为空格 - normalized = re.sub(r'[?&]pwd=', ' ', normalized) - # 替换掉提取码字样为空格 - normalized = re.sub(r'提取码*[::]', ' ', normalized) - # 替换 http 为 https,顺便处理掉开头没用的文字 - normalized = re.sub(r'^.*?(https?://)', 'https://', normalized) - # 替换连续的空格 - normalized = re.sub(r'\s+', ' ', normalized) - return normalized - - -def parse_url_and_code(url_code: str) -> Tuple[str, str]: - """ - 以空格分割出 URL 和提取码。 - - :param url_code: 输入的标准链接格式 - :return: 链接和提取码 - """ - # 不会分割失败 - url, code = map(str.strip, url_code.split(' ', 1)) - # 暴力切片,如果输入链接不是以提取码结尾,会得到错误提取码 - return url[:47], code[-4:] - - -def parse_response(response: str) -> Union[List[str], int]: - """ - 验证提取码通过后,再次访问网盘地址,此函数解析返回的页面源码并提取所需要参数。 - shareid_list 和 user_id_list 只有一个值,fs_id_list 需要完整返回 - - :param response: 响应内容 - :return: 没有获取到足够参数时,返回错误代码 -1;否则返回三个参数的列表 - """ - shareid_list = SHARE_ID_REGEX.findall(response) - user_id_list = USER_ID_REGEX.findall(response) - fs_id_list = FS_ID_REGEX.findall(response) - server_filename_list = SERVER_FILENAME_REGEX.findall(response) - isdir_list = ISDIR_REGEX.findall(response) - if not all([shareid_list, user_id_list, fs_id_list, server_filename_list, isdir_list]): - return -1 - - return [shareid_list[0], user_id_list[0], fs_id_list, list(dict.fromkeys(server_filename_list)), isdir_list] - - -def update_cookie(bdclnd: str, cookie: str) -> str: - """ - 更新 cookie 字符串,以包含新的 BDCLND 值。 - - :param bdclnd: 新的 BDCLND 值 - :param cookie: 当前的 cookie 字符串 - :return: 返回新 cookie 字符串 - """ - # 拆分 cookie 字符串到字典。先用 ; 分割成列表,再用 = 分割出键和值 - cookies_dict = dict(map(lambda item: item.split('=', 1), filter(None, cookie.split(';')))) - # 在 cookie 字典中,更新或添加 BDCLND 的值 - cookies_dict['BDCLND'] = bdclnd - # 从更新后的字典重新构建 cookie 字符串 - updated_cookie = ';'.join([f'{key}={value}' for key, value in cookies_dict.items()]) - - return updated_cookie - - -def generate_code() -> str: - """ - 生成一个四位的随机提取码,包含大小写字母和数字。 - - :return: 随机提取码 - """ - # 包含大小写字母和数字 - characters = string.ascii_letters + string.digits - # 随机选择四个字符 - code = ''.join(random.choice(characters) for _ in range(4)) - - return code