mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-17 09:50:42 +08:00
Compare commits
7 Commits
92c9f0ad61
...
f1df77f8f2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1df77f8f2 | ||
|
|
668897d1df | ||
|
|
9866a9d93d | ||
|
|
9b9c5fe00a | ||
|
|
dc8362db08 | ||
|
|
bc2cd1504e | ||
|
|
50d01bb4d8 |
36
app/run.py
36
app/run.py
@ -1,6 +1,7 @@
|
||||
# !/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from flask import (
|
||||
json,
|
||||
Flask,
|
||||
url_for,
|
||||
session,
|
||||
@ -23,6 +24,7 @@ import logging
|
||||
import base64
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
sys.path.insert(0, parent_dir)
|
||||
@ -175,20 +177,22 @@ def update():
|
||||
|
||||
|
||||
# 处理运行脚本请求
|
||||
@app.route("/run_script_now", methods=["GET"])
|
||||
@app.route("/run_script_now", methods=["POST"])
|
||||
def run_script_now():
|
||||
if not is_login():
|
||||
return jsonify({"success": False, "message": "未登录"})
|
||||
task_index = request.args.get("task_index", "")
|
||||
command = [PYTHON_PATH, "-u", SCRIPT_PATH, CONFIG_PATH, task_index]
|
||||
tasklist = request.json.get("tasklist", [])
|
||||
command = [PYTHON_PATH, "-u", SCRIPT_PATH, CONFIG_PATH]
|
||||
logging.info(
|
||||
f">>> 手动运行任务{int(task_index)+1 if task_index.isdigit() else 'all'}"
|
||||
f">>> 手动运行任务 [{tasklist[0].get('taskname') if len(tasklist)>0 else 'ALL'}] 开始执行..."
|
||||
)
|
||||
|
||||
def generate_output():
|
||||
# 设置环境变量
|
||||
process_env = os.environ.copy()
|
||||
process_env["PYTHONIOENCODING"] = "utf-8"
|
||||
if tasklist:
|
||||
process_env["TASKLIST"] = json.dumps(tasklist, ensure_ascii=False)
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.PIPE,
|
||||
@ -255,12 +259,12 @@ def get_task_suggestions():
|
||||
return jsonify({"success": True, "message": f"error: {str(e)}"})
|
||||
|
||||
|
||||
@app.route("/get_share_detail")
|
||||
@app.route("/get_share_detail", methods=["POST"])
|
||||
def get_share_detail():
|
||||
if not is_login():
|
||||
return jsonify({"success": False, "message": "未登录"})
|
||||
shareurl = request.args.get("shareurl", "")
|
||||
stoken = request.args.get("stoken", "")
|
||||
shareurl = request.json.get("shareurl", "")
|
||||
stoken = request.json.get("stoken", "")
|
||||
account = Quark("", 0)
|
||||
pwd_id, passcode, pdir_fid, paths = account.extract_url(shareurl)
|
||||
if not stoken:
|
||||
@ -271,6 +275,24 @@ def get_share_detail():
|
||||
share_detail["paths"] = paths
|
||||
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})
|
||||
|
||||
|
||||
|
||||
@ -298,7 +298,7 @@
|
||||
<div class="input-group">
|
||||
<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">
|
||||
<button type="button" class="btn btn-outline-secondary" @click="fileSelect.selectDir=true;showFolderSelect(index)" title="选择文件夹"><i class="bi bi-folder"></i></button>
|
||||
<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>
|
||||
<div class="input-group-text">
|
||||
<a target="_blank" :href="task.shareurl"><i class="bi bi-box-arrow-up-right"></i></a>
|
||||
</div>
|
||||
@ -323,7 +323,7 @@
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">正则处理</span>
|
||||
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=true;fileSelect.previewRegex=true;showShareSelect(index)" title="预览正则处理效果">正则处理</button>
|
||||
</div>
|
||||
<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="替换表达式">
|
||||
@ -344,7 +344,7 @@
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="可选,只转存修改日期>此文件的文件" name="startfid[]" v-model="task.startfid">
|
||||
<div class="input-group-append" v-if="task.shareurl">
|
||||
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=false; showFolderSelect(index)">选择</button>
|
||||
<button class="btn btn-outline-secondary" type="button" @click="fileSelect.selectDir=false;fileSelect.previewRegex=false;showShareSelect(index)">选择</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -424,8 +424,9 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<b v-if="fileSelect.selectDir">选择{{fileSelect.selectShare ? '分享' : '保存'}}文件夹</b>
|
||||
<b v-else>选择文件</b>
|
||||
<b v-if="fileSelect.previewRegex">正则处理预览</b>
|
||||
<b v-else-if="fileSelect.selectDir">选择{{fileSelect.selectShare ? '需转存的' : '保存到的'}}文件夹</b>
|
||||
<b v-else>选择起始文件</b>
|
||||
<div v-if="modalLoading" class="spinner-border spinner-border-sm m-1" role="status"></div>
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
@ -446,30 +447,41 @@
|
||||
</ol>
|
||||
</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">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">文件名</th>
|
||||
<th scope="col">大小</th>
|
||||
<th scope="col">修改日期 ↓</th>
|
||||
<th scope="col" v-if="!fileSelect.selectShare">操作</th>
|
||||
<th scope="col" v-if="fileSelect.selectShare">正则处理</th>
|
||||
<template v-if="!fileSelect.previewRegex">
|
||||
<th scope="col">大小</th>
|
||||
<th scope="col">修改日期 ↓</th>
|
||||
<th scope="col" v-if="!fileSelect.selectShare">操作</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<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)}">
|
||||
<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}">
|
||||
<td><i class="bi" :class="file.dir ? 'bi-folder-fill text-warning' : 'bi-file-earmark'"></i> {{file.file_name}}</td>
|
||||
<td v-if="file.dir">{{ file.include_items }}项</td>
|
||||
<td v-else>{{file.size | size}}</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>
|
||||
<td v-if="fileSelect.selectShare" :class="file.file_name_re ? 'text-success' : 'text-danger'">{{file.file_name_re || '×'}}</td>
|
||||
<template v-if="!fileSelect.previewRegex">
|
||||
<td v-if="file.dir">{{ file.include_items }}项</td>
|
||||
<td v-else>{{file.size | size}}</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>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="fileSelect.selectDir">
|
||||
<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>
|
||||
<div class="modal-footer" v-if="fileSelect.selectDir && !fileSelect.previewRegex">
|
||||
<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" v-if="!fileSelect.selectShare" @click="selectCurrentFolder(true)">当前文件夹<span class="badge badge-light" v-html="'/'+formData.tasklist[fileSelect.index].taskname"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -535,6 +547,7 @@
|
||||
paths: [],
|
||||
selectDir: true,
|
||||
selectShare: true,
|
||||
previewRegex: false,
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
@ -776,36 +789,74 @@
|
||||
clearData(target) {
|
||||
this[target] = "";
|
||||
},
|
||||
runScriptNow(task_index = "") {
|
||||
if (this.configModified) {
|
||||
async runScriptNow(task_index = null) {
|
||||
body = {};
|
||||
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('配置已修改但未保存,是否继续运行?')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$('#logModal').modal('toggle')
|
||||
this.modalLoading = true
|
||||
this.run_log = ''
|
||||
const source = new EventSource(`/run_script_now?task_index=${task_index}`);
|
||||
source.onmessage = (event) => {
|
||||
if (event.data == "[DONE]") {
|
||||
this.modalLoading = false
|
||||
source.close();
|
||||
// 运行后刷新数据
|
||||
this.fetchData();
|
||||
} else {
|
||||
this.run_log += event.data + '\n';
|
||||
// 在更新 run_log 后将滚动条滚动到底部
|
||||
this.$nextTick(() => {
|
||||
const modalBody = document.querySelector('.modal-body');
|
||||
modalBody.scrollTop = modalBody.scrollHeight;
|
||||
});
|
||||
$('#logModal').modal('toggle');
|
||||
this.modalLoading = true;
|
||||
this.run_log = '';
|
||||
try {
|
||||
// 1. 发送 POST 请求
|
||||
const response = await fetch(`/run_script_now`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
};
|
||||
source.onerror = (error) => {
|
||||
this.modalLoading = false
|
||||
// 2. 处理 SSE 流
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
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);
|
||||
source.close();
|
||||
};
|
||||
}
|
||||
},
|
||||
getParentDirectory(path) {
|
||||
parentDir = path.substring(0, path.lastIndexOf('/'))
|
||||
@ -867,7 +918,7 @@
|
||||
},
|
||||
selectSuggestion(index, suggestion) {
|
||||
this.smart_param.showSuggestions = false;
|
||||
this.showFolderSelect(index, suggestion.shareurl);
|
||||
this.showShareSelect(index, suggestion.shareurl);
|
||||
},
|
||||
addMagicRegex() {
|
||||
const newKey = `$MAGIC_${Object.keys(this.formData.magic_regex).length + 1}`;
|
||||
@ -919,13 +970,14 @@
|
||||
this.modalLoading = false;
|
||||
}).catch(error => {
|
||||
console.error('Error /get_savepath_detail:', error);
|
||||
this.fileSelect = { error: "获取文件夹列表失败" };
|
||||
this.fileSelect.error = "获取文件夹列表失败";
|
||||
this.modalLoading = false;
|
||||
});
|
||||
},
|
||||
showSavepathSelect(index) {
|
||||
this.fileSelect.selectShare = false;
|
||||
this.fileSelect.selectDir = true;
|
||||
this.fileSelect.previewRegex = false;
|
||||
this.fileSelect.error = undefined;
|
||||
this.fileSelect.fileList = [];
|
||||
this.fileSelect.paths = [];
|
||||
@ -935,10 +987,13 @@
|
||||
},
|
||||
getShareDetail() {
|
||||
this.modalLoading = true;
|
||||
axios.get('/get_share_detail', {
|
||||
params: {
|
||||
shareurl: this.fileSelect.shareurl,
|
||||
stoken: this.fileSelect.stoken
|
||||
axios.post('/get_share_detail', {
|
||||
shareurl: this.fileSelect.shareurl,
|
||||
stoken: this.fileSelect.stoken,
|
||||
regex: {
|
||||
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 => {
|
||||
if (response.data.success) {
|
||||
@ -951,11 +1006,11 @@
|
||||
this.modalLoading = false;
|
||||
}).catch(error => {
|
||||
console.error('Error getting folders:', error);
|
||||
this.fileSelect = { error: "获取文件夹列表失败" };
|
||||
this.fileSelect.error = "获取文件夹列表失败";
|
||||
this.modalLoading = false;
|
||||
});
|
||||
},
|
||||
showFolderSelect(index, shareurl = "") {
|
||||
showShareSelect(index, shareurl = null) {
|
||||
this.fileSelect.selectShare = true;
|
||||
this.fileSelect.fileList = [];
|
||||
this.fileSelect.paths = [];
|
||||
|
||||
@ -632,8 +632,7 @@ class Quark:
|
||||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
if os.environ.get("DEBUG") == True:
|
||||
print(f"转存测试失败: {str(e)}")
|
||||
print(f"转存测试失败: {str(e)}")
|
||||
|
||||
def do_save_task(self, task):
|
||||
# 判断资源失效记录
|
||||
@ -692,6 +691,17 @@ class Quark:
|
||||
to_pdir_fid = self.savepath_fid[savepath]
|
||||
dir_file_list = self.ls_dir(to_pdir_fid)
|
||||
# print("dir_file_list: ", dir_file_list)
|
||||
# 清空目标文件夹
|
||||
fid_list = [item["fid"] for item in dir_file_list]
|
||||
if fid_list:
|
||||
self.delete(fid_list)
|
||||
recycle_list = self.recycle_list()
|
||||
record_id_list = [
|
||||
item["record_id"] for item in recycle_list if item["fid"] in fid_list
|
||||
]
|
||||
self.recycle_remove(record_id_list)
|
||||
# 重新获取目标目录文件列表
|
||||
dir_file_list = self.ls_dir(to_pdir_fid)
|
||||
|
||||
tree.create_node(
|
||||
savepath,
|
||||
@ -905,7 +915,7 @@ def do_save(account, tasklist=[]):
|
||||
# 获取全部保存目录fid
|
||||
account.update_savepath_fid(tasklist)
|
||||
|
||||
def check_date(task):
|
||||
def is_time(task):
|
||||
return (
|
||||
not task.get("enddate")
|
||||
or (
|
||||
@ -913,31 +923,33 @@ def do_save(account, tasklist=[]):
|
||||
<= datetime.strptime(task["enddate"], "%Y-%m-%d").date()
|
||||
)
|
||||
) and (
|
||||
not task.get("runweek")
|
||||
"runweek" not in task
|
||||
# 星期一为0,星期日为6
|
||||
or (datetime.today().weekday() + 1 in task.get("runweek"))
|
||||
)
|
||||
|
||||
# 执行任务
|
||||
for index, task in enumerate(tasklist):
|
||||
# 判断任务期限
|
||||
if check_date(task):
|
||||
print()
|
||||
print(f"#{index+1}------------------")
|
||||
print(f"任务名称: {task['taskname']}")
|
||||
print(f"分享链接: {task['shareurl']}")
|
||||
print(f"保存路径: {task['savepath']}")
|
||||
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"):
|
||||
print(f"忽略后缀: {task['ignore_extension']}")
|
||||
if task.get("update_subdir"):
|
||||
print(f"更子目录: {task['update_subdir']}")
|
||||
print()
|
||||
print()
|
||||
print(f"#{index+1}------------------")
|
||||
print(f"任务名称: {task['taskname']}")
|
||||
print(f"分享链接: {task['shareurl']}")
|
||||
print(f"保存路径: {task['savepath']}")
|
||||
if task.get("pattern"):
|
||||
print(f"正则匹配: {task['pattern']}")
|
||||
if task.get("replace"):
|
||||
print(f"正则替换: {task['replace']}")
|
||||
if task.get("update_subdir"):
|
||||
print(f"更子目录: {task['update_subdir']}")
|
||||
if task.get("runweek") or task.get("enddate"):
|
||||
print(
|
||||
f"运行周期: WK{task.get("runweek",[])} ~ {task.get('enddate','forever')}"
|
||||
)
|
||||
print()
|
||||
# 判断任务周期
|
||||
if not is_time(task):
|
||||
print(f"任务不在运行周期内,跳过")
|
||||
else:
|
||||
is_new_tree = account.do_save_task(task)
|
||||
is_rename = account.do_rename_task(task)
|
||||
|
||||
@ -977,7 +989,13 @@ def main():
|
||||
print()
|
||||
# 读取启动参数
|
||||
config_path = sys.argv[1] if len(sys.argv) > 1 else "quark_config.json"
|
||||
task_index = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2].isdigit() else ""
|
||||
# 从环境变量中获取 TASKLIST
|
||||
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 os.environ.get("QUARK_COOKIE"):
|
||||
@ -1008,7 +1026,7 @@ def main():
|
||||
accounts = [Quark(cookie, index) for index, cookie in enumerate(cookies)]
|
||||
# 签到
|
||||
print(f"===============签到任务===============")
|
||||
if type(task_index) is int:
|
||||
if tasklist_from_env:
|
||||
verify_account(accounts[0])
|
||||
else:
|
||||
for account in accounts:
|
||||
@ -1019,11 +1037,10 @@ def main():
|
||||
if accounts[0].is_active and cookie_form_file:
|
||||
print(f"===============转存任务===============")
|
||||
# 任务列表
|
||||
tasklist = CONFIG_DATA.get("tasklist", [])
|
||||
if type(task_index) is int:
|
||||
do_save(accounts[0], [tasklist[task_index]])
|
||||
if tasklist_from_env:
|
||||
do_save(accounts[0], tasklist_from_env)
|
||||
else:
|
||||
do_save(accounts[0], tasklist)
|
||||
do_save(accounts[0], CONFIG_DATA.get("tasklist", []))
|
||||
print()
|
||||
# 通知
|
||||
if NOTIFYS:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user