| import asyncio |
| import os |
| import sqlite3 |
| import threading |
| import time |
| import uuid |
| from pathlib import Path |
| from queue import Queue |
| from flask_cors import CORS |
| from myUtils.auth import check_cookie |
| from flask import Flask, request, jsonify, Response, render_template, send_from_directory |
| from conf import BASE_DIR |
| from myUtils.login import get_tencent_cookie, douyin_cookie_gen, get_ks_cookie, xiaohongshu_cookie_gen |
| from myUtils.postVideo import post_video_tencent, post_video_DouYin, post_video_ks, post_video_xhs |
| from db.init_db import ensure_database |
|
|
| ensure_database(BASE_DIR) |
|
|
| active_queues = {} |
| app = Flask(__name__) |
|
|
| |
| CORS(app) |
|
|
| |
| app.config['MAX_CONTENT_LENGTH'] = 160 * 1024 * 1024 |
|
|
| |
| current_dir = os.path.dirname(os.path.abspath(__file__)) |
|
|
| |
| @app.route('/assets/<filename>') |
| def custom_static(filename): |
| return send_from_directory(os.path.join(current_dir, 'assets'), filename) |
|
|
| |
| @app.route('/favicon.ico') |
| def favicon(): |
| return send_from_directory(os.path.join(current_dir, 'assets'), 'vite.svg') |
|
|
| @app.route('/vite.svg') |
| def vite_svg(): |
| return send_from_directory(os.path.join(current_dir, 'assets'), 'vite.svg') |
|
|
| |
| @app.route('/') |
| def index(): |
| return send_from_directory(current_dir, 'index.html') |
|
|
| @app.route('/upload', methods=['POST']) |
| def upload_file(): |
| if 'file' not in request.files: |
| return jsonify({ |
| "code": 400, |
| "data": None, |
| "msg": "No file part in the request" |
| }), 400 |
| file = request.files['file'] |
| if file.filename == '': |
| return jsonify({ |
| "code": 400, |
| "data": None, |
| "msg": "No selected file" |
| }), 400 |
| try: |
| |
| uuid_v1 = uuid.uuid1() |
| print(f"UUID v1: {uuid_v1}") |
| filepath = Path(BASE_DIR / "videoFile" / f"{uuid_v1}_{file.filename}") |
| file.save(filepath) |
| return jsonify({"code":200,"msg": "File uploaded successfully", "data": f"{uuid_v1}_{file.filename}"}), 200 |
| except Exception as e: |
| return jsonify({"code":500,"msg": str(e),"data":None}), 500 |
|
|
| @app.route('/getFile', methods=['GET']) |
| def get_file(): |
| |
| filename = request.args.get('filename') |
|
|
| if not filename: |
| return jsonify({"code": 400, "msg": "filename is required", "data": None}), 400 |
|
|
| |
| if '..' in filename or filename.startswith('/'): |
| return jsonify({"code": 400, "msg": "Invalid filename", "data": None}), 400 |
|
|
| |
| file_path = str(Path(BASE_DIR / "videoFile")) |
|
|
| |
| return send_from_directory(file_path,filename) |
|
|
|
|
| @app.route('/uploadSave', methods=['POST']) |
| def upload_save(): |
| if 'file' not in request.files: |
| return jsonify({ |
| "code": 400, |
| "data": None, |
| "msg": "No file part in the request" |
| }), 400 |
|
|
| file = request.files['file'] |
| if file.filename == '': |
| return jsonify({ |
| "code": 400, |
| "data": None, |
| "msg": "No selected file" |
| }), 400 |
|
|
| |
| custom_filename = request.form.get('filename', None) |
| if custom_filename: |
| filename = custom_filename + "." + file.filename.split('.')[-1] |
| else: |
| filename = file.filename |
|
|
| try: |
| |
| uuid_v1 = uuid.uuid1() |
| print(f"UUID v1: {uuid_v1}") |
|
|
| |
| final_filename = f"{uuid_v1}_{filename}" |
| filepath = Path(BASE_DIR / "videoFile" / f"{uuid_v1}_{filename}") |
|
|
| |
| file.save(filepath) |
|
|
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| cursor = conn.cursor() |
| cursor.execute(''' |
| INSERT INTO file_records (filename, filesize, file_path) |
| VALUES (?, ?, ?) |
| ''', (filename, round(float(os.path.getsize(filepath)) / (1024 * 1024),2), final_filename)) |
| conn.commit() |
| print("✅ 上传文件已记录") |
|
|
| return jsonify({ |
| "code": 200, |
| "msg": "File uploaded and saved successfully", |
| "data": { |
| "filename": filename, |
| "filepath": final_filename |
| } |
| }), 200 |
|
|
| except Exception as e: |
| print(f"Upload failed: {e}") |
| return jsonify({ |
| "code": 500, |
| "msg": f"upload failed: {e}", |
| "data": None |
| }), 500 |
|
|
| @app.route('/getFiles', methods=['GET']) |
| def get_all_files(): |
| try: |
| |
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
|
|
| |
| cursor.execute("SELECT * FROM file_records") |
| rows = cursor.fetchall() |
|
|
| |
| data = [] |
| for row in rows: |
| row_dict = dict(row) |
| |
| if row_dict.get('file_path'): |
| file_path_parts = row_dict['file_path'].split('_', 1) |
| if len(file_path_parts) > 0: |
| row_dict['uuid'] = file_path_parts[0] |
| else: |
| row_dict['uuid'] = '' |
| else: |
| row_dict['uuid'] = '' |
| data.append(row_dict) |
|
|
| return jsonify({ |
| "code": 200, |
| "msg": "success", |
| "data": data |
| }), 200 |
| except Exception as e: |
| return jsonify({ |
| "code": 500, |
| "msg": str("get file failed!"), |
| "data": None |
| }), 500 |
|
|
|
|
| @app.route("/getAccounts", methods=['GET']) |
| def getAccounts(): |
| """快速获取所有账号信息,不进行cookie验证""" |
| try: |
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
| cursor.execute(''' |
| SELECT * FROM user_info''') |
| rows = cursor.fetchall() |
| rows_list = [list(row) for row in rows] |
|
|
| print("\n📋 当前数据表内容(快速获取):") |
| for row in rows: |
| print(row) |
|
|
| return jsonify( |
| { |
| "code": 200, |
| "msg": None, |
| "data": rows_list |
| }), 200 |
| except Exception as e: |
| print(f"获取账号列表时出错: {str(e)}") |
| return jsonify({ |
| "code": 500, |
| "msg": f"获取账号列表失败: {str(e)}", |
| "data": None |
| }), 500 |
|
|
|
|
| @app.route("/getValidAccounts",methods=['GET']) |
| async def getValidAccounts(): |
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| cursor = conn.cursor() |
| cursor.execute(''' |
| SELECT * FROM user_info''') |
| rows = cursor.fetchall() |
| rows_list = [list(row) for row in rows] |
| print("\n📋 当前数据表内容:") |
| for row in rows: |
| print(row) |
| for row in rows_list: |
| flag = await check_cookie(row[1],row[2]) |
| if not flag: |
| row[4] = 0 |
| cursor.execute(''' |
| UPDATE user_info |
| SET status = ? |
| WHERE id = ? |
| ''', (0,row[0])) |
| conn.commit() |
| print("✅ 用户状态已更新") |
| for row in rows: |
| print(row) |
| return jsonify( |
| { |
| "code": 200, |
| "msg": None, |
| "data": rows_list |
| }),200 |
|
|
| @app.route('/deleteFile', methods=['GET']) |
| def delete_file(): |
| file_id = request.args.get('id') |
|
|
| if not file_id or not file_id.isdigit(): |
| return jsonify({ |
| "code": 400, |
| "msg": "Invalid or missing file ID", |
| "data": None |
| }), 400 |
|
|
| try: |
| |
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
|
|
| |
| cursor.execute("SELECT * FROM file_records WHERE id = ?", (file_id,)) |
| record = cursor.fetchone() |
|
|
| if not record: |
| return jsonify({ |
| "code": 404, |
| "msg": "File not found", |
| "data": None |
| }), 404 |
|
|
| record = dict(record) |
|
|
| |
| file_path = Path(BASE_DIR / "videoFile" / record['file_path']) |
| if file_path.exists(): |
| try: |
| file_path.unlink() |
| print(f"✅ 实际文件已删除: {file_path}") |
| except Exception as e: |
| print(f"⚠️ 删除实际文件失败: {e}") |
| |
| else: |
| print(f"⚠️ 实际文件不存在: {file_path}") |
|
|
| |
| cursor.execute("DELETE FROM file_records WHERE id = ?", (file_id,)) |
| conn.commit() |
|
|
| return jsonify({ |
| "code": 200, |
| "msg": "File deleted successfully", |
| "data": { |
| "id": record['id'], |
| "filename": record['filename'] |
| } |
| }), 200 |
|
|
| except Exception as e: |
| return jsonify({ |
| "code": 500, |
| "msg": str("delete failed!"), |
| "data": None |
| }), 500 |
|
|
| @app.route('/deleteAccount', methods=['GET']) |
| def delete_account(): |
| account_id = request.args.get('id') |
|
|
| if not account_id or not account_id.isdigit(): |
| return jsonify({ |
| "code": 400, |
| "msg": "Invalid or missing account ID", |
| "data": None |
| }), 400 |
|
|
| account_id = int(account_id) |
|
|
| try: |
| |
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
|
|
| |
| cursor.execute("SELECT * FROM user_info WHERE id = ?", (account_id,)) |
| record = cursor.fetchone() |
|
|
| if not record: |
| return jsonify({ |
| "code": 404, |
| "msg": "account not found", |
| "data": None |
| }), 404 |
|
|
| record = dict(record) |
|
|
| |
| if record.get('filePath'): |
| cookie_file_path = Path(BASE_DIR / "cookiesFile" / record['filePath']) |
| if cookie_file_path.exists(): |
| try: |
| cookie_file_path.unlink() |
| print(f"✅ Cookie文件已删除: {cookie_file_path}") |
| except Exception as e: |
| print(f"⚠️ 删除Cookie文件失败: {e}") |
|
|
| |
| cursor.execute("DELETE FROM user_info WHERE id = ?", (account_id,)) |
| conn.commit() |
|
|
| return jsonify({ |
| "code": 200, |
| "msg": "account deleted successfully", |
| "data": None |
| }), 200 |
|
|
| except Exception as e: |
| return jsonify({ |
| "code": 500, |
| "msg": f"delete failed: {str(e)}", |
| "data": None |
| }), 500 |
|
|
|
|
| |
| @app.route('/login') |
| def login(): |
| |
| type = request.args.get('type') |
| |
| id = request.args.get('id') |
|
|
| |
| status_queue = Queue() |
| active_queues[id] = status_queue |
|
|
| def on_close(): |
| print(f"清理队列: {id}") |
| del active_queues[id] |
| |
| thread = threading.Thread(target=run_async_function, args=(type,id,status_queue), daemon=True) |
| thread.start() |
| response = Response(sse_stream(status_queue,), mimetype='text/event-stream') |
| response.headers['Cache-Control'] = 'no-cache' |
| response.headers['X-Accel-Buffering'] = 'no' |
| response.headers['Content-Type'] = 'text/event-stream' |
| response.headers['Connection'] = 'keep-alive' |
| return response |
|
|
| @app.route('/postVideo', methods=['POST']) |
| def postVideo(): |
| |
| data = request.get_json() |
|
|
| if not data: |
| return jsonify({"code": 400, "msg": "请求数据不能为空", "data": None}), 400 |
|
|
| |
| file_list = data.get('fileList', []) |
| account_list = data.get('accountList', []) |
| type = data.get('type') |
| title = data.get('title') |
| tags = data.get('tags') |
| category = data.get('category') |
| enableTimer = data.get('enableTimer') |
| if category == 0: |
| category = None |
| productLink = data.get('productLink', '') |
| productTitle = data.get('productTitle', '') |
| thumbnail_path = data.get('thumbnail', '') |
| is_draft = data.get('isDraft', False) |
|
|
| videos_per_day = data.get('videosPerDay') |
| daily_times = data.get('dailyTimes') |
| start_days = data.get('startDays') |
|
|
| |
| if not file_list: |
| return jsonify({"code": 400, "msg": "文件列表不能为空", "data": None}), 400 |
| if not account_list: |
| return jsonify({"code": 400, "msg": "账号列表不能为空", "data": None}), 400 |
| if not type: |
| return jsonify({"code": 400, "msg": "平台类型不能为空", "data": None}), 400 |
| if not title: |
| return jsonify({"code": 400, "msg": "标题不能为空", "data": None}), 400 |
|
|
| |
| print("File List:", file_list) |
| print("Account List:", account_list) |
|
|
| try: |
| match type: |
| case 1: |
| post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days) |
| case 2: |
| post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days, is_draft) |
| case 3: |
| post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days, thumbnail_path, productLink, productTitle) |
| case 4: |
| post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days) |
| case _: |
| return jsonify({"code": 400, "msg": f"不支持的平台类型: {type}", "data": None}), 400 |
|
|
| |
| return jsonify( |
| { |
| "code": 200, |
| "msg": "发布任务已提交", |
| "data": None |
| }), 200 |
| except Exception as e: |
| print(f"发布视频时出错: {str(e)}") |
| return jsonify({ |
| "code": 500, |
| "msg": f"发布失败: {str(e)}", |
| "data": None |
| }), 500 |
|
|
|
|
| @app.route('/updateUserinfo', methods=['POST']) |
| def updateUserinfo(): |
| |
| data = request.get_json() |
|
|
| |
| user_id = data.get('id') |
| type = data.get('type') |
| userName = data.get('userName') |
| try: |
| |
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
|
|
| |
| cursor.execute(''' |
| UPDATE user_info |
| SET type = ?, |
| userName = ? |
| WHERE id = ?; |
| ''', (type, userName, user_id)) |
| conn.commit() |
|
|
| return jsonify({ |
| "code": 200, |
| "msg": "account update successfully", |
| "data": None |
| }), 200 |
|
|
| except Exception as e: |
| return jsonify({ |
| "code": 500, |
| "msg": str("update failed!"), |
| "data": None |
| }), 500 |
|
|
| @app.route('/postVideoBatch', methods=['POST']) |
| def postVideoBatch(): |
| data_list = request.get_json() |
|
|
| if not isinstance(data_list, list): |
| return jsonify({"code": 400, "msg": "Expected a JSON array", "data": None}), 400 |
| for data in data_list: |
| |
| file_list = data.get('fileList', []) |
| account_list = data.get('accountList', []) |
| type = data.get('type') |
| title = data.get('title') |
| tags = data.get('tags') |
| category = data.get('category') |
| enableTimer = data.get('enableTimer') |
| if category == 0: |
| category = None |
| productLink = data.get('productLink', '') |
| productTitle = data.get('productTitle', '') |
| is_draft = data.get('isDraft', False) |
|
|
| videos_per_day = data.get('videosPerDay') |
| daily_times = data.get('dailyTimes') |
| start_days = data.get('startDays') |
| |
| print("File List:", file_list) |
| print("Account List:", account_list) |
| match type: |
| case 1: |
| post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days) |
| case 2: |
| post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days, is_draft) |
| case 3: |
| post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days, productLink, productTitle) |
| case 4: |
| post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, |
| start_days) |
| |
| return jsonify( |
| { |
| "code": 200, |
| "msg": None, |
| "data": None |
| }), 200 |
|
|
| |
| @app.route('/uploadCookie', methods=['POST']) |
| def upload_cookie(): |
| try: |
| if 'file' not in request.files: |
| return jsonify({ |
| "code": 400, |
| "msg": "没有找到Cookie文件", |
| "data": None |
| }), 400 |
|
|
| file = request.files['file'] |
| if file.filename == '': |
| return jsonify({ |
| "code": 400, |
| "msg": "Cookie文件名不能为空", |
| "data": None |
| }), 400 |
|
|
| if not file.filename.endswith('.json'): |
| return jsonify({ |
| "code": 400, |
| "msg": "Cookie文件必须是JSON格式", |
| "data": None |
| }), 400 |
|
|
| |
| account_id = request.form.get('id') |
| platform = request.form.get('platform') |
|
|
| if not account_id or not platform: |
| return jsonify({ |
| "code": 400, |
| "msg": "缺少账号ID或平台信息", |
| "data": None |
| }), 400 |
|
|
| |
| with sqlite3.connect(Path(BASE_DIR / "db" / "database.db")) as conn: |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
| cursor.execute('SELECT filePath FROM user_info WHERE id = ?', (account_id,)) |
| result = cursor.fetchone() |
|
|
| if not result: |
| return jsonify({ |
| "code": 500, |
| "msg": "账号不存在", |
| "data": None |
| }), 404 |
|
|
| |
| cookie_file_path = Path(BASE_DIR / "cookiesFile" / result['filePath']) |
| cookie_file_path.parent.mkdir(parents=True, exist_ok=True) |
|
|
| file.save(str(cookie_file_path)) |
|
|
| |
| |
|
|
| return jsonify({ |
| "code": 200, |
| "msg": "Cookie文件上传成功", |
| "data": None |
| }), 200 |
|
|
| except Exception as e: |
| print(f"上传Cookie文件时出错: {str(e)}") |
| return jsonify({ |
| "code": 500, |
| "msg": f"上传Cookie文件失败: {str(e)}", |
| "data": None |
| }), 500 |
|
|
|
|
| |
| @app.route('/downloadCookie', methods=['GET']) |
| def download_cookie(): |
| try: |
| file_path = request.args.get('filePath') |
| if not file_path: |
| return jsonify({ |
| "code": 500, |
| "msg": "缺少文件路径参数", |
| "data": None |
| }), 400 |
|
|
| |
| cookie_file_path = Path(BASE_DIR / "cookiesFile" / file_path).resolve() |
| base_path = Path(BASE_DIR / "cookiesFile").resolve() |
|
|
| if not cookie_file_path.is_relative_to(base_path): |
| return jsonify({ |
| "code": 500, |
| "msg": "非法文件路径", |
| "data": None |
| }), 400 |
|
|
| if not cookie_file_path.exists(): |
| return jsonify({ |
| "code": 500, |
| "msg": "Cookie文件不存在", |
| "data": None |
| }), 404 |
|
|
| |
| return send_from_directory( |
| directory=str(cookie_file_path.parent), |
| path=cookie_file_path.name, |
| as_attachment=True |
| ) |
|
|
| except Exception as e: |
| print(f"下载Cookie文件时出错: {str(e)}") |
| return jsonify({ |
| "code": 500, |
| "msg": f"下载Cookie文件失败: {str(e)}", |
| "data": None |
| }), 500 |
|
|
|
|
| |
| def run_async_function(type,id,status_queue): |
| match type: |
| case '1': |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| loop.run_until_complete(xiaohongshu_cookie_gen(id, status_queue)) |
| loop.close() |
| case '2': |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| loop.run_until_complete(get_tencent_cookie(id,status_queue)) |
| loop.close() |
| case '3': |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| loop.run_until_complete(douyin_cookie_gen(id,status_queue)) |
| loop.close() |
| case '4': |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| loop.run_until_complete(get_ks_cookie(id,status_queue)) |
| loop.close() |
|
|
| |
| def sse_stream(status_queue): |
| while True: |
| if not status_queue.empty(): |
| msg = status_queue.get() |
| yield f"data: {msg}\n\n" |
| else: |
| |
| time.sleep(0.1) |
|
|
| if __name__ == '__main__': |
| |
| port = int(os.environ.get("PORT", "5409")) |
| app.run(host="0.0.0.0", port=port) |
|
|