bug修改
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
@@ -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', '重复跳过转存');
|
||||||
|
|||||||
@@ -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
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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']
|
|
||||||
@@ -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}')
|
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user