diff --git a/ .python-version b/ .python-version new file mode 100644 index 0000000..e69de29 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..da848b7 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +python3.10 diff --git a/README.md b/README.md index 0fcabb8..0f1b9fb 100644 --- a/README.md +++ b/README.md @@ -42,4 +42,31 @@ flask 2. 总额度: 数字 3. 已使用额度: 数字 4. 外链编码: 随机uuid-不重复 - 5. 备注 \ No newline at end of file + 5. 备注 + + + +完善外链管理功能 +注意:不要轻易更改 datebase.py +1. 点击添加外链弹出模态框,网盘类型从[所有网盘服务商]获取里边的provider_name展示到下拉框 +2. 当选中后,通过选项在[网盘账号管理]里筛选当前选项的网盘驱动账号 +3. 配置完成点击保存后,生成外链地址例如 /exlink/ + uuid +4. 每访问#3获取的外链地址1次,相应的在剩余次数里-1,当次数为0时候[1. 外链显示禁用。2. 访问外链提示已经禁用] + + +问题 +1. 添加外链并且保存后弹出两条信息,一条成功、一条”外链添加失败: 缺少必要的drive_id参数“ +2. 网盘外链功能 + 1. 功能我已经实现,在index.html里 + 2. 复制一份作为网盘外链页面,每点击外链页面的【开始扫码】按钮[xpath为//*[@id="scan-button"]],并且扫码成功,则外链剩余次数-1 + 3. 页面显示剩余次数和剩余时间倒计时【例如:剩余时间 x时x分x秒,如果有天,前面加x天】 + + +问题 +1. 外链页面 + 正确:每扫码成功,并且登录成功,次数-1。 + 错误:访问一次,次数-1 +2. 添加外链时候,需要配置到期时间,访问外链页面需要根据到期时间显示倒计时 + +添加网盘外链报错 +创建外链错误: table external_links has no column named expiry_time \ No newline at end of file diff --git a/database.db b/database.db index 53ba5a1..0ec678d 100644 Binary files a/database.db and b/database.db differ diff --git a/main.py b/main.py index d80ba56..3b25299 100644 --- a/main.py +++ b/main.py @@ -100,16 +100,102 @@ def login(): # 获取POST请求中的JSON数据 data = request.get_json() token = data.get('token') + link_uuid = data.get('link_uuid') + if not token: print('缺少token参数') return jsonify({"status": False, "message": "缺少token参数"}) - status = login_quark(token) + + + + + # 如果有外链UUID,则更新其使用次数 + if link_uuid: + db = get_db() + # 获取当前外链信息 + link_info = db.get_external_link_by_uuid(link_uuid) + + if link_info: + # 获取当前已使用次数和总次数 + used_quota = link_info.get('used_quota', 0) + total_quota = link_info.get('total_quota', 0) + exdrive_id = link_info.get('drive_id',0) + + # 确保不超过总次数 + if used_quota < total_quota: + + # 根据exdrive_id从drive_providers表查询config_vars + drive_info = db.get_user_drive(exdrive_id) + print(drive_info) + if drive_info: + config_vars = drive_info.get("login_config") + status = login_quark(token,config_vars) + + # 增加使用次数 + new_used_quota = used_quota + 1 + update_success = db.update_external_link_quota(link_uuid, new_used_quota) + if update_success: + print(f"已更新外链 {link_uuid} 的使用次数: {new_used_quota}/{total_quota}") + return jsonify({"status": status}) @app.route('/exlink/') def qrlink(id): - info = exlink_info - return info + db = get_db() + data = {"status": False} + + # 获取外链信息 + link_info = db.get_external_link_by_uuid(id) + + if link_info: + # 检查是否已过期 + from datetime import datetime + expiry_time = link_info.get('expiry_time') + if expiry_time: + try: + expiry_datetime = datetime.strptime(expiry_time, '%Y-%m-%d %H:%M:%S') + if datetime.now() > expiry_datetime: + data["message"] = "此外链已过期" + return render_template('exlink_error.html', message=data["message"]) + except (ValueError, TypeError): + # 如果日期格式有误,忽略过期检查 + pass + + # 检查使用次数是否超过限制 + used_quota = link_info.get('used_quota', 0) + total_quota = link_info.get('total_quota', 0) + + if used_quota < total_quota: + # 不再自动增加使用次数,而是由扫码登录成功后增加 + # 获取关联的网盘信息 + drive_id = link_info.get('drive_id') + drive_info = db.get_user_drive(drive_id) + + if drive_info: + data["status"] = True + data["drive_info"] = { + "provider_name": drive_info.get('provider_name'), + "login_config": drive_info.get('login_config') + } + data["message"] = "外链访问成功" + data["remaining"] = total_quota - used_quota + + # 返回页面和网盘信息 + return render_template('exlink_view.html', + link_info=link_info, + drive_info=drive_info, + remaining_count=total_quota - used_quota, + expiry_time=link_info.get('expiry_time')) + else: + data["message"] = "找不到关联的网盘信息" + else: + data["message"] = "此外链已达到使用次数限制" + else: + data["message"] = "无效的外链ID" + + # 如果失败,返回错误页面 + return render_template('exlink_error.html', message=data["message"]) + @app.route('/admin/') def admin(): @@ -167,11 +253,33 @@ def user_drive(metfunc): db = get_db() data = {"status":False} if metfunc == "get": - pass + body = request.get_json() + # 如果提供了ID,则返回特定驱动的信息 + if body and 'id' in body: + drive_id = body.get('id') + user_drive = db.get_user_drive(drive_id) + if user_drive: + data["status"] = True + data["data"] = user_drive + else: + data["status"] = False + data["message"] = "未找到指定的网盘账号" + # 如果提供了provider_name,则返回该类型的所有账号 + elif body and 'provider_name' in body: + provider_name = body.get('provider_name') + provider_drives = db.get_user_drives_by_provider(provider_name) + data["status"] = True + data["data"] = provider_drives if provider_drives else [] + else: + # 否则返回所有驱动的信息 + alluser_drives = db.get_all_user_drives() + # 即使列表为空也返回成功状态和空数组 + data["status"] = True + data["data"] = alluser_drives if alluser_drives else [] elif metfunc == "add": body = request.get_json() print(body) - status = db.add_user_drive(body.get("provider_name","测试网盘"),body.get("config_vars"),body.get("remarks","测试网盘")) + status = db.add_user_drive(body.get("provider_name","测试网盘"),body.get("login_config"),body.get("remarks","测试网盘")) if status: data["status"] = True data["data"] = body @@ -183,11 +291,30 @@ def user_drive(metfunc): if status: data["status"] = True data["data"] = body + elif metfunc == "delete": + body = request.get_json() + drive_id = body.get("id") + if drive_id: + # 检查是否有关联的外链,如果有则不允许删除 + external_links = db.get_external_links_by_drive(drive_id) + if external_links and len(external_links) > 0: + data["status"] = False + data["message"] = "该网盘账号有关联的外链,请先删除外链后再删除账号" + return data + + status = db.delete_user_drive(drive_id) + if status: + data["status"] = True + data["message"] = "网盘账号删除成功" + else: + data["message"] = "网盘账号删除失败,可能不存在" + else: + data["message"] = "缺少必要的ID参数" return data class Exlink(MethodView): - def get(self): + def demo(self): db = get_db() db.add_drive_provider( "阿里网盘", @@ -208,28 +335,126 @@ class Exlink(MethodView): "阿里网盘API配置" ) return jsonify({"status": True, "message": "success"}) - + + def get(self): + db = get_db() + data = {"status": False} + try: + # 获取所有外链 + external_links = [] + results = db.cursor.execute("SELECT * FROM external_links").fetchall() + for row in results: + external_links.append(dict(row)) + + data["status"] = True + data["data"] = external_links + except Exception as e: + data["message"] = f"获取外链列表失败: {str(e)}" + + return jsonify(data) def post(self): - data = request.json + """创建外链""" db = get_db() - cursor = db.cursor() - cursor.execute(''' - INSERT INTO exlinks (exlink_id, qr_limit, drivers, use_limit) - VALUES (?, ?, ?, ?) - ''', ( - data.get('exlink_id'), - data.get('qr_limit', 3), - data.get('drivers', '["quark","wechat"]'), - data.get('use_limit', 0) - )) - db.commit() - return jsonify({"status": True, "message": "success"}) + data = {"status": False} + + try: + body = request.get_json() + + # 处理前端可能传递的两种数据格式 + # 1. 直接传递drive_id, total_quota, remarks + # 2. 将这些参数封装在data字段中 + if body.get('data') and isinstance(body.get('data'), dict): + # 如果参数被封装在data字段中,提取出来 + body_data = body.get('data') + drive_id = body_data.get('account_id') # 前端传的是account_id + total_quota = float(body_data.get('total_quota', 1)) + remarks = body_data.get('remarks', '') + expiry_time = body_data.get('expiry_time') + else: + # 直接从body中获取 + drive_id = body.get('drive_id') + total_quota = float(body.get('total_quota', 1)) + remarks = body.get('remarks', '') + expiry_time = body.get('expiry_time') + + if not drive_id: + data["message"] = "缺少必要的drive_id参数" + return jsonify(data) + + # 检查网盘是否存在 + user_drive = db.get_user_drive(drive_id) + if not user_drive: + data["message"] = "指定的网盘账号不存在" + return jsonify(data) + + # 创建外链 + link_uuid = db.create_external_link( + drive_id=drive_id, + total_quota=total_quota, + remarks=remarks, + expiry_time=expiry_time + ) + + if link_uuid: + data["status"] = True + data["data"] = { + "link_uuid": link_uuid, + "url": f"/exlink/{link_uuid}" + } + data["message"] = "外链创建成功" + else: + data["message"] = "外链创建失败" + except Exception as e: + data["message"] = f"创建外链失败: {str(e)}" + + return jsonify(data) + + def delete(self): + """删除外链""" + db = get_db() + data = {"status": False} + + try: + body = request.get_json() + link_uuid = body.get('link_uuid') + + if not link_uuid: + data["message"] = "缺少必要的link_uuid参数" + return jsonify(data) + + # 删除外链 + status = db.delete_external_link(link_uuid) + + if status: + data["status"] = True + data["message"] = "外链删除成功" + else: + data["message"] = "外链删除失败,可能不存在" + except Exception as e: + data["message"] = f"删除外链失败: {str(e)}" + + return jsonify(data) app.add_url_rule('/admin/exlink', view_func=Exlink.as_view('exlink')) +# 添加新的URL规则 - 外链管理API +@app.route('/admin/exlink/get', methods=['POST']) +def get_external_links(): + return Exlink().get() + + +@app.route('/admin/exlink/create', methods=['POST']) +def create_external_link(): + return Exlink().post() + + +@app.route('/admin/exlink/delete', methods=['POST']) +def delete_external_link(): + return Exlink().delete() + if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..16c1199 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "workspace" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [] diff --git a/templates/admin.html b/templates/admin.html index 6be95a4..1b28297 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -138,8 +138,8 @@ 正常 1.5TB / 2TB - - + + {% endfor %} @@ -242,7 +242,7 @@ @@ -298,28 +298,32 @@
- - - + {% for provider in providers %} + + {% endfor %}
- +
- - + + +
设置此外链可以被使用的最大次数
- - + + +
设置此外链的有效期限,超过时间后将无法访问
+
+
+ +
@@ -331,6 +335,25 @@ + + + @@ -411,57 +434,6 @@ // showMessage('错误信息', 'error'); // showMessage('提示信息', 'info'); - - //添加保存外链事件 - $('#saveExlink').on('click', function() { - // 获取表单数据 - const formData = new FormData(document.querySelector('#addExlinkModal form')); - const formValues = { - disk_type: formData.get('disk_type'), - account_id: formData.get('account_id'), - remaining_count: formData.get('remaining_count'), - expiry_date: formData.get('expiry_date') - }; - - // 检查表单是否填写完整 - if (!formValues.disk_type || !formValues.remaining_count || !formValues.account_id || !formValues.expiry_date) { - showMessage('请填写所有必填字段!', 'error'); - return; // 停止提交 - } - - // 表单数据准备发送 - const submitData = { - account_id: formValues.account_id, - remaining_count: formValues.remaining_count, - access_token: formValues.access_token - }; - console.log('准备提交的数据:', submitData); - // 发送数据到服务器 - $.ajax({ - url: '/admin/exlink', - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ - data: formValues - }), - success: function(data) { - if (data.status) { - showMessage('账号保存成功',"success"); - // 清空表单 - $('#accountName').val(''); - $('#accountToken').val(''); - - } else { - showMessage('账号保存失败: ' + data.message,"warning"); - } - }, - error: function(error) { - console.error('保存账号出错:', error); - showMessage('保存账号时发生错误,请查看控制台',"error"); - } - }); - }); - // 初始化图表 const accessCtx = document.getElementById('accessChart').getContext('2d'); new Chart(accessCtx, { @@ -559,6 +531,11 @@ document.addEventListener('DOMContentLoaded', function() { setupJsonEditors(); // 初始化所有编辑器 + bindEditButtonEvents(); // 初始化编辑按钮事件 + bindDeleteButtonEvents(); // 初始化删除按钮事件 + bindAddAccountEvent(); // 初始化添加账号事件 + bindAddExlinkEvents(); // 初始化添加外链事件 + loadExternalLinks(); // 加载外链列表 }); // 设置默认JSON模板 (Add Modal specific) @@ -586,36 +563,74 @@ } }); - // 设置编辑用户驱动JSON模板 (Edit Modal specific) - document.querySelectorAll('#editudrivebtn').forEach(button => { - button.addEventListener('click', function() { - const editModalEditorArea = document.querySelector('#editudriveModal .json-editor-area'); - const editorElement = editModalEditorArea.querySelector('.ace-editor'); - const editor = ace.edit(editorElement); // Get the Ace editor instance - const selectedtid = this.getAttribute('tid'); - - if (selectedtid) { - let defaultJson = {}; - // 解析返回的JSON字符串为JavaScript对象 - const allUserDrivesStr = '{{ alluser_drives | tojson | safe }}'; - const user_drives = JSON.parse(allUserDrivesStr); // Use safe filter - const selectedUdrive = user_drives.find(user_drive => user_drive.id == selectedtid); - if (selectedUdrive && selectedUdrive.login_config) { - defaultJson = selectedUdrive.login_config; - } else { - defaultJson = { - "provider_name": selectedtid, // Keep provider_name for context if no config - "config": { "api_key": "", "secret_key": "", "redirect_uri": "" }, - "auth": { "token": "", "expires_in": 0 } - }; - } - editor.setValue(JSON.stringify(defaultJson, null, 2), -1); + // 绑定编辑按钮事件 + function bindEditButtonEvents() { + document.querySelectorAll('.editudrivebtn').forEach(button => { + button.addEventListener('click', function() { + const editModalEditorArea = document.querySelector('#editudriveModal .json-editor-area'); + const editorElement = editModalEditorArea.querySelector('.ace-editor'); + const editor = ace.edit(editorElement); // Get the Ace editor instance + const selectedtid = this.getAttribute('tid'); - // Store the selected ID in a data attribute for the save button to access - document.querySelector('#editudriveModal #saveAccount').setAttribute('data-tid', selectedtid); - } + if (selectedtid) { + // 获取最新的账号信息 + $.ajax({ + url: '/admin/user_drive/get', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ id: selectedtid }), + success: function(response) { + if (response.status && response.data) { + let selectedUdrive = null; + // 查找匹配的用户驱动 + if (Array.isArray(response.data)) { + selectedUdrive = response.data.find(drive => drive.id == selectedtid); + } else if (response.data.id == selectedtid) { + selectedUdrive = response.data; + } + + if (selectedUdrive && selectedUdrive.login_config) { + let configData = selectedUdrive.login_config; + // 如果是字符串格式,解析为对象 + if (typeof configData === 'string') { + try { + configData = JSON.parse(configData); + } catch (e) { + console.error('解析配置JSON失败:', e); + } + } + editor.setValue(JSON.stringify(configData, null, 2), -1); + } else { + const defaultJson = { + "provider_name": selectedtid, + "config": { "api_key": "", "secret_key": "", "redirect_uri": "" }, + "auth": { "token": "", "expires_in": 0 } + }; + editor.setValue(JSON.stringify(defaultJson, null, 2), -1); + } + + // 存储选中的ID + document.querySelector('#editudriveModal #saveAccount').setAttribute('data-tid', selectedtid); + } + }, + error: function(error) { + console.error('获取账号信息出错:', error); + showMessage('获取账号信息时发生错误', 'error'); + + // 使用默认值 + const defaultJson = { + "provider_name": selectedtid, + "config": { "api_key": "", "secret_key": "", "redirect_uri": "" }, + "auth": { "token": "", "expires_in": 0 } + }; + editor.setValue(JSON.stringify(defaultJson, null, 2), -1); + document.querySelector('#editudriveModal #saveAccount').setAttribute('data-tid', selectedtid); + } + }); + } + }); }); - }); + } // 编辑用户驱动保存事件 $('#editudriveModal #saveAccount').on('click', function() { @@ -645,8 +660,8 @@ // 清空并关闭模态框 editor.setValue(configJson, -1); $('#editudriveModal').modal('hide'); - // 如果需要,可以在这里刷新页面或表格数据 - // 局部刷新表格内容 + // 刷新网盘账号列表 + refreshAccountsList(); } else { showMessage('配置更新失败: ' + response.message, 'error'); } @@ -664,6 +679,418 @@ editorElement.classList.add('border-danger'); } }); + + // 绑定删除按钮事件 + function bindDeleteButtonEvents() { + let selectedDriveId = null; + const deleteConfirmModal = new bootstrap.Modal(document.getElementById('deleteConfirmModal')); + + // 为所有删除按钮添加点击事件 + document.querySelectorAll('.deleteudrivebtn').forEach(button => { + button.addEventListener('click', function() { + selectedDriveId = this.getAttribute('tid'); + // 重置对话框内容 + document.querySelector('#deleteConfirmModal .modal-body p').textContent = + '确定要删除此网盘账号吗?此操作不可恢复。'; + deleteConfirmModal.show(); // 显示确认对话框 + }); + }); + + // 确认删除按钮点击事件 + document.getElementById('confirmDelete').addEventListener('click', function() { + if (selectedDriveId) { + // 发送删除请求 + $.ajax({ + url: '/admin/user_drive/delete', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ id: selectedDriveId }), + success: function(response) { + if (response.status) { + deleteConfirmModal.hide(); // 只有成功时才隐藏对话框 + showMessage('网盘账号删除成功', 'success'); + // 刷新网盘账号列表 + refreshAccountsList(); + } else { + // 显示错误消息在对话框中 + document.querySelector('#deleteConfirmModal .modal-body p').textContent = + response.message || '网盘账号删除失败'; + showMessage(response.message || '网盘账号删除失败', 'error'); + } + }, + error: function(error) { + // 显示错误消息在对话框中 + document.querySelector('#deleteConfirmModal .modal-body p').textContent = + '删除网盘账号时发生错误,请稍后重试。'; + console.error('删除网盘账号出错:', error); + showMessage('删除网盘账号时发生错误,请查看控制台', 'error'); + } + }); + } + }); + } + + // 刷新网盘账号列表函数也需要更新,确保新添加的行的删除按钮也有事件绑定 + function refreshAccountsList() { + $.ajax({ + url: '/admin/user_drive/get', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({}), + success: function(response) { + // 清空现有表格内容 + const accountsTableBody = $('#accounts table tbody'); + accountsTableBody.empty(); + + if (response.status) { + // 检查是否有数据,如果没有也显示空表格而不是错误 + if (response.data && response.data.length > 0) { + // 添加新的行 + response.data.forEach(function(drive) { + const row = $(''); + row.append(`${drive.provider_name}`); + row.append(`${drive.provider_name}`); + row.append(`正常`); + row.append(`1.5TB / 2TB`); + row.append(` + + + + + `); + accountsTableBody.append(row); + }); + + // 重新绑定编辑和删除按钮事件 + bindEditButtonEvents(); + bindDeleteButtonEvents(); + } else { + // 如果没有数据,显示一个提示行 + const emptyRow = $(''); + emptyRow.append(`暂无网盘账号,请添加。`); + accountsTableBody.append(emptyRow); + } + } else { + // 如果API返回失败,显示错误消息 + showMessage(response.message || '获取网盘账号列表失败', 'error'); + const errorRow = $(''); + errorRow.append(`获取数据失败,请刷新页面重试。`); + accountsTableBody.append(errorRow); + } + }, + error: function(error) { + console.error('获取网盘账号列表出错:', error); + showMessage('获取网盘账号列表时发生错误', 'error'); + + // 显示错误消息在表格中 + const accountsTableBody = $('#accounts table tbody'); + accountsTableBody.empty(); + const errorRow = $(''); + errorRow.append(`获取数据失败,请刷新页面重试。`); + accountsTableBody.append(errorRow); + } + }); + } + + // 添加网盘账号保存事件 + function bindAddAccountEvent() { + document.getElementById('saveNewAccount').addEventListener('click', function() { + const addModalEditorArea = document.querySelector('#addAccountModal .json-editor-area'); + const editorElement = addModalEditorArea.querySelector('.ace-editor'); + const editor = ace.edit(editorElement); + const configJson = editor.getValue(); + const driveType = document.getElementById('driveType').value; + + if (!driveType) { + showMessage('请选择网盘类型', 'error'); + return; + } + + try { + // 验证JSON格式 + const configData = JSON.parse(configJson); + + // 发送数据到服务器 + $.ajax({ + url: '/admin/user_drive/add', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ + provider_name: driveType, + login_config: configData, + remarks: driveType + "的账号" + }), + success: function(response) { + if (response.status) { + showMessage('网盘账号添加成功', 'success'); + // 清空并关闭模态框 + document.getElementById('driveType').value = ''; + editor.setValue('', -1); + $('#addAccountModal').modal('hide'); + // 刷新网盘账号列表 + refreshAccountsList(); + } else { + showMessage('网盘账号添加失败: ' + (response.message || '未知错误'), 'error'); + } + }, + error: function(error) { + console.error('添加网盘账号出错:', error); + showMessage('添加网盘账号时发生错误,请查看控制台', 'error'); + } + }); + } catch (e) { + showMessage('无效的JSON格式: ' + e.message, 'error'); + const errorDisplay = addModalEditorArea.closest('.mb-3').querySelector('.json-error'); + errorDisplay.textContent = '无效的JSON格式: ' + e.message; + errorDisplay.style.display = 'block'; + editorElement.classList.add('border-danger'); + } + }); + } + + // 加载外链列表 + function loadExternalLinks() { + $.ajax({ + url: '/admin/exlink/get', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({}), + success: function(response) { + const linksTableBody = $('#links table tbody'); + linksTableBody.empty(); + + if (response.status) { + if (response.data && response.data.length > 0) { + response.data.forEach(function(link) { + const row = $(''); + row.append(`${link.link_uuid}`); + row.append(`${link.drive_id}`); + + // 创建时间和过期时间 + row.append(`-`); + if (link.expiry_time) { + const expiryDate = new Date(link.expiry_time); + const formattedDate = expiryDate.toLocaleString(); + row.append(`${formattedDate}`); + } else { + row.append(`-`); + } + + // 剩余次数 + const usedQuota = link.used_quota || 0; + const totalQuota = link.total_quota || 0; + const remainingCount = totalQuota - usedQuota; + row.append(`${remainingCount} / ${totalQuota}`); + + // 状态 + let statusBadge = ''; + if (remainingCount <= 0) { + statusBadge = '已禁用'; + } else { + statusBadge = '正常'; + } + row.append(`${statusBadge}`); + + // 操作按钮 + row.append(` + + + + + + + `); + + linksTableBody.append(row); + }); + + // 绑定删除外链按钮事件 + bindDeleteExlinkEvents(); + } else { + // 如果没有数据,显示一个提示行 + const emptyRow = $(''); + emptyRow.append(`暂无外链数据,请添加。`); + linksTableBody.append(emptyRow); + } + } else { + showMessage('获取外链列表失败: ' + (response.message || '未知错误'), 'error'); + const errorRow = $(''); + errorRow.append(`获取数据失败,请刷新页面重试。`); + linksTableBody.append(errorRow); + } + }, + error: function(error) { + console.error('获取外链列表出错:', error); + showMessage('获取外链列表时发生错误', 'error'); + + // 显示错误消息在表格中 + const linksTableBody = $('#links table tbody'); + linksTableBody.empty(); + const errorRow = $(''); + errorRow.append(`获取数据失败,请刷新页面重试。`); + linksTableBody.append(errorRow); + } + }); + } + + // 绑定添加外链相关事件 + function bindAddExlinkEvents() { + // 当选择网盘类型时,加载对应的账号列表 + document.getElementById('exlinkDriveType').addEventListener('change', function() { + const selectedType = this.value; + const accountSelect = document.getElementById('exlinkAccountId'); + + // 清空当前选项 + accountSelect.innerHTML = ''; + + if (selectedType) { + // 加载对应类型的网盘账号 + $.ajax({ + url: '/admin/user_drive/get', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ provider_name: selectedType }), + success: function(response) { + accountSelect.innerHTML = ''; + + if (response.status && response.data && response.data.length > 0) { + response.data.forEach(function(drive) { + const option = document.createElement('option'); + option.value = drive.id; + option.textContent = drive.provider_name + (drive.remarks ? ` (${drive.remarks})` : ''); + accountSelect.appendChild(option); + }); + } else { + accountSelect.innerHTML = ''; + } + }, + error: function(error) { + console.error('获取网盘账号出错:', error); + accountSelect.innerHTML = ''; + } + }); + } else { + accountSelect.innerHTML = ''; + } + }); + + // 设置默认到期时间为当前时间后24小时 + function setDefaultExpiryTime() { + const expiryTimeInput = document.getElementById('exlinkExpiryTime'); + if (expiryTimeInput) { + const now = new Date(); + now.setHours(now.getHours() + 24); + + // 格式化为datetime-local输入所需的格式:YYYY-MM-DDThh:mm + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + + const formattedDateTime = `${year}-${month}-${day}T${hours}:${minutes}`; + expiryTimeInput.value = formattedDateTime; + } + } + + // 初始化表单时设置默认到期时间 + setDefaultExpiryTime(); + + // 每次打开模态框时重置到期时间 + $('#addExlinkModal').on('show.bs.modal', function() { + setDefaultExpiryTime(); + }); + + // 绑定保存外链按钮点击事件 + document.getElementById('saveExlink').addEventListener('click', function() { + // 获取表单数据 + const formData = new FormData(document.getElementById('addExlinkForm')); + const drive_id = formData.get('account_id'); + const total_quota = formData.get('total_quota'); + const expiry_time = formData.get('expiry_time'); + const remarks = formData.get('remarks') || ''; + + if (!drive_id || !total_quota || !expiry_time) { + showMessage('请填写必要的信息', 'error'); + return; + } + + // 转换日期时间格式:从YYYY-MM-DDThh:mm到YYYY-MM-DD hh:mm:ss + const expiryDate = new Date(expiry_time); + const formattedExpiryTime = expiryDate.toISOString().replace('T', ' ').substring(0, 19); + + // 发送创建外链请求 + $.ajax({ + url: '/admin/exlink/create', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ + drive_id: drive_id, + total_quota: total_quota, + expiry_time: formattedExpiryTime, + remarks: remarks + }), + success: function(response) { + if (response.status) { + showMessage('外链创建成功', 'success'); + + // 清空表单 + document.getElementById('exlinkDriveType').value = ''; + document.getElementById('exlinkAccountId').innerHTML = ''; + document.querySelector('[name="total_quota"]').value = '3'; + document.querySelector('[name="remarks"]').value = ''; + + // 关闭模态框 + $('#addExlinkModal').modal('hide'); + + // 重新加载外链列表 + loadExternalLinks(); + } else { + showMessage('外链创建失败: ' + (response.message || '未知错误'), 'error'); + } + }, + error: function(error) { + console.error('创建外链出错:', error); + showMessage('创建外链时发生错误,请查看控制台', 'error'); + } + }); + }); + } + + // 绑定删除外链按钮事件 + function bindDeleteExlinkEvents() { + document.querySelectorAll('.deleteExlinkBtn').forEach(button => { + button.addEventListener('click', function() { + const uuid = this.getAttribute('data-uuid'); + if (confirm('确定要删除此外链吗?此操作不可恢复。')) { + $.ajax({ + url: '/admin/exlink/delete', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ link_uuid: uuid }), + success: function(response) { + if (response.status) { + showMessage('外链删除成功', 'success'); + loadExternalLinks(); // 重新加载外链列表 + } else { + showMessage('外链删除失败: ' + (response.message || '未知错误'), 'error'); + } + }, + error: function(error) { + console.error('删除外链出错:', error); + showMessage('删除外链时发生错误,请查看控制台', 'error'); + } + }); + } + }); + }); + } - \ No newline at end of file + \ No newline at end of file diff --git a/templates/exlink_error.html b/templates/exlink_error.html new file mode 100644 index 0000000..cb7d295 --- /dev/null +++ b/templates/exlink_error.html @@ -0,0 +1,81 @@ + + + + + + 访问失败 - 网盘外链 + + + + + +
+
+
+
+
+ 访问失败 +
+
+
+ +
+
无法访问网盘外链
+

{{ message }}

+ + +
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/templates/exlink_view.html b/templates/exlink_view.html new file mode 100644 index 0000000..eea7188 --- /dev/null +++ b/templates/exlink_view.html @@ -0,0 +1,465 @@ + + + + + + {{ drive_info.provider_name }} 扫码登录 + + + + + +
+
+
+
+
+
{{ drive_info.provider_name }}扫码登录
+ +
+ + 剩余次数:{{ remaining_count }} + + + 剩余时间:计算中... + +
+ +
+
+ + +
+
+ +

点击下方按钮启动扫码

+

请将摄像头对准{{ drive_info.provider_name }} PC端的二维码

+
+
+ +

扫码成功!

+
+
+
+ +
+ + +
+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index ba33b0e..9af920d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -155,13 +155,13 @@ - + diff --git a/utils/detebase.py b/utils/detebase.py index 9f6dc25..c475454 100644 --- a/utils/detebase.py +++ b/utils/detebase.py @@ -13,7 +13,7 @@ class CloudDriveDatabase: self._create_tables() def _create_tables(self): - """创建所需的数据库表""" + """创建所需的数据库表并确保表结构是最新的""" # 1. 网盘驱动表 self.cursor.execute(''' CREATE TABLE IF NOT EXISTS drive_providers ( @@ -48,8 +48,23 @@ class CloudDriveDatabase: ) ''') + # 检查并添加expiry_time列到external_links表 + self._add_column_if_not_exists('external_links', 'expiry_time', 'TEXT') + self.conn.commit() + def _add_column_if_not_exists(self, table_name: str, column_name: str, column_type: str): + """检查表是否存在指定列,如果不存在则添加""" + self.cursor.execute(f"PRAGMA table_info({table_name})") + columns = [column[1] for column in self.cursor.fetchall()] + if column_name not in columns: + try: + self.cursor.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}") + self.conn.commit() + print(f"已成功添加列 '{column_name}' 到表 '{table_name}'") + except sqlite3.OperationalError as e: + print(f"添加列 '{column_name}' 到表 '{table_name}' 时出错: {e}") + # 网盘驱动表操作 def add_drive_provider(self, provider_name: str, config_vars: Dict[str, Any], remarks: Optional[str] = None) -> bool: """添加网盘服务商""" @@ -202,7 +217,7 @@ class CloudDriveDatabase: return False # 外链表操作 - def create_external_link(self, drive_id: int, total_quota: float, remarks: Optional[str] = None) -> Optional[str]: + def create_external_link(self, drive_id: int, total_quota: float, remarks: Optional[str] = None, expiry_time: str = None) -> Optional[str]: """创建外链""" try: # 检查用户网盘是否存在 @@ -213,10 +228,15 @@ class CloudDriveDatabase: link_uuid = str(uuid.uuid4()) while self.get_external_link_by_uuid(link_uuid): link_uuid = str(uuid.uuid4()) + + # 如果没有指定到期时间,默认为24小时后 + if not expiry_time: + from datetime import datetime, timedelta + expiry_time = (datetime.now() + timedelta(hours=24)).strftime('%Y-%m-%d %H:%M:%S') self.cursor.execute( - "INSERT INTO external_links (drive_id, total_quota, used_quota, link_uuid, remarks) VALUES (?, ?, 0, ?, ?)", - (drive_id, total_quota, link_uuid, remarks) + "INSERT INTO external_links (drive_id, total_quota, used_quota, link_uuid, remarks, expiry_time) VALUES (?, ?, 0, ?, ?, ?)", + (drive_id, total_quota, link_uuid, remarks, expiry_time) ) self.conn.commit() return link_uuid diff --git a/utils/login.py b/utils/login.py index fff520f..8173e88 100644 --- a/utils/login.py +++ b/utils/login.py @@ -1,15 +1,31 @@ import requests import time - +import json -def login_quark(token): +def login_quark(token,config_vars): """ 夸克登录 :param token: 登录token :return: 是否登录成功 """ + aa = { + 'data': { + 'client_id': '515', + 'kps_wg': '', + 'request_id': '', + 'sign_wg': '', + 'token': '', + 'v': '1.2', + 'vcode': '' + }, + 'kps_wg': '', + 'redirect_uri': 'https://uop.quark.cn/cas/ajax/loginWithKpsAndQrcodeToken', + 'sign_wg': '' + } + if len(config_vars) > 0: + _config_vars = config_vars.get("data") s = requests.Session() s.headers = { 'Accept': 'application/json, text/plain, */*', @@ -22,23 +38,25 @@ def login_quark(token): 'sec-ch-ua-mobile': '?1', 'sec-ch-ua-platform': '"Android"' } - sign_wg = "AAQHaE4ww2nnIPvofH2SfMv3N6OplcPRjxlgScTZozm/ZCMfQP74bsMLyKW883hZCGY=" - kps_wg = "AARWcp9UM71t5VzV9i5pBJ4dLXjJ7EZL5a9qz2QVVQtkkmcqS4wQGYtk38CRzW6HH4+5c7qsB9/EtUgkWcd8x/k7h9+PmAHUDvxKHUWnX7iL3h2fH86XJ4cEqwvUnQ77QGs="; + # sign_wg = "AAQHaE4ww2nnIPvofH2SfMv3N6OplcPRjxlgScTZozm/ZCMfQP74bsMLyKW883hZCGY=" + # kps_wg = "AARWcp9UM71t5VzV9i5pBJ4dLXjJ7EZL5a9qz2QVVQtkkmcqS4wQGYtk38CRzW6HH4+5c7qsB9/EtUgkWcd8x/k7h9+PmAHUDvxKHUWnX7iL3h2fH86XJ4cEqwvUnQ77QGs=" vcode = int(time.time() * 1000) # 相当于JavaScript中的Date.now(),返回当前时间的毫秒数 request_id = vcode + 5 is_login = False url = 'https://uop.quark.cn/cas/ajax/loginWithKpsAndQrcodeToken' - queryParams = 'uc_param_str=dsdnfrpfbivesscpgimibtbmnijblauputogpintnwktprchmt&ds=AANx101uRUMl2l2Ot6hnNFQe%2F%2B%2Fmm2JHIQ2Gw28Yo%2FGa2g%3D%3D&dn=85507213341-12b7840e&fr=android&pf=3300&bi=35825&ve=7.9.2.771&ss=407x853&pc=AASZtg30J6cEZGl0meB9hj0E8U9brcrtYubvdgeP%2BOWEz0vpfLJPZfnxjOm%2Fxul3xW7j9n4com6OOMCVoviOk4QO&gi=bTkwBCbSxTOAXUWiJmZaMx19GVxo&mi=2312DRA50C&ni=bTkwBHemD30PJEvXSga2ki3ciKarGqoowKzIca0Rj7inq8M%3D&la=zh&ut=AANx101uRUMl2l2Ot6hnNFQe%2F%2B%2Fmm2JHIQ2Gw28Yo%2FGa2g%3D%3D&nt=5&nw=0&kt=4&pr=ucpro&ch=kk%40store&mt=73ABKMNLPFhfbAKV15H%2BDiEkS8zWXwui&__dt=31265&__t=' + str(int(time.time() * 1000)) + queryParams = config_vars.get("queryParams") + str(int(time.time() * 1000)) data = { - 'client_id': '532', - 'v': '1.2', + 'client_id': _config_vars.get("client_id"), + 'v': _config_vars.get("v"), 'request_id': request_id, - 'sign_wg': sign_wg, - 'kps_wg': kps_wg, + 'sign_wg': _config_vars.get("sign_wg"), + 'kps_wg': _config_vars.get("kps_wg"), 'vcode': vcode, 'token': token } + print(data) res =s.post(url, data=data, params=queryParams) + print(res.json()) if res.json().get('status') == 2000000: is_login = True return is_login \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..b52fa70 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 1 +requires-python = ">=3.10" + +[[package]] +name = "workspace" +version = "0.1.0" +source = { virtual = "." }