更新3.0

This commit is contained in:
admin
2025-03-28 17:00:42 +08:00
parent 86038423e1
commit 6cc49273b6
62 changed files with 6470 additions and 5240 deletions

6
.env
View File

@@ -1,5 +1,5 @@
APP_DEBUG = false
SYSTEM_SALT= YAdmin
APP_DEBUG = true
SYSTEM_SALT= admin
[APP]
DEFAULT_TIMEZONE = Asia/Chongqing
@@ -7,7 +7,7 @@ DEFAULT_TIMEZONE = Asia/Chongqing
[DATABASE]
TYPE = mysql
HOSTNAME = 127.0.0.1
DATABASE = www_dj_com
DATABASE = www_abc_com
USERNAME = 123456
PASSWORD = 123456
HOSTPORT = 3306

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
/.svn
/.vscode
runtime
nginx.htaccess
.env

View File

@@ -1 +0,0 @@

View File

@@ -10,8 +10,18 @@
如有任何问题或建议,欢迎交流探讨! 😊
> **声明**:本项目由 Trae AI 辅助编写。由于本人时间有限,仅在空闲时维护。如遇使用问题,请优先自行排查,感谢理解!
## 更新日志
### v3.0
- 新增 项目安装向导,简化部署流程
- 新增 多网盘转存导入功能支持夸克、阿里、百度、UC
- 优化 解决部分数据导致的路由报错
- 优化 AI重构转存功能提升稳定性与效率
### v2.1
- 增加网页全网搜功能
@@ -20,7 +30,6 @@
- 后台增加批量删除功能
- 优化表格导入功能
如何更新见文件夹updateLog/24.11.4
### v2
@@ -33,53 +42,10 @@
- 支持多网盘导入功能(目前仅夸克支持转存分享)
- 增加资源分类功能
鉴于数据库改动大最快升级方式1、重新搭建新项目2、旧项目后台导出资源表格3、新项目导入这个表格
## 搭建教程
## 后台安装教程
[教程](https://tcn6g7hyxvir.feishu.cn/wiki/WYT4wZtrjijeswkI0RSc4ofTnah)
0、PHP选择7.2,其它版本不兼容)
1、上传源码到服务器
2、设置网站运行目录public
3、设置thinkphp伪静态
4、导入数据库文件
5、修改.env文件数据库参数
后台地址https://你的域名/qfadmin
账号密码admin 123456
## 常见问题
0、系统不再支持全部转存及每日更新有能力者可自行修改使用
1、全部转存执行1分钟~5分钟后中断问题修改超时限制
该操作用时很长请设置最大值86400 设置后需重启下服务
宝塔设置教程 https://www.kancloud.cn/loveouu/bthelp/1541867
2、非7.2版本导致的报错请自行解决不要再问不会就百度或者老老实实用7.2
3、nginx 404 Not Found 伪静态设置
```shell
location ~* (runtime|application)/{
return 403;
}
location / {
if (!-e $request_filename){
rewrite ^(.*)$ /index.php?s=$1 last; break;
}
}
```
4、网站经常打不开出现500的情况未具体找到原因可能是因为服务器配置太低分词功能占用内存过高导致的
升级服务器配置或后台修改搜索模式改为“精准搜索”;
5、全网搜的内容为临时分享资源需要配置计划任务 每5分钟执行一次 即可
https://XXXXX/api/other/delete_search
## 前台截图
@@ -96,13 +62,6 @@ https://XXXXX/api/other/delete_search
![image](github/2.png)
## 如何获取夸克网盘Cookie
登录夸克网盘后按下F12刷新页面
![image](github/cookie.jpg)
# 免费交流社群
可以进交流群,一起交流学习,添加时请备注来源(如果项目对你有所帮助,也可以请我喝杯咖啡 ☕️ ~

View File

@@ -342,7 +342,6 @@ class Source extends QfShop
$this->excelField = $excelField;
$this->exportExcelData($dataList);
print_r(12);
}
/**
@@ -362,50 +361,7 @@ class Source extends QfShop
$source_category_id = input('source_category_id')??0;
$urls = input("urls");
$urls = explode("\n", $urls);
// 去掉数组元素中的空白字符
$urls = array_map('trim', $urls);
// 过滤掉空值的数组元素
$urls = array_filter($urls);
$allData = array_values(array_filter(array_map(function ($item) {
// 提取 URL
if (!preg_match('/https?:\/\/[^\s]+/', $item, $matches)) {
return null; // 没有匹配到 URL直接丢弃
}
$url = trim($matches[0]);
$code = '';
// 提取提取码(?pwd= 或 , 分割)
if (preg_match('/\?pwd=([^,\s]+)/', $item, $pwdMatch)) {
$code = trim($pwdMatch[1]);
} elseif (preg_match('/,(.+)$/', $item, $commaMatch)) {
$code = trim($commaMatch[1]);
}
// 返回结果时,确保 title 保持为空字符串
return [
'url' => $url,
'title' => '',
'code' => $code
];
}, $urls)));
// 去重,使用 'url' 字段来去重
$uniqueUrls = [];
$allData = array_filter($allData, function($item) use (&$uniqueUrls) {
if (!in_array($item['url'], $uniqueUrls)) {
$uniqueUrls[] = $item['url']; // 添加到已处理的 URL 列表
return true; // 保留此项目
}
return false; // 去掉重复的项目
});
$allData = parsePanLinks(input("urls"));
$quarkPlugin = new QuarkPlugin();
if(input("type")==2){
@@ -416,7 +372,7 @@ class Source extends QfShop
$res = $quarkPlugin->import($allData,$source_category_id);
}
return jok('已提交任务稍后查看结果2',$res);
return jok('已提交任务稍后查看结果2',$allData);
}
/**
@@ -449,10 +405,13 @@ class Source extends QfShop
if ($error) {
return $error;
}
$quarkPlugin = new QuarkPlugin();
$result = $quarkPlugin->getFiles(Config('qfshop.quark_cookie'));
return jok('获取成功',$result);
$result = $quarkPlugin->getFiles(input('type')??0,input('pdir_fid')??0);
if($result['code'] != 200){
return jerr($result['message']);
}
return jok('获取成功',$result['data']);
}
}

View File

@@ -12,8 +12,246 @@ class Index extends QfShop
{
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() {
$fc = new VicWord();
$keywords = input('keywords');

View File

@@ -5,703 +5,48 @@ namespace app\api\controller;
use think\App;
use think\facade\Cache;
use app\api\QfShop;
use app\model\Source as SourceModel;
class Open extends QfShop
{
public function __construct(App $app)
{
parent::__construct($app);
$this->url = "";
$this->is_type = 0;
$this->cookie = ""; //夸克登录凭证
$this->Authorization = ""; //阿里登录凭证
$this->expired_type = 1; //1分享永久 2临时
$this->to_pdir_fid = ""; //存入目标文件
$this->ad_fid = "";
$this->code = ""; //提取码
$this->isType = 0; //等于1时仅校验是否有效并提取资源信息
$this->urlHeader = array(
'Accept: application/json, text/plain, */*',
'Accept-Language: zh-CN,zh;q=0.9',
'content-type: application/json;charset=UTF-8',
'sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform: "Windows"',
'sec-fetch-dest: empty',
'sec-fetch-mode: cors',
'sec-fetch-site: same-site',
'Referer: https://pan.quark.cn/',
'Referrer-Policy: strict-origin-when-cross-origin',
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
);
}
/**
*
*
* @return void
*/
public function index()
{
return jok('Hello World');
}
/**
* 获取夸克网盘文件夹
*
* @return void
*/
public function getFiles()
{
$this->cookie = input('cookie')??'';
$urlData = [];
$queryParams = [
'pr' => 'ucpro',
'fr' => 'pc',
'uc_param_str' => '',
'pdir_fid' => 0,
'_page' => 1,
'_size' => 50,
'_fetch_total' => 1,
'_fetch_sub_dirs' => 0,
'_sort' => 'file_type:asc,updated_at:desc',
];
$this->urlHeader[] = 'cookie: ' . $this->cookie;
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr($res['message']=='require login [guest]'?'夸克未登录请检查cookie':$res['message']);
}
return jok('获取成功',$res['data']['list']);
}
/**
* 一键转存并分享资源
*
* type 0 夸克 1阿里
*
* @return void
*/
public function transfer()
{
$url = input("url");
$this->code = input('code')??'';
$this->isType = input('isType')??0;
if($this->isType != 1){ //直接入口就不用cookie 防止账号异常
$this->cookie = input('cookie')??'';
$this->Authorization = input('Authorization')??'';
}
$this->expired_type = input('expired_type')??1;
$this->to_pdir_fid = input('to_pdir_fid')??"";
$this->ad_fid = input('ad_fid')??"";
if (strpos($url, '?entry=') !== false) {
$entry = preg_match('/\?entry=([^&]+)/', $url, $matches) ? $matches[1] : '';
$url = preg_match('/.*(?=\?entry=)/', $url, $matches) ? $matches[0] : '';
}
$substring = strstr($url, 's/');
if ($substring !== false) {
$pwd_id = substr($substring, 2); // 去除 's/' 部分
} else {
return jerr("资源地址格式有误");
}
$this->urlHeader[] = 'cookie: ' . $this->cookie;
$patterns = [
'pan.quark.cn' => 0,
'www.alipan.com' => 1,
'www.aliyundrive.com' => 1,
// 'pan.baidu.com' => 2,
'drive.uc.cn' => 3,
// 'pan.xunlei.com' => 4,
$urlData = [
'expired_type' => input('expired_type')??1, // 1正式资源 2临时资源
'url' => input("url")?? '',
'code' => input('code')??'',
'isType' => input('isType')??0,
];
$url_type = -1; // 默认值为 -1
foreach ($patterns as $pattern => $type) {
if (strpos($url, $pattern) !== false) {
$url_type = $type;
break; // 一旦匹配成功,退出循环
}
}
$this->url = $url;
if ($url_type == 0) {
//夸克
if(empty($this->cookie) && $this->isType==0){
jerr("参数有误");
}
$this->transferQuark(strtok($pwd_id, '#'));
} else if($url_type == 1){
//阿里
$this->transferAlipan($pwd_id);
} else if($url_type == 3){
//UC
$this->transferUc($pwd_id);
} else {
return jerr("资源地址格式有误");
}
}
/**
* 夸克 - 一键转存并分享资源
*
* @return void
*/
public function transferQuark($pwd_id){
//获取要转存夸克资源的stoken
$infoData = $this->getStoken($pwd_id);
if($this->isType == 1){
$urls['title'] = $infoData['title'];
$urls['share_url'] = $this->url;
return jok('检验成功', $urls);
}
$stoken = $infoData['stoken'];
//获取要转存夸克资源的详细内容
$detail = $this->getShare($pwd_id,$stoken);
$fid_list = [];
$fid_token_list = [];
$title = $detail['share']['title']; //资源名称
foreach ($detail['list'] as $key => $value) {
$fid_list[] = $value['fid'];
$fid_token_list[] = $value['share_fid_token'];
if(empty($urlData['url'])){
return jerr('资源地址不能为空');
}
//转存资源到指定文件夹
$task_id = $this->getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list);
$transfer = new \netdisk\Transfer();
$res = $transfer->transfer($urlData);
//转存后根据task_id获取转存到自己网盘后的信息
$retry_index = 0;
$myData = '';
while ($myData=='' || $myData['status'] != 2) {
$myData = $this->getShareTask($task_id, $retry_index);
$retry_index++;
// 可以添加一个最大重试次数的限制,防止无限循环
if ($retry_index > 50) {
break;
}
}
try {
//删除转存后可能有的广告
$banned = Config('qfshop.quark_banned')??''; //如果出现这些字样就删除
if(!empty($banned)){
$bannedList = explode(',', $banned);
$pdir_fid = $myData['save_as']['save_as_top_fids'][0];
$dellist = [];
$plist = $this->getPdirFid($pdir_fid);
if(!empty($plist)){
foreach ($plist as $key => $value) {
// 检查$value['file_name']是否包含$bannedList中的任何一项
$contains = false;
foreach ($bannedList as $item) {
if (strpos($value['file_name'], $item) !== false) {
$contains = true;
break;
}
}
if ($contains) {
$dellist[] = $value['fid'];
}
}
if(count($plist) === count($dellist)){
//要删除的资源数如果和原数据资源数一样 就全部删除并终止下面的分享
$this->deletepdirFid([$pdir_fid]);
return jerr("资源内容为空");
}else{
if (!empty($dellist)) {
$this->deletepdirFid($dellist);
}
}
}
}
} catch (Exception $e) {
}
$shareFid = $myData['save_as']['save_as_top_fids'];
//分享资源并拿到更新后的task_id
$task_id = $this->getShareBtn($myData['save_as']['save_as_top_fids'],$title);
//根据task_id拿到share_id
$retry_index = 0;
$myData = '';
while ($myData=='' || $myData['status'] != 2) {
$myData = $this->getShareTask($task_id, $retry_index);
$retry_index++;
// 可以添加一个最大重试次数的限制,防止无限循环
if ($retry_index > 50) {
break;
}
}
//根据share_id 获取到分享链接
$share = $this->getSharePassword($myData['share_id']);
// $share['fid'] = $share['first_file']['fid'];
$share['fid'] = (is_array($shareFid) && count($shareFid) > 1) ? $shareFid : $share['first_file']['fid'];
return jok('转存成功', $share);
}
/**
* 阿里 - 一键转存并分享资源
*
* @return void
*/
public function transferAlipan($share_id)
{
$data = [];
$infos = $this->getAlipan1($share_id);
if($this->isType == 1){
$urls['title'] = $infos['share_name'];
$urls['share_url'] = $this->url;
return jok('检验成功', $urls);
}else{
return jerr('阿里暂不支持转存');
}
//通过分享id获取file_id
$file_infos = $infos['file_infos'];
//通过分享id获取X-Share-Token
$share_token = $this->getAlipan2($share_id);
$data3['requests'] = [];
$data3['resource'] = 'file';
foreach ($file_infos as $key=>$value) {
$data3['requests'][$key]['body']['auto_rename'] = true;
$data3['requests'][$key]['body']['file_id'] = $value['file_id'];
$data3['requests'][$key]['body']['share_id'] = $share_id;
$data3['requests'][$key]['body']['to_drive_id'] = '2008425230';
$data3['requests'][$key]['body']['to_parent_file_id'] = '66a20824c3846d890f6542c6aac2c79822d2a64f';
$data3['requests'][$key]['headers']['Content-Type'] = 'application/json';
$data3['requests'][$key]['id'] = $key.'';
$data3['requests'][$key]['method'] = 'POST';
$data3['requests'][$key]['url'] = '/file/copy';
}
//保存
$responses = $this->getAlipan3($data3,$share_token);
$data4['drive_id'] = '2008425230';
$data4['expiration'] = '';
$data4['share_pwd'] = '';
$data4['file_id_list'] = [];
foreach ($responses as $key=>$value){
$data4['file_id_list'][] = $value['body']['file_id'];
}
//分享
$share = $this->getAlipan4($data4);
$data['share_url'] = $share['share_url'];
$data['title'] = $share['share_title'];
return jok('转存成功', $data);
}
/**
* UC- 一键转存并分享资源
*
* @return void
*/
public function transferUc($pwd_id){
$infoData = $this->getStokenUc($pwd_id);
if($this->isType == 1){
$urls['title'] = $infoData['title'];
$urls['share_url'] = $this->url;
return jok('检验成功', $urls);
}else{
return jerr('UC暂不支持转存');
}
}
/**
* 获取要转存资源的stoken
*
* @return void
*/
public function getStoken($pwd_id)
{
$urlData = array(
'passcode' => '',
'pwd_id' => $pwd_id,
);
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/token?pr=ucpro&fr=pc&uc_param_str=", "POST",json_encode($urlData), $this->urlHeader)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
if($res['code'] !== 200){
return jerr($res['message']);
}
$data = $res['data'];
return $data;
$isSave = input('isSave')??0;
if($isSave == 1){
$data = [
"title" => $res['data']['title'],
"url" => $res['data']['share_url'],
"is_type" => determineIsType($res['data']['share_url']),
"code" => $res['data']['code'] ?? $urlData['code'] ?? '',
"is_time" => $urlData['expired_type']==2 ? 1 : 0,
"update_time" => time(),
"create_time" => time(),
"fid" => is_array($res['data']['fid'] ?? '') ? json_encode($res['data']['fid']) : ($res['data']['fid'] ?? '')
];
$SourceModel = new SourceModel();
$SourceModel->insertGetId($data);
}
return jok("获取成功",$res['data']);
}
/**
* 获取要转存资源的详细内容
*
* @return void
*/
public function getShare($pwd_id,$stoken)
{
$urlData = array();
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => "",
"pwd_id" => $pwd_id,
"stoken" => $stoken,
"pdir_fid" => "0",
"force" => "0",
"_page" => "1",
"_size" => "100",
"_fetch_banner" => "1",
"_fetch_share" => "1",
"_fetch_total" => "1",
"_sort" => "file_type:asc,updated_at:desc"
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr($res['message']);
}
return $res['data'];
}
/**
* 转存资源到指定文件夹
*
* @return void
*/
public function getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list)
{
$to_pdir_fid = $this->to_pdir_fid??"";
$urlData = array(
'fid_list' => $fid_list,
'fid_token_list' => $fid_token_list,
'to_pdir_fid' => $to_pdir_fid,
'pwd_id' => $pwd_id,
'stoken' => $stoken,
'pdir_fid' => "0",
'scene' => "link",
);
$queryParams = [
"entry" => "update_share",
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/save", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr($res['message']=='require login [guest]'?'夸克未登录请检查cookie':$res['message']);
}
return $res['data']['task_id'];
}
/**
* 分享资源拿到task_id
*
* @return void
*/
public function getShareBtn($fid_list,$title)
{
if(!empty($this->ad_fid)){
$fid_list[] = $this->ad_fid;
}
$urlData = array(
'fid_list' => $fid_list,
'expired_type' => $this->expired_type,
'title' => $title,
'url_type' => 1,
);
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr($res['message']);
}
return $res['data']['task_id'];
}
/**
* 根据task_id拿到自己的资源信息
*
* @return void
*/
public function getShareTask($task_id,$retry_index)
{
$urlData = array();
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => "",
"task_id" => $task_id,
"retry_index" => $retry_index
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/task", "GET", json_encode($urlData), $this->urlHeader, $queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr($res['message']);
}
return $res['data'];
}
/**
* 根据share_id 获取到分享链接
*
* @return void
*/
public function getSharePassword($share_id)
{
$urlData = array(
'share_id' => $share_id,
);
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/password", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr($res['message']);
}
return $res['data'];
}
/**
* 删除指定资源
*
* @return void
*/
public function deletepdirFid($filelist)
{
$urlData = array(
'action_type' => 2,
'exclude_fids' => [],
'filelist' => $filelist,
);
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/delete", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
}
/**
* 获取夸克网盘指定文件夹内容
*
* @return void
*/
public function getPdirFid($pdir_fid)
{
$urlData = [];
$queryParams = [
'pr' => 'ucpro',
'fr' => 'pc',
'uc_param_str' => '',
'pdir_fid' => $pdir_fid,
'_page' => 1,
'_size' => 200,
'_fetch_total' => 1,
'_fetch_sub_dirs' => 0,
'_sort' => 'file_type:asc,updated_at:desc',
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return [];
}
return $res['data']['list'];
}
/**
* 阿里-0-通过分享id获取file_id
*
* @return void
*/
public function getAlipan1($share_id)
{
$urlData = [
'share_id' => $share_id,
];
$urlHeader = array(
'Content-Type: application/json',
);
$res = curlHelper("https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous", "POST", json_encode($urlData),$urlHeader)['body'];
$res = json_decode($res, true);
if(!isset($res['file_infos'])){
return jerr($res['message']??'转存失败1');
}
return $res;
}
/**
* 阿里-0-通过分享id获取X-Share-Token
*
* @return void
*/
public function getAlipan2($share_id)
{
$urlData = array(
'share_id' => $share_id,
);
$urlHeader = array(
'Accept: application/json, text/plain, */*',
'Accept-Encoding: gzip, deflate, br, zstd',
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization: '.$this->Authorization,
'Content-Type: application/json',
'Origin: https://www.alipan.com',
'Priority: u=1, i',
'Referer: https://www.alipan.com/',
'Sec-Ch-Ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile: ?0',
'Sec-Ch-Ua-Platform: "Windows"',
'Sec-Fetch-Dest: empty',
'Sec-Fetch-Mode: cors',
'Sec-Fetch-Site: same-site',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0',
'X-Canary: client=web,app=share,version=v2.3.1'
);
$res = curlHelper("https://api.aliyundrive.com/v2/share_link/get_share_token", "POST", json_encode($urlData), $urlHeader)['body'];
$res = json_decode($res, true);
if(!isset($res['share_token'])){
return jerr($res['message']??'转存失败2');
}
return $res['share_token'];
}
/**
* 阿里-1-保存
*
* @return void
*/
public function getAlipan3($urlData,$share_token)
{
$urlHeader = array(
'Accept: application/json, text/plain, */*',
'Accept-Encoding: gzip, deflate, br, zstd',
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization: '.$this->Authorization,
'Content-Type: application/json',
'Origin: https://www.alipan.com',
'Priority: u=1, i',
'Referer: https://www.alipan.com/',
'Sec-Ch-Ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile: ?0',
'Sec-Ch-Ua-Platform: "Windows"',
'Sec-Fetch-Dest: empty',
'Sec-Fetch-Mode: cors',
'Sec-Fetch-Site: same-site',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0',
'X-Canary: client=web,app=share,version=v2.3.1',
'X-Share-Token: '.$share_token
);
$res = curlHelper("https://api.aliyundrive.com/adrive/v4/batch", "POST", json_encode($urlData), $urlHeader)['body'];
$res = json_decode($res, true);
if(!isset($res['responses'])){
return jerr($res['message']??'转存失败3');
}
return $res['responses'];
}
/**
* 阿里-2-分享
*
* @return void
*/
public function getAlipan4($urlData)
{
$urlHeader = array(
'Accept: application/json, text/plain, */*',
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization: '.$this->Authorization,
'Content-Type: application/json',
'Origin: https://www.alipan.com',
'Priority: u=1, i',
'Referer: https://www.alipan.com/',
'Sec-Ch-Ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile: ?0',
'Sec-Ch-Ua-Platform: "Windows"',
'Sec-Fetch-Dest: empty',
'Sec-Fetch-Mode: cors',
'Sec-Fetch-Site: same-site',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0',
'X-Canary: client=web,app=share,version=v2.3.1'
);
$res = curlHelper("https://api.aliyundrive.com/adrive/v2/share_link/create", "POST", json_encode($urlData), $urlHeader)['body'];
$res = json_decode($res, true);
return jerr($res);
if(!isset($res['share_url'])){
return jerr($res['message']??'转存失败4');
}
return $res;
}
/**
* UC 00000
*
* @return void
*/
public function getStokenUc($pwd_id)
{
$urlData = array(
'passcode' => '',
'pwd_id' => $pwd_id,
);
$urlHeader = array(
'Accept: application/json, text/plain, */*',
'Accept-Language: zh-CN,zh;q=0.9',
'content-type: application/json;charset=UTF-8',
'sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform: "Windows"',
'sec-fetch-dest: empty',
'sec-fetch-mode: cors',
'sec-fetch-site: same-site',
'Referer: https://drive.uc.cn/',
'Referrer-Policy: strict-origin-when-cross-origin',
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
);
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/sharepage/token?pr=UCBrowser&fr=pc", "POST",json_encode($urlData),$urlHeader)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr($res['message']);
}
$data = $res['data'];
return $data;
}
}

View File

@@ -15,7 +15,6 @@ class Other extends QfShop
{
parent::__construct($app);
$this->model = new SourceModel();
$this->cookie = Config('qfshop.quark_cookie');
}
/**
@@ -25,14 +24,9 @@ class Other extends QfShop
*/
public function all_search($param='')
{
if(empty($param)){
$searchdata = input('post.');
if (empty($searchdata['title'])) {
return jerr("请输入要看的内容");
}
$title = $searchdata['title'];
}else{
$title = $param;
$title = $param ?: input('post.title', '');
if (empty($title)) {
return jerr("请输入要看的内容");
}
$map[] = ['status', '=', 1];
@@ -42,29 +36,17 @@ class Other extends QfShop
$urls = $this->model->where($map)->field('source_id as id, title, url,is_time')->order('update_time', 'desc')->limit(5)->select()->toArray();
if (!empty($urls)) {
// 获取所有需要更新的ID
$ids = [];
foreach ($urls as $item) {
$ids[] = $item['id'];
}
// 更新数据库中的 update_time 字段
if (!empty($ids)) {
$this->model->whereIn('source_id', $ids)->update(['update_time' => time()]);
}
$ids = array_column($urls, 'id');
$this->model->whereIn('source_id', $ids)->update(['update_time' => time()]);
return !empty($param) ? $urls : jok('临时资源获取成功', $urls);
}
//同一个搜索内容锁机
if (Cache::has($title)) {
//同一个搜索内容锁机
if (Cache::has($title)) {
// 检查缓存中是否已有结果
return !empty($param) ? Cache::get($title) : jok('临时资源获取成功', Cache::get($title));
}
// 检查是否有正在处理的请求
if (Cache::has($title . '_processing')) {
// 如果当前正在处理相同关键词的请求,等待结果
@@ -89,41 +71,19 @@ class Other extends QfShop
$num_total = 2; //最多想要几条结果
$num_success = 0;
// 处理第2个源
foreach (source2($title) as $value) {
// 定义源的顺序
$sources = ['source2', 'source4', 'source5', 'source3', 'source1'];
foreach ($sources as $source) {
if ($num_success >= $num_total) {
break; // 有效结果数量已达到,则跳出循环
break;
}
// 如果 URL 不存在则新增 $value
if (!$this->urlExists($searchList, $value['url'])) {
$searchList[] = $value;
$this->processUrl($value, $num_success, $datas);
}
}
// 处理第4个源
if ($num_success < $num_total) {
foreach (source4($title) as $value) {
foreach ($source($title) as $value) {
if ($num_success >= $num_total) {
break; // 有效结果数量已达到,则跳出循环
break;
}
// 如果 URL 不存在则新增 $value
if (!$this->urlExists($searchList, $value['url'])) {
$searchList[] = $value;
$this->processUrl($value, $num_success, $datas);
}
}
}
// 处理第5个源
if ($num_success < $num_total) {
foreach (source5($title) as $value) {
if ($num_success >= $num_total) {
break; // 有效结果数量已达到,则跳出循环
}
// 如果 URL 不存在则新增 $value
if (!$this->urlExists($searchList, $value['url'])) {
$searchList[] = $value;
$this->processUrl($value, $num_success, $datas);
@@ -131,36 +91,6 @@ class Other extends QfShop
}
}
// 处理第3个源
if ($num_success < $num_total) {
foreach (source3($title) as $value) {
if ($num_success >= $num_total) {
break; // 有效结果数量已达到,则跳出循环
}
// 如果 URL 不存在则新增 $value
if (!$this->urlExists($searchList, $value['url'])) {
$searchList[] = $value;
$this->processUrl($value, $num_success, $datas);
}
}
}
// 处理第1个源 第一个源放最后
if ($num_success < $num_total) {
foreach (source1($title) as $value) {
if ($num_success >= $num_total) {
break; // 有效结果数量已达到,则跳出循环
}
// 如果 URL 不存在则新增 $value
if (!$this->urlExists($searchList, $value['url'])) {
$searchList[] = $value;
$this->processUrl($value, $num_success, $datas);
}
}
}
Cache::set($title, $datas, 60); // 缓存结果60秒
Cache::delete($title . '_processing'); // 解锁
@@ -201,15 +131,14 @@ class Other extends QfShop
$pwd_id = substr($substring, 2); // 去除 's/' 部分
$urlData = array(
'cookie' => $this->cookie,
'url' => $value['url'],
'expired_type' => 2,
'to_pdir_fid' => '', //存入目标文件
'ad_fid' => '', //分享时带上这个文件
);
$res = curlHelper(Request::domain()."/api/open/transfer", "POST", $urlData)['body'];
$res = json_decode($res, true);
$transfer = new \netdisk\Transfer();
$res = $transfer->transfer($urlData);
if($res['code'] !== 200){
return; // 模拟 continue 行为
}
@@ -247,40 +176,15 @@ class Other extends QfShop
$this->model->where($map)->chunk(100, function ($order) {
foreach ($order as $value) {
$deles = $value->toArray();
// $filelist = [];
// $filelist[] = $deles['fid'];
$fid = $deles['fid'];
// 尝试解码,如果是有效的 JSON 数组则使用,否则转为单元素数组
$filelist = (is_string($fid) && ($decodedFid = json_decode($fid, true)) && is_array($decodedFid)) ? $decodedFid : (array)$fid;
$urlData = array(
'action_type' => 2,
'exclude_fids' => [],
'filelist' => $filelist,
);
$urlHeader = array(
'Accept: application/json, text/plain, */*',
'Accept-Language: zh-CN,zh;q=0.9',
'content-type: application/json;charset=UTF-8',
'sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform: "Windows"',
'sec-fetch-dest: empty',
'sec-fetch-mode: cors',
'sec-fetch-site: same-site',
'Referer: https://pan.quark.cn/',
'Referrer-Policy: strict-origin-when-cross-origin',
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'cookie: '.$this->cookie,
);
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/delete?pr=ucpro&fr=pc&uc_param_str=", "POST", json_encode($urlData), $urlHeader)['body'];
$res = json_decode($res, true);
if($res['status'] == 200){
$this->model->where('fid', $deles['fid'])->delete();
}
$this->model->where('fid', $deles['fid'])->delete();
$transfer = new \netdisk\Transfer();
$transfer->deletepdirFid($deles['is_type'],$filelist);
}
});

View File

@@ -17,6 +17,14 @@ function jok($message = 'success', $data = null)
}
die;
}
function jok2($message = 'success', $data = null)
{
if ($data) {
return ["code" => 200, "message" => $message, 'data' => $data];
} else {
return ["code" => 200, "message" => $message, 'data' => $data??''];
}
}
/**
* 输出错误JSON
*
@@ -30,6 +38,10 @@ function jerr($message = 'error', $code = 500)
echo json_encode(["code" => $code, "message" => $message]);
die;
}
function jerr2($message = 'error', $code = 500)
{
return ["code" => $code, "message" => $message];
}
/**
* 密码+盐 加密
*
@@ -789,6 +801,85 @@ function determineIsType($url) {
}
}
/**
* 解析网盘分享链接
*
* @param string $input 输入的链接文本,可以是多行
* @return array 解析后的链接数组每个元素包含url、title和code
*/
function parsePanLinks($input)
{
// 分割多行输入
$links = explode("\n", $input);
// 去掉数组元素中的空白字符
$links = array_map('trim', $links);
// 过滤掉空值的数组元素
$links = array_filter($links);
$parsedLinks = array_values(array_filter(array_map(function ($item) {
// 统一处理百度网盘分享格式,同时匹配两种格式
if (preg_match('/链接:?\s*(https?:\/\/[^\s]+)\s*提取码:?\s*([a-zA-Z0-9]{4})/i', $item, $matches)) {
$url = trim($matches[1]);
$code = trim($matches[2]);
// 确保URL中包含提取码
if (!empty($code) && strpos($url, '?pwd=') === false) {
$url .= (strpos($url, '?') === false ? '?' : '&') . 'pwd=' . $code;
}
return [
'url' => $url,
'title' => '',
'code' => $code
];
}
// 提取 URL
if (!preg_match('/https?:\/\/[^\s]+/', $item, $matches)) {
return null; // 没有匹配到 URL直接丢弃
}
$url = trim($matches[0]);
$code = '';
// 提取提取码(?pwd= 或 , 分割)
if (preg_match('/\?pwd=([^,\s&]+)/', $item, $pwdMatch)) {
$code = trim($pwdMatch[1]);
} elseif (preg_match('/,\s*([a-zA-Z0-9]{4})\s*$/', $item, $commaMatch)) {
$code = trim($commaMatch[1]);
// 添加提取码到URL
if (!empty($code)) {
$url .= (strpos($url, '?') === false ? '?' : '&') . 'pwd=' . $code;
}
} elseif (preg_match('/提取码:?\s*([a-zA-Z0-9]{4})/i', $item, $codeMatch)) {
$code = trim($codeMatch[1]);
// 添加提取码到URL
if (!empty($code)) {
$url .= (strpos($url, '?') === false ? '?' : '&') . 'pwd=' . $code;
}
}
// 返回结果时,确保 title 保持为空字符串
return [
'url' => $url,
'title' => '',
'code' => $code
];
}, $links)));
// 去重,使用 'url' 字段来去重
$uniqueUrls = [];
$result = array_filter($parsedLinks, function($item) use (&$uniqueUrls) {
if (!in_array($item['url'], $uniqueUrls)) {
$uniqueUrls[] = $item['url']; // 添加到已处理的 URL 列表
return true; // 保留此项目
}
return false; // 去掉重复的项目
});
return array_values($result);
}
/**
* 网络资源搜索源一

View File

@@ -60,6 +60,9 @@ class Source extends QfShop
*/
public function getDetail(array $data)
{
if(empty($data['id'])){
return jerr('参数错误');
}
$map[] = ['status', '=', 1];
$map[] = ['is_delete', '=', 0];
$map[] = ['source_id', '=', $data['id']];

View File

@@ -34,7 +34,7 @@
{/volist}
<div style="height: 120px;"></div>
</el-menu>
<div class="version">资源管理系统<br> Version 1.0.0</div>
<div class="version">资源管理系统<br> Version 3.0.0</div>
</el-aside>
{else}
<div style="position: absolute;top: -72px;left: 0;width: 160px;background-color: #ffffff;box-shadow: 0 0 12px 0 rgb(47 75 168 / 6%);border-radius: 12px;">

View File

@@ -10,27 +10,181 @@
</style>
{include file="common/header"/}
<el-card class="box-card" shadow="never">
<div slot="header" class="clearfix">
<span>目前仅支持夸克</span>
</div>
<div class="text item">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>设置夸克cookie</span>
</div>
<div style="font-size:14px;color:#666;">
<el-form :model="form">
<el-form-item>
<el-input style="width: 100%;" v-model="form.cookie" placeholder="夸克云盘网页版的cookie"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
<p>Tips夸克云盘网页版的cookie,不懂如何获取请百度;填写并保存后才能使用下面功能</p>
</div>
</el-card>
</div>
<el-tabs v-model="activeName">
<el-tab-pane label="夸克网盘" name="0">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>夸克网盘</span>
</div>
<div style="font-size:14px;color:#666;">
<el-form :model="form">
<el-form-item label="设置cookie">
<el-input style="width: 100%;" v-model="form.quark_cookie" placeholder="请输入">
<el-button slot="prepend" @click="getFile(0)">账号检测</el-button>
</el-input>
<span class="f_tips">cookie修改后请保存后再选择转存目录和检测</span>
</el-form-item>
<el-form-item label="默认转存目录">
<el-input style="width: 100%" v-model="form.quark_file" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.quark_file" :props="quark_props"></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item label="临时资源目录">
<el-input style="width: 100%" v-model="form.quark_file_time" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.quark_file_time" :props="quark_props"></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
</el-tab-pane>
<el-tab-pane label="阿里云盘" name="1">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>阿里云盘</span>
</div>
<div style="font-size:14px;color:#666;">
<el-form :model="form">
<el-form-item label="设置Token">
<el-input style="width: 100%;" v-model="form.Authorization" placeholder="请输入">
<el-button slot="prepend" @click="getFile(1)">账号检测</el-button>
</el-input>
<span class="f_tips">token修改后请保存后再选择转存目录和检测</span>
</el-form-item>
<el-form-item label="默认转存目录">
<el-input style="width: 100%" v-model="form.ali_file" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.ali_file" :props="ali_props"></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item label="临时资源目录">
<el-input style="width: 100%" v-model="form.ali_file_time" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.ali_file_time" :props="ali_props"></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
<p>Tips转存目录不能为空为空将无法转存; <font color=orangered>阿里网盘禁止香港及海外服务器调用分享接口</font></p>
</div>
</el-card>
</el-tab-pane>
<el-tab-pane label="百度网盘" name="2">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>百度网盘</span>
</div>
<div style="font-size:14px;color:#666;">
<el-form :model="form">
<el-form-item label="设置cookie">
<el-input style="width: 100%;" v-model="form.baidu_cookie" placeholder="请输入">
<el-button slot="prepend" @click="getFile(2)">账号检测</el-button>
</el-input>
<span class="f_tips">cookie修改后请保存后再选择转存目录和检测</span>
</el-form-item>
<el-form-item label="默认转存目录">
<el-input style="width: 100%;" v-model="form.baidu_file" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.baidu_file" :props="baidu_props"></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item label="临时资源目录">
<el-input style="width: 100%;" v-model="form.baidu_file_time" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.baidu_file_time" :props="baidu_props" ></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
</el-tab-pane>
<el-tab-pane label="UC网盘" name="3">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>UC网盘</span>
</div>
<div style="font-size:14px;color:#666;">
<el-form :model="form">
<el-form-item label="设置cookie">
<el-input style="width: 100%;" v-model="form.uc_cookie" placeholder="请输入">
<el-button slot="prepend" @click="getFile(3)">账号检测</el-button>
</el-input>
<span class="f_tips">cookie修改后请保存后再选择转存目录和检测</span>
</el-form-item>
<el-form-item label="默认转存目录">
<el-input style="width: 100%" v-model="form.uc_file" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.uc_file" :props="uc_props"></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item label="临时资源目录">
<el-input style="width: 100%" v-model="form.uc_file_time" placeholder="请输入或选择">
<div slot="prepend" style="position: relative;">
<p>请选择</p>
<el-cascader style="opacity: 0; height: 38px; position: absolute;inset: 0;" v-model="form.uc_file_time" :props="uc_props"></el-cascader>
</div>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
</el-tab-pane>
<el-tab-pane label="迅雷云盘" name="4">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>迅雷云盘</span>
</div>
<div style="font-size:14px;color:#666;">
<p>Tips<font color=orangered>迅雷云盘暂不支持</font></p>
<el-form :model="form">
<el-form-item label="设置cookie">
<el-input style="width: 100%;" v-model="form.xunlei_cookie" placeholder="请输入">
</el-input>
<span class="f_tips">cookie修改后请保存后再选择转存目录和检测</span>
</el-form-item>
<el-form-item label="默认转存目录">
<el-input style="width: 100%" v-model="form.xunlei_file" placeholder="请输入或选择">
</el-input>
</el-form-item>
<el-form-item label="临时资源目录">
<el-input style="width: 100%" v-model="form.xunlei_file_time" placeholder="请输入或选择">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
</el-tab-pane>
</el-tabs>
</el-card>
{include file="common/footer"/}
<script>
@@ -38,16 +192,140 @@
el: '#app',
data() {
return {
activeName: '0',
configKeys: ['quark_cookie', 'quark_file', 'quark_file_time','baidu_cookie', 'baidu_file', 'baidu_file_time','Authorization', 'ali_file', 'ali_file_time','uc_cookie', 'uc_file', 'uc_file_time','xunlei_cookie', 'xunlei_file', 'xunlei_file_time'],
form: {
cookie: '',
quark_file: ''
},
quark_files: [],
quark_props: {
lazy: true,
emitPath: false,
checkStrictly: true,
lazyLoad (node, resolve) {
const { level, value } = node;
let that = this;
axios.post('/admin/source/getFiles', Object.assign({
type: 0,
pdir_fid: value || 0
}, PostBase))
.then(function (res) {
if (res.data.code == 200) {
// 只显示文件夹
const nodes = (res.data.data || [])
.map(item => ({
value: item.fid,
label: item.file_name,
disabled: !item.dir,
}));
resolve(nodes);
} else {
resolve([]);
}
})
.catch(function (error) {
console.error(error);
resolve([]);
});
}
},
baidu_props: {
lazy: true,
emitPath: false,
checkStrictly: true,
lazyLoad (node, resolve) {
const { level, value } = node;
let that = this;
axios.post('/admin/source/getFiles', Object.assign({
type: 2,
pdir_fid: value || 0
}, PostBase))
.then(function (res) {
if (res.data.code == 200) {
// 只显示文件夹
const nodes = (res.data.data || [])
.map(item => ({
value: item.path,
label: item.server_filename,
disabled: !item.isdir,
}));
resolve(nodes);
} else {
resolve([]);
}
})
.catch(function (error) {
console.error(error);
resolve([]);
});
}
},
ali_props: {
lazy: true,
emitPath: false,
checkStrictly: true,
lazyLoad (node, resolve) {
const { level, value } = node;
let that = this;
axios.post('/admin/source/getFiles', Object.assign({
type: 1,
pdir_fid: value || 0
}, PostBase))
.then(function (res) {
if (res.data.code == 200) {
// 只显示文件夹
const nodes = (res.data.data || [])
.map(item => ({
value: item.file_id,
label: item.name,
disabled: item.type!='folder',
}));
resolve(nodes);
} else {
resolve([]);
}
})
.catch(function (error) {
console.error(error);
resolve([]);
});
}
},
uc_props: {
lazy: true,
emitPath: false,
checkStrictly: true,
lazyLoad (node, resolve) {
const { level, value } = node;
let that = this;
axios.post('/admin/source/getFiles', Object.assign({
type: 3,
pdir_fid: value || 0
}, PostBase))
.then(function (res) {
if (res.data.code == 200) {
// 只显示文件夹
const nodes = (res.data.data || [])
.map(item => ({
value: item.fid,
label: item.file_name,
disabled: !item.dir,
}));
resolve(nodes);
} else {
resolve([]);
}
})
.catch(function (error) {
console.error(error);
resolve([]);
});
}
},
file: '',
files: [],
};
},
created() {
this.getData();
// this.getFile()
},
methods: {
getData(){
@@ -56,11 +334,8 @@
.then(function (response) {
if (response.data.code == 200) {
for (let item of response.data.data) {
if(item.conf_key === 'quark_cookie'){
that.form.cookie = item.conf_value
}
if(item.conf_key === 'quark_file'){
that.file = item.conf_value
if (that.configKeys.includes(item.conf_key)) {
that.$set(that.form, item.conf_key, item.conf_value)
}
}
} else {
@@ -73,16 +348,13 @@
},
onSubmit(){
let that = this
axios.post('/admin/conf/updateBaseConfig', Object.assign({}, PostBase, {
quark_cookie: that.form.cookie
}))
axios.post('/admin/conf/updateBaseConfig', Object.assign({}, PostBase, that.form))
.then(function (response) {
if (response.data.code == 200) {
that.$message({
message: response.data.message,
type: 'success'
});
that.getFile()
} else {
that.$message.error(response.data.message);
}
@@ -93,12 +365,14 @@
});
},
getFile(){
getFile(type){
let that = this
axios.post('/admin/source/getFiles', Object.assign({}, PostBase))
axios.post('/admin/source/getFiles', Object.assign({
type
}, PostBase))
.then(function (res) {
if (res.data.code == 200) {
that.files = res.data || []
that.$message.success("已登录cookie可用");
} else {
that.$message.error(res.data.message);
}

View File

@@ -10,7 +10,7 @@
<el-button icon="el-icon-plus" size="small" @click="clickAdd" plain>添加资源</el-button>
<el-button icon="el-icon-delete" size="small" @click="postMultDelete" plain>批量删除</el-button>
<el-button icon="el-icon-document-copy" size="small" @click="getExport" plain>导出资源</el-button>
<el-button icon="el-icon-plus" size="small" @click="ImportShow" plain>夸克导入专用</el-button>
<el-button icon="el-icon-plus" size="small" @click="ImportShow" plain>表格导入</el-button>
<el-button icon="el-icon-plus" size="small" @click="ImportBatch" plain>批量导入</el-button>
</el-form-item>
<div style="float:right">
@@ -167,9 +167,9 @@
<el-radio-button :label="2">转存分享导入</el-radio-button>
</el-radio-group>
<p style="color: #999;" v-if='Batchform.type==1'>直接导入链接校验有效后直接入库Tips该功能不会检测是否重复</p>
<p style="color: #999;" v-else-if='Batchform.type==2'>将资源转存到自己网盘后分享入库<font color=orangered>此功能仅支持夸克</font>Tips该功能不会检测是否重复</p>
<span style="color: #999;" v-if='Batchform.type==1'>支持<font color=orangered>夸克、阿里、UC</font>的网盘资源(一次最多可以上传500条资源)</span>
<span style="color: #999;" v-else-if='Batchform.type==2'>目前仅支持<font color=orangered>夸克</font>的网盘资源(一次最多可以上传500条资源)</span>
<p style="color: #999;" v-else-if='Batchform.type==2'>将资源转存到自己网盘后分享入库Tips该功能不会检测是否重复</p>
<span style="color: #999;" v-if='Batchform.type==1'>支持<font color=orangered>夸克、阿里、UC、百度</font>的网盘资源(一次最多可以上传500条资源)</span>
<span style="color: #999;" v-else-if='Batchform.type==2'>支持<font color=orangered>夸克、阿里、UC、百度</font>的网盘资源(一次最多可以上传500条资源)</span>
</el-form-item>
<el-form-item prop="source_category_id" label="资源分类" label-width="70px" v-if="Batchform.type">
<el-select size="medium" v-model="Batchform.source_category_id" placeholder="请选择分类" clearable>
@@ -187,7 +187,8 @@
一条资源一行
https://pan.quark.cn/s/xxxxxxxx
https://www.alipan.com/s/xxxxxxxxx
https://drive.uc.cn/s/xxxxxxxxxxx"
https://drive.uc.cn/s/xxxxxxxxxxx
https://pan.baidu.com/s/xxxxxx?pwd=xxxx"
v-model="Batchform.urls"
rows="20"
show-word-limit

View File

@@ -1,54 +0,0 @@
{
"name": "hamm/y-admin",
"description": "Fast build your php project",
"type": "project",
"keywords": [
"YAdmin"
],
"homepage": "https://www.baidu.com/",
"license": "Apache-2.0",
"authors": [
{
"name": "",
"email": "admin@qq.cn"
}
],
"require": {
"php": ">=7.1.0",
"topthink/framework": "^6.0.0",
"topthink/think-orm": "^2.0",
"topthink/think-multi-app": "^1.0",
"topthink/think-view": "^1.0",
"alibabacloud/client": "^1.5",
"phpoffice/phpexcel": "^1.8",
"overtrue/wechat": "~4.0",
"jaeger/querylist": "^4.2",
"topthink/think-queue": "3.0",
"topthink/think-filesystem": "1.0.1",
"lizhichao/word": "^2.1"
},
"require-dev": {
"symfony/var-dumper": "^4.2",
"topthink/think-trace": "^1.0"
},
"autoload": {
"psr-4": {
"app\\": "app"
},
"psr-0": {
"": "extend/"
}
},
"config": {
"preferred-install": "dist",
"allow-plugins": {
"easywechat-composer/easywechat-composer": true
}
},
"scripts": {
"post-autoload-dump": [
"@php think service:discover",
"@php think vendor:publish"
]
}
}

4187
composer.lock generated

File diff suppressed because it is too large Load Diff

122
extend/netdisk/Transfer.php Normal file
View File

@@ -0,0 +1,122 @@
<?php
namespace netdisk;
class Transfer
{
public function __construct()
{
// 原 Open 类的构造函数内容
$this->url = ""; // 资源地址
$this->expired_type = 1; //有效期 1分享永久 2临时
$this->ad_fid = ""; //夸克专用 - 分享时带上这个文件的fid
$this->code = ""; //分享码
$this->isType = 0; //0 转存并分享后的资源信息 1直接获取资源信息
}
public function getFiles($type=0,$pdir_fid=0)
{
if($type == 1){
//阿里
$pan = new \netdisk\pan\AlipanPan();
return $pan->getFiles($pdir_fid);
} else if($type == 2){
//百度网盘
$pan = new \netdisk\pan\BaiduPan();
return $pan->getFiles($pdir_fid);
} else if($type == 3){
//UC
$pan = new \netdisk\pan\UcPan();
return $pan->getFiles($pdir_fid);
} else {
//夸克
$pan = new \netdisk\pan\QuarkPan();
return $pan->getFiles($pdir_fid);
}
}
public function transfer($urlData = [])
{
$url = $urlData['url']??'';
$config = [
'isType' => $urlData['isType'] ?? input('isType') ?? 0,
'url' => $url,
'code' => $urlData['code'] ?? input('code') ?? '',
'expired_type' => $urlData['expired_type'] ?? input('expired_type') ?? 1,
'ad_fid' => $urlData['ad_fid'] ?? input('ad_fid') ?? "",
'stoken' => $urlData['stoken'] ?? '',
];
if (strpos($url, '?entry=') !== false) {
$entry = preg_match('/\?entry=([^&]+)/', $url, $matches) ? $matches[1] : '';
$url = preg_match('/.*(?=\?entry=)/', $url, $matches) ? $matches[0] : '';
}
$substring = strstr($url, 's/');
if ($substring !== false) {
$pwd_id = substr($substring, 2); // 去除 's/' 部分
} else {
return jerr2("资源地址格式有误");
}
$patterns = [
'pan.quark.cn' => 0,
'www.alipan.com' => 1,
'www.aliyundrive.com' => 1,
'pan.baidu.com' => 2,
'drive.uc.cn' => 3,
// 'pan.xunlei.com' => 4,
];
$url_type = -1; // 默认值为 -1
foreach ($patterns as $pattern => $type) {
if (strpos($url, $pattern) !== false) {
$url_type = $type;
break; // 一旦匹配成功,退出循环
}
}
$this->url = $url;
if ($url_type == 0) {
//夸克
$pan = new \netdisk\pan\QuarkPan($config);
return $pan->transfer(strtok($pwd_id, '#'));
} else if($url_type == 1){
//阿里
$pan = new \netdisk\pan\AlipanPan($config);
return $pan->transfer(strtok($pwd_id, '#'));
} else if($url_type == 2){
//百度网盘
$pan = new \netdisk\pan\BaiduPan($config);
return $pan->transfer(strtok($pwd_id, '#'));
} else if($url_type == 3){
//UC
$pan = new \netdisk\pan\UcPan($config);
return $pan->transfer(strtok($pwd_id, '#'));
} else {
return jerr2("资源地址格式有误");
}
}
public function deletepdirFid($type=0,$filelist)
{
if($type == 1){
//阿里
$pan = new \netdisk\pan\AlipanPan();
return $pan->deletepdirFid($filelist);
} else if($type == 2){
//百度网盘
$pan = new \netdisk\pan\BaiduPan();
return $pan->deletepdirFid($filelist);
} else if($type == 3){
//UC
$pan = new \netdisk\pan\UcPan();
return $pan->deletepdirFid(filelist);
} else {
//夸克
$pan = new \netdisk\pan\QuarkPan();
return $pan->deletepdirFid($filelist);
}
}
}

View File

@@ -0,0 +1,308 @@
<?php
namespace netdisk\pan;
class AlipanPan extends BasePan
{
public function __construct($config = [])
{
parent::__construct($config);
$this->urlHeader = [
'Accept: application/json, text/plain, */*',
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization: ',
'Content-Type: application/json',
'Origin: https://www.alipan.com',
'Priority: u=1, i',
'Referer: https://www.alipan.com/',
'Sec-Ch-Ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'Sec-Ch-Ua-Mobile: ?0',
'Sec-Ch-Ua-Platform: "Windows"',
'Sec-Fetch-Dest: empty',
'Sec-Fetch-Mode: cors',
'Sec-Fetch-Site: same-site',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0',
'X-Canary: client=web,app=share,version=v2.3.1'
];
}
public function getFiles($pdir_fid=0)
{
$tokenFile = __DIR__ . '/access_token.json';
$access_token = $this->manageAccessToken($tokenFile);
if(empty($access_token)){
return jerr2('登录状态异常获取access_token失败');
}
foreach ($this->urlHeader as &$header) {
if (str_starts_with($header, 'Authorization: ')) {
$header = 'Authorization: Bearer ' . $access_token;
break;
}
}
if($pdir_fid === 0){
$pdir_fid = 'root';
}
$urlData = array(
'all' => false,
'drive_id' => '2008425230',
'fields' => "*",
'limit' => 100,
'order_by' => "updated_at",
'order_direction' => "DESC",
'parent_file_id' => $pdir_fid,
'url_expire_sec' => 14400,
);
$res = curlHelper("https://api.aliyundrive.com/adrive/v3/file/list", "POST", json_encode($urlData),$this->urlHeader)['body'];
$res = json_decode($res, true);
if(!empty($res['message'])){
return jerr2($res['message']);
}
return jok2('获取成功',$res['items']);
}
public function transfer($share_id)
{
$tokenFile = __DIR__ . '/access_token.json';
$access_token = $this->manageAccessToken($tokenFile);
if(empty($access_token)){
return jerr2('登录状态异常获取access_token失败');
}
foreach ($this->urlHeader as &$header) {
if (str_starts_with($header, 'Authorization: ')) {
$header = 'Authorization: Bearer ' . $access_token;
break;
}
}
$data = [];
$res = $this->getAlipan1($share_id);
if(!isset($res['file_infos'])){
return jerr2($res['message']);
}
$infos = $res;
if($this->isType == 1){
$urls['title'] = $infos['share_name'];
$urls['share_url'] = $this->url;
return jok2('检验成功', $urls);
}
//通过分享id获取file_id
$file_infos = $infos['file_infos'];
//通过分享id获取X-Share-Token
$res = $this->getAlipan2($share_id);
if(!isset($res['share_token'])){
return jerr2($res['message']);
}
$share_token = $res['share_token'];
$to_pdir_fid = Config('qfshop.ali_file'); //默认存储路径
if($this->expired_type == 2){
$to_pdir_fid = Config('qfshop.ali_file_time'); //临时资源路径
}
$data3['requests'] = [];
$data3['resource'] = 'file';
foreach ($file_infos as $key=>$value) {
$data3['requests'][$key]['body']['auto_rename'] = true;
$data3['requests'][$key]['body']['file_id'] = $value['file_id'];
$data3['requests'][$key]['body']['share_id'] = $share_id;
$data3['requests'][$key]['body']['to_drive_id'] = '2008425230';
$data3['requests'][$key]['body']['to_parent_file_id'] = $to_pdir_fid;
$data3['requests'][$key]['headers']['Content-Type'] = 'application/json';
$data3['requests'][$key]['id'] = $key.'';
$data3['requests'][$key]['method'] = 'POST';
$data3['requests'][$key]['url'] = '/file/copy';
}
//保存
$res = $this->getAlipan3($data3,$share_token);
if (!isset($res['responses'])) {
return jerr2($res['message'] ?? '请求失败,无响应数据');
}
$response = $res['responses'][0];
$body = $response['body'];
if (isset($body['code'])) {
return jerr2($body['message'] ?? '请求失败');
}
$responses = $res['responses'];
$data4['drive_id'] = '2008425230';
$data4['expiration'] = '';
$data4['share_pwd'] = '';
$data4['file_id_list'] = [];
foreach ($responses as $key=>$value){
$data4['file_id_list'][] = $value['body']['file_id'];
}
//分享
$res = $this->getAlipan4($data4);
if(!isset($res['share_url'])){
return jerr2($res['message']??'转存失败4');
}
$share = $res;
$data['share_url'] = $share['share_url'];
$data['title'] = $share['share_title'];
$data['fid'] = $share['file_id_list'];
return jok2('转存成功', $data);
}
/**
* 阿里-0-通过分享id获取file_id
*
* @return void
*/
public function getAlipan1($share_id)
{
$urlData = [
'share_id' => $share_id,
];
$urlHeader = array(
'Content-Type: application/json',
);
$res = curlHelper("https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous", "POST", json_encode($urlData),$urlHeader)['body'];
return json_decode($res, true);
}
/**
* 阿里-0-通过分享id获取X-Share-Token
*
* @return void
*/
public function getAlipan2($share_id)
{
$urlData = array(
'share_id' => $share_id,
);
$res = curlHelper("https://api.aliyundrive.com/v2/share_link/get_share_token", "POST", json_encode($urlData), $this->urlHeader)['body'];
return json_decode($res, true);
}
/**
* 阿里-1-保存
*
* @return void
*/
public function getAlipan3($urlData,$share_token)
{
$urlHeader= $this->urlHeader;
$urlHeader[] = 'X-Share-Token: '.$share_token;
$res = curlHelper("https://api.aliyundrive.com/adrive/v4/batch", "POST", json_encode($urlData), $urlHeader)['body'];
return json_decode($res, true);
}
/**
* 阿里-2-分享
*
* @return void
*/
public function getAlipan4($urlData)
{
$res = curlHelper("https://api.aliyundrive.com/adrive/v2/share_link/create", "POST", json_encode($urlData), $this->urlHeader)['body'];
return json_decode($res, true);
}
/**
* 管理 access_token
* @param string $tokenFile token文件路径
* @return string access_token
*/
private function manageAccessToken($tokenFile)
{
$tokenData = [];
$currentRefreshToken = Config('qfshop.Authorization');
// 检查文件是否存在
if (file_exists($tokenFile)) {
$tokenData = json_decode(file_get_contents($tokenFile), true);
// 检查token是否存在且未过期且不为空且refresh_token未改变
if (isset($tokenData['access_token'])
&& isset($tokenData['expires_at'])
&& isset($tokenData['refresh_token'])
&& !empty($tokenData['access_token'])
&& time() < $tokenData['expires_at']
&& $tokenData['refresh_token'] === $currentRefreshToken
) {
return $tokenData['access_token'];
}
}
// 获取新的token
$newToken = $this->getNewAccessToken();
// 保存新token
$tokenData = [
'access_token' => $newToken,
'refresh_token' => $currentRefreshToken,
'expires_at' => time() + 3600
];
file_put_contents($tokenFile, json_encode($tokenData));
return $newToken;
}
/**
* 获取新的access_token
* @return string
*/
private function getNewAccessToken()
{
$urlData = [
'refresh_token' => Config('qfshop.Authorization'),
];
$urlHeader = array(
'Content-Type: application/json',
);
$res = curlHelper("https://api.aliyundrive.com/token/refresh", "POST", json_encode($urlData),$urlHeader)['body'];
$res = json_decode($res, true);
return $res['access_token']??'';
}
/**
* 删除指定资源
*
* @return void
*/
public function deletepdirFid($filelist)
{
$urlData['requests'] = [];
$urlData['resource'] = 'file';
foreach ($filelist as $key=>$value) {
$urlData['requests'][$key]['body']['file_id'] = $value??'';
$urlData['requests'][$key]['body']['drive_id'] = '2008425230';
$urlData['requests'][$key]['headers']['Content-Type'] = 'application/json';
$urlData['requests'][$key]['id'] = $value??'';
$urlData['requests'][$key]['method'] = 'POST';
$urlData['requests'][$key]['url'] = '/recyclebin/trash';
}
$tokenFile = __DIR__ . '/access_token.json';
$access_token = $this->manageAccessToken($tokenFile);
if(empty($access_token)){
return jerr2('登录状态异常获取access_token失败');
}
foreach ($this->urlHeader as &$header) {
if (str_starts_with($header, 'Authorization: ')) {
$header = 'Authorization: Bearer ' . $access_token;
break;
}
}
$res = curlHelper("https://api.aliyundrive.com/adrive/v4/batch", "POST", json_encode($urlData), $this->urlHeader)['body'];
return json_decode($res, true);
}
}

View File

@@ -0,0 +1,244 @@
<?php
namespace netdisk\pan;
class BaiduPan extends BasePan
{
public function getFiles($pdir_fid=0)
{
if($pdir_fid === 0){
$pdir_fid = '/';
}
$cookie = Config('qfshop.baidu_cookie');
$network = new \netdisk\pan\BaiduWork($cookie);
$res = $network->getDirList($pdir_fid);
return jok2('获取成功',$res);
}
public function transfer($pwd_id)
{
$cookie = Config('qfshop.baidu_cookie');
$network = new \netdisk\pan\BaiduWork($cookie);
$bdstoken = $network->getBdstoken();
$network->setBdstoken($bdstoken);
$urlParts = parse_url($this->url);
$linkUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . $urlParts['path']; // 分享链接
$passCode = $this->code; // 4位提取码
// 先判断是否有提取码
if (!empty($passCode)) {
// 验证提取码
$randsk = $network->verifyPassCode($linkUrl, $passCode);
if (is_numeric($randsk)) {
return jerr2($network->getErrorMessage($randsk));
}
// 验证成功,更新 cookie
$network->updateBdclnd($randsk);
}
// 获取转存参数
$transferParams = $network->getTransferParams($linkUrl);
if (is_numeric($transferParams)) {
return jerr2($network->getErrorMessage($transferParams));
}
// 解析返回的参数
list($shareId, $userId, $fsIds, $fileNames, $isDirs) = $transferParams;
if($this->isType == 1){
$urls['title'] = $fileNames[0];
$urls['share_url'] = $this->url;
return jok2('检验成功', $urls);
}
$folderName = Config('qfshop.baidu_file'); //默认存储路径
if($this->expired_type == 2){
$folderName = Config('qfshop.baidu_file_time'); //临时资源路径
}
if(empty($folderName)){
$folderName = '/心悦转存文件'; // 未设置时默认目录
}
// 检查目录名是否包含非法字符
$invalidChars = ['<', '>', '|', '*', '?', '\\', ':'];
foreach ($invalidChars as $char) {
if (strpos($folderName, $char) !== false) {
return jerr2('转存目录名有非法字符,不能包含:< > | * ? \\ :');
}
}
// 先检查目录是否存在,不存在再创建
$dirList = $network->getDirList($folderName);
// 如果返回的是错误码,说明目录不存在,需要创建
if (is_numeric($dirList)) {
$createResult = $network->createDir($folderName);
if ($createResult !== 0) {
return jerr2($network->getErrorMessage($createResult));
}
}
// 执行文件转存
$transferResult = $network->transferFile([$shareId, $userId, $fsIds], $folderName);
if ($transferResult !== 0) {
return jerr2($network->getErrorMessage($transferResult));
}
// 转存成功后,获取目录中的文件列表
$dirList = $network->getDirList('/' . $folderName);
if (is_numeric($dirList)) {
return jerr2($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 jerr2('分享失败,找不到刚转存的文件');
}
// 如果所有文件都是广告,删除整个转存的内容
if ($allFilesAreAds && !empty($targetFiles)) {
// 删除所有转存的文件
$deleteResult = $network->batchDeleteFiles($filePaths);
if ($deleteResult['errno'] === 0) {
return jerr2('资源内容为空或所有转存的文件都包含广告内容,已全部删除');
} else {
// return jerr2('删除广告文件失败');
}
}
// 如果只有部分文件是广告,只删除广告文件
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 jerr2('资源内容为空或所有转存的文件都包含广告内容,已全部删除');
}
// 创建分享
$expiry = 0; // 0为永久
$password = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 4); // 随机4位提取码
$shareLink = $network->createShare(implode(',', $fsIdList), $expiry, $password);
if (is_numeric($shareLink)) {
return jerr2($network->getErrorMessage($shareLink));
}
if(!empty($password)){
$shareLink = $shareLink. '?pwd='. $password;
}
// 转存成功
return jok2("文件转存成功", [
'title' => $fileNames[0],
'share_url' => $shareLink,
'fid' => $filePaths,
'code' => $password,
]);
}
/**
* 检查文件名是否包含广告关键词
* @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;
}
/**
* 删除指定资源
*
* @return void
*/
public function deletepdirFid($filePaths)
{
$cookie = Config('qfshop.baidu_cookie');
$network = new \netdisk\pan\BaiduWork($cookie);
// 调用批量删除方法
$result = $network->batchDeleteFiles($filePaths);
return $result;
}
}

View File

@@ -0,0 +1,414 @@
<?php
namespace netdisk\pan;
class BaiduWork
{
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
];
// print_r($params);
// die;
$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
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace netdisk\pan;
abstract class BasePan
{
protected $urlHeader;
protected $code;
protected $url;
protected $isType;
protected $expired_type;
protected $ad_fid;
protected $stoken;
public function __construct($config = [])
{
$this->code = $config['code'] ?? '';
$this->url = $config['url'] ?? '';
$this->isType = $config['isType'] ?? 0;
$this->expired_type = $config['expired_type'] ?? 1;
$this->ad_fid = $config['ad_fid'] ?? '';
$this->stoken = $config['stoken'] ?? '';
}
abstract public function transfer($share_id);
}

View File

@@ -0,0 +1,361 @@
<?php
namespace netdisk\pan;
class QuarkPan extends BasePan
{
public function __construct($config = [])
{
parent::__construct($config);
$this->urlHeader = [
'Accept: application/json, text/plain, */*',
'Accept-Language: zh-CN,zh;q=0.9',
'content-type: application/json;charset=UTF-8',
'sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform: "Windows"',
'sec-fetch-dest: empty',
'sec-fetch-mode: cors',
'sec-fetch-site: same-site',
'Referer: https://pan.quark.cn/',
'Referrer-Policy: strict-origin-when-cross-origin',
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'cookie: ' . Config('qfshop.quark_cookie')
];
}
public function getFiles($pdir_fid=0)
{
// 原 getFiles 方法内容
$urlData = [];
$queryParams = [
'pr' => 'ucpro',
'fr' => 'pc',
'uc_param_str' => '',
'pdir_fid' => $pdir_fid,
'_page' => 1,
'_size' => 50,
'_fetch_total' => 1,
'_fetch_sub_dirs' => 0,
'_sort' => 'file_type:asc,updated_at:desc',
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr2($res['message']=='require login [guest]'?'夸克未登录请检查cookie':$res['message']);
}
return jok2('获取成功',$res['data']['list']);
}
public function transfer($pwd_id)
{
if(empty($this->stoken)){
//获取要转存夸克资源的stoken
$res = $this->getStoken($pwd_id);
if($res['status'] !== 200) return jerr2($res['message']);
$infoData = $res['data'];
if($this->isType == 1){
$urls['title'] = $infoData['title'];
$urls['share_url'] = $this->url;
return jok2('检验成功', $urls);
}
$stoken = $infoData['stoken'];
$stoken = str_replace(' ', '+', $stoken);
}else{
$stoken = str_replace(' ', '+', $this->stoken);
}
//获取要转存夸克资源的详细内容
$res = $this->getShare($pwd_id,$stoken);
if($res['status']!== 200) return jerr2($res['message']);
$detail = $res['data'];
$fid_list = [];
$fid_token_list = [];
$title = $detail['share']['title']; //资源名称
foreach ($detail['list'] as $key => $value) {
$fid_list[] = $value['fid'];
$fid_token_list[] = $value['share_fid_token'];
}
//转存资源到指定文件夹
$res = $this->getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list);
if($res['status']!== 200) return jerr2($res['message']);
$task_id = $res['data']['task_id'];
//转存后根据task_id获取转存到自己网盘后的信息
$retry_index = 0;
$myData = '';
while ($myData=='' || $myData['status'] != 2) {
$res = $this->getShareTask($task_id, $retry_index);
if($res['message']== 'capacity limit[{0}]'){
return jerr2('容量不足');
}
if($res['status']!== 200) {
return jerr2($res['message']);
}
$myData = $res['data'];
$retry_index++;
// 可以添加一个最大重试次数的限制,防止无限循环
if ($retry_index > 50) {
break;
}
}
try {
//删除转存后可能有的广告
$banned = Config('qfshop.quark_banned')??''; //如果出现这些字样就删除
if(!empty($banned)){
$bannedList = explode(',', $banned);
$pdir_fid = $myData['save_as']['save_as_top_fids'][0];
$dellist = [];
$plist = $this->getPdirFid($pdir_fid);
if(!empty($plist)){
foreach ($plist as $key => $value) {
// 检查$value['file_name']是否包含$bannedList中的任何一项
$contains = false;
foreach ($bannedList as $item) {
if (strpos($value['file_name'], $item) !== false) {
$contains = true;
break;
}
}
if ($contains) {
$dellist[] = $value['fid'];
}
}
if(count($plist) === count($dellist)){
//要删除的资源数如果和原数据资源数一样 就全部删除并终止下面的分享
$this->deletepdirFid([$pdir_fid]);
return jerr2("资源内容为空");
}else{
if (!empty($dellist)) {
$this->deletepdirFid($dellist);
}
}
}
}
} catch (Exception $e) {
}
$shareFid = $myData['save_as']['save_as_top_fids'];
//分享资源并拿到更新后的task_id
$res = $this->getShareBtn($myData['save_as']['save_as_top_fids'],$title);
if($res['status']!== 200) return jerr2($res['message']);
$task_id = $res['data']['task_id'];
//根据task_id拿到share_id
$retry_index = 0;
$myData = '';
while ($myData=='' || $myData['status'] != 2) {
$res = $this->getShareTask($task_id, $retry_index);
if($res['status']!== 200) continue;
$myData = $res['data'];
$retry_index++;
// 可以添加一个最大重试次数的限制,防止无限循环
if ($retry_index > 50) {
break;
}
}
//根据share_id 获取到分享链接
$res = $this->getSharePassword($myData['share_id']);
if($res['status']!== 200) return jerr2($res['message']);
$share = $res['data'];
// $share['fid'] = $share['first_file']['fid'];
$share['fid'] = (is_array($shareFid) && count($shareFid) > 1) ? $shareFid : $share['first_file']['fid'];
return jok2('转存成功', $share);
}
/**
* 获取要转存资源的stoken
*
* @return void
*/
public function getStoken($pwd_id)
{
$urlData = array(
'passcode' => '',
'pwd_id' => $pwd_id,
);
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/token?pr=ucpro&fr=pc&uc_param_str=", "POST",json_encode($urlData), $this->urlHeader)['body'];
return json_decode($res, true);
}
/**
* 获取要转存资源的详细内容
*
* @return void
*/
public function getShare($pwd_id,$stoken)
{
$urlData = array();
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => "",
"pwd_id" => $pwd_id,
"stoken" => $stoken,
"pdir_fid" => "0",
"force" => "0",
"_page" => "1",
"_size" => "100",
"_fetch_banner" => "1",
"_fetch_share" => "1",
"_fetch_total" => "1",
"_sort" => "file_type:asc,updated_at:desc"
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 转存资源到指定文件夹
*
* @return void
*/
public function getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list)
{
$to_pdir_fid = Config('qfshop.quark_file'); //默认存储路径
if($this->expired_type == 2){
$to_pdir_fid = Config('qfshop.quark_file_time'); //临时资源路径
}
$urlData = array(
'fid_list' => $fid_list,
'fid_token_list' => $fid_token_list,
'to_pdir_fid' => $to_pdir_fid,
'pwd_id' => $pwd_id,
'stoken' => $stoken,
'pdir_fid' => "0",
'scene' => "link",
);
$queryParams = [
"entry" => "update_share",
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/save", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 分享资源拿到task_id
*
* @return void
*/
public function getShareBtn($fid_list,$title)
{
if(!empty($this->ad_fid)){
$fid_list[] = $this->ad_fid;
}
$urlData = array(
'fid_list' => $fid_list,
'expired_type' => $this->expired_type,
'title' => $title,
'url_type' => 1,
);
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 根据task_id拿到自己的资源信息
*
* @return void
*/
public function getShareTask($task_id,$retry_index)
{
$urlData = array();
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => "",
"task_id" => $task_id,
"retry_index" => $retry_index
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/task", "GET", json_encode($urlData), $this->urlHeader, $queryParams)['body'];
return json_decode($res, true);
}
/**
* 根据share_id 获取到分享链接
*
* @return void
*/
public function getSharePassword($share_id)
{
$urlData = array(
'share_id' => $share_id,
);
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/share/password", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 删除指定资源
*
* @return void
*/
public function deletepdirFid($filelist)
{
$urlData = array(
'action_type' => 2,
'exclude_fids' => [],
'filelist' => $filelist,
);
$queryParams = [
"pr" => "ucpro",
"fr" => "pc",
"uc_param_str" => ""
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/delete", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 获取夸克网盘指定文件夹内容
*
* @return void
*/
public function getPdirFid($pdir_fid)
{
$urlData = [];
$queryParams = [
'pr' => 'ucpro',
'fr' => 'pc',
'uc_param_str' => '',
'pdir_fid' => $pdir_fid,
'_page' => 1,
'_size' => 200,
'_fetch_total' => 1,
'_fetch_sub_dirs' => 0,
'_sort' => 'file_type:asc,updated_at:desc',
];
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return [];
}
return $res['data']['list'];
}
}

View File

@@ -0,0 +1,348 @@
<?php
namespace netdisk\pan;
class UcPan extends BasePan
{
public function __construct($config = [])
{
parent::__construct($config);
$this->urlHeader = [
'Accept: application/json, text/plain, */*',
'Accept-Language: zh-CN,zh;q=0.9',
'content-type: application/json;charset=UTF-8',
'sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"',
'sec-ch-ua-mobile: ?0',
'sec-ch-ua-platform: "Windows"',
'sec-fetch-dest: empty',
'sec-fetch-mode: cors',
'sec-fetch-site: same-site',
'Referer: https://drive.uc.cn/',
'Referrer-Policy: strict-origin-when-cross-origin',
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'cookie: ' . Config('qfshop.uc_cookie')
];
}
public function getFiles($pdir_fid=0)
{
// 原 getFiles 方法内容
$urlData = [];
$queryParams = [
'pr' => 'UCBrowser',
'fr' => 'pc',
'pdir_fid' => $pdir_fid,
'_page' => 1,
'_size' => 50,
'_fetch_total' => 1,
'_fetch_sub_dirs' => 0,
'_sort' => 'file_type:asc,updated_at:desc',
];
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return jerr2($res['message']=='require login [guest]'?'UC未登录请检查cookie':$res['message']);
}
return jok2('获取成功',$res['data']['list']);
}
public function transfer($pwd_id)
{
//获取要转存UC资源的stoken
$res = $this->getStoken($pwd_id);
if($res['status'] !== 200) return jerr2($res['message']);
$infoData = $res['data'];
if($this->isType == 1){
$urls['title'] = $infoData['token_info']['title'];
$urls['share_url'] = $this->url;
return jok2('检验成功', $urls);
}
$stoken = $infoData['token_info']['stoken'];
$stoken = str_replace(' ', '+', $stoken);
//获取要转存UC资源的详细内容
$res = $this->getShare($pwd_id,$stoken);
if($res['status']!== 200) return jerr2($res['message']);
$detail = $res['data'];
$fid_list = [];
$fid_token_list = [];
$title = $detail['share']['title']; //资源名称
foreach ($detail['list'] as $key => $value) {
$fid_list[] = $value['fid'];
$fid_token_list[] = $value['share_fid_token'];
}
//转存资源到指定文件夹
$res = $this->getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list);
if($res['status']!== 200) return jerr2($res['message']);
$task_id = $res['data']['task_id'];
//转存后根据task_id获取转存到自己网盘后的信息
$retry_index = 0;
$myData = '';
while ($myData=='' || $myData['status'] != 2) {
$res = $this->getShareTask($task_id, $retry_index);
if($res['message']== 'capacity limit[{0}]'){
return jerr2('容量不足');
}
if($res['status']!== 200) {
return jerr2($res['message']);
}
$myData = $res['data'];
$retry_index++;
// 可以添加一个最大重试次数的限制,防止无限循环
if ($retry_index > 50) {
break;
}
}
try {
//删除转存后可能有的广告
$banned = Config('qfshop.quark_banned')??''; //如果出现这些字样就删除
if(!empty($banned)){
$bannedList = explode(',', $banned);
$pdir_fid = $myData['save_as']['save_as_top_fids'][0];
$dellist = [];
$plist = $this->getPdirFid($pdir_fid);
if(!empty($plist)){
foreach ($plist as $key => $value) {
// 检查$value['file_name']是否包含$bannedList中的任何一项
$contains = false;
foreach ($bannedList as $item) {
if (strpos($value['file_name'], $item) !== false) {
$contains = true;
break;
}
}
if ($contains) {
$dellist[] = $value['fid'];
}
}
if(count($plist) === count($dellist)){
//要删除的资源数如果和原数据资源数一样 就全部删除并终止下面的分享
$this->deletepdirFid([$pdir_fid]);
return jerr2("资源内容为空");
}else{
if (!empty($dellist)) {
$this->deletepdirFid($dellist);
}
}
}
}
} catch (Exception $e) {
}
$shareFid = $myData['save_as']['save_as_top_fids'];
//分享资源并拿到更新后的task_id
$res = $this->getShareBtn($myData['save_as']['save_as_top_fids'],$title);
if($res['status']!== 200) return jerr2($res['message']);
$task_id = $res['data']['task_id'];
//根据task_id拿到share_id
$retry_index = 0;
$myData = '';
while ($myData=='' || $myData['status'] != 2) {
$res = $this->getShareTask($task_id, $retry_index);
if($res['status']!== 200) continue;
$myData = $res['data'];
$retry_index++;
// 可以添加一个最大重试次数的限制,防止无限循环
if ($retry_index > 50) {
break;
}
}
//根据share_id 获取到分享链接
$res = $this->getSharePassword($myData['share_id']);
if($res['status']!== 200) return jerr2($res['message']);
$share = $res['data'];
// $share['fid'] = $share['first_file']['fid'];
$share['fid'] = (is_array($shareFid) && count($shareFid) > 1) ? $shareFid : $share['first_file']['fid'];
return jok2('转存成功', $share);
}
/**
* 获取要转存资源的stoken
*
* @return void
*/
public function getStoken($pwd_id)
{
$urlData = array(
'passcode' => '',
'pwd_id' => $pwd_id,
);
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/sharepage/v2/detail?pr=UCBrowser&fr=pc", "POST",json_encode($urlData), $this->urlHeader)['body'];
return json_decode($res, true);
}
/**
* 获取要转存资源的详细内容
*
* @return void
*/
public function getShare($pwd_id,$stoken)
{
$urlData = array();
$queryParams = [
"pr" => "UCBrowser",
"fr" => "pc",
"pwd_id" => $pwd_id,
"stoken" => $stoken,
"pdir_fid" => "0",
"force" => "0",
"_page" => "1",
"_size" => "100",
"_fetch_banner" => "1",
"_fetch_share" => "1",
"_fetch_total" => "1",
"_sort" => "file_type:asc,updated_at:desc"
];
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/sharepage/detail", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 转存资源到指定文件夹
*
* @return void
*/
public function getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list)
{
$to_pdir_fid = Config('qfshop.uc_file'); //默认存储路径
if($this->expired_type == 2){
$to_pdir_fid = Config('qfshop.uc_file_time'); //临时资源路径
}
$urlData = array(
'fid_list' => $fid_list,
'fid_token_list' => $fid_token_list,
'to_pdir_fid' => $to_pdir_fid,
'pwd_id' => $pwd_id,
'stoken' => $stoken,
'pdir_fid' => "0",
'scene' => "link",
);
$queryParams = [
"entry" => "update_share",
"pr" => "UCBrowser",
"fr" => "pc",
];
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/sharepage/save", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 分享资源拿到task_id
*
* @return void
*/
public function getShareBtn($fid_list,$title)
{
if(!empty($this->ad_fid)){
$fid_list[] = $this->ad_fid;
}
$urlData = array(
'fid_list' => $fid_list,
'expired_type' => $this->expired_type,
'title' => $title,
'url_type' => 1,
);
$queryParams = [
"pr" => "UCBrowser",
"fr" => "pc",
];
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 根据task_id拿到自己的资源信息
*
* @return void
*/
public function getShareTask($task_id,$retry_index)
{
$urlData = array();
$queryParams = [
"pr" => "UCBrowser",
"fr" => "pc",
"task_id" => $task_id,
"retry_index" => $retry_index
];
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/task", "GET", json_encode($urlData), $this->urlHeader, $queryParams)['body'];
return json_decode($res, true);
}
/**
* 根据share_id 获取到分享链接
*
* @return void
*/
public function getSharePassword($share_id)
{
$urlData = array(
'share_id' => $share_id,
);
$queryParams = [
"pr" => "UCBrowser",
"fr" => "pc",
];
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/password", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
return json_decode($res, true);
}
/**
* 删除指定资源
*
* @return void
*/
public function deletepdirFid($filelist)
{
$urlData = array(
'action_type' => 2,
'exclude_fids' => [],
'filelist' => $filelist,
);
$queryParams = [
"pr" => "UCBrowser",
"fr" => "pc",
];
curlHelper("https://pc-api.uc.cn/1/clouddrive/file/delete", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
}
/**
* 获取夸克网盘指定文件夹内容
*
* @return void
*/
public function getPdirFid($pdir_fid)
{
$urlData = [];
$queryParams = [
'pr' => 'UCBrowser',
'fr' => 'pc',
'pdir_fid' => $pdir_fid,
'_page' => 1,
'_size' => 200,
'_fetch_total' => 1,
'_fetch_sub_dirs' => 0,
'_sort' => 'file_type:asc,updated_at:desc',
];
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
$res = json_decode($res, true);
if($res['status'] !== 200){
return [];
}
return $res['data']['list'];
}
}

View File

@@ -0,0 +1 @@
{"access_token":"","refresh_token":"","expires_at":1743155645}

View File

@@ -23,19 +23,10 @@ class QuarkPlugin
$this->source_category_id = 0;
}
public function getFiles($quark_cookie)
public function getFiles($type=0,$pdir_fid=0)
{
$urlData = [
'cookie' => $quark_cookie ?? '',
];
$res = curlHelper(Request::domain() . "/api/open/getFiles", "POST", $urlData)['body'];
$res = json_decode($res, true);
if ($res['code'] !== 200) {
return jerr($res['message']);
}
return $res['data'];
$transfer = new \netdisk\Transfer();
return $transfer->getFiles($type,$pdir_fid);
}
public function import($allData, $source_category_id)
@@ -68,6 +59,10 @@ class QuarkPlugin
public function transferAll($source_category_id, $day = 0)
{
if(empty($this->url)){
return jerr('未配置转存接口地址');
}
@set_time_limit(999999);
$this->source_category_id = $source_category_id;
@@ -133,17 +128,14 @@ class QuarkPlugin
}
$urlData = [
'cookie' => Config('qfshop.quark_cookie') ?? '',
'Authorization' => Config('qfshop.Authorization') ?? '',
'expired_type' => 1, // 1正式资源 2临时资源
'to_pdir_fid' => '', //存入目标文件
'url' => $url,
'code' => $value['code'] ?? '',
'isType' => $isType
];
$res = curlHelper(Request::domain() . "/api/open/transfer", "POST", $urlData)['body'];
$res = json_decode($res, true);
$transfer = new \netdisk\Transfer();
$res = $transfer->transfer($urlData);
if ($res['code'] !== 200) {
if (!empty($logId)) {
@@ -159,7 +151,7 @@ class QuarkPlugin
"title" => $title,
"url" => $res['data']['share_url'],
"is_type" => determineIsType($res['data']['share_url']),
"code" => $value['code'] ?? '',
"code" => $res['data']['code'] ?? $value['code'] ?? '',
"source_category_id" => $source_category_id,
"update_time" => time(),
"create_time" => time(),

412
extend/util/Network.php Normal file
View File

@@ -0,0 +1,412 @@
<?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
extend/util/Operations.php Normal file
View File

@@ -0,0 +1,211 @@
<?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
extend/util/network.py Normal file
View File

@@ -0,0 +1,219 @@
"""
所有和网络请求相关函数。
: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
extend/util/operations.py Normal file
View File

@@ -0,0 +1,327 @@
"""
主要功能实现逻辑,包括批量转存和分享。
: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
extend/util/utils.py Normal file
View File

@@ -0,0 +1,161 @@
"""
通用的工具函数,主要是一些静态函数。
: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

View File

@@ -12,6 +12,17 @@
// [ 应用入口文件 ]
namespace think;
// 检测PHP环境
if(version_compare(PHP_VERSION,'7.2.0','<')) die('require 7.2.0 < PHP <= 7.3.0 !');
if(version_compare(PHP_VERSION,'7.3.0','>')) die('require 7.2.0 < PHP <= 7.3.0 !');
// 检测是否是新安装
if(file_exists("./install") && !file_exists("./install/install.lock")){
$url=$_SERVER['HTTP_HOST'].trim($_SERVER['SCRIPT_NAME'],'index.php').'install/index.php';
header("Location:http://$url");
die;
}
require __DIR__ . '/../vendor/autoload.php';
// 执行HTTP应用并响应

BIN
public/install/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,27 @@
<?php
if(!defined('HAIZAN_VERSION')) die('what do you want?');
return array(
//数据库类型
'DB_TYPE' => 'mysql',
//服务器
'DB_HOST'=>'#DB_HOST#',
//数据库名
'DB_NAME'=>'#DB_NAME#',
//数据库用户名
'DB_USER'=>'#DB_USER#',
//数据库用户密码
'DB_PWD'=> '#DB_PWD#',
//数据库库表前缀
'DB_PREFIX'=>'#DB_PREFIX#',
// 端口
'DB_PORT'=> '#DB_PORT#',
//Cookie前缀 避免冲突
'COOKIE_PREFIX' => '#COOKIE_PREFIX#',
// 缓存前缀
'DATA_CACHE_PREFIX' => '#DATA_CACHE_PREFIX#',
// session 前缀
'SESSION_PREFIX' => '#SESSION_PREFIX#',
//authcode加密函数密钥
"AUTHCODE" => '#AUTHCODE#',
);

39
public/install/config.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
return array(
/* ------系统------ */
//系统名称
'name'=>'心悦搜索管理系统',
//系统版本
'version'=>'1.0',
//系统powered
'powered'=>'后台管理系统',
//系统脚部信息
'footerInfo'=> '',
/* ------配置------ */
'php'=> '7.2.0', //最低要求
'php_t'=> '7.2.5', //推荐配置
/* ------站点------ */
//数据库文件
'sqlFileName'=>'data.sql',
//初始化商品文件
'sqlFileGoods'=>'goods.sql',
//数据库名
'dbName' => '',
//数据库表前缀
'dbPrefix' => 'qf_',
//站点名称
'siteName' => '',
//需要读写权限的目录
'dirAccess' => array(
'/',
'../uploads',
),
/* ------写入数据库完成后处理的文件------ */
'handleFile' => 'main.php',
/* ------安装验证/生成文件;非云平台安装有效------ */
'installFile' => './install.lock',
'alreadyInstallInfo' => '你已经安装过该系统如果想重新安装请先删除站点install目录下的 install.lock 文件,然后再尝试安装!',
);

View File

@@ -31,12 +31,7 @@ CREATE TABLE `qf_access` (
`access_createtime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
`access_updatetime` int(11) NOT NULL DEFAULT 0 COMMENT '修改时间',
PRIMARY KEY (`access_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '授权信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of qf_access
-- ----------------------------
INSERT INTO `qf_access` VALUES (1, 1, 'e87d3ab11608533d4ee0cb05a21aee6f8e44865c100000e87d3ab11608533d4ee0cb05a21aee6f8e44865c', 'admin', '127.0.0.1', 0, 1726298717, 1726303168);
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '授权信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for qf_admin
@@ -62,12 +57,7 @@ CREATE TABLE `qf_admin` (
INDEX `admin_name`(`admin_name`) USING BTREE,
INDEX `admin_password`(`admin_password`) USING BTREE,
INDEX `admin_account`(`admin_account`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of qf_admin
-- ----------------------------
INSERT INTO `qf_admin` VALUES (1, 'admin', 'b806edc1a9e170c683c73e9ea486bbc9ffc07eb5', 'BuVf', '超级管理员', '', '超级管理员', '', 0.00, 1, '127.0.0.1', 0, 0, 1726303168);
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for qf_attach
@@ -128,7 +118,7 @@ CREATE TABLE `qf_conf` (
-- ----------------------------
-- Records of qf_conf
-- ----------------------------
INSERT INTO `qf_conf` VALUES (1, 'app_name', '资源管理系统', '网站名称', '', 0, 0, NULL, 0, 1, 99, 1, 0, 1725411498);
INSERT INTO `qf_conf` VALUES (1, 'app_name', '', '网站名称', '', 0, 0, NULL, 0, 1, 99, 1, 0, 1725411498);
INSERT INTO `qf_conf` VALUES (2, 'upload_max_file', '4097152', '最大文件上传限制', '', 0, 0, NULL, 2, 1, 0, 1, 0, 1617352067);
INSERT INTO `qf_conf` VALUES (3, 'upload_file_type', 'csv,xlsx', '允许文件上传类型', '', 0, 0, NULL, 2, 1, 0, 1, 0, 1617351959);
INSERT INTO `qf_conf` VALUES (4, 'upload_max_image', '2097152', '最大图片上传限制', '', 0, 0, NULL, 2, 1, 0, 1, 0, 1617351961);
@@ -137,17 +127,17 @@ INSERT INTO `qf_conf` VALUES (21, 'logo', '', '网站LOGO', '方形LOGO最佳
INSERT INTO `qf_conf` VALUES (22, 'quark_cookie', '', '夸克Cookie', '', 0, 0, NULL, 4, 1, 0, 1, 1712114435, 1712114652);
INSERT INTO `qf_conf` VALUES (23, 'qcode', '', '群二维码', '前台加入群聊开关;有图显示按钮,无图不显示', 0, 4, NULL, 3, 1, 80, 1, 1712451616, 1725326400);
INSERT INTO `qf_conf` VALUES (24, 'app_description', '', 'SEO描述', '', 0, 1, NULL, 9, 1, 996, 1, 1712451778, 1725411481);
INSERT INTO `qf_conf` VALUES (25, 'quark_banned', '失效,年会员,空间容量,微信,微信群,全网资源,影视资源,扫码,最新资源,公众号,IMG_,资源汇总,緑铯粢源,.url,网盘推广,大额优惠券,资源文档,dy8.xyz,妙妙屋,资源合集,kkdm', '广告词', '出现这些词的资源,转存时删除;格式如:影视资源,年会员', 0, 1, NULL, 4, 1, 0, 1, 1714035639, 1723795683);
INSERT INTO `qf_conf` VALUES (25, 'quark_banned', '失效,年会员,空间容量,微信,微信群,全网资源,影视资源,扫码,最新资源,公众号,IMG_,资源汇总,緑铯粢源,.url,网盘推广,大额优惠券,资源文档,dy8.xyz,妙妙屋,资源合集,kkdm', '广告词', '出现这些词的资源,转存时删除;格式如:影视资源,年会员', 0, 1, NULL, 4, 1, 999, 1, 1714035639, 1723795683);
INSERT INTO `qf_conf` VALUES (26, 'Authorization', '', '阿里Authorization', '此版本用不着', 0, 0, NULL, 4, 1, 0, 1, 1722010465, 1722010465);
INSERT INTO `qf_conf` VALUES (27, 'mp4_online', '0', '在线观看资源', '此版本用不着', 0, 2, '开启=>1\n关闭=>0', 4, 1, 0, 1, 1723014926, 1723014926);
INSERT INTO `qf_conf` VALUES (28, 'search_type', '1', '搜索模式', '精准搜索:只有查包含关键词的;模糊搜索:关键词顺序可乱但必须都包含;分词搜索:只要满足其中一个字就会搜索到', 0, 2, '精准搜索=>0\n模糊搜索=>1\n分词搜索=>2', 1, 1, 0, 1, 1724493746, 1724494058);
INSERT INTO `qf_conf` VALUES (29, 'app_keywords', 'XXX,XXX,XXXX', 'SEO关键词', '网站关键词有利于对整站的SEO优化', 0, 1, NULL, 9, 1, 998, 1, 1725006403, 1725411476);
INSERT INTO `qf_conf` VALUES (30, 'app_title', 'XXXXXXXXXXXX', 'SEO标题', '', 0, 0, NULL, 9, 1, 999, 1, 1725006679, 1725325013);
INSERT INTO `qf_conf` VALUES (31, 'app_subname', 'Hello World', '网站宣传语', '免费分享百万级网盘资源,致力打造顶尖网盘搜索引擎,让您畅享资源无忧!', 0, 0, NULL, 0, 1, 94, 1, 1725006792, 1725006869);
INSERT INTO `qf_conf` VALUES (29, 'app_keywords', '', 'SEO关键词', '网站关键词有利于对整站的SEO优化', 0, 1, NULL, 9, 1, 998, 1, 1725006403, 1725411476);
INSERT INTO `qf_conf` VALUES (30, 'app_title', '', 'SEO标题', '', 0, 0, NULL, 9, 1, 999, 1, 1725006679, 1725325013);
INSERT INTO `qf_conf` VALUES (31, 'app_subname', '', '网站宣传语', '免费分享百万级网盘资源,致力打造顶尖网盘搜索引擎,让您畅享资源无忧!', 0, 0, NULL, 0, 1, 94, 1, 1725006792, 1725006869);
INSERT INTO `qf_conf` VALUES (32, 'home_bg', '', '大图背景', '', 0, 4, '', 3, 1, 75, 1, 1725007588, 1725007613);
INSERT INTO `qf_conf` VALUES (33, 'home_background', NULL, '背景颜色', '默认:#fafafa', 0, 7, NULL, 3, 1, 74, 1, 1725007770, 1725027349);
INSERT INTO `qf_conf` VALUES (34, 'footer_dec', '声明:本站是网盘索引系统,所有内容均来自互联网所提供的公开引用资源,未提供资源上传、存储服务。', '底部介绍', '示例:声明:本站是网盘索引系统,所有内容均来自互联网所提供的公开引用资源,未提供资源上传、存储服务。', 0, 1, NULL, 0, 1, 90, 1, 1725025185, 1725325534);
INSERT INTO `qf_conf` VALUES (35, 'footer_copyright', '© 2024 心悦 Powered by <a href=\"https://github.com/675061370/xinyue-search/\" target=\"_blank\">心悦</a>', '底部版权', '示例:© 2024 心悦 Powered by <a href=\"https://github.com/675061370/xinyue-search/\" target=\"_blank\">心悦</a>', 0, 1, NULL, 0, 1, 89, 1, 1725025262, 1725325624);
INSERT INTO `qf_conf` VALUES (34, 'footer_dec', '', '底部介绍','示例:声明:本站是网盘索引系统,所有内容均来自互联网所提供的公开引用资源,未提供资源上传、存储服务。', 0, 1, NULL, 0, 1, 90, 1, 1725025185, 1725325534);
INSERT INTO `qf_conf` VALUES (35, 'footer_copyright', '', '底部版权','示例:© 2024 心悦搜剧 Powered by <a href=\"https://www.xinyuedh.com/\" target=\"_blank\">心悦导航</a>', 0, 1, NULL, 0, 1, 89, 1, 1725025262, 1725325624);
INSERT INTO `qf_conf` VALUES (36, 'home_color', NULL, '文字颜色', '默认文字颜色:#000000', 0, 7, NULL, 3, 1, 73, 1, 1725027432, 1725027445);
INSERT INTO `qf_conf` VALUES (37, 'home_theme', NULL, '主题色', '默认:#1e80ff', 0, 7, NULL, 3, 1, 72, 1, 1725027499, 1725027504);
INSERT INTO `qf_conf` VALUES (38, 'other_background', NULL, '其它元素背景', '搜索框及其它元素北背景色 默认:#ffffff', 0, 7, NULL, 3, 1, 71, 1, 1725028468, 1725028478);
@@ -157,7 +147,7 @@ INSERT INTO `qf_conf` VALUES (41, 'home_css', '', '自定义CSS', '直接写css
INSERT INTO `qf_conf` VALUES (42, 'seo_statistics', '', '统计代码', '直接填写统计代码即可如51LA <script charset=\"UTF-8\" id=\"XXXXX\" src=\"//sdk.51.la/js-sdk-pro.min.js\"></script> <script>LA.init({id:\"XXXXX\",ck:\"XXXX\",hashMode:true})</script>', 0, 1, NULL, 9, 1, 995, 1, 1725325341, 1725411486);
INSERT INTO `qf_conf` VALUES (43, 'app_icon', '', '网站icon', '', 0, 4, NULL, 0, 1, 92, 1, 1725326071, 1725326071);
INSERT INTO `qf_conf` VALUES (44, 'app_demand', '0', '提交需求', '前台是否开启此功能 默认开启', 0, 2, '开启=>0\n关闭=>1', 3, 1, 81, 1, 1725326640, 1725326707);
INSERT INTO `qf_conf` VALUES (45, 'app_links', '<a href=\"https://github.com/675061370/xinyue-search/\" target=\"_blank\">更多资源</a>', '顶部其他外链', '一行一个外链(a标签)<a href=\"https://github.com/675061370/xinyue-search/\" target=\"_blank\">更多资源</a>', 0, 1, NULL, 3, 1, 80, 1, 1725326838, 1725326838);
INSERT INTO `qf_conf` VALUES (45, 'app_links', '', '顶部其他外链', '一行一个外链(a标签)<a href=\"https://www.xinyuedh.com/\" target=\"_blank\">更多资源</a>', 0, 1, NULL, 3, 1, 80, 1, 1725326838, 1725326838);
INSERT INTO `qf_conf` VALUES (46, 'app_name_hide', '0', '隐藏网站名称', '默认显示logo包含文字的可以隐藏网站名称', 0, 2, '显示=>0\n隐藏=>1', 0, 1, 98, 1, 1725411632, 1725411763);
INSERT INTO `qf_conf` VALUES (47, 'ranking_m_num', '6', '移动端限制数量', '释:移动端最多显示数量', 0, 0, NULL, 3, 1, 77, 1, 1725412329, 1725412329);
INSERT INTO `qf_conf` VALUES (48, 'search_tips', '', '未搜索提示词', '为空时默认:未找到,可换个关键词尝试哦~', 0, 0, NULL, 1, 1, 0, 1, 1726108804, 1726108804);
@@ -165,6 +155,20 @@ INSERT INTO `qf_conf` VALUES (49, 'search_bg', '', '未搜索提示图', '', 0,
INSERT INTO `qf_conf` VALUES (50, 'home_new', '1', '最新列表', '仅无图模式有效', 0, 2, '开启=>0\n关闭=>1', 3, 1, 79, 1, 1726299605, 1726299605);
INSERT INTO `qf_conf` VALUES (51, 'home_new_img', '', '最新图标', '', 0, 4, NULL, 3, 1, 79, 1, 1726302688, 1726302688);
INSERT INTO `qf_conf` VALUES (52, 'is_quan', '0', '全网搜', '', 0, 2, '关闭=>0\n开启=>1', 1, 1, 1, 1, 1729928547, 1729928547);
INSERT INTO `qf_conf` VALUES ('53', 'baidu_cookie', '', '百度Cookie', '', '0', '0', NULL, '4', '1', '89', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('54', 'quark_file', '', '夸克默认转存目录', '', '0', '0', NULL, '4', '1', '98', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('55', 'quark_file_time', '', '夸克临时资源目录', '', '0', '0', NULL, '4', '1', '97', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('56', 'baidu_file', '', '百度默认转存目录', '', '0', '0', NULL, '4', '1', '88', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('57', 'baidu_file_time', '', '百度临时资源目录', '', '0', '0', NULL, '4', '1', '87', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('58', 'ali_file', '', '阿里默认转存目录', '', '0', '0', NULL, '4', '1', '78', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('59', 'ali_file_time', '', '阿里临时资源目录', '', '0', '0', NULL, '4', '1', '77', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('60', 'uc_cookie', '', 'UcCookie', '', '0', '0', NULL, '4', '1', '69', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('61', 'uc_file', '', 'UC默认转存目录', '', '0', '0', NULL, '4', '1', '68', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('62', 'uc_file_time', '', 'UC临时资源目录', '', '0', '0', NULL, '4', '1', '67', '1', '1743145595', '1743145595');
INSERT INTO `qf_conf` VALUES ('63', 'xunlei_cookie', '', '迅雷Cookie', '', '0', '0', NULL, '4', '1', '59', '1', '1743149794', '1743149794');
INSERT INTO `qf_conf` VALUES ('64', 'xunlei_file', '', '迅雷默认转存目录', '', '0', '0', NULL, '4', '1', '58', '1', '1743149819', '1743149819');
INSERT INTO `qf_conf` VALUES ('65', 'xunlei_file_time', '', '迅雷临时资源目录', '', '0', '0', NULL, '4', '1', '57', '1', '1743149860', '1743149860');
-- ----------------------------

381
public/install/index.php Normal file
View File

@@ -0,0 +1,381 @@
<?php
header("Content-type: text/html; charset=utf-8");
//开启session
session_start();
//配置信息
$config = include './config.php';
if(empty($config)){
exit(get_tip_html('安装配置信息不存在,无法继续安装!'));
}
//安装环境验证,获取相应判断信息
define('INSTALLTYPE', 'HOST');
//本地
require './localhost.php';
//限制最大的执行时间
set_time_limit(1000);
//php版本
$phpversion = phpversion();
//数据库文件
if(!file_exists('./'.$config['sqlFileName'])){
exit(get_tip_html('数据库文件不存在,无法继续安装!'));
}
//写入数据库完成后处理的文件
if (!file_exists('./'.$config['handleFile'])) {
exit(get_tip_html('处理文件不存在,无法继续安装!'));
}
//设置报错级别并返回当前级别。
error_reporting(E_ALL & ~E_NOTICE);
//安装步骤
$steps = array(
'1' => '安装许可协议',
'2' => '运行环境检测',
'3' => '安装参数设置',
'4' => '安装详细过程',
'5' => '安装完成',
);
$step = isset($_GET['step']) ? $_GET['step'] : 1;
//当前安装步骤
$step_html = '';
foreach ($steps as $key => $value) {
$current = $key == $step? 'current':'';
$step_html .= '<li class="'.$current.'"><em>'.$key.'</em>'.$value.'</li>';
}
//安装页面
switch ($step) {
//安装许可协议
case '1':
$license = file_get_contents('./license.txt');
include ("./templates/1.php");
break;
//运行环境检测
case '2':
$server = array(
//操作系统
'os' => php_uname(),
//PHP版本
'php' => $phpversion,
);
$error = 0;
//php版本
if ($phpversion>=$config['php']) {
$server['php'] = '<span class="correct_span">&radic;</span> 支持';
} else {
$server['php'] = '<span class="correct_span error_span">&radic;</span> '.$phpversion;
$error++;
}
//上传限制
if (ini_get('file_uploads')) {
$server['uploadSize'] = '<span class="correct_span">&radic;</span> ' . ini_get('upload_max_filesize');
} else {
$server['uploadSize'] = '<span class="correct_span error_span">&radic;</span>禁止上传';
}
//session
if (function_exists('session_start')) {
$server['session'] = '<span class="correct_span">&radic;</span> 支持';
} else {
$server['session'] = '<span class="correct_span error_span">&radic;</span> 不支持';
$error++;
}
//需要读写权限的目录
$folder = $config['dirAccess'];
$install_path = str_replace('\\','/',getcwd()).'/';
$site_path = str_replace('Install/', '', $install_path);
include ("./templates/2.php");
$_SESSION['INSTALLSTATUS'] = $error == 0?'SUCCESS':$error;
break;
//安装参数设置
case '3':
verify(3);
//测试数据库链接
if (isset($_GET['testdbpwd'])) {
empty($_POST['dbhost'])?alert(0,'数据库服务器地址不能为空!','dbhost'):'';
empty($_POST['dbuser'])?alert(0,'数据库用户名不能为空!','dbuser'):'';
empty($_POST['dbname'])?alert(0,'数据库名不能为空!','dbname'):'';
empty($_POST['dbport'])?alert(0,'数据库端口不能为空!','dbport'):'';
$dbHost = $_POST['dbhost'] . ':' . $_POST['dbport'];
$mysqli = new mysqli($dbHost, $_POST['dbuser'], $_POST['dbpw']);
if(!$mysqli->server_info) {
alert(0,'数据库链接失败!','dbpw');
}else{
alert(1,'数据库链接成功!','dbpw');
}
$mysqli->close();
}
//域名+路径
$domain = empty($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];
if ((int) $_SERVER['SERVER_PORT'] != 80) {
$domain .= ":" . $_SERVER['SERVER_PORT'];
}
$scriptName = !empty($_SERVER["REQUEST_URI"]) ? $scriptName = $_SERVER["REQUEST_URI"] : $scriptName = $_SERVER["PHP_SELF"];
$rootpath = preg_replace("/\/(I|i)nstall\/index\.php(.*)$/", "", $scriptName);
$domain = $domain . $rootpath;
include ("./templates/3.php");
break;
//安装详细过程
case '4':
if (!isset($_GET['install'])){
switch (INSTALLTYPE){
case 'SAE':
// 服务器地址
$_POST['dbhost'] = SAE_MYSQL_HOST_M;
// 端口
$_POST['dbport'] = SAE_MYSQL_PORT;
// 数据库名
$_POST['dbname'] = SAE_MYSQL_DB;
// 用户名
$_POST['dbuser'] = SAE_MYSQL_USER;
// 密码
$_POST['dbpw'] = SAE_MYSQL_PASS;
break;
case 'BAE':
// 服务器地址
$_POST['dbhost'] = HTTP_BAE_ENV_ADDR_SQL_IP;
// 端口
$_POST['dbport'] = HTTP_BAE_ENV_ADDR_SQL_PORT;
// 用户名
$_POST['dbuser'] = HTTP_BAE_ENV_SK;
// 密码
$_POST['dbpw'] = SAE_MYSQL_PASS;
break;
}
}
verify(4);
if (intval($_GET['install'])) {
dataVerify();
//关闭特殊字符提交处理到数据库
//设置时区
date_default_timezone_set('PRC');
//当前进行的数据库操作
$n = intval($_GET['n']);
$arr = array();
//数据库服务器地址
$dbHost = trim($_POST['dbhost']);
//数据库端口
$dbPort = trim($_POST['dbport']);
//数据库名
$dbName = trim($_POST['dbname']);
$dbHost = empty($dbPort) || $dbPort == 3306 ? $dbHost : $dbHost . ':' . $dbPort;
//数据库用户名
$dbUser = trim($_POST['dbuser']);
//数据库密码
$dbPwd = trim($_POST['dbpw']);
//表前缀
$dbPrefix = empty($_POST['dbprefix']) ? 'db_' : trim($_POST['dbprefix']);
//链接数据库
$mysqli = new mysqli($dbHost, $dbUser, $dbPwd);
//导入政采商品
$sitegoods = trim($_POST['sitegoods']);
// 获取错误信息
$error=$mysqli->connect_error;
if (!is_null($error)) {
alert(0,'数据库链接失败!');
}
// 设置字符集
$mysqli->query("SET NAMES 'utf8'");
if ($mysqli->server_info < 5.0) {
alert(0,'请将您的mysql升级到5.0以上');
}
// 创建数据库并选中
if(!$mysqli->select_db($dbName)){
$create_sql='CREATE DATABASE IF NOT EXISTS '.$dbName.' DEFAULT CHARACTER SET utf8;';
$mysqli->query($create_sql) or alert(0,'创建数据库失败');
$mysqli->select_db($dbName);
}
// 导入sql数据并创建表
$sqldata = file_get_contents('./'.$config['sqlFileName']);
if(empty($sqldata)){
alert(0,'数据库文件不能为空!');
}
$sql_array=preg_split("/;[\r\n]+/", str_replace($config['dbPrefix'],$dbPrefix,$sqldata));
$counts = count($sql_array);
for ($i = $n; $i < $counts; $i++) {
$sql = trim($sql_array[$i]);
if (strstr($sql, 'CREATE TABLE')) {
preg_match('/CREATE TABLE `([^ ]*)`/', $sql, $matches);
$mysqli->query($sql_array[$i]);
$info = '<li><span class="correct_span">&radic;</span>创建数据表' . $matches[1] . ',完成!<span style="float: right;">'.date('Y-m-d H:i:s').'</span></li> ';
$i++;
alert(1,$info,$i);
}else{
if(!empty($sql)){
$mysqli->query($sql);
}
}
}
//处理
$data = include './'.$config['handleFile'];
$_SESSION['INSTALLOK'] = $data['status']?1:0;
alert($data['status'],$data['info']);
}
include ("./templates/4.php");
break;
//安装完成
case '5':
verify(5);
include ("./templates/5.php");
//安装完成,生成.lock文件
if(isset($_SESSION['INSTALLOK']) && $_SESSION['INSTALLOK'] == 1){
filewrite($config['installFile']);
}
unset($_SESSION);
break;
}
/**
* 错误提示html
*/
function get_tip_html($info){
return '<div>'.$info.'</div>';
}
//返回提示信息
function alert($status,$info,$type = 0){
exit(json_encode(array('status'=>$status,'info'=>$info,'type'=>$type)));
}
function verify($step = 3){
if($step >= 3){
//未运行环境检测,跳转到安装许可协议页面
if(!isset($_SESSION['INSTALLSTATUS'])){
header('location:./index.php');
exit();
}
//运行环境检测存在错误,返回运行环境检测
if($_SESSION['INSTALLSTATUS'] != 'SUCCESS'){
header('location:./index.php?step=2');
exit();
}
}
if($step == 4){
//未提交数据
if(empty($_POST)){
header('location:./index.php?step=3');
exit();
}
}
if($step >= 5){
//数据库未写入完成
if(!isset($_SESSION['INSTALLOK'])){
header('location:./index.php?step=4');
exit();
}
}
}
function dataVerify(){
empty($_POST['dbhost'])?alert(0,'数据库服务器不能为空!'):'';
empty($_POST['dbport'])?alert(0,'数据库端口不能为空!'):'';
empty($_POST['dbuser'])?alert(0,'数据库用户名不能为空!'):'';
empty($_POST['dbname'])?alert(0,'数据库名不能为空!'):'';
empty($_POST['dbprefix'])?alert(0,'数据库表前缀不能为空!'):'';
empty($_POST['manager'])?alert(0,'管理员帐号不能为空!'):'';
empty($_POST['manager_pwd'])?alert(0,'管理员密码不能为空!'):'';
}
/**
* 判断目录是否可写
*/
function testwrite($d) {
$tfile = "_test.txt";
$fp = fopen($d . "/" . $tfile, "w");
if (!$fp) {
return false;
}
fclose($fp);
$rs = unlink($d . "/" . $tfile);
if ($rs) {
return true;
}
return false;
}
/**
* 创建目录
*/
function dir_create($path, $mode = 0777) {
if (is_dir($path)) {
return TRUE;
}
mkdir($path, $mode, true);
chmod($path, $mode);
}
/**
* 数据库语句解析
* @param $sql 数据库
* @param $newTablePre 新的前缀
* @param $oldTablePre 旧的前缀
*/
function sql_split($sql, $newTablePre, $oldTablePre) {
//前缀替换
if ($newTablePre != $oldTablePre){
$sql = str_replace($oldTablePre, $newTablePre, $sql);
}
$sql = preg_replace("/TYPE=(InnoDB|MyISAM|MEMORY)( DEFAULT CHARSET=[^; ]+)?/", "ENGINE=\\1 DEFAULT CHARSET=utf8", $sql);
$sql = str_replace("\r", "\n", $sql);
$ret = array();
$queriesarray = explode(";\n", trim($sql));
unset($sql);
foreach ($queriesarray as $k=>$query) {
$ret[$k] = '';
$queries = explode("\n", trim($query));
$queries = array_filter($queries);
foreach ($queries as $query) {
$str1 = substr($query, 0, 1);
if ($str1 != '#' && $str1 != '-')
$ret[$k] .= $query;
}
}
return $ret;
}
/**
* 产生随机字符串
* 产生一个指定长度的随机字符串,并返回给用户
* @access public
* @param int $len 产生字符串的位数
* @return string
*/
function genRandomString($len = 6) {
$chars = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
"3", "4", "5", "6", "7", "8", "9", '!', '@', '#', '$',
'%', '^', '&', '*', '(', ')'
);
$charsLen = count($chars) - 1;
shuffle($chars); // 将数组打乱
$output = "";
for ($i = 0; $i < $len; $i++) {
$output .= $chars[mt_rand(0, $charsLen)];
}
return $output;
}
/**
* 获取客户端IP地址
* @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
* @return mixed
*/
function get_client_ip($type = 0) {
$type = $type ? 1 : 0;
static $ip = NULL;
if ($ip !== NULL) return $ip[$type];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown',$arr);
if(false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
}elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u",ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}

View File

View File

@@ -0,0 +1,14 @@
请您在使用前仔细阅读如下条款。包括免除或者限制作者责任的免责条款及对用户的权利限制。您的安装使用行为将视为对本《用户许可协议》的接受,并同意接受本《用户许可协议》各项条款的约束。
一、安装和使用:
您必须保证在不进行非法活动,不违反国家相关政策法规的前提下使用本软件。
二:郑重声明:
1、任何个人或组织不得在未经授权的情况下删除、修改、拷贝本软件及其他副本上一切关于版权的信息。
三、免责声明:
本软件并无附带任何形式的明示的或暗示的保证,包括任何关于本软件的适用性, 无侵犯知识产权或适合作某一特定用途的保证。
在任何情况下,对于因使用本软件或无法使用本软件而导致的任何损害赔偿,作者均无须承担法律责任。作者不保证本软件所包含的资料,文字、图形、链接或其它事项的准确性或完整性。
所有由用户自己制作、下载、使用的第三方信息数据和插件所引起的一切版权问题或纠纷,本软件概不承担任何责任。
版权所有 (c) 2024-2025系统保留所有权利。

View File

@@ -0,0 +1,10 @@
<?php
//检测是否已经安装
if(file_exists($config['installFile'])){
exit(get_tip_html($config['alreadyInstallInfo']));
}
//写入文件
function filewrite($file){
@touch($file);
}

46
public/install/main.php Normal file
View File

@@ -0,0 +1,46 @@
<?php
$username = trim($_POST['manager']);
$password = trim($_POST['manager_pwd']);
//网站名称
$site_name = addslashes(trim($_POST['sitename']));
//更新配置信息
$mysqli->query("UPDATE `{$dbPrefix}conf` SET `conf_value` = '$site_name' WHERE conf_key='app_name'");
if(INSTALLTYPE == 'HOST'){
$db_str=<<<php
APP_DEBUG = true
SYSTEM_SALT= {$site_name}
[APP]
DEFAULT_TIMEZONE = Asia/Chongqing
[DATABASE]
TYPE = mysql
HOSTNAME = {$dbHost}
DATABASE = {$dbName}
USERNAME = {$dbUser}
PASSWORD = {$dbPwd}
HOSTPORT = {$dbPort}
CHARSET = utf8mb4
DEBUG = false
PREFIX = {$dbPrefix}
[LANG]
default_lang = zh-cn
php;
// 创建数据库链接配置文件
file_put_contents('../../.env', $db_str);
}
//插入管理员
//生成随机认证码
$salt = genRandomString(4);
$time = time();
$ip = get_client_ip();
$password = sha1($password . $salt . $password . $salt);
$url = "insert into `{$dbPrefix}admin` VALUES (1,'{$username}', '{$password}', '{$salt}', '超级管理员','','超级管理员','',0.00,1,'127.0.0.1',0,'{$time}','{$time}')";
$mysqli->query($url);
$mysqli->close();
return array('status'=>2,'info'=>'成功添加管理员<br />成功写入配置文件<br>安装完成...');

118
public/install/sae.php Normal file
View File

@@ -0,0 +1,118 @@
<?php
//设置storage的domain
if($_GET['step'] == 0){
if(empty($_POST['storagedomain'])){
$step_html = '<li class="current"><em>0</em>Storage设置</li>';
include './templates/0.php';
exit;
} else {
$_SESSION['STORAGEDOMAIN'] = $_POST['storagedomain'];
if(!empty($_SESSION['STORAGEDOMAIN'])){
header('location:./index.php?step=1');
exit();
}
}
}
if(!isset($_SESSION['STORAGEDOMAIN']) || empty($_SESSION['STORAGEDOMAIN'])){
header('location:./index.php?step=0');
exit;
}
$config['uploaddir'] = $_SESSION['STORAGEDOMAIN'];
//测试该该storage是否有效
if(!filewrite($config['uploaddir'].'/install_file.lock')){
exit(get_tip_html('该storage无效'));
} else{
file_delete($config['uploaddir'].'/install_file.lock');
}
define('SAESTOR_INSTALL_NAME', $config['uploaddir'].'/saestor_'. $_SERVER['HTTP_APPVERSION'] . '_install.lock');
$config['alreadySaeInstallInfo'] = "版本" . $_SERVER['HTTP_APPVERSION'] . "已完成安装!请删除网站根目录下的install目录<br />如果需要重新安装请先删除storage内的 saestor_" . $_SERVER['HTTP_APPVERSION'] . "_install.lock 文件";
if(fileExists(SAESTOR_INSTALL_NAME)){
exit(get_tip_html($config['alreadySaeInstallInfo']));
}
if(!is_storage){
exit(get_tip_html('请开启storage服务'));
}
if(!is_mc){
exit(get_tip_html('请开启memcahce服务'));
}
if(!is_mysql){
exit(get_tip_html('请开启mysql服务'));
}
if(!is_kv){
exit(get_tip_html('请开启KV数据库服务'));
}
//SaeStorage
function SaeStorage(){
static $SaeStorage = array();
if(!isset($SaeStorage['SaeStorage'])){
$SaeStorage['SaeStorage'] = new SaeStorage();
}
return $SaeStorage['SaeStorage'];
}
//domain 路径
function file_getdomainfilepath($filename){
$arr=explode('/',ltrim($filename,'./'));
if($arr[count($arr)-1] == ''){
unset($arr[count($arr)-1]);
}
$domain=array_shift($arr);
$filePath=implode('/',$arr);
return array('domain'=>$domain,'filepath'=>$filePath);
}
//检查文件是否存在
function fileExists($filename){
$arr=file_getdomainfilepath($filename);
return SaeStorage()->fileExists($arr['domain'], $arr['filepath']);
}
//写入文件
function filewrite($file = ''){
$file = empty($file) ? SAESTOR_INSTALL_NAME:$file;
$arr=file_getdomainfilepath($file);
SaeStorage()->write($arr['domain'], $arr['filepath'],'1');
}
//删除文件
function file_delete($filename){
$arr=file_getdomainfilepath($filename);
return SaeStorage()->delete($arr['domain'], $arr['filepath']);
}
//判断是否开启storage
function is_storage() {
$s = new SaeStorage();
if (!$s->write(SAESTOR_NAME, 'is_storage', '1')) {
return FALSE;
} else {
return TRUE;
}
}
//判断是否开启memcahce
function is_mc() {
$mmc = @memcache_init();
if ($mmc) {
return TRUE;
} else {
return FALSE;
}
}
//判断是否开启mysql
function is_mysql() {
$mysql = @new SaeMysql();
$sql = "select database()";
$data = @$mysql->getData($sql);
if ($data) {
return TRUE;
} else {
return FALSE;
}
}
//判断是否开启KV数据库
function is_kv(){
$kv=new SaeKV();
if($kv->init()){
return TRUE;
} else {
return FALSE;
}
}

BIN
public/install/templates/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,10 @@
<?php require './templates/header.php';?>
<div class="section">
<div class="main">
<pre class="pact"><?php echo $license;?></pre>
</div>
</div>
<div class="btn-box">
<a href="./index.php?step=2" class="btn">接 受</a>
</div>
<?php require './templates/footer.php';?>

View File

@@ -0,0 +1,78 @@
<?php require './templates/header.php';?>
<div class="section">
<div class="main server">
<table width="100%">
<tr>
<td class="td1">环境检测</td>
<td class="td1" width="25%">推荐配置</td>
<td class="td1" width="25%">当前状态</td>
<td class="td1" width="25%">最低要求</td>
</tr>
<tr>
<td>操作系统</td>
<td>类UNIX</td>
<td><span class="correct_span">&radic;</span> <?php echo $server['os']; ?></td>
<td>不限制</td>
</tr>
<tr>
<td>PHP版本</td>
<td><?php echo $config['php_t']; ?></td>
<td><?php echo $server['php']; ?></td>
<td><?php echo $config['php']; ?></td>
</tr>
<tr>
<td>附件上传</td>
<td>>2M</td>
<td><?php echo $server['uploadSize']; ?></td>
<td>不限制</td>
</tr>
<tr>
<td>session</td>
<td>开启</td>
<td><?php echo $server['session']; ?></td>
<td>开启</td>
</tr>
</table>
<?php if(INSTALLTYPE == 'HOST'){ ?>
<table width="100%">
<tr>
<td class="td1">目录、文件权限检查</td>
<td class="td1" width="25%">写入</td>
<td class="td1" width="25%">读取</td>
</tr>
<?php
foreach($folder as $dir){
$Testdir = $site_path.$dir;
dir_create($Testdir);
if(TestWrite($Testdir)){
$w = '<span class="correct_span">&radic;</span>可写 ';
}else{
$w = '<span class="correct_span error_span">&radic;</span>不可写 ';
$error++;
}
if(is_readable($Testdir)){
$r = '<span class="correct_span">&radic;</span>可读' ;
}else{
$r = '<span class="correct_span error_span">&radic;</span>不可读';
$error++;
}
?>
<tr>
<td><?php echo $dir; ?></td>
<td><?php echo $w; ?></td>
<td><?php echo $r; ?></td>
</tr>
<?php } ?>
</table>
<?php } ?>
</div>
</div>
<div class="btn-box">
<a href="./index.php?step=2" class="btn">重新检测</a>
<?php if(empty($error)){ ?>
<a href="./index.php?step=3" class="btn">下一步</a>
<?php } else { ?>
<button href="#" class="btn error" disabled>当前有误</button>
<?php } ?>
</div>
<?php require './templates/footer.php';?>

View File

@@ -0,0 +1,264 @@
<?php require './templates/header.php';?>
<form id="J_install_form" action="index.php?step=4" method="post">
<div class="section">
<div class="main server">
<table width="100%">
<tr>
<td class="td1" width="100">数据库信息</td>
<td class="td1" width="200">&nbsp;</td>
<td class="td1">&nbsp;</td>
</tr>
<?php if(INSTALLTYPE == 'HOST'){?>
<tr>
<td class="tar">数据库服务器:</td>
<td><input type="text" name="dbhost" id="dbhost" value="127.0.0.1" class="input"></td>
<td><div id="J_install_tip_dbhost"><span class="gray">数据库服务器地址一般为localhost</span></div></td>
</tr>
<tr>
<td class="tar">数据库端口:</td>
<td><input type="text" name="dbport" id="dbport" value="3306" class="input"></td>
<td><div id="J_install_tip_dbport"><span class="gray">数据库服务器端口一般为3306</span></div></td>
</tr>
<tr>
<td class="tar">数据库用户名:</td>
<td><input type="text" name="dbuser" id="dbuser" value="" class="input"></td>
<td><div id="J_install_tip_dbuser"></div></td>
</tr>
<tr>
<td class="tar">数据库密码:</td>
<td><input type="text" name="dbpw" id="dbpw" value="" class="input" autoComplete="off"></td>
<td><div id="J_install_tip_dbpw"></div></td>
</tr>
<?php }?>
<? if(INSTALLTYPE == 'HOST' || INSTALLTYPE == 'BAE'){ ?>
<tr>
<td class="tar">数据库名:</td>
<td><input type="text" name="dbname" id="dbname" value="<?php echo $config['dbName'] ?>" class="input"></td>
<td><div id="J_install_tip_dbname"></div></td>
</tr>
<?php } ?>
<tr>
<td class="tar">数据库表前缀:</td>
<td><input type="text" name="dbprefix" id="dbprefix" value="<?php echo $config['dbPrefix'] ?>" class="input"></td>
<td><div id="J_install_tip_dbprefix"><span class="gray">建议使用默认</span></div></td>
</tr>
<tr>
<td class="tar">测试数据库连接:</td>
<td><button type="button" class="btn" onclick="TestDbPwd()">测试连接</button></td>
<td>&nbsp;</td>
</tr>
</table>
<table width="100%">
<tr>
<td class="td1" width="100">网站信息</td>
<td class="td1" width="200">&nbsp;</td>
<td class="td1">&nbsp;</td>
</tr>
<tr>
<td class="tar">网站名称:</td>
<td><input type="text" name="sitename" value="<?php echo $config['siteName'] ?>" class="input"></td>
<td><div id="J_install_tip_sitename"></div></td>
</tr>
<tr>
<td class="tar">管理员帐号:</td>
<td><input type="text" name="manager" value="" class="input"></td>
<td><div id="J_install_tip_manager"></div></td>
</tr>
<tr>
<td class="tar">密码:</td>
<td><input type="text" name="manager_pwd" id="J_manager_pwd" class="input" autoComplete="off"></td>
<td><div id="J_install_tip_manager_pwd"></div></td>
</tr>
<tr>
<td class="tar">重复密码:</td>
<td><input type="text" name="manager_ckpwd" class="input" autoComplete="off"></td>
<td><div id="J_install_tip_manager_ckpwd"></div></td>
</tr>
</table>
<div id="J_response_tips" style="display:none;"></div>
</div>
</div>
<div class="btn-box">
<a href="./index.php?step=2" class="btn">上一步</a>
<button type="submit" class="btn btn_submit J_install_btn">创建数据</button>
</div>
</form>
<script src="./templates/js/jquery.js"></script>
<script src="./templates/js/validate.js"></script>
<script>
function TestDbPwd(){
$.ajax({
type: "POST",
dataType:'json',
url: "./index.php?step=3&testdbpwd=1",
data: {'dbhost':$('#dbhost').val(),'dbuser':$('#dbuser').val(),'dbpw':$('#dbpw').val(),'dbname':$('#dbname').val(),'dbport':$('#dbport').val()},
success: function(data){
if(data.status != 1){
$('#'+ data.type).focus();
}
alert(data.info);
},
error:function(){
alert('数据库链接配置失败');
$('#dbpw').focus();
}
});
}
$(function(){
//聚焦时默认提示
var focus_tips = {
dbhost : '数据库服务器地址一般为localhost',
dbport : '数据库服务器端口一般为3306',
dbuser : '请输入数据库用户名',
dbpw : '请输入数据库密码',
dbname : '请输入数据库名',
dbprefix : '建议使用默认,同一数据库安装多个时需修改',
manager : '创始人帐号,拥有站点后台所有管理权限',
manager_pwd : '请输入管理员密码',
manager_ckpwd : '请再次输入管理员密码',
sitename : '请再次输入网站名称',
sitekeywords : '',
siteinfo : '',
manager_email : ''
};
var install_form = $("#J_install_form"),
response_tips = $('#J_response_tips'); //后端返回提示
install_form.validate({
//debug : true,
//onsubmit : false,
errorPlacement: function(error, element) {
//错误提示容器
$('#J_install_tip_'+ element[0].name).html(error);
},
errorElement: 'span',
errorClass : 'tips_error',
validClass : 'tips_error',
onkeyup : false,
focusInvalid : false,
rules: {
dbhost: {
required : true
},
dbport:{
required : true
},
dbuser: {
required : true
},
dbname: {
required : true
},
dbprefix : {
required : true
},
manager: {
required : true
},
manager_pwd: {
required : true,
minlength : 6
},
sitename: {
required : true,
},
manager_ckpwd: {
required : true,
equalTo : '#J_manager_pwd'
},
manager_email: {
required : true,
email : true
}
},
highlight : false,
unhighlight : function(element, errorClass, validClass) {
var tip_elem = $('#J_install_tip_'+ element.name);
tip_elem.html('<span class="'+ validClass +'" data-text="text"><span>');
},
onfocusin : function(element){
var name = element.name;
$('#J_install_tip_'+ name).html('<span data-text="text">'+ focus_tips[name] +'</span>');
$(element).parents('tr').addClass('current');
},
onfocusout : function(element){
var _this = this;
$(element).parents('tr').removeClass('current');
if(element.name === 'email') {
//邮箱匹配点击后,延时处理
setTimeout(function(){
_this.element(element);
}, 150);
}else{
_this.element(element);
}
},
messages: {
dbhost: {
required : '数据库服务器地址不能为空'
},
dbport:{
required : '数据库服务器端口不能为空'
},
dbuser: {
required : '数据库用户名不能为空'
},
dbname: {
required : '数据库名不能为空'
},
dbprefix : {
required : '数据库表前缀不能为空'
},
sitename : {
required : '请再次输入网站名称'
},
manager: {
required : '管理员帐号不能为空'
},
manager_pwd: {
required : '密码不能为空',
minlength : '密码至少6位'
},
manager_ckpwd: {
required : '请再次输入管理员密码',
equalTo : '两次输入的密码不一致。请重新输入'
},
manager_email: {
required : 'Email不能为空',
email : '请输入正确的电子邮箱地址'
}
},
submitHandler:function(form) {
$.ajax({
type: "POST",
dataType:'json',
url: "./index.php?step=3&testdbpwd=1",
data: {'dbhost':$('#dbhost').val(),'dbuser':$('#dbuser').val(),'dbpw':$('#dbpw').val(),'dbname':$('#dbname').val(),'dbport':$('#dbport').val()},
success: function(data){
if(data.status != 1){
alert(data.info);
$('#'+ data.type).focus();
return;
}
form.submit();
return true;
},
error:function(){
alert('数据库链接配置失败');
$('#dbpw').focus();
return;
}
});
}
});
});
</script>
<?php require './templates/footer.php';?>

View File

@@ -0,0 +1,48 @@
<?php require './templates/header.php';?>
<div class="section">
<div class="main install">
<ul id="loginner"></ul>
</div>
</div>
<div class="btn-box">
<a href="javascript:;" class="btn_old" id="installloading">
<img src="templates/images/loading.gif" align="absmiddle">&nbsp;正在安装...
</a>
</div>
<script src="./templates/js/jquery.js"></script>
<script type="text/javascript">
var n=0;
var data = <?php echo json_encode($_POST);?>;
$.ajaxSetup ({ cache: false });
function reloads(n) {
var url = "./index.php?step=4&install=1&n="+n;
$.ajax({
type: "POST",
url: url,
data: data,
dataType: 'json',
success: function(data){
$('#loginner').append(data.info);
if(data.status == 1){
reloads(data.type);
}
if(data.status == 0){
$('#installloading').removeClass('btn_old').addClass('btn').html('继续安装').unbind('click').click(function(){
reloads(0);
});
alert('安装已停止!');
}
if(data.status == 2){
$('#installloading').removeClass('btn_old').addClass('btn').attr('href','./index.php?step=5').html('安装完成');
setTimeout(function(){
window.location.href='./index.php?step=5';
},5000);
}
}
});
}
$(function(){
reloads(n);
})
</script>
<?php require './templates/footer.php';?>

View File

@@ -0,0 +1,20 @@
<?php require './templates/header.php';?>
<div class="section">
<div class="main">
<div class="success_tip cc">
<a href="../qfadmin">安装完成,进入后台管理</a>
<?php if(INSTALLTYPE == 'HOST'){ ?>
<p><?php echo $config['alreadyInstallInfo']?><p>
<?php } else if(INSTALLTYPE == 'SAE'){ ?>
<p><?php echo $config['alreadySaeInstallInfo']?><p>
<?php } else if(INSTALLTYPE == 'BAE'){ ?>
<p><?php echo $config['alreadyBaeInstallInfo']?><p>
<?php } ?>
</div>
</div>
</div>
<div class="btn-box">
<a href="../index" class="btn">进入前台</a>
<a href="../qfadmin" class="btn">进入后台</a>
</div>
<?php require './templates/footer.php';?>

View File

@@ -0,0 +1,219 @@
/* 初始化 */
html{-webkit-text-size-adjust:none;overflow-y:scroll;}
body,input,button,select,textarea{font:12px/1.5 Microsoft YaHei,Tahoma,Helvetica,'SimSun',sans-serif;color:#444;}
html,body,h1,h2,h3,h4,h5,h6,hr,p,blockquote,dl,dt,dd,ul,ol,li,pre,form,fieldset,legend,button,th,td{margin:0;padding:0;border:none}
body{background:url(../images/bg.png);}
td,th,div {word-break:break-all;word-wrap:break-word;}
address,cite,dfn,em,var{font-style:normal;font-weight:normal}
table{empty-cells:show;border-spacing:0;}
ul,ol,li{list-style:none}
ul li,ol li{list-style:none;}
img{border:none;}
label{cursor:pointer;}
a{text-decoration:none;}
a:hover{text-decoration:underline;}
button,input,select,textarea{font-size:100%;}
hr{display:block;clear:both;*margin-top:-8px !important;*margin-bottom:-8px !important;}
/* input select textarea */
input[type="checkbox"]{vertical-align:middle; margin-top:0;}
input,textarea{padding:6px;border-radius: 4px;font-size: 100%;line-height: 16px\9;*line-height: 18px;*vertical-align: -2px;background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s;margin-right: 3px;}
textarea{resize:none;min-height:60px;line-height: 15px;}
textarea:focus,input:focus,select:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6)}
select{height: 30px;line-height: 30px;background-color: #fff;border: 1px solid #ccc;display: inline-block;padding: 6px;color: #555;margin-top: -2px;padding-top: 4px;*margin-top: -8px;vertical-align: middle;-webkit-border-radius: 4px;-moz-border-radius: 4px;border-radius: 4px;font-size: 100%;}
/* 基础样式 */
.clearfix {_zoom: 1;}
.clearfix:after {content: ".";display: block;visibility: hidden;clear: both;height: 0px;}
.wrap{margin:90px auto 0;box-shadow:0 0 20px #3F3F3F;width:980px;background-color:#fff;border-radius: 6px;overflow: hidden;}
/* 头部样式 */
.header{height:80px;padding: 0 5px;box-shadow:0 3px 3px #ddd;position:relative;color:#fff;background:#133ab3;}
.header-title{float:left;font-size: 40px;line-height: 80px;}
.header-install{float: left;margin-top: 26px;font-size: 20px; padding-left: 20px;}
.header-version{float: right;margin-top: 50px;}
/* 步骤 */
.step{border-bottom:1px solid #dce1e5;height:60px;}
.step li{float:left;height:59px;line-height:60px;width:20%;text-align:center;font-size:14px;color:#6f7885;font-weight:700;}
.step li em{width:32px;height:32px;text-align:center;line-height:32px;display:inline-block;background:#999999;border-radius: 50%;font-size:20px;color:#fff;font-family:Microsoft Yahei;margin-right:10px;vertical-align:-1px}
.step li.current{background-position:right -106px;background-repeat:no-repeat;color:#246ea5;}
.step li.current em{background:#133ab3}
.step li.on{background-position:0 -176px;}
.step li.on em{background-position:-70px -70px;}
.pact {box-shadow: 5px 5px 5px #f7f7f7 inset;height: 350px;padding: 20px 20px; white-space: pre-wrap; overflow: hidden;display: block;overflow-y: scroll;margin: 0 auto;font-size: 12px;line-height: 1.5;outline: none;}
/* 服务器检测/信息输入 */
.server{padding:20px 20px 0 65px;}
.server table{margin-bottom:20px;}
.server td{padding:3px 5px;}
.server .td1{color:#417b9d;font-weight:700;}
.server .input{border:1px solid;border-color:#e3e9ef;padding:3px;width:200px;}
.server .input:hover,
.server .input:focus{border:1px solid #0e85d5;outline:none;}
.gray{color:#bbb;}
.server tr:hover .gray{color:#333;}
/* 正确错误 */
.correct_span,
.error_span{display:block;float:left;width:20px;height:16px;text-indent:-2000em;margin-top: 2px;overflow:hidden;background:url(../images/icon.png) no-repeat;margin-right:5px;}
.error_span{background-position:0 -23px;}
/* 成功提示 */
.success_tip{background:#fff url(../images/complete.png) 210px center no-repeat;font-size: 14px;padding:50px 80px 50px 300px;line-height:2;}
.success_tip a{color:#0166a5;text-decoration:none;font-size: 20px;font-weight: 700;}
.success_tip a:hover{text-decoration:underline;}
/*
===================
操作提示
>> tips 普通
>> tips_error 错误
>> tips_success 正确
>> tips_loading 加载中
使用方法
可独立样式使用亦可与tips组装
<div class="tips"><span class="tips_error">错误内容</span></div>
可在外出包裹 tips_block 对错误提示进行块级效果展示
===================
*/
.tips,.tips_block span{line-height:25px;padding:0 6px;}
.tips{border:1px solid #faebd2;background:#ffffe9;color:#666;}
.tips_error,.tips_success{color:#cc3333;padding-left:16px;background:url(../images/tips_system.png) -20px 2px no-repeat;display:inline-block;line-height:18px;}
.tips_success{color:#008800;background-position:0 -19px;}
.tips_loading{color:#cc3333;color:#333333;display:inline-block;line-height:20px;}
/* 数据表写入 */
.install{ box-shadow:5px 5px 5px #f7f7f7 inset;border:1px solid #bdbcbc;width:900px;height:350px;padding:10px;overflow:hidden;display:block;overflow-y:scroll;margin:25px auto;font-size:12px;margin-bottom:22px;outline:none}
.install ul{line-height:1.8;}
/* loading */
.loading{border-radius: 16px;width: 100px;height: 30px;line-height: 30px;text-align: center;background:#000;background-color: rgba(0, 0, 0, 0.5);color: white;position: fixed;_position:absolute;left: 50%;margin-left: -50px;top: 50%;margin-top:-15px;}
.loading span{background:url(../images/pop_loading.gif) 0 center no-repeat;padding-left:24px;}
/* 按钮 */
.btn-box{padding: 10px;text-align: center;border-top: 1px solid #ccc;}
.btn,.btn_old{display:inline-block;width:100px;height:30px;background:#133ab3;font:bold 14px/30px Arial,"Microsoft Yahei",Simsun;text-decoration:none;color:#fff;margin:0 5px;border:0 none;cursor:pointer;vertical-align:top;}
.btn:hover{background-position:0 -30px;text-decoration:none;}
.btn.error{background-position: 0 -60px;color: #DF0000;}
.btn_old,.btn_old:hover{background-position:0 -60px;color:#999 !important;cursor:default;text-decoration:none;}
/* 底部 */
.footer{text-align:center;padding:15px 0 50px;color:#999999;}
.footer a{color:#999;text-decoration:none;}
body {
background: #f5f6fa;
}
.wrap {
margin: 50px auto 0;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
width: 1000px;
background-color: #fff;
border-radius: 8px;
overflow: hidden;
}
.header {
height: 90px;
padding: 0 30px;
box-shadow: 0 2px 10px rgba(19, 58, 179, 0.1);
position: relative;
color: #fff;
background: linear-gradient(135deg, #133ab3, #1e56ff);
}
.header-title {
float: left;
font-size: 42px;
line-height: 90px;
font-weight: 300;
}
.header-install {
float: left;
margin-top: 30px;
font-size: 22px;
padding-left: 25px;
}
.step {
border-bottom: 1px solid #eef0f5;
height: 70px;
background: #fff;
}
.step li {
float: left;
height: 69px;
line-height: 70px;
width: 20%;
text-align: center;
font-size: 15px;
color: #6f7885;
font-weight: 600;
position: relative;
transition: all 0.3s ease;
}
.step li em {
width: 36px;
height: 36px;
text-align: center;
line-height: 36px;
display: inline-block;
background: #e0e6ed;
border-radius: 50%;
font-size: 18px;
color: #fff;
margin-right: 12px;
transition: all 0.3s ease;
}
.step li.current {
color: #133ab3;
}
.step li.current em {
background: #133ab3;
box-shadow: 0 2px 10px rgba(19, 58, 179, 0.2);
}
/* 按钮样式优化 */
.btn {
display: inline-block;
width: 120px;
height: 40px;
background: #133ab3;
font: 600 15px/40px "Microsoft Yahei";
text-decoration: none;
color: #fff;
margin: 0 8px;
border: 0 none;
cursor: pointer;
border-radius: 4px;
transition: all 0.3s ease;
}
.btn:hover {
background: #1e56ff;
transform: translateY(-1px);
box-shadow: 0 2px 10px rgba(19, 58, 179, 0.2);
}
/* 输入框样式优化 */
input, textarea {
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
line-height: 1.5;
background-color: #fff;
border: 1px solid #dce1e5;
transition: all 0.3s ease;
}
input:focus, textarea:focus {
border-color: #133ab3;
box-shadow: 0 0 0 2px rgba(19, 58, 179, 0.1);
}
.btn.error{
color: #999999;
background: #eeeeee;
}

View File

@@ -0,0 +1,4 @@
</div>
<div class="footer"> <?php echo $config['footerInfo'];?> </div>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title><?php echo $config['name'].' '.$config['version']; ?> - <?php echo $config['powered']; ?></title>
<link rel="stylesheet" href="./templates/css/install.css" />
</head>
<body>
<div class="wrap">
<div class="header">
<div class="header-install"><?php echo $config['name']; ?>-安装向导</div>
<div class="header-version">版本:<?php echo $config['version'];?></div>
</div>
<div class="step">
<ul>
<?php echo $step_html;?>
</ul>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

2
public/install/templates/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
更新方式
1、app文件夹整包上传覆盖
2、替换 public\static\index\css\app.css和public\static\index\css\m.css
3、执行sql语句
INSERT INTO `qf_conf` VALUES (52, 'is_quan', '0', '全网搜', '', 0, 2, '关闭=>0\n开启=>1', 1, 1, 1, 1, 1729928547, 1729928547);
如何对接微信对话功能
详情见 app\api\controller\Chatbot.php