Compare commits

..

No commits in common. "668897d1dfdfed05c93a797c7fea3710c9b55758" and "b118231f585291b879d05874cd1abb740d0d0454" have entirely different histories.

3 changed files with 83 additions and 166 deletions

View File

@ -1,7 +1,6 @@
# !/usr/bin/env python3 # !/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from flask import ( from flask import (
json,
Flask, Flask,
url_for, url_for,
session, session,
@ -24,7 +23,6 @@ import logging
import base64 import base64
import sys import sys
import os import os
import re
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, parent_dir) sys.path.insert(0, parent_dir)
@ -177,22 +175,20 @@ def update():
# 处理运行脚本请求 # 处理运行脚本请求
@app.route("/run_script_now", methods=["POST"]) @app.route("/run_script_now", methods=["GET"])
def run_script_now(): def run_script_now():
if not is_login(): if not is_login():
return jsonify({"success": False, "message": "未登录"}) return jsonify({"success": False, "message": "未登录"})
tasklist = request.json.get("tasklist", []) task_index = request.args.get("task_index", "")
command = [PYTHON_PATH, "-u", SCRIPT_PATH, CONFIG_PATH] command = [PYTHON_PATH, "-u", SCRIPT_PATH, CONFIG_PATH, task_index]
logging.info( logging.info(
f">>> 手动运行任务 [{tasklist[0].get('taskname') if len(tasklist)>0 else 'ALL'}] 开始执行..." f">>> 手动运行任务{int(task_index)+1 if task_index.isdigit() else 'all'}"
) )
def generate_output(): def generate_output():
# 设置环境变量 # 设置环境变量
process_env = os.environ.copy() process_env = os.environ.copy()
process_env["PYTHONIOENCODING"] = "utf-8" process_env["PYTHONIOENCODING"] = "utf-8"
if tasklist:
process_env["TASKLIST"] = json.dumps(tasklist, ensure_ascii=False)
process = subprocess.Popen( process = subprocess.Popen(
command, command,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -259,12 +255,12 @@ def get_task_suggestions():
return jsonify({"success": True, "message": f"error: {str(e)}"}) return jsonify({"success": True, "message": f"error: {str(e)}"})
@app.route("/get_share_detail", methods=["POST"]) @app.route("/get_share_detail")
def get_share_detail(): def get_share_detail():
if not is_login(): if not is_login():
return jsonify({"success": False, "message": "未登录"}) return jsonify({"success": False, "message": "未登录"})
shareurl = request.json.get("shareurl", "") shareurl = request.args.get("shareurl", "")
stoken = request.json.get("stoken", "") stoken = request.args.get("stoken", "")
account = Quark("", 0) account = Quark("", 0)
pwd_id, passcode, pdir_fid, paths = account.extract_url(shareurl) pwd_id, passcode, pdir_fid, paths = account.extract_url(shareurl)
if not stoken: if not stoken:
@ -275,24 +271,6 @@ def get_share_detail():
share_detail["paths"] = paths share_detail["paths"] = paths
share_detail["stoken"] = stoken share_detail["stoken"] = stoken
# 正则处理预览
def preview_regex(share_detail):
regex = request.json.get("regex")
pattern, replace = account.magic_regex_func(
regex.get("pattern", ""),
regex.get("replace", ""),
regex.get("taskname", ""),
)
for item in share_detail["list"]:
file_name = item["file_name"]
if re.search(pattern, item["file_name"]):
item["file_name_re"] = (
re.sub(pattern, replace, file_name) if replace != "" else file_name
)
return share_detail
share_detail = preview_regex(share_detail)
return jsonify({"success": True, "data": share_detail}) return jsonify({"success": True, "data": share_detail})

View File

@ -298,7 +298,7 @@
<div class="input-group"> <div class="input-group">
<input type="text" name="shareurl[]" class="form-control" v-model="task.shareurl" placeholder="必填" @blur="changeShareurl(task)"> <input type="text" name="shareurl[]" class="form-control" v-model="task.shareurl" placeholder="必填" @blur="changeShareurl(task)">
<div class="input-group-append" v-if="task.shareurl"> <div class="input-group-append" v-if="task.shareurl">
<button type="button" class="btn btn-outline-secondary" @click="fileSelect.selectDir=true;fileSelect.previewRegex=false;showShareSelect(index)" title="选择文件夹"><i class="bi bi-folder"></i></button> <button type="button" class="btn btn-outline-secondary" @click="fileSelect.selectDir=true;showFolderSelect(index)" title="选择文件夹"><i class="bi bi-folder"></i></button>
<div class="input-group-text"> <div class="input-group-text">
<a target="_blank" :href="task.shareurl"><i class="bi bi-box-arrow-up-right"></i></a> <a target="_blank" :href="task.shareurl"><i class="bi bi-box-arrow-up-right"></i></a>
</div> </div>
@ -323,7 +323,7 @@
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=true;fileSelect.previewRegex=true;showShareSelect(index)" title="预览正则处理效果">正则处理</button> <span class="input-group-text">正则处理</span>
</div> </div>
<input type="text" name="pattern[]" class="form-control" v-model="task.pattern" placeholder="匹配表达式" list="magicRegex"> <input type="text" name="pattern[]" class="form-control" v-model="task.pattern" placeholder="匹配表达式" list="magicRegex">
<input type="text" name="replace[]" class="form-control" v-model="task.replace" placeholder="替换表达式"> <input type="text" name="replace[]" class="form-control" v-model="task.replace" placeholder="替换表达式">
@ -344,7 +344,7 @@
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" placeholder="可选,只转存修改日期>此文件的文件" name="startfid[]" v-model="task.startfid"> <input type="text" class="form-control" placeholder="可选,只转存修改日期>此文件的文件" name="startfid[]" v-model="task.startfid">
<div class="input-group-append" v-if="task.shareurl"> <div class="input-group-append" v-if="task.shareurl">
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=false;fileSelect.previewRegex=false;showShareSelect(index)">选择</button> <button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=false; showFolderSelect(index)">选择</button>
</div> </div>
</div> </div>
</div> </div>
@ -424,9 +424,8 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"> <h5 class="modal-title">
<b v-if="fileSelect.previewRegex">正则处理预览</b> <b v-if="fileSelect.selectDir">选择{{fileSelect.selectShare ? '分享' : '保存'}}文件夹</b>
<b v-else-if="fileSelect.selectDir">选择{{fileSelect.selectShare ? '需转存的' : '保存到的'}}文件夹</b> <b v-else>选择文件</b>
<b v-else>选择起始文件</b>
<div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div> <div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
</h5> </h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
@ -447,41 +446,30 @@
</ol> </ol>
</nav> </nav>
<!-- 文件列表 --> <!-- 文件列表 -->
<div class="mb-3" v-if="fileSelect.previewRegex">
<div><b>匹配表达式:</b><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].pattern"></span></div>
<div><b>替换表达式:</b><span class="badge badge-info" v-html="formData.tasklist[fileSelect.index].replace"></span></div>
</div>
<table class="table table-hover table-sm"> <table class="table table-hover table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">文件名</th> <th scope="col">文件名</th>
<th scope="col" v-if="fileSelect.selectShare">正则处理</th> <th scope="col">大小</th>
<template v-if="!fileSelect.previewRegex"> <th scope="col">修改日期 ↓</th>
<th scope="col">大小</th> <th scope="col" v-if="!fileSelect.selectShare">操作</th>
<th scope="col">修改日期 ↓</th>
<th scope="col" v-if="!fileSelect.selectShare">操作</th>
</template>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(file, key) in fileSelect.fileList" :key="key" @click="fileSelect.selectDir ? (file.dir ? navigateTo(file.fid, file.file_name) : null) : selectStartFid(file.fid)" :class="{'cursor-pointer': fileSelect.selectDir ? file.dir : true}"> <tr v-for="(file, key) in fileSelect.fileList" :key="key" @click="fileSelect.selectDir ? (file.dir ? navigateTo(file.fid, file.file_name) : null) : selectStartFid(file.fid)" :class="{'cursor-pointer': (fileSelect.selectDir && file.dir)}">
<td><i class="bi" :class="file.dir ? 'bi-folder-fill text-warning' : 'bi-file-earmark'"></i> {{file.file_name}}</td> <td><i class="bi" :class="file.dir ? 'bi-folder-fill text-warning' : 'bi-file-earmark'"></i> {{file.file_name}}</td>
<td v-if="fileSelect.selectShare" :class="file.file_name_re ? 'text-success' : 'text-danger'">{{file.file_name_re || '&times;'}}</td> <td v-if="file.dir">{{ file.include_items }}项</td>
<template v-if="!fileSelect.previewRegex"> <td v-else>{{file.size | size}}</td>
<td v-if="file.dir">{{ file.include_items }}项</td> <td>{{file.updated_at | ts2date}}</td>
<td v-else>{{file.size | size}}</td> <td v-if="!fileSelect.selectShare"><a @click.stop.prevent="deleteFile(file.fid, file.file_name, file.dir)">删除</a></td>
<td>{{file.updated_at | ts2date}}</td>
<td v-if="!fileSelect.selectShare"><a @click.stop.prevent="deleteFile(file.fid, file.file_name, file.dir)">删除</a></td>
</template>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div class="modal-footer" v-if="fileSelect.selectDir && !fileSelect.previewRegex"> <div class="modal-footer" v-if="fileSelect.selectDir">
<span v-html="fileSelect.selectShare ? '转存:' : '保存到:'"></span> <button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">选择当前文件夹</button>
<button type="button" class="btn btn-primary btn-sm" @click="selectCurrentFolder()">当前文件夹</button> <button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">选择当前文件夹+/任务名称</button>
<button type="button" class="btn btn-primary btn-sm" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">当前文件夹<span class="badge badge-light" v-html="'/'+formData.tasklist[fileSelect.index].taskname"></span></button>
</div> </div>
</div> </div>
</div> </div>
@ -547,7 +535,6 @@
paths: [], paths: [],
selectDir: true, selectDir: true,
selectShare: true, selectShare: true,
previewRegex: false,
}, },
}, },
filters: { filters: {
@ -789,74 +776,36 @@
clearData(target) { clearData(target) {
this[target] = ""; this[target] = "";
}, },
async runScriptNow(task_index = null) { runScriptNow(task_index = "") {
body = {}; if (this.configModified) {
if (task_index != null) {
task = { ...this.formData.tasklist[task_index] };
delete task.runweek;
delete task.enddate;
body = {
"tasklist": [task]
};
} else if (this.configModified) {
if (!confirm('配置已修改但未保存,是否继续运行?')) { if (!confirm('配置已修改但未保存,是否继续运行?')) {
return; return;
} }
} }
$('#logModal').modal('toggle'); $('#logModal').modal('toggle')
this.modalLoading = true; this.modalLoading = true
this.run_log = ''; this.run_log = ''
try { const source = new EventSource(`/run_script_now?task_index=${task_index}`);
// 1. 发送 POST 请求 source.onmessage = (event) => {
const response = await fetch(`/run_script_now`, { if (event.data == "[DONE]") {
method: 'POST', this.modalLoading = false
headers: { source.close();
'Content-Type': 'application/json' // 运行后刷新数据
}, this.fetchData();
body: JSON.stringify(body) } else {
}); this.run_log += event.data + '\n';
if (!response.ok) { // 在更新 run_log 后将滚动条滚动到底部
throw new Error(`HTTP error! Status: ${response.status}`); this.$nextTick(() => {
const modalBody = document.querySelector('.modal-body');
modalBody.scrollTop = modalBody.scrollHeight;
});
} }
// 2. 处理 SSE 流 };
const reader = response.body.getReader(); source.onerror = (error) => {
const decoder = new TextDecoder(); this.modalLoading = false
let partialData = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
console.log('Stream complete.');
this.modalLoading = false;
// 运行后刷新数据
this.fetchData();
break;
}
partialData += decoder.decode(value);
const lines = partialData.split('\n').filter(line => line.trim() !== '');
for (const line of lines) {
if (line.startsWith('data:')) {
const eventData = line.substring(5).trim();
if (eventData === '[DONE]') {
this.modalLoading = false;
this.fetchData();
return;
}
this.run_log += eventData + '\n';
// 在更新 run_log 后将滚动条滚动到底部
this.$nextTick(() => {
const modalBody = document.querySelector('.modal-body');
modalBody.scrollTop = modalBody.scrollHeight;
});
} else {
console.warn('Unexpected line:', line);
}
}
partialData = '';
}
} catch (error) {
this.modalLoading = false;
console.error('Error:', error); console.error('Error:', error);
} source.close();
};
}, },
getParentDirectory(path) { getParentDirectory(path) {
parentDir = path.substring(0, path.lastIndexOf('/')) parentDir = path.substring(0, path.lastIndexOf('/'))
@ -918,7 +867,7 @@
}, },
selectSuggestion(index, suggestion) { selectSuggestion(index, suggestion) {
this.smart_param.showSuggestions = false; this.smart_param.showSuggestions = false;
this.showShareSelect(index, suggestion.shareurl); this.showFolderSelect(index, suggestion.shareurl);
}, },
addMagicRegex() { addMagicRegex() {
const newKey = `$MAGIC_${Object.keys(this.formData.magic_regex).length + 1}`; const newKey = `$MAGIC_${Object.keys(this.formData.magic_regex).length + 1}`;
@ -970,14 +919,13 @@
this.modalLoading = false; this.modalLoading = false;
}).catch(error => { }).catch(error => {
console.error('Error /get_savepath_detail:', error); console.error('Error /get_savepath_detail:', error);
this.fileSelect.error = "获取文件夹列表失败"; this.fileSelect = { error: "获取文件夹列表失败" };
this.modalLoading = false; this.modalLoading = false;
}); });
}, },
showSavepathSelect(index) { showSavepathSelect(index) {
this.fileSelect.selectShare = false; this.fileSelect.selectShare = false;
this.fileSelect.selectDir = true; this.fileSelect.selectDir = true;
this.fileSelect.previewRegex = false;
this.fileSelect.error = undefined; this.fileSelect.error = undefined;
this.fileSelect.fileList = []; this.fileSelect.fileList = [];
this.fileSelect.paths = []; this.fileSelect.paths = [];
@ -987,13 +935,10 @@
}, },
getShareDetail() { getShareDetail() {
this.modalLoading = true; this.modalLoading = true;
axios.post('/get_share_detail', { axios.get('/get_share_detail', {
shareurl: this.fileSelect.shareurl, params: {
stoken: this.fileSelect.stoken, shareurl: this.fileSelect.shareurl,
regex: { stoken: this.fileSelect.stoken
pattern: this.formData.tasklist[this.fileSelect.index].pattern,
replace: this.formData.tasklist[this.fileSelect.index].replace,
taskname: this.formData.tasklist[this.fileSelect.index].taskname
} }
}).then(response => { }).then(response => {
if (response.data.success) { if (response.data.success) {
@ -1006,11 +951,11 @@
this.modalLoading = false; this.modalLoading = false;
}).catch(error => { }).catch(error => {
console.error('Error getting folders:', error); console.error('Error getting folders:', error);
this.fileSelect.error = "获取文件夹列表失败"; this.fileSelect = { error: "获取文件夹列表失败" };
this.modalLoading = false; this.modalLoading = false;
}); });
}, },
showShareSelect(index, shareurl = null) { showFolderSelect(index, shareurl = "") {
this.fileSelect.selectShare = true; this.fileSelect.selectShare = true;
this.fileSelect.fileList = []; this.fileSelect.fileList = [];
this.fileSelect.paths = []; this.fileSelect.paths = [];

View File

@ -632,7 +632,8 @@ class Quark:
else: else:
return False return False
except Exception as e: except Exception as e:
print(f"转存测试失败: {str(e)}") if os.environ.get("DEBUG") == True:
print(f"转存测试失败: {str(e)}")
def do_save_task(self, task): def do_save_task(self, task):
# 判断资源失效记录 # 判断资源失效记录
@ -904,7 +905,7 @@ def do_save(account, tasklist=[]):
# 获取全部保存目录fid # 获取全部保存目录fid
account.update_savepath_fid(tasklist) account.update_savepath_fid(tasklist)
def is_time(task): def check_date(task):
return ( return (
not task.get("enddate") not task.get("enddate")
or ( or (
@ -912,33 +913,31 @@ def do_save(account, tasklist=[]):
<= datetime.strptime(task["enddate"], "%Y-%m-%d").date() <= datetime.strptime(task["enddate"], "%Y-%m-%d").date()
) )
) and ( ) and (
"runweek" not in task not task.get("runweek")
# 星期一为0星期日为6 # 星期一为0星期日为6
or (datetime.today().weekday() + 1 in task.get("runweek")) or (datetime.today().weekday() + 1 in task.get("runweek"))
) )
# 执行任务 # 执行任务
for index, task in enumerate(tasklist): for index, task in enumerate(tasklist):
print() # 判断任务期限
print(f"#{index+1}------------------") if check_date(task):
print(f"任务名称: {task['taskname']}") print()
print(f"分享链接: {task['shareurl']}") print(f"#{index+1}------------------")
print(f"保存路径: {task['savepath']}") print(f"任务名称: {task['taskname']}")
if task.get("pattern"): print(f"分享链接: {task['shareurl']}")
print(f"正则匹配: {task['pattern']}") print(f"保存路径: {task['savepath']}")
if task.get("replace"): if task.get("pattern"):
print(f"正则替换: {task['replace']}") print(f"正则匹配: {task['pattern']}")
if task.get("update_subdir"): if task.get("replace"):
print(f"更子目录: {task['update_subdir']}") print(f"正则替换: {task['replace']}")
if task.get("runweek") or task.get("enddate"): if task.get("enddate"):
print( print(f"任务截止: {task['enddate']}")
f"运行周期: WK{task.get("runweek",[])} ~ {task.get('enddate','forever')}" if task.get("ignore_extension"):
) print(f"忽略后缀: {task['ignore_extension']}")
print() if task.get("update_subdir"):
# 判断任务周期 print(f"更子目录: {task['update_subdir']}")
if not is_time(task): print()
print(f"任务不在运行周期内,跳过")
else:
is_new_tree = account.do_save_task(task) is_new_tree = account.do_save_task(task)
is_rename = account.do_rename_task(task) is_rename = account.do_rename_task(task)
@ -978,13 +977,7 @@ def main():
print() print()
# 读取启动参数 # 读取启动参数
config_path = sys.argv[1] if len(sys.argv) > 1 else "quark_config.json" config_path = sys.argv[1] if len(sys.argv) > 1 else "quark_config.json"
# 从环境变量中获取 TASKLIST task_index = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2].isdigit() else ""
tasklist_from_env = []
if tasklist_json := os.environ.get("TASKLIST"):
try:
tasklist_from_env = json.loads(tasklist_json)
except Exception as e:
print(f"从环境变量解析任务列表失败 {e}")
# 检查本地文件是否存在,如果不存在就下载 # 检查本地文件是否存在,如果不存在就下载
if not os.path.exists(config_path): if not os.path.exists(config_path):
if os.environ.get("QUARK_COOKIE"): if os.environ.get("QUARK_COOKIE"):
@ -1015,7 +1008,7 @@ def main():
accounts = [Quark(cookie, index) for index, cookie in enumerate(cookies)] accounts = [Quark(cookie, index) for index, cookie in enumerate(cookies)]
# 签到 # 签到
print(f"===============签到任务===============") print(f"===============签到任务===============")
if tasklist_from_env: if type(task_index) is int:
verify_account(accounts[0]) verify_account(accounts[0])
else: else:
for account in accounts: for account in accounts:
@ -1026,10 +1019,11 @@ def main():
if accounts[0].is_active and cookie_form_file: if accounts[0].is_active and cookie_form_file:
print(f"===============转存任务===============") print(f"===============转存任务===============")
# 任务列表 # 任务列表
if tasklist_from_env: tasklist = CONFIG_DATA.get("tasklist", [])
do_save(accounts[0], tasklist_from_env) if type(task_index) is int:
do_save(accounts[0], [tasklist[task_index]])
else: else:
do_save(accounts[0], CONFIG_DATA.get("tasklist", [])) do_save(accounts[0], tasklist)
print() print()
# 通知 # 通知
if NOTIFYS: if NOTIFYS: