Compare commits

...

5 Commits

Author SHA1 Message Date
xiaoQQya
6ed5d310c6
Merge 78581b15a7 into 98e53b38db 2025-10-21 15:56:38 +08:00
Cp0204
98e53b38db QAS一键推送助手:优化错误提示 #127
Some checks failed
Docker Publish / build-and-push (push) Has been cancelled
- 设置按钮改为利用 '.pc-member-entrance'
- 增强任务推送接口的错误提示
2025-10-16 12:49:51 +08:00
Cp0204
846bf0345a 🔧 增强代码可读性与优化日志
Some checks failed
Docker Publish / build-and-push (push) Has been cancelled
2025-10-14 14:12:30 +08:00
ypq123456789
95ddc95c79
🐛 补充修复:添加 APScheduler 调度器参数,彻底解决任务堆积问题 (#126) 2025-10-14 13:31:02 +08:00
xiaoQQya
78581b15a7 feat: 魔法变量 {E} 支持零填充格式化 2025-08-20 23:33:51 +08:00
3 changed files with 130 additions and 23 deletions

View File

@ -33,6 +33,19 @@ parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, parent_dir) sys.path.insert(0, parent_dir)
from quark_auto_save import Quark, Config, MagicRename from quark_auto_save import Quark, Config, MagicRename
print(
r"""
____ ___ _____
/ __ \ / | / ___/
/ / / / / /| | \__ \
/ /_/ / / ___ |___/ /
\___\_\/_/ |_/____/
-- Quark-Auto-Save --
"""
)
sys.stdout.flush()
def get_app_ver(): def get_app_ver():
"""获取应用版本""" """获取应用版本"""
@ -60,6 +73,7 @@ PLUGIN_FLAGS = os.environ.get("PLUGIN_FLAGS", "")
DEBUG = os.environ.get("DEBUG", "false").lower() == "true" DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
HOST = os.environ.get("HOST", "0.0.0.0") HOST = os.environ.get("HOST", "0.0.0.0")
PORT = os.environ.get("PORT", 5005) PORT = os.environ.get("PORT", 5005)
TASK_TIMEOUT = int(os.environ.get("TASK_TIMEOUT", 1800))
config_data = {} config_data = {}
task_plugins_config_default = {} task_plugins_config_default = {}
@ -83,6 +97,8 @@ logging.basicConfig(
# 过滤werkzeug日志输出 # 过滤werkzeug日志输出
if not DEBUG: if not DEBUG:
logging.getLogger("werkzeug").setLevel(logging.ERROR) logging.getLogger("werkzeug").setLevel(logging.ERROR)
logging.getLogger("apscheduler").setLevel(logging.ERROR)
sys.modules["flask.cli"].show_server_banner = lambda *x: None
def gen_md5(string): def gen_md5(string):
@ -472,34 +488,42 @@ def add_task():
def run_python(args): def run_python(args):
logging.info(f">>> 定时运行任务") logging.info(f">>> 定时运行任务")
try: try:
# 使用 subprocess 替代 os.system并设置超时时间默认30分钟
timeout = int(os.environ.get("TASK_TIMEOUT", "1800")) # 秒
result = subprocess.run( result = subprocess.run(
f"{PYTHON_PATH} {args}", f"{PYTHON_PATH} {args}",
shell=True, shell=True,
timeout=timeout, timeout=TASK_TIMEOUT,
capture_output=True, capture_output=True,
text=True, text=True,
encoding="utf-8", encoding="utf-8",
errors="replace" errors="replace",
) )
# 输出执行日志 # 输出执行日志
if result.stdout: if result.stdout:
for line in result.stdout.strip().split('\n'): for line in result.stdout.strip().split("\n"):
if line.strip(): if line.strip():
logging.info(line) logging.info(line)
if result.returncode == 0: if result.returncode == 0:
logging.info(f">>> 任务执行成功") logging.info(f">>> 任务执行成功")
else: else:
logging.error(f">>> 任务执行失败,返回码: {result.returncode}") logging.error(f">>> 任务执行失败,返回码: {result.returncode}")
if result.stderr: if result.stderr:
logging.error(f"错误信息: {result.stderr[:500]}") logging.error(f"错误信息: {result.stderr[:500]}")
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired as e:
logging.error(f">>> 任务执行超时(超过 {timeout} 秒),已强制终止") logging.error(f">>> 任务执行超时(>{TASK_TIMEOUT}s),强制终止")
# 尝试终止进程
if e.process:
try:
e.process.kill()
logging.info(">>> 已终止超时进程")
except:
pass
except Exception as e: except Exception as e:
logging.error(f">>> 任务执行异常: {str(e)}") logging.error(f">>> 任务执行异常: {str(e)}")
logging.error(traceback.format_exc()) logging.error(traceback.format_exc())
finally:
# 确保函数能够正常返回
logging.debug(f">>> run_python 函数执行完成")
# 重新加载任务 # 重新加载任务
@ -515,6 +539,10 @@ def reload_tasks():
trigger=trigger, trigger=trigger,
args=[f"{SCRIPT_PATH} {CONFIG_PATH}"], args=[f"{SCRIPT_PATH} {CONFIG_PATH}"],
id=SCRIPT_PATH, id=SCRIPT_PATH,
max_instances=1, # 最多允许1个实例运行
coalesce=True, # 合并错过的任务,避免堆积
misfire_grace_time=300, # 错过任务的宽限期(秒),超过则跳过
replace_existing=True, # 替换已存在的同ID任务
) )
if scheduler.state == 0: if scheduler.state == 0:
scheduler.start() scheduler.start()
@ -533,7 +561,7 @@ def reload_tasks():
def init(): def init():
global config_data, task_plugins_config_default global config_data, task_plugins_config_default
logging.info(f">>> 初始化配置") logging.info(">>> 初始化配置")
# 检查配置文件是否存在 # 检查配置文件是否存在
if not os.path.exists(CONFIG_PATH): if not os.path.exists(CONFIG_PATH):
if not os.path.exists(os.path.dirname(CONFIG_PATH)): if not os.path.exists(os.path.dirname(CONFIG_PATH)):
@ -571,6 +599,8 @@ def init():
if __name__ == "__main__": if __name__ == "__main__":
init() init()
reload_tasks() reload_tasks()
logging.info(">>> 启动Web服务")
logging.info(f"运行在: http://{HOST}:{PORT}")
app.run( app.run(
debug=DEBUG, debug=DEBUG,
host=HOST, host=HOST,

View File

@ -2,7 +2,7 @@
// @name QAS一键推送助手 // @name QAS一键推送助手
// @namespace https://github.com/Cp0204/quark-auto-save // @namespace https://github.com/Cp0204/quark-auto-save
// @license AGPL // @license AGPL
// @version 0.5 // @version 0.6
// @description 在夸克网盘分享页面添加推送到 QAS 的按钮 // @description 在夸克网盘分享页面添加推送到 QAS 的按钮
// @icon https://pan.quark.cn/favicon.ico // @icon https://pan.quark.cn/favicon.ico
// @author Cp0204 // @author Cp0204
@ -76,16 +76,16 @@
} }
} }
waitForElement('.DetailLayout--client-download--FpyCkdW.ant-dropdown-trigger', (clientDownloadButton) => { waitForElement('.pc-member-entrance', (PcMemberButton) => {
const qasSettingButton = document.createElement('div'); const qasSettingButton = document.createElement('div');
qasSettingButton.className = 'DetailLayout--client-download--FpyCkdW ant-dropdown-trigger'; qasSettingButton.className = 'pc-member-entrance';
qasSettingButton.innerHTML = 'QAS设置'; qasSettingButton.innerHTML = 'QAS设置';
qasSettingButton.addEventListener('click', () => { qasSettingButton.addEventListener('click', () => {
showQASSettingDialog(); showQASSettingDialog();
}); });
clientDownloadButton.parentNode.insertBefore(qasSettingButton, clientDownloadButton.nextSibling); PcMemberButton.parentNode.insertBefore(qasSettingButton, PcMemberButton.nextSibling);
}); });
} }
@ -155,6 +155,63 @@
}, },
data: JSON.stringify(data), data: JSON.stringify(data),
onload: function (response) { onload: function (response) {
// 检查 HTTP 状态码
if (response.status === 401) {
Swal.fire({
title: '认证失败',
text: 'Token 无效或已过期,请重新配置 QAS Token',
icon: 'error',
confirmButtonText: '重新配置',
showCancelButton: true,
cancelButtonText: '取消'
}).then((result) => {
if (result.isConfirmed) {
showQASSettingDialog();
}
});
return;
}
if (response.status === 503) {
Swal.fire({
title: '服务器不可用',
html: `服务器暂时无法处理请求 (503)<br><br>
<small>可能原因<br>
QAS 服务未运行<br>
服务器过载<br>
网络连接问题</small>`,
icon: 'error',
confirmButtonText: '重新配置',
showCancelButton: true,
cancelButtonText: '取消'
}).then((result) => {
if (result.isConfirmed) {
showQASSettingDialog();
}
});
return;
}
// 检查响应内容类型
const contentType = response.responseHeaders.match(/content-type:\s*([^;\s]+)/i);
if (contentType && !contentType[1].includes('application/json')) {
Swal.fire({
title: '认证失败',
html: `服务器返回了非 JSON 响应,可能是 Token 错误<br><br>
<small>响应类型: ${contentType[1]}</small><br>
<small>响应状态: ${response.status}</small>`,
icon: 'error',
confirmButtonText: '重新配置',
showCancelButton: true,
cancelButtonText: '取消'
}).then((result) => {
if (result.isConfirmed) {
showQASSettingDialog();
}
});
return;
}
try { try {
const jsonResponse = JSON.parse(response.responseText); const jsonResponse = JSON.parse(response.responseText);
if (jsonResponse.success) { if (jsonResponse.success) {
@ -177,16 +234,34 @@
} catch (e) { } catch (e) {
Swal.fire({ Swal.fire({
title: '解析响应失败', title: '解析响应失败',
text: `无法解析 JSON 响应: ${response.responseText}`, html: `<small>
icon: 'error' 响应状态: ${response.status}<br>
响应内容: ${response.responseText.substring(0, 200)}...<br><br>
错误详情: ${e.message}
</small>`,
icon: 'error',
confirmButtonText: '重新配置',
showCancelButton: true,
cancelButtonText: '取消'
}).then((result) => {
if (result.isConfirmed) {
showQASSettingDialog();
}
}); });
} }
}, },
onerror: function (error) { onerror: function (error) {
Swal.fire({ Swal.fire({
title: '任务创建失败', title: '网络请求失败',
text: error, text: '无法连接到 QAS 服务器,请检查网络连接和服务器地址',
icon: 'error' icon: 'error',
confirmButtonText: '重新配置',
showCancelButton: true,
cancelButtonText: '取消'
}).then((result) => {
if (result.isConfirmed) {
showQASSettingDialog();
}
}); });
} }
}); });

View File

@ -128,7 +128,6 @@ class Config:
task_plugins_config[module_name] = plugin.default_task_config task_plugins_config[module_name] = plugin.default_task_config
except (ImportError, AttributeError) as e: except (ImportError, AttributeError) as e:
print(f"载入模块 {module_name} 失败: {e}") print(f"载入模块 {module_name} 失败: {e}")
print()
return plugins_available, plugins_config, task_plugins_config return plugins_available, plugins_config, task_plugins_config
def breaking_change_update(config_data): def breaking_change_update(config_data):
@ -164,7 +163,7 @@ class MagicRename:
"{YEAR}": [r"(?<!\d)(18|19|20)\d{2}(?!\d)"], "{YEAR}": [r"(?<!\d)(18|19|20)\d{2}(?!\d)"],
"{S}": [r"(?<=[Ss])\d{1,2}(?=[EeXx])", r"(?<=[Ss])\d{1,2}"], "{S}": [r"(?<=[Ss])\d{1,2}(?=[EeXx])", r"(?<=[Ss])\d{1,2}"],
"{SXX}": [r"[Ss]\d{1,2}(?=[EeXx])", r"[Ss]\d{1,2}"], "{SXX}": [r"[Ss]\d{1,2}(?=[EeXx])", r"[Ss]\d{1,2}"],
"{E}": [ "{E+}": [
r"(?<=[Ss]\d\d[Ee])\d{1,3}", r"(?<=[Ss]\d\d[Ee])\d{1,3}",
r"(?<=[Ee])\d{1,3}", r"(?<=[Ee])\d{1,3}",
r"(?<=[Ee][Pp])\d{1,3}", r"(?<=[Ee][Pp])\d{1,3}",
@ -224,7 +223,7 @@ class MagicRename:
return file_name return file_name
# 预处理替换变量 # 预处理替换变量
for key, p_list in self.magic_variable.items(): for key, p_list in self.magic_variable.items():
if key in replace: if match_key := re.search(key, replace):
# 正则类替换变量 # 正则类替换变量
if p_list and isinstance(p_list, list): if p_list and isinstance(p_list, list):
for p in p_list: for p in p_list:
@ -240,7 +239,10 @@ class MagicRename:
value = ( value = (
str(datetime.now().year)[: (8 - len(value))] + value str(datetime.now().year)[: (8 - len(value))] + value
) )
replace = replace.replace(key, value) # 集数零填充处理
elif key == "{E+}":
value = value.lstrip("0").zfill(match_key.group().count("E"))
replace = re.sub(key, value, replace)
break break
# 非正则类替换变量 # 非正则类替换变量
if key == "{TASKNAME}": if key == "{TASKNAME}":
@ -251,7 +253,7 @@ class MagicRename:
continue continue
else: else:
# 清理未匹配的 magic_variable key # 清理未匹配的 magic_variable key
replace = replace.replace(key, "") replace = re.sub(key, "", replace)
if pattern and replace: if pattern and replace:
file_name = re.sub(pattern, replace, file_name) file_name = re.sub(pattern, replace, file_name)
else: else: