- 新增 remote_browser.py: CDP screencast截图流 + 鼠标/键盘事件转发 - Flask-SocketIO 实时通信 - 短信登录时弹出远程浏览器窗口,用户直接在Web页面拖滑块 - 自动检测登录成功并保存auth状态
207 lines
6.2 KiB
Python
207 lines
6.2 KiB
Python
import asyncio
|
|
import os
|
|
import threading
|
|
import time
|
|
from flask import Blueprint, request, jsonify, render_template
|
|
from server.database import get_db
|
|
from server.services.auth_service import (
|
|
get_auth_path, has_auth, login_with_password, login_with_sms
|
|
)
|
|
|
|
bp = Blueprint('accounts', __name__, url_prefix='/accounts')
|
|
|
|
|
|
@bp.route('/')
|
|
def list_accounts():
|
|
db = get_db()
|
|
accounts = db.execute('SELECT * FROM accounts ORDER BY id DESC').fetchall()
|
|
db.close()
|
|
return render_template('accounts.html', accounts=accounts)
|
|
|
|
|
|
@bp.route('/add', methods=['POST'])
|
|
def add_account():
|
|
name = request.form.get('name', '').strip()
|
|
phone = request.form.get('phone', '').strip()
|
|
password = request.form.get('password', '').strip()
|
|
|
|
if not name or not phone:
|
|
return jsonify(success=False, msg='请填写名称和手机号'), 400
|
|
|
|
db = get_db()
|
|
cursor = db.execute(
|
|
'INSERT INTO accounts (name, phone, password, auth_file, login_msg) VALUES (?, ?, ?, ?, ?)',
|
|
(name, phone, password, '', '待登录')
|
|
)
|
|
account_id = cursor.lastrowid
|
|
auth_file = get_auth_path(account_id)
|
|
db.execute('UPDATE accounts SET auth_file = ? WHERE id = ?', (auth_file, account_id))
|
|
db.commit()
|
|
db.close()
|
|
|
|
return jsonify(success=True, id=account_id)
|
|
|
|
|
|
@bp.route('/delete/<int:account_id>', methods=['POST'])
|
|
def delete_account(account_id):
|
|
db = get_db()
|
|
db.execute('DELETE FROM tasks WHERE account_id = ?', (account_id,))
|
|
db.execute('DELETE FROM accounts WHERE id = ?', (account_id,))
|
|
db.commit()
|
|
db.close()
|
|
path = get_auth_path(account_id)
|
|
if os.path.exists(path):
|
|
os.remove(path)
|
|
return jsonify(success=True)
|
|
|
|
|
|
@bp.route('/login/<int:account_id>', methods=['POST'])
|
|
def do_login(account_id):
|
|
"""密码登录(自动,无需人参与)"""
|
|
db = get_db()
|
|
account = db.execute('SELECT * FROM accounts WHERE id = ?', (account_id,)).fetchone()
|
|
db.close()
|
|
|
|
if not account:
|
|
return jsonify(success=False, msg='账号不存在'), 404
|
|
|
|
phone = account['phone']
|
|
password = account['password']
|
|
|
|
if not phone or not password:
|
|
return jsonify(success=False, msg='该账号未设置手机号或密码'), 400
|
|
|
|
# 标记为登录中
|
|
db = get_db()
|
|
db.execute(
|
|
"UPDATE accounts SET is_logged_in = 0, login_msg = '登录中...', "
|
|
"updated_at = datetime('now','localtime') WHERE id = ?",
|
|
(account_id,)
|
|
)
|
|
db.commit()
|
|
db.close()
|
|
|
|
# 后台异步登录
|
|
_start_bg_login(account_id, phone, password)
|
|
|
|
return jsonify(success=True, msg='登录中...')
|
|
|
|
|
|
@bp.route('/login_sms/<int:account_id>', methods=['POST'])
|
|
def do_sms_login(account_id):
|
|
"""短信验证码登录 — 远程浏览器模式,在 Web 面板中操作"""
|
|
db = get_db()
|
|
account = db.execute('SELECT * FROM accounts WHERE id = ?', (account_id,)).fetchone()
|
|
db.close()
|
|
|
|
if not account:
|
|
return jsonify(success=False, msg='账号不存在'), 404
|
|
|
|
phone = account['phone']
|
|
if not phone:
|
|
return jsonify(success=False, msg='该账号未设置手机号'), 400
|
|
|
|
# 标记为等待交互
|
|
db = get_db()
|
|
db.execute(
|
|
"UPDATE accounts SET is_logged_in = 0, login_msg = '等待人机交互...', "
|
|
"updated_at = datetime('now','localtime') WHERE id = ?",
|
|
(account_id,)
|
|
)
|
|
db.commit()
|
|
db.close()
|
|
|
|
# 启动远程浏览器会话
|
|
from server.app import socketio
|
|
from server.services.remote_browser import start_session
|
|
session_id = start_session(account_id, phone, socketio)
|
|
|
|
return jsonify(success=True, session_id=session_id,
|
|
msg='远程浏览器已启动,请在弹出窗口中操作')
|
|
|
|
|
|
@bp.route('/status/<int:account_id>')
|
|
def get_status(account_id):
|
|
"""轮询账号登录状态"""
|
|
db = get_db()
|
|
account = db.execute(
|
|
'SELECT is_logged_in, login_msg FROM accounts WHERE id = ?',
|
|
(account_id,)
|
|
).fetchone()
|
|
db.close()
|
|
if not account:
|
|
return jsonify(is_logged_in=False, login_msg='账号不存在', done=True)
|
|
|
|
msg = account['login_msg'] or ''
|
|
is_logged_in = bool(account['is_logged_in'])
|
|
done = msg not in ('登录中...', '等待人机交互...')
|
|
return jsonify(is_logged_in=is_logged_in, login_msg=msg, done=done)
|
|
|
|
|
|
def _start_bg_login(account_id, phone, password):
|
|
"""后台线程执行密码登录"""
|
|
def _run():
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
try:
|
|
ok, msg = loop.run_until_complete(
|
|
login_with_password(account_id, phone, password)
|
|
)
|
|
except Exception as e:
|
|
ok, msg = False, str(e)
|
|
finally:
|
|
loop.close()
|
|
|
|
db = get_db()
|
|
if ok:
|
|
db.execute(
|
|
"UPDATE accounts SET is_logged_in = 1, login_msg = '登录成功', "
|
|
"updated_at = datetime('now','localtime') WHERE id = ?",
|
|
(account_id,)
|
|
)
|
|
else:
|
|
db.execute(
|
|
"UPDATE accounts SET is_logged_in = 0, login_msg = ?, "
|
|
"updated_at = datetime('now','localtime') WHERE id = ?",
|
|
(msg, account_id)
|
|
)
|
|
db.commit()
|
|
db.close()
|
|
|
|
t = threading.Thread(target=_run, daemon=True)
|
|
t.start()
|
|
|
|
|
|
def _start_bg_sms_login(account_id, phone):
|
|
"""后台线程执行短信登录"""
|
|
def _run():
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
try:
|
|
ok, msg = loop.run_until_complete(
|
|
login_with_sms(phone, account_id=account_id)
|
|
)
|
|
except Exception as e:
|
|
ok, msg = False, str(e)
|
|
finally:
|
|
loop.close()
|
|
|
|
db = get_db()
|
|
if ok:
|
|
db.execute(
|
|
"UPDATE accounts SET is_logged_in = 1, login_msg = '登录成功', "
|
|
"updated_at = datetime('now','localtime') WHERE id = ?",
|
|
(account_id,)
|
|
)
|
|
else:
|
|
db.execute(
|
|
"UPDATE accounts SET is_logged_in = 0, login_msg = ?, "
|
|
"updated_at = datetime('now','localtime') WHERE id = ?",
|
|
(msg, account_id)
|
|
)
|
|
db.commit()
|
|
db.close()
|
|
|
|
t = threading.Thread(target=_run, daemon=True)
|
|
t.start()
|