bug修改

This commit is contained in:
675061370
2025-03-28 22:03:01 +08:00
parent 0f68bfb937
commit cb6a08ea80
7 changed files with 2 additions and 1569 deletions
+1 -238
View File
@@ -12,244 +12,7 @@ class Index extends QfShop
{ {
public function index() public function index()
{ {
// return jok("Hello World!"); 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']);
}
} }
public function search() { public function search() {
+1 -1
View File
@@ -110,7 +110,7 @@ class QuarkPlugin
function processSingleData($value, $logId = 0, $total_result = 0, $isType = 0) 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($detail)) {
if (!empty($logId)) { if (!empty($logId)) {
$this->SourceLogModel->editLog($logId, $total_result, 'skip_num', '重复跳过转存'); $this->SourceLogModel->editLog($logId, $total_result, 'skip_num', '重复跳过转存');
-412
View File
@@ -1,412 +0,0 @@
<?php
namespace util;
class Network
{
protected $errorCodes = [
-1 => '链接错误,链接失效或缺少提取码',
-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
];
}
}
-211
View File
@@ -1,211 +0,0 @@
<?php
namespace util;
class Operations
{
protected $network;
protected $cookie;
protected $folderName;
protected $linkList;
protected $linkListOrg;
protected $totalTaskCount;
protected $completedTaskCount;
protected $running;
protected $customMode;
protected $checkMode;
protected $dirListAll;
const SAVE_LIMIT = 1000;
const DELAY_SECONDS = 0.1;
const INVALID_CHARS = ['<', '>', '|', '*', '?', '\\', ':'];
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;
}
}
-219
View File
@@ -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']
-327
View File
@@ -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("<Button-1>")
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}')
-161
View File
@@ -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