Compare commits

...

15 Commits

Author SHA1 Message Date
Super-Passerby
e9ae452cd2
Merge e0cb7443fe into 4b2d78a0b2 2025-04-12 21:39:08 +08:00
Cp0204
4b2d78a0b2 📝 更新 CloudSaver 相关说明
Some checks failed
Docker Publish / build-and-push (push) Has been cancelled
2025-04-12 21:19:58 +08:00
Cp0204
e1e0a6afc4 📝 更新帮助说明链接 2025-04-12 20:31:23 +08:00
Cp0204
d5a802c218 🐛 修复 CloudSaver 登录和搜索逻辑
- 更新 login 方法,增加对未设置用户名或密码的检查
- 调整 search 方法,处理未提供 token 的情况
- 优化 auto_login_search 方法,改进错误处理和 token 刷新逻辑
- 更新前端模板,使用 message 属性替代 error 属性
2025-04-12 19:52:38 +08:00
Cp0204
70093a3f2c ♻️ 重构登录和 API 验证逻辑
- 可使用 token 访问所有接口
- 重命名 get_api_token 函数为 get_login_token,以更清晰地表示其用途
- 优化了多个接口的返回格式,统一使用 success 和 message 字段
2025-04-12 18:52:45 +08:00
Cp0204
81d4098b6c ♻️ 优化配置更新接口和前端处理逻辑
- 修改后端 update 函数返回值格式,使用 JSON 格式返回成功和失败信息
- 更新前端 saveConfig 方法,根据后端返回的成功状态进行不同处理
- 优化配置保存后的提示信息展示逻辑
2025-04-12 18:52:44 +08:00
Cp0204
6f976f242a 增加配置修改后的保存提示
- configModified 跟踪配置是否被修改
- 窗口关闭前提醒保存配置
- 在运行脚本前提醒保存配置
2025-04-12 17:09:12 +08:00
Cp0204
50090db1f4 🔧 优化任务建议数据结构和展示逻辑
- 在后端增加 success 字段以区分请求是否成功
- 前端根据 success 字段决定是否显示建议列表
- 优化错误处理和提示信息展示
- 调整搜索逻辑,增加异常捕获
2025-04-12 16:26:44 +08:00
Cp0204
4225f1986b 添加 CloudSaver 资源搜索功能
- 新增 CloudSaver 类实现云盘资源搜索
- 集成 CloudSaver 到任务建议功能中
- 添加 CloudSaver 配置界面
- 优化任务建议展示逻辑,支持搜索错误提示
2025-04-12 15:26:44 +08:00
Cp0204
f398f3fa07 🐛 修复配置数据获取逻辑
- 修改数据获取方式,使用 Config 类读取最新配置
2025-04-12 13:15:18 +08:00
Cp0204
ffe95fcf66 重构配置文件读取与写入逻辑
- 将读取和写入 JSON 文件的功能封装到 Config 类中
- 更新相关代码以使用新的读取和写入方法
- 优化配置初始化流程,确保默认值的设置
2025-04-12 09:28:48 +08:00
Cp0204
83fd60f1a1 添加添加任务API接口
- 支持第三方任务添加功能
- 前端展示API Token
- 优化任务添加的错误处理和日志记录
2025-04-12 08:58:02 +08:00
Cp0204
dda9ec0a01 🔧 正则处理改为可选参数 2025-04-11 21:01:01 +08:00
Super-Passerby
e0cb7443fe
Update quark_auto_save.py
同步最新代码:2024-11-13
当设定:
MAX_SAVE_FILES = 0 
不限制保存数量,即与原代码功能一致
2025-03-14 09:10:31 +08:00
Super-Passerby
fd46206024
增加了最大转存文件数量的限制
增加了最大转存文件数量的限制,可配合自动下载、删除脚本,方便网盘容量有限的用户使用。
2025-02-26 20:18:57 +08:00
6 changed files with 461 additions and 121 deletions

View File

@ -41,6 +41,7 @@
- [x] 支持分享链接的子目录
- [x] 记录失效分享并跳过任务
- [x] 支持需提取码的分享链接 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/使用技巧集锦#支持需提取码的分享链接)</sup>
- [x] 智能搜索资源并自动填充 <sup>[?](https://github.com/Cp0204/quark-auto-save/wiki/CloudSaver搜索源)</sup>
- 文件管理
- [x] 目标目录不存在时自动新建

View File

@ -14,13 +14,13 @@ from flask import (
)
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from sdk.cloudsaver import CloudSaver
from datetime import timedelta
import subprocess
import requests
import hashlib
import logging
import base64
import json
import sys
import os
@ -48,7 +48,8 @@ CONFIG_PATH = os.environ.get("CONFIG_PATH", "./config/quark_config.json")
PLUGIN_FLAGS = os.environ.get("PLUGIN_FLAGS", "")
DEBUG = os.environ.get("DEBUG", False)
task_plugins_config = {}
config_data = {}
task_plugins_config_default = {}
app = Flask(__name__)
app.config["APP_VERSION"] = get_app_ver()
@ -77,24 +78,18 @@ def gen_md5(string):
return md5.hexdigest()
# 读取 JSON 文件内容
def read_json():
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
return data
# 将数据写入 JSON 文件
def write_json(data):
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, sort_keys=False, indent=2)
def get_login_token():
username = config_data["webui"]["username"]
password = config_data["webui"]["password"]
return gen_md5(f"token{username}{password}+-*/")[8:24]
def is_login():
data = read_json()
username = data["webui"]["username"]
password = data["webui"]["password"]
if session.get("login") == gen_md5(username + password):
login_token = get_login_token()
if (
session.get("token") == login_token
or request.args.get("token") == login_token
):
return True
else:
return False
@ -114,16 +109,15 @@ def favicon():
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
data = read_json()
username = data["webui"]["username"]
password = data["webui"]["password"]
username = config_data["webui"]["username"]
password = config_data["webui"]["password"]
# 验证用户名和密码
if (username == request.form.get("username")) and (
password == request.form.get("password")
):
logging.info(f">>> 用户 {username} 登录成功")
session["login"] = gen_md5(username + password)
session.permanent = True
session["token"] = get_login_token()
return redirect(url_for("index"))
else:
logging.info(f">>> 用户 {username} 登录失败")
@ -137,7 +131,7 @@ def login():
# 退出登录
@app.route("/logout")
def logout():
session.pop("login", None)
session.pop("token", None)
return redirect(url_for("login"))
@ -155,37 +149,39 @@ def index():
@app.route("/data")
def get_data():
if not is_login():
return redirect(url_for("login"))
data = read_json()
return jsonify({"success": False, "message": "未登录"})
data = Config.read_json(CONFIG_PATH)
del data["webui"]
data["task_plugins_config"] = task_plugins_config
return jsonify(data)
data["api_token"] = get_login_token()
data["task_plugins_config_default"] = task_plugins_config_default
return jsonify({"success": True, "data": data})
# 更新数据
@app.route("/update", methods=["POST"])
def update():
global config_data
if not is_login():
return "未登录"
data = request.json
data["webui"] = read_json()["webui"]
if "task_plugins_config" in data:
del data["task_plugins_config"]
write_json(data)
return jsonify({"success": False, "message": "未登录"})
dont_save_keys = ["task_plugins_config_default", "api_token"]
for key, value in request.json.items():
if key not in dont_save_keys:
config_data.update({key: value})
Config.write_json(CONFIG_PATH, config_data)
# 重新加载任务
if reload_tasks():
logging.info(f">>> 配置更新成功")
return "配置更新成功"
return jsonify({"success": True, "message": "配置更新成功"})
else:
logging.info(f">>> 配置更新失败")
return "配置更新失败"
return jsonify({"success": False, "message": "配置更新失败"})
# 处理运行脚本请求
@app.route("/run_script_now", methods=["GET"])
def run_script_now():
if not is_login():
return "未登录"
return jsonify({"success": False, "message": "未登录"})
task_index = request.args.get("task_index", "")
command = [PYTHON_PATH, "-u", SCRIPT_PATH, CONFIG_PATH, task_index]
logging.info(
@ -224,64 +220,106 @@ def run_script_now():
@app.route("/task_suggestions")
def get_task_suggestions():
if not is_login():
return jsonify({"error": "未登录"})
base_url = base64.b64decode("aHR0cHM6Ly9zLjkxNzc4OC54eXo=").decode()
return jsonify({"success": False, "message": "未登录"})
query = request.args.get("q", "").lower()
deep = request.args.get("d", "").lower()
url = f"{base_url}/task_suggestions?q={query}&d={deep}"
try:
response = requests.get(url)
return jsonify(response.json())
if cs_data := config_data.get("source", {}).get("cloudsaver", {}):
cs = CloudSaver(cs_data.get("server"))
cs.set_auth(
cs_data.get("username", ""),
cs_data.get("password", ""),
cs_data.get("token", ""),
)
search = cs.auto_login_search(query)
if search.get("success"):
if search.get("new_token"):
cs_data["token"] = search.get("new_token")
Config.write_json(CONFIG_PATH, config_data)
search_results = cs.clean_search_results(search.get("data"))
return jsonify({"success": True, "data": search_results})
else:
return jsonify({"success": True, "message": search.get("message")})
else:
base_url = base64.b64decode("aHR0cHM6Ly9zLjkxNzc4OC54eXo=").decode()
url = f"{base_url}/task_suggestions?q={query}&d={deep}"
response = requests.get(url)
return jsonify({"success": True, "data": response.json()})
except Exception as e:
return jsonify({"error": str(e)})
return jsonify({"success": False, "message": str(e)})
@app.route("/get_share_detail")
def get_share_files():
if not is_login():
return jsonify({"error": "未登录"})
return jsonify({"success": False, "message": "未登录"})
shareurl = request.args.get("shareurl", "")
account = Quark("", 0)
pwd_id, passcode, pdir_fid = account.get_id_from_url(shareurl)
is_sharing, stoken = account.get_stoken(pwd_id, passcode)
if not is_sharing:
return jsonify({"error": stoken})
return jsonify({"success": False, "data": {"error": stoken}})
share_detail = account.get_detail(pwd_id, stoken, pdir_fid, 1)
return jsonify(share_detail)
return jsonify({"success": True, "data": share_detail})
@app.route("/get_savepath")
def get_savepath():
if not is_login():
return jsonify({"error": "未登录"})
data = read_json()
account = Quark(data["cookie"][0], 0)
return jsonify({"success": False, "message": "未登录"})
account = Quark(config_data["cookie"][0], 0)
if path := request.args.get("path"):
if path == "/":
fid = 0
elif get_fids := account.get_fids([path]):
fid = get_fids[0]["fid"]
else:
return jsonify([])
return jsonify({"success": False, "message": "获取fid失败"})
else:
fid = request.args.get("fid", 0)
file_list = account.ls_dir(fid)
return jsonify(file_list)
return jsonify({"success": True, "data": file_list})
@app.route("/delete_file", methods=["POST"])
def delete_file():
if not is_login():
return jsonify({"error": "未登录"})
data = read_json()
account = Quark(data["cookie"][0], 0)
return jsonify({"success": False, "message": "未登录"})
account = Quark(config_data["cookie"][0], 0)
if fid := request.json.get("fid"):
response = account.delete([fid])
else:
response = {"error": "fid not found"}
response = {"success": False, "message": "缺失必要字段: fid"}
return jsonify(response)
# 添加任务接口
@app.route("/api/add_task", methods=["POST"])
def add_task():
global config_data
# 验证token
if not is_login():
return jsonify({"success": False, "code": 1, "message": "未登录"}), 401
# 必选字段
request_data = request.json
required_fields = ["taskname", "shareurl", "savepath"]
for field in required_fields:
if field not in request_data or not request_data[field]:
return (
jsonify(
{"success": False, "code": 2, "message": f"缺少必要字段: {field}"}
),
400,
)
# 添加任务
config_data["tasklist"].append(request_data)
Config.write_json(CONFIG_PATH, config_data)
logging.info(f">>> 通过API添加任务: {request_data['taskname']}")
return jsonify(
{"success": True, "code": 0, "message": "任务添加成功", "data": request_data}
)
# 定时任务执行的函数
def run_python(args):
logging.info(f">>> 定时运行任务")
@ -290,11 +328,8 @@ def run_python(args):
# 重新加载任务
def reload_tasks():
# 读取数据
data = read_json()
# 添加新任务
crontab = data.get("crontab")
if crontab:
# 读取定时规则
if crontab := config_data.get("crontab"):
if scheduler.state == 1:
scheduler.pause() # 暂停调度器
trigger = CronTrigger.from_crontab(crontab)
@ -321,7 +356,7 @@ def reload_tasks():
def init():
global task_plugins_config
global config_data, task_plugins_config_default
logging.info(f">>> 初始化配置")
# 检查配置文件是否存在
if not os.path.exists(CONFIG_PATH):
@ -329,23 +364,30 @@ def init():
os.makedirs(os.path.dirname(CONFIG_PATH))
with open("quark_config.json", "rb") as src, open(CONFIG_PATH, "wb") as dest:
dest.write(src.read())
data = read_json()
Config.breaking_change_update(data)
# 读取配置
config_data = Config.read_json(CONFIG_PATH)
Config.breaking_change_update(config_data)
# 默认管理账号
data["webui"] = {
config_data["webui"] = {
"username": os.environ.get("WEBUI_USERNAME")
or data.get("webui", {}).get("username", "admin"),
or config_data.get("webui", {}).get("username", "admin"),
"password": os.environ.get("WEBUI_PASSWORD")
or data.get("webui", {}).get("password", "admin123"),
or config_data.get("webui", {}).get("password", "admin123"),
}
# 默认定时规则
if not data.get("crontab"):
data["crontab"] = "0 8,18,20 * * *"
if not config_data.get("crontab"):
config_data["crontab"] = "0 8,18,20 * * *"
# 初始化插件配置
_, plugins_config_default, task_plugins_config = Config.load_plugins()
plugins_config_default.update(data.get("plugins", {}))
data["plugins"] = plugins_config_default
write_json(data)
_, plugins_config_default, task_plugins_config_default = Config.load_plugins()
plugins_config_default.update(config_data.get("plugins", {}))
config_data["plugins"] = plugins_config_default
# 更新配置
Config.write_json(CONFIG_PATH, config_data)
if __name__ == "__main__":

144
app/sdk/cloudsaver.py Normal file
View File

@ -0,0 +1,144 @@
import requests
class CloudSaver:
"""
CloudSaver 用于获取云盘资源
"""
def __init__(self, server):
self.server = server
self.username = None
self.password = None
self.token = None
self.session = requests.Session()
self.session.headers.update({"Content-Type": "application/json"})
def set_auth(self, username, password, token=""):
self.username = username
self.password = password
self.token = token
self.session.headers.update({"Authorization": f"Bearer {self.token}"})
def login(self):
if not self.username or not self.password:
return {"success": False, "message": "CloudSaver未设置用户名或密码"}
try:
url = f"{self.server}/api/user/login"
data = {"username": self.username, "password": self.password}
response = self.session.post(url, json=data)
result = response.json()
if result.get("success"):
self.token = result.get("data", {}).get("token")
self.session.headers.update({"Authorization": f"Bearer {self.token}"})
return {"success": True, "token": self.token}
else:
return {
"success": False,
"message": f"CloudSaver登录{result.get('message', '未知错误')}",
}
except Exception as e:
return {"success": False, "message": str(e)}
def search(self, keyword, last_message_id=""):
"""
搜索资源
Args:
keyword (str): 搜索关键词
last_message_id (str): 上一条消息ID用于分页
Returns:
list: 搜索结果列表
"""
try:
url = f"{self.server}/api/search"
params = {"keyword": keyword, "lastMessageId": last_message_id}
response = self.session.get(url, params=params)
result = response.json()
if result.get("success"):
data = result.get("data", [])
return {"success": True, "data": data}
else:
return {"success": False, "message": result.get("message", "未知错误")}
except Exception as e:
return {"success": False, "message": str(e)}
def auto_login_search(self, keyword, last_message_id=""):
"""
自动登录并搜索资源
Args:
keyword (str): 搜索关键词
last_message_id (str): 上一条消息ID用于分页
"""
result = self.search(keyword, last_message_id)
if result.get("success"):
return result
else:
if result.get("message") == "无效的 token" or result.get("message") == "未提供 token":
login_result = self.login()
if login_result.get("success"):
result = self.search(keyword, last_message_id)
result["new_token"] = login_result.get("token")
return result
else:
return {
"success": False,
"message": login_result.get("message", "未知错误"),
}
return {"success": False, "message": result.get("message", "未知错误")}
def clean_search_results(self, search_results):
"""
清洗搜索结果
Args:
search_results (list): 搜索结果列表
Returns:
list: 夸克网盘链接列表
"""
clean_results = []
for channel in search_results:
for item in channel.get("list", []):
cloud_links = item.get("cloudLinks", [])
for link in cloud_links:
if link.get("cloudType") == "quark":
clean_results.append(
{
"shareurl": link.get("link"),
"taskname": item.get("title", "")
.strip("名称:")
.replace("&amp;", "&"),
"content": item.get("content", "")
.split("描述:")[1]
.split("链接:")[0]
.replace('<mark class="highlight">', "")
.replace("</mark>", ""),
"tags": item.get("tags", []),
}
)
return clean_results
# 测试示例
if __name__ == "__main__":
# 创建CloudSaver实例
server = ""
username = ""
password = ""
token = ""
cloud_saver = CloudSaver(server)
cloud_saver.set_auth(username, password, token)
# 搜索资源
results = cloud_saver.auto_login_search("黑镜")
# 提取夸克网盘链接
clean_results = cloud_saver.clean_search_results(results.get("data", []))
# 打印结果
for item in clean_results:
print(f"标题: {item['taskname']}")
print(f"描述: {item['content']}")
print(f"链接: {item['shareurl']}")
print(f"标签: {' '.join(item['tags'])}")
print("-" * 50)

View File

@ -42,6 +42,7 @@ body {
.title {
margin-top: 30px;
margin-bottom: 10px;
}
table.jsoneditor-tree>tbody>tr.jsoneditor-expandable:first-child {
@ -59,7 +60,7 @@ table.jsoneditor-tree>tbody>tr.jsoneditor-expandable:first-child {
.task-suggestions {
width: 100%;
max-height: 500px;
max-height: 250px;
overflow-y: auto;
transform: translate(0, -100%);
top: 0;

View File

@ -66,7 +66,7 @@
<h2><i class="bi bi-cookie"></i> Cookie</h2>
</div>
<div class="col-2 text-right">
<button type="button" class="btn btn-outline-primary mb-3" @click="addCookie()">+</button>
<button type="button" class="btn btn-outline-primary" @click="addCookie()">+</button>
</div>
</div>
<p>1. 所有账号执行签到,纯签到只需移动端参数即可!</p>
@ -86,7 +86,7 @@
</span>
</div>
</div>
<div class="input-group mt-2 mb-2">
<div class="input-group mb-2">
<div class="input-group-prepend">
<span class="input-group-text">Crontab</span>
</div>
@ -101,7 +101,7 @@
</span>
</div>
<div class="col-2 text-right">
<button type="button" class="btn btn-outline-primary mb-3" @click="addPush()">+</button>
<button type="button" class="btn btn-outline-primary" @click="addPush()">+</button>
</div>
</div>
<div v-for="(value, key) in formData.push_config" :key="key" class="input-group mb-2">
@ -151,7 +151,7 @@
</span>
</div>
<div class="col-2 text-right">
<button type="button" class="btn btn-outline-primary mb-3" @click="addMagicRegex()">+</button>
<button type="button" class="btn btn-outline-primary" @click="addMagicRegex()">+</button>
</div>
</div>
<div v-for="(value, key) in formData.magic_regex" :key="key" class="form-group mb-2">
@ -171,6 +171,49 @@
</div>
</div>
<div class="row title" title="API接口用以第三方添加任务等操作见Wiki">
<div class="col-10">
<h2 style="display: inline-block;"><i class="bi bi-link-45deg"></i> API</h2>
<span class="badge badge-pill badge-light">
<a href="https://github.com/Cp0204/quark-auto-save/wiki/API接口" target="_blank">?</a>
</span>
</div>
</div>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Token</span>
</div>
<input type="text" v-model="formData.api_token" class="form-control" style="background-color:white;" disabled>
</div>
<div class="row title" title="资源搜索服务配置,用于任务名称智能搜索">
<div class="col-10">
<h2 style="display: inline-block;"><i class="bi bi-search"></i> CloudSaver</h2>
<span class="badge badge-pill badge-light">
<a href="https://github.com/Cp0204/quark-auto-save/wiki/CloudSaver搜索源" target="_blank">?</a>
</span>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">服务器</label>
<div class="col-sm-10">
<input type="text" v-model="formData.source.cloudsaver.server" class="form-control" placeholder="资源搜索服务器地址">
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">用户名</label>
<div class="col-sm-10">
<input type="text" v-model="formData.source.cloudsaver.username" class="form-control" placeholder="用户名">
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">密码</label>
<div class="col-sm-10">
<input type="password" v-model="formData.source.cloudsaver.password" class="form-control" placeholder="密码">
</div>
</div>
</div>
<div v-if="activeTab === 'tasklist'">
@ -228,9 +271,9 @@
<div class="col-sm-10">
<div class="input-group">
<input type="text" name="taskname[]" class="form-control" v-model="task.taskname" placeholder="必填" @focus="smart_param.showSuggestions=true;focusTaskname(index, task)" @input="changeTaskname(index, task)">
<div class="dropdown-menu show task-suggestions" v-if="smart_param.showSuggestions && smart_param.taskSuggestions.length && smart_param.index === index">
<div class="text-muted text-center" style="font-size: small;">以下资源来自第三方,网络公开搜集,请自行辨识,如有侵权请联系夸克官方</div>
<div v-for="suggestion in smart_param.taskSuggestions" :key="suggestion.taskname" class="dropdown-item" @click.prevent="selectSuggestion(task, suggestion)" style="cursor: pointer;">
<div class="dropdown-menu show task-suggestions" v-if="smart_param.showSuggestions && smart_param.taskSuggestions.success && smart_param.index === index">
<div class="text-muted text-center" style="font-size:12px;">{{ smart_param.taskSuggestions.message ? smart_param.taskSuggestions.message : smart_param.taskSuggestions.data.length ? "以下资源来自网络公开搜索,请自行辨识,如有侵权请联系资源方" : "未搜索到资源" }}</div>
<div v-for="suggestion in smart_param.taskSuggestions.data" :key="suggestion.taskname" class="dropdown-item" @click.prevent="selectSuggestion(task, suggestion)" style="cursor: pointer;font-size: 12px;" :title="suggestion.content">
<span v-html="suggestion.verify ? '✅': '❔'"></span> {{ suggestion.taskname }}
<small class="text-muted">
<a :href="suggestion.shareurl" target="_blank" @click.stop>{{ suggestion.shareurl }}</a>
@ -438,7 +481,15 @@
push_config: {},
media_servers: {},
tasklist: [],
magic_regex: {}
magic_regex: {},
source: {
cloudsaver: {
server: "",
username: "",
password: "",
token: ""
}
},
},
newTask: {
taskname: "",
@ -463,12 +514,13 @@
index: null,
savepath: "",
origin_savepath: "",
taskSuggestions: [],
taskSuggestions: {},
showSuggestions: false,
lastSuggestionsTime: 0,
isSearching: false,
},
activeTab: 'tasklist',
configModified: false,
},
filters: {
ts2date: function (value) {
@ -485,6 +537,12 @@
}
},
watch: {
formData: {
handler(newVal, oldVal) {
this.configModified = true;
},
deep: true
}
},
mounted() {
this.fetchData();
@ -496,6 +554,10 @@
this.smart_param.showSuggestions = false;
}
});
window.addEventListener('beforeunload', this.handleBeforeUnload);
},
beforeDestroy() {
window.removeEventListener('beforeunload', this.handleBeforeUnload);
},
methods: {
changeTab(tab) {
@ -521,24 +583,40 @@
fetchData() {
axios.get('/data')
.then(response => {
config_data = response.data.data
// cookie兼容
if (typeof response.data.cookie === 'string')
response.data.cookie = [response.data.cookie];
if (typeof config_data.cookie === 'string')
config_data.cookie = [config_data.cookie];
// 添加星期预设
response.data.tasklist = response.data.tasklist.map(task => {
config_data.tasklist = config_data.tasklist.map(task => {
if (!task.hasOwnProperty('runweek')) {
task.runweek = [1, 2, 3, 4, 5, 6, 7];
}
return task;
});
// 获取所有任务父目录
response.data.tasklist.forEach(item => {
config_data.tasklist.forEach(item => {
parentDir = this.getParentDirectory(item.savepath)
if (!this.taskDirs.includes(parentDir))
this.taskDirs.push(parentDir);
});
this.newTask.addition = response.data.task_plugins_config;
this.formData = response.data;
this.newTask.addition = config_data.task_plugins_config_default;
// 确保source配置存在
if (!config_data.source) {
config_data.source = {};
}
if (!config_data.source.cloudsaver) {
config_data.source.cloudsaver = {
server: "",
username: "",
password: "",
token: ""
};
}
this.formData = config_data;
setTimeout(() => {
this.configModified = false;
}, 100);
})
.catch(error => {
console.error('Error fetching data:', error);
@ -555,11 +633,23 @@
}
}
},
handleBeforeUnload(e) {
if (this.configModified) {
e.preventDefault();
e.returnValue = '配置已修改但未保存,确定要离开吗?';
return e.returnValue;
}
},
saveConfig() {
axios.post('/update', this.formData)
.then(response => {
alert(response.data);
console.log('Config saved successfully:', response.data);
if (response.data.success) {
this.configModified = false;
alert(response.data.message);
} else {
alert(response.data.message);
}
console.log('Config saved result:', response.data);
})
.catch(error => {
console.error('Error saving config:', error);
@ -642,19 +732,20 @@
// 从分享中提取任务名
axios.get('/get_share_detail', { params: { shareurl: task.shareurl } })
.then(response => {
if (response.data.error) {
if (response.data.error.includes("提取码")) {
const passcode = prompt("检查失败[" + response.data.error + "],请输入提取码:");
share_detail = response.data.data
if (!response.data.success) {
if (share_detail.error.includes("提取码")) {
const passcode = prompt("检查失败[" + share_detail.error + "],请输入提取码:");
if (passcode != null) {
task.shareurl = task.shareurl.replace(/pan.quark.cn\/s\/(\w+)(\?pwd=\w*)*/, `pan.quark.cn/s/$1?pwd=${passcode}`);
this.changeShareurl(task);
return;
}
}
this.$set(task, "shareurl_ban", response.data.error);
this.$set(task, "shareurl_ban", share_detail.error);
} else {
task.taskname = task.taskname == "" ? response.data.share.title : task.taskname;
task.savepath = task.savepath.replace(/TASKNAME/g, response.data.share.title);
task.taskname = task.taskname == "" ? share_detail.share.title : task.taskname;
task.savepath = task.savepath.replace(/TASKNAME/g, share_detail.share.title);
this.$set(task, "shareurl_ban", undefined);
}
})
@ -679,7 +770,7 @@
this.savepaths = [{ fid: 0, dir: true, file_name: "加载中..." }]
axios.get('/get_savepath', { params: params })
.then(response => {
this.savepaths = response.data
this.savepaths = response.data.data
})
.catch(error => {
console.error('Error get_savepath:', error);
@ -710,7 +801,7 @@
axios.get('/get_share_detail', { params: { shareurl: this.formData.tasklist[index].shareurl } })
.then(response => {
this.forceTaskIndex = index
this.shareFiles = response.data.list;
this.shareFiles = response.data.data.list;
this.modalLoading = false
})
.catch(error => {
@ -718,6 +809,11 @@
});
},
runScriptNow(task_index = "") {
if (this.configModified) {
if (!confirm('配置已修改但未保存,是否继续运行?')) {
return;
}
}
$('#logModal').modal('toggle')
this.modalLoading = true
this.run_log = ''
@ -785,19 +881,25 @@
}
this.smart_param.isSearching = true
this.smart_param.index = index;
axios.get('/task_suggestions', {
params: {
q: taskname,
d: limit_msec == 0 ? 1 : 0
}
}).then(response => {
this.smart_param.taskSuggestions = response.data;
this.smart_param.showSuggestions = true;
}).catch(error => {
console.error('Error fetching suggestions:', error);
}).finally(() => {
this.smart_param.isSearching = false;
});
try {
axios.get('/task_suggestions', {
params: {
q: taskname,
d: limit_msec == 0 ? 1 : 0
}
}).then(response => {
this.smart_param.taskSuggestions = response.data;
this.smart_param.showSuggestions = true;
}).catch(error => {
console.error('Error fetching suggestions:', error);
}).finally(() => {
this.smart_param.isSearching = false;
});
} catch (e) {
this.smart_param.taskSuggestions = {
error: "网络异常"
};
}
},
selectSuggestion(task, suggestion) {
task.taskname = suggestion.taskname;

View File

@ -17,6 +17,10 @@ import requests
import importlib
from datetime import datetime
# ========== 修改内容开始 ==========
MAX_SAVE_FILES = 0 # 最大保存文件数量0 为不限制保存数量
# ========== 修改内容结束 ==========
# 兼容青龙
try:
from treelib import Tree
@ -78,6 +82,17 @@ class Config:
else:
return False
# 读取 JSON 文件内容
def read_json(config_path):
with open(config_path, "r", encoding="utf-8") as f:
data = json.load(f)
return data
# 将数据写入 JSON 文件
def write_json(config_path, data):
with open(config_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, sort_keys=False, indent=2)
# 读取CK
def get_cookies(cookie_val):
if isinstance(cookie_val, list):
@ -391,9 +406,20 @@ class Quark:
"__dt": int(random.uniform(1, 5) * 60 * 1000),
"__t": datetime.now().timestamp(),
}
# ========== 修改内容开始 ==========
# 当 MAX_SAVE_FILES = 0 时,不限制保存文件数量
if MAX_SAVE_FILES > 0:
files_to_save = fid_list[:MAX_SAVE_FILES]
tokens_to_save = fid_token_list[:MAX_SAVE_FILES]
else:
files_to_save = fid_list
tokens_to_save = fid_token_list
# ========== 修改内容结束 ==========
payload = {
"fid_list": fid_list,
"fid_token_list": fid_token_list,
"fid_list": files_to_save, # 修改为 files_to_save
"fid_token_list": tokens_to_save, # 修改为 tokens_to_save
"to_pdir_fid": to_pdir_fid,
"pwd_id": pwd_id,
"stoken": stoken,
@ -677,7 +703,7 @@ class Quark:
pattern, replace = task["update_subdir"], ""
else:
pattern, replace = self.magic_regex_func(
task["pattern"], task["replace"], task["taskname"]
task.get("pattern", ""), task.get("replace", ""), task["taskname"]
)
# 正则文件名匹配
if re.search(pattern, share_file["file_name"]):
@ -731,7 +757,13 @@ class Quark:
# 指定文件开始订阅/到达指定文件(含)结束历遍
if share_file["fid"] == task.get("startfid", ""):
break
# ========== 修改内容开始 ==========
# 当 MAX_SAVE_FILES = 0 时,不限制保存文件数量
if MAX_SAVE_FILES > 0:
need_save_list = need_save_list[:MAX_SAVE_FILES]
# ========== 修改内容结束 ==========
fid_list = [item["fid"] for item in need_save_list]
fid_token_list = [item["share_fid_token"] for item in need_save_list]
if fid_list:
@ -770,7 +802,7 @@ class Quark:
def do_rename_task(self, task, subdir_path=""):
pattern, replace = self.magic_regex_func(
task["pattern"], task["replace"], task["taskname"]
task.get("pattern", ""), task.get("replace", ""), task["taskname"]
)
if not pattern or not replace:
return 0
@ -873,6 +905,11 @@ def do_save(account, tasklist=[]):
# 获取全部保存目录fid
account.update_savepath_fid(tasklist)
# ========== 修改内容开始 ==========
# 初始化计数器
total_files_transferred = 0 # 无论 MAX_SAVE_FILES 的值如何,都初始化计数器
# ========== 修改内容结束 ==========
def check_date(task):
return (
not task.get("enddate")
@ -890,13 +927,21 @@ def do_save(account, tasklist=[]):
for index, task in enumerate(tasklist):
# 判断任务期限
if check_date(task):
# ========== 修改内容开始 ==========
# 当 MAX_SAVE_FILES > 0 时,检查是否已转存超过限制
if MAX_SAVE_FILES > 0 and total_files_transferred >= MAX_SAVE_FILES:
print("⚠️ 已达到转存文件总数上限,停止转存任务")
break
# ========== 修改内容结束 ==========
print()
print(f"#{index+1}------------------")
print(f"任务名称: {task['taskname']}")
print(f"分享链接: {task['shareurl']}")
print(f"保存路径: {task['savepath']}")
print(f"正则匹配: {task['pattern']}")
print(f"正则替换: {task['replace']}")
if task.get("pattern"):
print(f"正则匹配: {task['pattern']}")
if task.get("replace"):
print(f"正则替换: {task['replace']}")
if task.get("enddate"):
print(f"任务截止: {task['enddate']}")
if task.get("ignore_extension"):
@ -906,6 +951,13 @@ def do_save(account, tasklist=[]):
print()
is_new_tree = account.do_save_task(task)
is_rename = account.do_rename_task(task)
# ========== 修改内容开始 ==========
# 当 MAX_SAVE_FILES > 0 时,更新计数器
if MAX_SAVE_FILES > 0 and (is_new_tree or is_rename):
total_files_transferred += 1 # 每成功转存或重命名一个文件计数器加1
# ========== 修改内容结束 ==========
# 补充任务的插件配置
def merge_dicts(a, b):
@ -960,9 +1012,8 @@ def main():
return
else:
print(f"⚙️ 正从 {config_path} 文件中读取配置")
with open(config_path, "r", encoding="utf-8") as file:
CONFIG_DATA = json.load(file)
Config.breaking_change_update(CONFIG_DATA)
CONFIG_DATA = Config.read_json(config_path)
Config.breaking_change_update(CONFIG_DATA)
cookie_val = CONFIG_DATA.get("cookie")
if not CONFIG_DATA.get("magic_regex"):
CONFIG_DATA["magic_regex"] = MAGIC_REGEX
@ -1000,8 +1051,7 @@ def main():
print()
if cookie_form_file:
# 更新配置
with open(config_path, "w", encoding="utf-8") as file:
json.dump(CONFIG_DATA, file, ensure_ascii=False, sort_keys=False, indent=2)
Config.write_json(config_path, CONFIG_DATA)
print(f"===============程序结束===============")
duration = datetime.now() - start_time