| 1 | import asyncio |
| 2 | import os |
| 3 | import sqlite3 |
| 4 | import threading |
| 5 | import time |
| 6 | import uuid |
| 7 | from pathlib import Path |
| 8 | from queue import Queue |
| 9 | from flask_cors import CORS |
| 10 | from myUtils.auth import check_cookie |
| 11 | from flask import Flask, request, jsonify, Response, render_template, send_from_directory |
| 12 | from werkzeug.utils import secure_filename |
| 13 | from conf import BASE_DIR |
| 14 | from myUtils.login import get_tencent_cookie, douyin_cookie_gen, get_ks_cookie, xiaohongshu_cookie_gen |
| 15 | from myUtils.postVideo import post_video_tencent, post_video_DouYin, post_video_ks, post_video_xhs |
| 16 | |
| 17 | active_queues = {} |
| 18 | app = Flask(__name__) |
| 19 | |
| 20 | #允许所有来源跨域访问 |
| 21 | CORS(app) |
| 22 | |
| 23 | # 限制上传文件大小为160MB |
| 24 | app.config['MAX_CONTENT_LENGTH'] = 160 * 1024 * 1024 |
| 25 | |
| 26 | # 获取当前目录(假设 index.html 和 assets 在这里) |
| 27 | current_dir = os.path.dirname(os.path.abspath(__file__)) |
| 28 | |
| 29 | # 处理所有静态资源请求(未来打包用) |
| 30 | @app.route('/assets/<filename>') |
| 31 | def custom_static(filename): |
| 32 | return send_from_directory(os.path.join(current_dir, 'assets'), filename) |
| 33 | |
| 34 | # 处理 favicon.ico 静态资源(未来打包用) |
| 35 | @app.route('/favicon.ico') |
| 36 | def favicon(): |
| 37 | return send_from_directory(os.path.join(current_dir, 'assets'), 'vite.svg') |
| 38 | |
| 39 | @app.route('/vite.svg') |
| 40 | def vite_svg(): |
| 41 | return send_from_directory(os.path.join(current_dir, 'assets'), 'vite.svg') |
| 42 | |
| 43 | # (未来打包用) |
| 44 | @app.route('/') |
| 45 | def index(): # put application's code here |
| 46 | return send_from_directory(current_dir, 'index.html') |
| 47 | |
| 48 | @app.route('/upload', methods=['POST']) |
| 49 | def upload_file(): |
| 50 | if 'file' not in request.files: |
| 51 | return jsonify({ |
| 52 | "code": 400, |
| 53 | "data": None, |
| 54 | "msg": "No file part in the request" |
| 55 | }), 400 |
| 56 | file = request.files['file'] |
| 57 | if file.filename == '': |
| 58 | return jsonify({ |
| 59 | "code": 400, |
| 60 | "data": None, |
| 61 | "msg": "No selected file" |
| 62 | }), 400 |
| 63 | try: |
| 64 | # 保存文件到指定位置 |
| 65 | uuid_v1 = uuid.uuid1() |
| 66 | print(f"UUID v1: {uuid_v1}") |
| 67 | safe_name = secure_filename(file.filename) |
| 68 | if not safe_name: |
| 69 | return jsonify({"code": 400, "data": None, "msg": "Invalid filename"}), 400 |
| 70 | filepath = Path(BASE_DIR / "videoFile" / f"{uuid_v1}_{safe_name}") |
| 71 | file.save(filepath) |
| 72 | return jsonify({"code":200,"msg": "File uploaded successfully", "data": f"{uuid_v1}_{safe_name}"}), 200 |
| 73 | except Exception as e: |
| 74 | return jsonify({"code":500,"msg": str(e),"data":None}), 500 |
| 75 | |
| 76 | @app.route('/getFile', methods=['GET']) |
| 77 | def get_file(): |
| 78 | # 获取 filename 参数 |
| 79 | filename = request.args.get('filename') |
| 80 | |
| 81 | if not filename: |
| 82 | return jsonify({"code": 400, "msg": "filename is required", "data": None}), 400 |
| 83 | |
| 84 | # 防止路径穿越攻击 |
| 85 | if '..' in filename or filename.startswith('/'): |
| 86 | return jsonify({"code": 400, "msg": "Invalid filename", "data": None}), 400 |
| 87 | |
| 88 | # 拼接完整路径 |
| 89 | file_path = str(Path(BASE_DIR / "videoFile")) |
| 90 | |
| 91 | # 返回文件 |
| 92 | return send_from_directory(file_path,filename) |
| 93 | |
| 94 | |
| 95 | @app.route('/uploadSave', methods=['POST']) |
| 96 | def upload_save(): |
| 97 | if 'file' not in request.files: |
| 98 | return jsonify({ |
| 99 | "code": 400, |
| 100 | "data": None, |
| 101 | "msg": "No file part in the request" |
| 102 | }), 400 |
| 103 | |
| 104 | file = request.files['file'] |
| 105 | if file.filename == '': |
| 106 | return jsonify({ |
| 107 | "code": 400, |
| 108 | "data": None, |
| 109 | "msg": "No selected file" |
| 110 | }), 400 |
| 111 | |
| 112 | # 获取表单中的自定义文件名(可选) |
| 113 | custom_filename = request.form.get('filename', None) |
| 114 | if custom_filename: |
| 115 | filename = secure_filename(custom_filename + "." + file.filename.split('.')[-1]) |
| 116 | else: |
| 117 | filename = secure_filename(file.filename) |
| 118 | if not filename: |
| 119 | return jsonify({"code": 400, "data": None, "msg": "Invalid filename"}), 400 |
| 120 | |
| 121 | try: |
| 122 | # 生成 UUID v1 |
| 123 | uuid_v1 = uuid.uuid1() |
| 124 | print(f"UUID v1: {uuid_v1}") |
| 125 | |
| 126 | # 构造文件名和路径 |
| 127 | final_filename = f"{uuid_v1}_{filename}" |
| 128 | filepath = Path(BASE_DIR / "videoFile" / f"{uuid_v1}_{filename}") |
| 129 | |
| 130 | # 保存文件 |
| 131 | file.save(filepath) |
| 132 | |
| 133 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 134 | cursor = conn.cursor() |
| 135 | cursor.execute(''' |
| 136 | INSERT INTO file_records (filename, filesize, file_path) |
| 137 | VALUES (?, ?, ?) |
| 138 | ''', (filename, round(float(os.path.getsize(filepath)) / (1024 * 1024),2), final_filename)) |
| 139 | conn.commit() |
| 140 | print("✅ 上传文件已记录") |
| 141 | |
| 142 | return jsonify({ |
| 143 | "code": 200, |
| 144 | "msg": "File uploaded and saved successfully", |
| 145 | "data": { |
| 146 | "filename": filename, |
| 147 | "filepath": final_filename |
| 148 | } |
| 149 | }), 200 |
| 150 | |
| 151 | except Exception as e: |
| 152 | print(f"Upload failed: {e}") |
| 153 | return jsonify({ |
| 154 | "code": 500, |
| 155 | "msg": f"upload failed: {e}", |
| 156 | "data": None |
| 157 | }), 500 |
| 158 | |
| 159 | @app.route('/getFiles', methods=['GET']) |
| 160 | def get_all_files(): |
| 161 | try: |
| 162 | # 使用 with 自动管理数据库连接 |
| 163 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 164 | conn.row_factory = sqlite3.Row # 允许通过列名访问结果 |
| 165 | cursor = conn.cursor() |
| 166 | |
| 167 | # 查询所有记录 |
| 168 | cursor.execute("SELECT * FROM file_records") |
| 169 | rows = cursor.fetchall() |
| 170 | |
| 171 | # 将结果转为字典列表,并提取UUID |
| 172 | data = [] |
| 173 | for row in rows: |
| 174 | row_dict = dict(row) |
| 175 | # 从 file_path 中提取 UUID (文件名的第一部分,下划线前) |
| 176 | if row_dict.get('file_path'): |
| 177 | file_path_parts = row_dict['file_path'].split('_', 1) # 只分割第一个下划线 |
| 178 | if len(file_path_parts) > 0: |
| 179 | row_dict['uuid'] = file_path_parts[0] # UUID 部分 |
| 180 | else: |
| 181 | row_dict['uuid'] = '' |
| 182 | else: |
| 183 | row_dict['uuid'] = '' |
| 184 | data.append(row_dict) |
| 185 | |
| 186 | return jsonify({ |
| 187 | "code": 200, |
| 188 | "msg": "success", |
| 189 | "data": data |
| 190 | }), 200 |
| 191 | except Exception as e: |
| 192 | return jsonify({ |
| 193 | "code": 500, |
| 194 | "msg": str("get file failed!"), |
| 195 | "data": None |
| 196 | }), 500 |
| 197 | |
| 198 | |
| 199 | @app.route("/getAccounts", methods=['GET']) |
| 200 | def getAccounts(): |
| 201 | """快速获取所有账号信息,不进行cookie验证""" |
| 202 | try: |
| 203 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 204 | conn.row_factory = sqlite3.Row |
| 205 | cursor = conn.cursor() |
| 206 | cursor.execute(''' |
| 207 | SELECT * FROM user_info''') |
| 208 | rows = cursor.fetchall() |
| 209 | rows_list = [list(row) for row in rows] |
| 210 | |
| 211 | print("\n📋 当前数据表内容(快速获取):") |
| 212 | for row in rows: |
| 213 | print(row) |
| 214 | |
| 215 | return jsonify( |
| 216 | { |
| 217 | "code": 200, |
| 218 | "msg": None, |
| 219 | "data": rows_list |
| 220 | }), 200 |
| 221 | except Exception as e: |
| 222 | print(f"获取账号列表时出错: {str(e)}") |
| 223 | return jsonify({ |
| 224 | "code": 500, |
| 225 | "msg": f"获取账号列表失败: {str(e)}", |
| 226 | "data": None |
| 227 | }), 500 |
| 228 | |
| 229 | |
| 230 | @app.route("/getValidAccounts",methods=['GET']) |
| 231 | async def getValidAccounts(): |
| 232 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 233 | cursor = conn.cursor() |
| 234 | cursor.execute(''' |
| 235 | SELECT * FROM user_info''') |
| 236 | rows = cursor.fetchall() |
| 237 | rows_list = [list(row) for row in rows] |
| 238 | print("\n📋 当前数据表内容:") |
| 239 | for row in rows: |
| 240 | print(row) |
| 241 | for row in rows_list: |
| 242 | flag = await check_cookie(row[1],row[2]) |
| 243 | if not flag: |
| 244 | row[4] = 0 |
| 245 | cursor.execute(''' |
| 246 | UPDATE user_info |
| 247 | SET status = ? |
| 248 | WHERE id = ? |
| 249 | ''', (0,row[0])) |
| 250 | conn.commit() |
| 251 | print("✅ 用户状态已更新") |
| 252 | for row in rows: |
| 253 | print(row) |
| 254 | return jsonify( |
| 255 | { |
| 256 | "code": 200, |
| 257 | "msg": None, |
| 258 | "data": rows_list |
| 259 | }),200 |
| 260 | |
| 261 | @app.route('/deleteFile', methods=['GET']) |
| 262 | def delete_file(): |
| 263 | file_id = request.args.get('id') |
| 264 | |
| 265 | if not file_id or not file_id.isdigit(): |
| 266 | return jsonify({ |
| 267 | "code": 400, |
| 268 | "msg": "Invalid or missing file ID", |
| 269 | "data": None |
| 270 | }), 400 |
| 271 | |
| 272 | try: |
| 273 | # 获取数据库连接 |
| 274 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 275 | conn.row_factory = sqlite3.Row |
| 276 | cursor = conn.cursor() |
| 277 | |
| 278 | # 查询要删除的记录 |
| 279 | cursor.execute("SELECT * FROM file_records WHERE id = ?", (file_id,)) |
| 280 | record = cursor.fetchone() |
| 281 | |
| 282 | if not record: |
| 283 | return jsonify({ |
| 284 | "code": 404, |
| 285 | "msg": "File not found", |
| 286 | "data": None |
| 287 | }), 404 |
| 288 | |
| 289 | record = dict(record) |
| 290 | |
| 291 | # 获取文件路径并删除实际文件 |
| 292 | file_path = Path(BASE_DIR / "videoFile" / record['file_path']) |
| 293 | if file_path.exists(): |
| 294 | try: |
| 295 | file_path.unlink() # 删除文件 |
| 296 | print(f"✅ 实际文件已删除: {file_path}") |
| 297 | except Exception as e: |
| 298 | print(f"⚠️ 删除实际文件失败: {e}") |
| 299 | # 即使删除文件失败,也要继续删除数据库记录,避免数据不一致 |
| 300 | else: |
| 301 | print(f"⚠️ 实际文件不存在: {file_path}") |
| 302 | |
| 303 | # 删除数据库记录 |
| 304 | cursor.execute("DELETE FROM file_records WHERE id = ?", (file_id,)) |
| 305 | conn.commit() |
| 306 | |
| 307 | return jsonify({ |
| 308 | "code": 200, |
| 309 | "msg": "File deleted successfully", |
| 310 | "data": { |
| 311 | "id": record['id'], |
| 312 | "filename": record['filename'] |
| 313 | } |
| 314 | }), 200 |
| 315 | |
| 316 | except Exception as e: |
| 317 | return jsonify({ |
| 318 | "code": 500, |
| 319 | "msg": str("delete failed!"), |
| 320 | "data": None |
| 321 | }), 500 |
| 322 | |
| 323 | @app.route('/deleteAccount', methods=['GET']) |
| 324 | def delete_account(): |
| 325 | account_id = request.args.get('id') |
| 326 | |
| 327 | if not account_id or not account_id.isdigit(): |
| 328 | return jsonify({ |
| 329 | "code": 400, |
| 330 | "msg": "Invalid or missing account ID", |
| 331 | "data": None |
| 332 | }), 400 |
| 333 | |
| 334 | account_id = int(account_id) |
| 335 | |
| 336 | try: |
| 337 | # 获取数据库连接 |
| 338 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 339 | conn.row_factory = sqlite3.Row |
| 340 | cursor = conn.cursor() |
| 341 | |
| 342 | # 查询要删除的记录 |
| 343 | cursor.execute("SELECT * FROM user_info WHERE id = ?", (account_id,)) |
| 344 | record = cursor.fetchone() |
| 345 | |
| 346 | if not record: |
| 347 | return jsonify({ |
| 348 | "code": 404, |
| 349 | "msg": "account not found", |
| 350 | "data": None |
| 351 | }), 404 |
| 352 | |
| 353 | record = dict(record) |
| 354 | |
| 355 | # 删除关联的cookie文件 |
| 356 | if record.get('filePath'): |
| 357 | cookie_file_path = Path(BASE_DIR / "cookiesFile" / record['filePath']) |
| 358 | if cookie_file_path.exists(): |
| 359 | try: |
| 360 | cookie_file_path.unlink() |
| 361 | print(f"✅ Cookie文件已删除: {cookie_file_path}") |
| 362 | except Exception as e: |
| 363 | print(f"⚠️ 删除Cookie文件失败: {e}") |
| 364 | |
| 365 | # 删除数据库记录 |
| 366 | cursor.execute("DELETE FROM user_info WHERE id = ?", (account_id,)) |
| 367 | conn.commit() |
| 368 | |
| 369 | return jsonify({ |
| 370 | "code": 200, |
| 371 | "msg": "account deleted successfully", |
| 372 | "data": None |
| 373 | }), 200 |
| 374 | |
| 375 | except Exception as e: |
| 376 | return jsonify({ |
| 377 | "code": 500, |
| 378 | "msg": f"delete failed: {str(e)}", |
| 379 | "data": None |
| 380 | }), 500 |
| 381 | |
| 382 | |
| 383 | # SSE 登录接口 |
| 384 | @app.route('/login') |
| 385 | def login(): |
| 386 | # 1 小红书 2 视频号 3 抖音 4 快手 |
| 387 | type = request.args.get('type') |
| 388 | # 账号名 |
| 389 | id = request.args.get('id') |
| 390 | |
| 391 | # 模拟一个用于异步通信的队列 |
| 392 | status_queue = Queue() |
| 393 | active_queues[id] = status_queue |
| 394 | |
| 395 | def on_close(): |
| 396 | print(f"清理队列: {id}") |
| 397 | del active_queues[id] |
| 398 | # 启动异步任务线程 |
| 399 | thread = threading.Thread(target=run_async_function, args=(type,id,status_queue), daemon=True) |
| 400 | thread.start() |
| 401 | response = Response(sse_stream(status_queue,), mimetype='text/event-stream') |
| 402 | response.headers['Cache-Control'] = 'no-cache' |
| 403 | response.headers['X-Accel-Buffering'] = 'no' # 关键:禁用 Nginx 缓冲 |
| 404 | response.headers['Content-Type'] = 'text/event-stream' |
| 405 | response.headers['Connection'] = 'keep-alive' |
| 406 | return response |
| 407 | |
| 408 | @app.route('/postVideo', methods=['POST']) |
| 409 | def postVideo(): |
| 410 | # 获取JSON数据 |
| 411 | data = request.get_json() |
| 412 | |
| 413 | if not data: |
| 414 | return jsonify({"code": 400, "msg": "请求数据不能为空", "data": None}), 400 |
| 415 | |
| 416 | # 从JSON数据中提取fileList和accountList |
| 417 | file_list = data.get('fileList', []) |
| 418 | account_list = data.get('accountList', []) |
| 419 | type = data.get('type') |
| 420 | title = data.get('title') |
| 421 | tags = data.get('tags') |
| 422 | category = data.get('category') |
| 423 | enableTimer = data.get('enableTimer') |
| 424 | if category == 0: |
| 425 | category = None |
| 426 | productLink = data.get('productLink', '') |
| 427 | productTitle = data.get('productTitle', '') |
| 428 | thumbnail_path = data.get('thumbnail', '') |
| 429 | is_draft = data.get('isDraft', False) # 新增参数:是否保存为草稿 |
| 430 | |
| 431 | videos_per_day = data.get('videosPerDay') |
| 432 | daily_times = data.get('dailyTimes') |
| 433 | start_days = data.get('startDays') |
| 434 | |
| 435 | # 参数校验 |
| 436 | if not file_list: |
| 437 | return jsonify({"code": 400, "msg": "文件列表不能为空", "data": None}), 400 |
| 438 | if not account_list: |
| 439 | return jsonify({"code": 400, "msg": "账号列表不能为空", "data": None}), 400 |
| 440 | if not type: |
| 441 | return jsonify({"code": 400, "msg": "平台类型不能为空", "data": None}), 400 |
| 442 | if not title: |
| 443 | return jsonify({"code": 400, "msg": "标题不能为空", "data": None}), 400 |
| 444 | |
| 445 | # 打印获取到的数据(仅作为示例) |
| 446 | print("File List:", file_list) |
| 447 | print("Account List:", account_list) |
| 448 | |
| 449 | try: |
| 450 | match type: |
| 451 | case 1: |
| 452 | post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 453 | start_days) |
| 454 | case 2: |
| 455 | post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 456 | start_days, is_draft) |
| 457 | case 3: |
| 458 | post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 459 | start_days, thumbnail_path, productLink, productTitle) |
| 460 | case 4: |
| 461 | post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 462 | start_days) |
| 463 | case _: |
| 464 | return jsonify({"code": 400, "msg": f"不支持的平台类型: {type}", "data": None}), 400 |
| 465 | |
| 466 | # 返回响应给客户端 |
| 467 | return jsonify( |
| 468 | { |
| 469 | "code": 200, |
| 470 | "msg": "发布任务已提交", |
| 471 | "data": None |
| 472 | }), 200 |
| 473 | except Exception as e: |
| 474 | print(f"发布视频时出错: {str(e)}") |
| 475 | return jsonify({ |
| 476 | "code": 500, |
| 477 | "msg": f"发布失败: {str(e)}", |
| 478 | "data": None |
| 479 | }), 500 |
| 480 | |
| 481 | |
| 482 | @app.route('/updateUserinfo', methods=['POST']) |
| 483 | def updateUserinfo(): |
| 484 | # 获取JSON数据 |
| 485 | data = request.get_json() |
| 486 | |
| 487 | # 从JSON数据中提取 type 和 userName |
| 488 | user_id = data.get('id') |
| 489 | type = data.get('type') |
| 490 | userName = data.get('userName') |
| 491 | try: |
| 492 | # 获取数据库连接 |
| 493 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 494 | conn.row_factory = sqlite3.Row |
| 495 | cursor = conn.cursor() |
| 496 | |
| 497 | # 更新数据库记录 |
| 498 | cursor.execute(''' |
| 499 | UPDATE user_info |
| 500 | SET type = ?, |
| 501 | userName = ? |
| 502 | WHERE id = ?; |
| 503 | ''', (type, userName, user_id)) |
| 504 | conn.commit() |
| 505 | |
| 506 | return jsonify({ |
| 507 | "code": 200, |
| 508 | "msg": "account update successfully", |
| 509 | "data": None |
| 510 | }), 200 |
| 511 | |
| 512 | except Exception as e: |
| 513 | return jsonify({ |
| 514 | "code": 500, |
| 515 | "msg": str("update failed!"), |
| 516 | "data": None |
| 517 | }), 500 |
| 518 | |
| 519 | @app.route('/postVideoBatch', methods=['POST']) |
| 520 | def postVideoBatch(): |
| 521 | data_list = request.get_json() |
| 522 | |
| 523 | if not isinstance(data_list, list): |
| 524 | return jsonify({"code": 400, "msg": "Expected a JSON array", "data": None}), 400 |
| 525 | for data in data_list: |
| 526 | # 从JSON数据中提取fileList和accountList |
| 527 | file_list = data.get('fileList', []) |
| 528 | account_list = data.get('accountList', []) |
| 529 | type = data.get('type') |
| 530 | title = data.get('title') |
| 531 | tags = data.get('tags') |
| 532 | category = data.get('category') |
| 533 | enableTimer = data.get('enableTimer') |
| 534 | if category == 0: |
| 535 | category = None |
| 536 | productLink = data.get('productLink', '') |
| 537 | productTitle = data.get('productTitle', '') |
| 538 | is_draft = data.get('isDraft', False) |
| 539 | |
| 540 | videos_per_day = data.get('videosPerDay') |
| 541 | daily_times = data.get('dailyTimes') |
| 542 | start_days = data.get('startDays') |
| 543 | # 打印获取到的数据(仅作为示例) |
| 544 | print("File List:", file_list) |
| 545 | print("Account List:", account_list) |
| 546 | match type: |
| 547 | case 1: |
| 548 | post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 549 | start_days) |
| 550 | case 2: |
| 551 | post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 552 | start_days, is_draft) |
| 553 | case 3: |
| 554 | post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 555 | start_days, productLink, productTitle) |
| 556 | case 4: |
| 557 | post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| 558 | start_days) |
| 559 | # 返回响应给客户端 |
| 560 | return jsonify( |
| 561 | { |
| 562 | "code": 200, |
| 563 | "msg": None, |
| 564 | "data": None |
| 565 | }), 200 |
| 566 | |
| 567 | # Cookie文件上传API |
| 568 | @app.route('/uploadCookie', methods=['POST']) |
| 569 | def upload_cookie(): |
| 570 | try: |
| 571 | if 'file' not in request.files: |
| 572 | return jsonify({ |
| 573 | "code": 400, |
| 574 | "msg": "没有找到Cookie文件", |
| 575 | "data": None |
| 576 | }), 400 |
| 577 | |
| 578 | file = request.files['file'] |
| 579 | if file.filename == '': |
| 580 | return jsonify({ |
| 581 | "code": 400, |
| 582 | "msg": "Cookie文件名不能为空", |
| 583 | "data": None |
| 584 | }), 400 |
| 585 | |
| 586 | if not file.filename.endswith('.json'): |
| 587 | return jsonify({ |
| 588 | "code": 400, |
| 589 | "msg": "Cookie文件必须是JSON格式", |
| 590 | "data": None |
| 591 | }), 400 |
| 592 | |
| 593 | # 获取账号信息 |
| 594 | account_id = request.form.get('id') |
| 595 | platform = request.form.get('platform') |
| 596 | |
| 597 | if not account_id or not platform: |
| 598 | return jsonify({ |
| 599 | "code": 400, |
| 600 | "msg": "缺少账号ID或平台信息", |
| 601 | "data": None |
| 602 | }), 400 |
| 603 | |
| 604 | # 从数据库获取账号的文件路径 |
| 605 | with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| 606 | conn.row_factory = sqlite3.Row |
| 607 | cursor = conn.cursor() |
| 608 | cursor.execute('SELECT filePath FROM user_info WHERE id = ?', (account_id,)) |
| 609 | result = cursor.fetchone() |
| 610 | |
| 611 | if not result: |
| 612 | return jsonify({ |
| 613 | "code": 500, |
| 614 | "msg": "账号不存在", |
| 615 | "data": None |
| 616 | }), 404 |
| 617 | |
| 618 | # 保存上传的Cookie文件到对应路径 |
| 619 | cookie_file_path = Path(BASE_DIR / "cookiesFile" / result['filePath']) |
| 620 | cookie_file_path.parent.mkdir(parents=True, exist_ok=True) |
| 621 | |
| 622 | file.save(str(cookie_file_path)) |
| 623 | |
| 624 | # 更新数据库中的账号信息(可选,比如更新更新时间) |
| 625 | # 这里可以根据需要添加额外的处理逻辑 |
| 626 | |
| 627 | return jsonify({ |
| 628 | "code": 200, |
| 629 | "msg": "Cookie文件上传成功", |
| 630 | "data": None |
| 631 | }), 200 |
| 632 | |
| 633 | except Exception as e: |
| 634 | print(f"上传Cookie文件时出错: {str(e)}") |
| 635 | return jsonify({ |
| 636 | "code": 500, |
| 637 | "msg": f"上传Cookie文件失败: {str(e)}", |
| 638 | "data": None |
| 639 | }), 500 |
| 640 | |
| 641 | |
| 642 | # Cookie文件下载API |
| 643 | @app.route('/downloadCookie', methods=['GET']) |
| 644 | def download_cookie(): |
| 645 | try: |
| 646 | file_path = request.args.get('filePath') |
| 647 | if not file_path: |
| 648 | return jsonify({ |
| 649 | "code": 500, |
| 650 | "msg": "缺少文件路径参数", |
| 651 | "data": None |
| 652 | }), 400 |
| 653 | |
| 654 | # 验证文件路径的安全性,防止路径遍历攻击 |
| 655 | cookie_file_path = Path(BASE_DIR / "cookiesFile" / file_path).resolve() |
| 656 | base_path = Path(BASE_DIR / "cookiesFile").resolve() |
| 657 | |
| 658 | if not cookie_file_path.is_relative_to(base_path): |
| 659 | return jsonify({ |
| 660 | "code": 500, |
| 661 | "msg": "非法文件路径", |
| 662 | "data": None |
| 663 | }), 400 |
| 664 | |
| 665 | if not cookie_file_path.exists(): |
| 666 | return jsonify({ |
| 667 | "code": 500, |
| 668 | "msg": "Cookie文件不存在", |
| 669 | "data": None |
| 670 | }), 404 |
| 671 | |
| 672 | # 返回文件 |
| 673 | return send_from_directory( |
| 674 | directory=str(cookie_file_path.parent), |
| 675 | path=cookie_file_path.name, |
| 676 | as_attachment=True |
| 677 | ) |
| 678 | |
| 679 | except Exception as e: |
| 680 | print(f"下载Cookie文件时出错: {str(e)}") |
| 681 | return jsonify({ |
| 682 | "code": 500, |
| 683 | "msg": f"下载Cookie文件失败: {str(e)}", |
| 684 | "data": None |
| 685 | }), 500 |
| 686 | |
| 687 | |
| 688 | # 包装函数:在线程中运行异步函数 |
| 689 | def run_async_function(type,id,status_queue): |
| 690 | match type: |
| 691 | case '1': |
| 692 | loop = asyncio.new_event_loop() |
| 693 | asyncio.set_event_loop(loop) |
| 694 | loop.run_until_complete(xiaohongshu_cookie_gen(id, status_queue)) |
| 695 | loop.close() |
| 696 | case '2': |
| 697 | loop = asyncio.new_event_loop() |
| 698 | asyncio.set_event_loop(loop) |
| 699 | loop.run_until_complete(get_tencent_cookie(id,status_queue)) |
| 700 | loop.close() |
| 701 | case '3': |
| 702 | loop = asyncio.new_event_loop() |
| 703 | asyncio.set_event_loop(loop) |
| 704 | loop.run_until_complete(douyin_cookie_gen(id,status_queue)) |
| 705 | loop.close() |
| 706 | case '4': |
| 707 | loop = asyncio.new_event_loop() |
| 708 | asyncio.set_event_loop(loop) |
| 709 | loop.run_until_complete(get_ks_cookie(id,status_queue)) |
| 710 | loop.close() |
| 711 | |
| 712 | # SSE 流生成器函数 |
| 713 | def sse_stream(status_queue): |
| 714 | while True: |
| 715 | if not status_queue.empty(): |
| 716 | msg = status_queue.get() |
| 717 | yield f"data: {msg}\n\n" |
| 718 | else: |
| 719 | # 避免 CPU 占满 |
| 720 | time.sleep(0.1) |
| 721 | |
| 722 | if __name__ == '__main__': |
| 723 | app.run(host='0.0.0.0' ,port=5409) |
| 724 |