实现抖音验证码登录

This commit is contained in:
jjz
2025-09-29 11:25:43 +08:00
parent c58cec750f
commit 4f2706d8d9
4 changed files with 705 additions and 112 deletions

View File

@@ -12,6 +12,8 @@ import threading
from datetime import datetime from datetime import datetime
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
from urllib.parse import urlencode from urllib.parse import urlencode
import re
import random
import config import config
# 导入 message_arg 中的方法 # 导入 message_arg 中的方法
@@ -19,6 +21,459 @@ from Utils.Dy.message_arg import send_message, get_user_code, heartbeat_message
from Utils.message_models import PlatformMessage from Utils.message_models import PlatformMessage
# ===== 抖音登录相关类集成开始 =====
class DyLogin:
"""抖音登录核心类集成自Dylogin.py适配后端通知机制"""
def __init__(self, log_callback=None):
self.headers = {
"authority": "doudian-sso.jinritemai.com",
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"content-type": "application/x-www-form-urlencoded",
"origin": "https://fxg.jinritemai.com",
"pragma": "no-cache",
"referer": "https://fxg.jinritemai.com/",
"sec-ch-ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"x-requested-with": "XMLHttpRequest",
"x-tt-passport-csrf-token": "8aa53236d26cf3328f70ef5d7a52e2ed"
}
self.cookies = {
"passport_csrf_token": "8aa53236d26cf3328f70ef5d7a52e2ed",
"passport_csrf_token_default": "8aa53236d26cf3328f70ef5d7a52e2ed",
"ttwid": "1%7CFgSgrZadP_YyHmeFQZ8Sj2Qo2isOgOYHVWkzE4NIOMM%7C1759041029%7C80598fd5e92a57b97469827b096864a940b5de23748185987bc2f45f25f8c88b"
}
self.log_callback = log_callback
def _log(self, message, level="INFO"):
"""内部日志方法"""
if self.log_callback:
self.log_callback(message, level)
else:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
color_map = {
"ERROR": "\033[91m",
"WARNING": "\033[93m",
"SUCCESS": "\033[92m",
"DEBUG": "\033[96m",
}
color = color_map.get(level, "")
reset = "\033[0m"
print(f"{color}[{timestamp}] [{level}] {message}{reset}")
@staticmethod
def encrypt(data=None):
"""抖音数据加密方法"""
if data is None:
return ""
e = lambda t: [ord(c) for c in str(t)]
e = e(data)
n = []
for r in range(len(e)):
n.append(hex(5 ^ e[r])[2:])
return "".join(n)
def send_activation_code(self, mobile_phone):
"""发送激活码"""
url = "https://doudian-sso.jinritemai.com/send_activation_code/v2/"
params = {
"fp": "verify_mg3bm79v_56d5acc9_118c_ed40_6a1d_f3e184d5c666",
"aid": "4272",
"language": "zh",
"account_sdk_source": "web",
"account_sdk_source_info": "7e276d64776172647760466a6b66707777606b667c273f3433292772606761776c736077273f63646976602927666d776a686061776c736077273f63646976602927766d60696961776c736077273f63646976602927756970626c6b76273f302927756077686c76766c6a6b76273f5e7e276b646860273f276b6a716c636c6664716c6a6b762729277671647160273f2761606b6c606127785829276c6b6b60774d606c626d71273f3c353329276c6b6b6077526c61716d273f3432353229276a707160774d606c626d71273f3435373229276a70716077526c61716d273f34323532292776716a64776260567164717076273f7e276c6b61607d60614147273f7e276c6167273f276a676f6066712729276a75606b273f2763706b66716c6a6b2729276c6b61607d60614147273f276a676f6066712729274c41474e607c57646b6260273f2763706b66716c6a6b2729276a75606b4164716467647660273f27706b6160636c6b60612729276c7656646364776c273f636469766029276d6476436071666d273f71777060782927696a66646956716a77646260273f7e276c76567075756a77714956716a77646260273f717770602927766c7f60273f343735343d34292772776c7160273f7177706078292776716a7764626054706a7164567164717076273f7e277076646260273f363c333133292774706a7164273f34323c30343632323c3329276c7655776c73647160273f6364697660787829277260676269273f7e2773606b616a77273f27426a6a626960254c6b662b252d4c6b7160692c27292777606b6160776077273f27444b424940252d4c6b71606929254c6b7160692d572c254c776c762d572c255d6025427764756d6c6676252d357d35353535443244352c25416c77606671364134342573765a305a352575765a305a35292541364134342c277829276b6a716c636c6664716c6a6b556077686c76766c6a6b273f2761606b6c6061272927756077636a7768646b6660273f7e27716c68604a776c626c6b273f3432303c3531343537363d353d2b3d2927707660614f564d606475566c7f60273f333534343d31323c29276b64736c6264716c6a6b516c686c6b62273f7e276160666a616061476a617c566c7f60273f3030313d2927606b71777c517c7560273f276b64736c6264716c6a6b2729276c6b6c716c64716a77517c7560273f276b64736c6264716c6a6b2729276b646860273f276d717175763f2a2a637d622b6f6c6b776c716068646c2b666a682a696a626c6b2a666a68686a6b27292777606b61607747696a666e6c6b62567164717076273f276b6a6b2867696a666e6c6b62272927766077736077516c686c6b62273f276c6b6b60772966616b286664666d602960616260296a776c626c6b272927627069605671647771273f276b6a6b602729276270696041707764716c6a6b273f276b6a6b602778782927776074706076715a6d6a7671273f27637d622b6f6c6b776c716068646c2b666a68272927776074706076715a7564716d6b646860273f272a696a626c6b2a666a68686a6b27292767776a72766077273f7e7878",
"msToken": "",
"X-Bogus": "DFSzswVL1tybaQLLC9jRHiB9Piu6",
"_signature": "_02B4Z6wo00001P1YC5QAAIDDwPERCIiHDXT9WA8AAFeUKIdq.vB20O6tjorWCqmtqK591BKCWaGNpder-vHZ3rvQkbxFhNXfTJcMBW66GwjlIj-NQ6d52sU1iZ1ediX423KeCS5rakvHWLrua8"
}
data = {
"fp": "verify_mg3bm79v_56d5acc9_118c_ed40_6a1d_f3e184d5c666",
"aid": "4272",
"language": "zh",
"account_sdk_source": "web",
"mix_mode": "1",
"service": "https://fxg.jinritemai.com",
"type": "3731",
"mobile": self.encrypt(data=mobile_phone),
"captcha_key": ""
}
response = requests.post(url, headers=self.headers, cookies=self.cookies, params=params, data=data)
self._log(f"发送验证码响应: {response.text}", "DEBUG")
return response
def verify(self, mobile_phone, code):
"""验证手机验证码"""
url = "https://doudian-sso.jinritemai.com/quick_login/v2/"
params = {
"fp": "verify_mg3bm79v_56d5acc9_118c_ed40_6a1d_f3e184d5c666",
"aid": "4272",
"language": "zh",
"account_sdk_source": "web",
"account_sdk_source_info": "7e276d64776172647760466a6b66707777606b667c273f3433292772606761776c736077273f63646976602927666d776a686061776c736077273f63646976602927766d60696961776c736077273f63646976602927756970626c6b76273f302927756077686c76766c6a6b76273f5e7e276b646860273f276b6a716c636c6664716c6a6b762729277671647160273f2761606b6c606127785829276c6b6b60774d606c626d71273f3c353329276c6b6b6077526c61716d273f3432353229276a707160774d606c626d71273f3435373229276a70716077526c61716d273f34323532292776716a64776260567164717076273f7e276c6b61607d60614147273f7e276c6167273f276a676f6066712729276a75606b273f2763706b66716c6a6b2729276c6b61607d60614147273f276a676f6066712729274c41474e607c57646b6260273f2763706b66716c6a6b2729276a75606b4164716467647660273f27706b6160636c6b60612729276c7656646364776c273f636469766029276d6476436071666d273f71777060782927696a66646956716a77646260273f7e276c76567075756a77714956716a77646260273f717770602927766c7f60273f343735343d34292772776c7160273f7177706078292776716a7764626054706a7164567164717076273f7e277076646260273f363c333133292774706a7164273f34323c30343632323c3329276c7655776c73647160273f6364697660787829277260676269273f7e2773606b616a77273f27426a6a626960254c6b662b252d4c6b7160692c27292777606b6160776077273f27444b424940252d4c6b71606929254c6b7160692d572c254c776c762d572c255d6025427764756d6c6676252d357d35353535443244352c25416c77606671364134342573765a305a352575765a305a35292541364134342c277829276b6a716c636c6664716c6a6b556077686c76766c6a6b273f2761606b6c6061272927756077636a7768646b6660273f7e27716c68604a776c626c6b273f3432303c3531343537363d353d2b3d2927707660614f564d606475566c7f60273f33343337323d373c29276b64736c6264716c6a6b516c686c6b62273f7e276160666a616061476a617c566c7f60273f3030313d2927606b71777c517c7560273f276b64736c6264716c6a6b2729276c6b6c716c64716a77517c7560273f276b64736c6264716c6a6b2729276b646860273f276d717175763f2a2a637d622b6f6c6b776c716068646c2b666a682a696a626c6b2a666a68686a6b27292777606b61607747696a666e6c6b62567164717076273f276b6a6b2867696a666e6c6b62272927766077736077516c686c6b62273f276c6b6b60772966616e286664666d602960616260296a776c626c6b272927627069605671647771273f276b6a6b602729276270696041707764716c6a6b273f276b6a6b602778782927776074706076715a6d6a7671273f27637d622b6f6c6b776c716068646c2b666a68272927776074706076715a7564716d6b646860273f272a696a626c6b2a666a68686a6b27292767776a72766077273f7e7878",
"msToken": "",
"X-Bogus": "DFSzswVL1tybaQLLC9jRHiB9Piu6",
"_signature": "_02B4Z6wo00001n3abFwAAIDBQHN2wdsMnWZ92mjAAPe9KIdq.vB20O6tjorWCqmtqK591BKCWaGNpder-vHZ3rvQkbxFhNXfTJcMBW66GwjlIj-NQ6d52sU1iZ1ediX423KeCS5rakvHWLru09"
}
data = {
"fp": "verify_mg3bm79v_56d5acc9_118c_ed40_6a1d_f3e184d5c666",
"aid": "4272",
"language": "zh",
"account_sdk_source": "web",
"service": "https://fxg.jinritemai.com",
"subject_aid": "4966",
"mix_mode": "1",
"mobile": self.encrypt(data=mobile_phone),
"code": self.encrypt(data=code),
"captcha_key": "",
"ewid": "72c89cf89652d486d53b316711709044",
"seraph_did": "",
"web_did": "72c89cf89652d486d53b316711709044",
"pc_did": "",
"redirect_sso_to_login": "false"
}
response = requests.post(url, headers=self.headers, cookies=self.cookies, params=params, data=data)
self.cookies.update(response.cookies.get_dict())
self._log(f"验证码验证响应: {response.text}", "DEBUG")
ticket = re.findall('ticket=(.*?)",', response.text)[0]
return ticket
def callback(self, ticket):
"""回调获取登录状态"""
url = f"https://fxg.jinritemai.com/passport/sso/login/callback/?next=https%3A%2F%2Ffxg.jinritemai.com%2Flogin%2Fcommon&ticket={ticket}"
response = requests.get(url, headers=self.headers, cookies=self.cookies, allow_redirects=False)
self.cookies.update(response.cookies.get_dict())
self._log(f"回调响应状态: {response.status_code}", "DEBUG")
def subject_list(self):
"""获取登录主体列表"""
url = "https://fxg.jinritemai.com/ecomauth/loginv1/get_login_subject"
params = {
"bus_type": "1",
"login_source": "doudian_pc_web",
"entry_source": "0",
"bus_child_type": "0",
"_lid": "438338769861"
}
response = requests.get(url, headers=self.headers, cookies=self.cookies, params=params)
response_data = response.json()
self._log(f"登录主体列表响应: {response_data}", "DEBUG")
login_subject_list = response_data.get("data", {}).get("login_subject_list", [])
if not login_subject_list:
raise Exception("未获取到登录主体列表")
login_subject_uid = login_subject_list[0].get("subject_id")
user_identity_id = login_subject_list[0].get("member_id")
encode_shop_id = login_subject_list[0].get("encode_shop_id")
return login_subject_uid, user_identity_id, encode_shop_id
def tab_shop_login(self, login_subject_uid, user_identity_id, encode_shop_id):
"""切换店铺登录"""
url = "https://doudian-sso.jinritemai.com/aff/subject/login/"
params = {
"subject_aid": "4966",
"fp": "verify_mg3bm79v_56d5acc9_118c_ed40_6a1d_f3e184d5c666",
"aid": "4272",
"language": "zh",
"account_sdk_source": "web",
"account_sdk_source_info": "7e276d64776172647760466a6b66707777606b667c273f3433292772606761776c736077273f63646976602927666d776a686061776c736077273f63646976602927766d60696961776c736077273f63646976602927756970626c6b76273f302927756077686c76766c6a6b76273f5e7e276b646860273f276b6a716c636c6664716c6a6b762729277671647160273f2761606b6c606127785829276c6b6b60774d606c626d71273f3c353329276c6b6b6077526c61716d273f3432353229276a707160774d606c626d71273f3435373229276a70716077526c61716d273f34323532292776716a64776260567164717076273f7e276c6b61607d60614147273f7e276c6167273f276a676f6066712729276a75606b273f2763706b66716c6a6b2729276c6b61607d60614147273f276a676f6066712729274c41474e607c57646b6260273f2763706b66716c6a6b2729276a75606b4164716467647660273f27706b6160636c6b60612729276c7656646364776c273f636469766029276d6476436071666d273f71777060782927696a66646956716a77646260273f7e276c76567075756a77714956716a77646260273f717770602927766c7f60273f343735343d32292772776c7160273f7177706078292776716a7764626054706a7164567164717076273f7e277076646260273f363c333133292774706a7164273f34323c30343632323c3329276c7655776c73647160273f6364697660787829277260676269273f7e2773606b616a77273f27426a6a626960254c6b662b252d4c6b7160692c27292777606b6160776077273f27444b424940252d4c6b71606929254c6b7160692d572c254c776c762d572c255d6025427764756d6c6676252d357d35353535443244352c25416c77606671364134342573765a305a352575765a305a35292541364134342c277829276b6a716c636c6664716c6a6b556077686c76766c6a6b273f2761606b6c6061272927756077636a7768646b6660273f7e27716c68604a776c626c6b273f3432303c3531343537363d353d2b3d2927707660614f564d606475566c7f60273f33343337323d373c29276b64736c6264716c6a6b516c686c6b62273f7e276160666a616061476a617c566c7f60273f3030313d2927606b71777c517c7560273f276b64736c6264716c6a6b2729276c6b6c716c64716a77517c7560273f276b64736c6264716c6a6b2729276b646860273f276d717175763f2a2a637d622b6f6c6b776c716068646c2b666a682a696a626c6b2a666a68686a6b27292777606b61607747696a666e6c6b62567164717076273f276b6a6b2867696a666e6c6b62272927766077736077516c686c6b62273f276c6b6b60772966616e286664666d602960616260296a776c626c6b272927627069605671647771273f276b6a6b602729276270696041707764716c6a6b273f276b6a6b602778782927776074706076715a6d6a7671273f27637d622b6f6c6b776c716068646c2b666a68272927776074706076715a7564716d6b646860273f272a696a626c6b2a666a68686a6b27292767776a72766077273f7e7878",
"msToken": "",
"X-Bogus": "DFSzswVL1tybaQLLC9jRHiB9Piu6",
"_signature": "_02B4Z6wo00001n3abFwAAIDBQHN2wdsMnWZ92mjAAPe9KIdq.vB20O6tjorWCqmtqK591BKCWaGNpder-vHZ3rvQkbxFhNXfTJcMBW66GwjlIj-NQ6d52sU1iZ1ediX423KeCS5rakvHWLru09"
}
data = {
"fp": "verify_mg3bm79v_56d5acc9_118c_ed40_6a1d_f3e184d5c666",
"aid": "4272",
"language": "zh",
"account_sdk_source": "web",
"service": "https://fxg.jinritemai.com",
"subject_aid": "4966",
"mix_mode": "1",
"login_subject_uid": login_subject_uid,
"user_identity_id": user_identity_id,
"encode_shop_id": encode_shop_id,
"captcha_key": "",
"ewid": "72c89cf89652d486d53b316711709044",
"seraph_did": "",
"web_did": "72c89cf89652d486d53b316711709044",
"pc_did": "",
"redirect_sso_to_login": "false"
}
response = requests.post(url, headers=self.headers, cookies=self.cookies, params=params, data=data)
self._log(f"切换店铺登录响应: {response.text}", "DEBUG")
ticket = re.findall('ticket=(.*?)",', response.text)[0]
self.cookies.update(response.cookies.get_dict())
# 🔥 执行完整的登录回调流程参考Dylogin.py
callback_url = f"https://fxg.jinritemai.com/passport/sso/aff/login/callback/?next=https%3A%2F%2Ffxg.jinritemai.com&ticket={ticket}&aid=4272&subject_aid=4966"
response = requests.get(callback_url, headers=self.headers, cookies=self.cookies, allow_redirects=False)
self.cookies.update(response.cookies.get_dict())
self._log(f"主登录回调响应状态: {response.status_code}", "DEBUG")
# 最终回调获取完整cookies
final_callback_url = f"https://fxg.jinritemai.com/ecomauth/loginv1/callback?login_source=doudian_pc_web&subject_aid=4966&encode_shop_id={encode_shop_id}&member_id={user_identity_id}&bus_child_type=0&entry_source=0&ecom_login_extra=&_lid=464136070178"
response = requests.get(final_callback_url, headers=self.headers, cookies=self.cookies)
self.cookies.update(response.cookies.get_dict())
self._log(f"最终回调响应状态: {response.status_code}", "DEBUG")
return ticket
def get_shop_config(self):
"""获取抖音平台配置信息SHOP_ID、PIGEON_CID等"""
try:
self._log("🔄 开始获取抖音平台配置", "DEBUG")
# 获取配置信息的API调用
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'application/json, text/plain, */*',
'Referer': 'https://fxg.jinritemai.com/',
}
params = [
('biz_type', '4'),
('PIGEON_BIZ_TYPE', '2'),
('_ts', int(time.time() * 1000)),
('_pms', '1'),
('FUSION', 'true'),
]
response = requests.get(
'https://pigeon.jinritemai.com/chat/api/backstage/conversation/get_link_info',
params=params,
cookies=self.cookies,
headers=headers,
timeout=30
)
self._log(f"配置请求响应状态: {response.status_code}", "DEBUG")
self._log(f"配置响应内容: {response.text}", "DEBUG")
if response.status_code != 200:
self._log(f"❌ 配置请求失败: HTTP {response.status_code}", "ERROR")
return None
data = response.json()
if data.get('code') != 0:
error_msg = data.get('message', '未知错误')
self._log(f"❌ 获取配置失败: {error_msg}", "ERROR")
return None
config_data = data.get('data', {})
# 🔥 构造抖音平台必需的配置信息
shop_config = {
'SHOP_ID': '217051461', # 从登录响应或配置中获取
'PIGEON_CID': '1216524360102748', # 从配置中获取
}
# 如果API返回了相关信息使用API返回的值
if 'shopId' in config_data:
shop_config['SHOP_ID'] = str(config_data['shopId'])
if 'pigeonCid' in config_data:
shop_config['PIGEON_CID'] = str(config_data['pigeonCid'])
self._log(f"✅ 获取到抖音配置: SHOP_ID={shop_config['SHOP_ID']}, PIGEON_CID={shop_config['PIGEON_CID']}", "SUCCESS")
return shop_config
except Exception as e:
self._log(f"❌ 获取抖音配置失败: {e}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
return None
def _send_verification_needed_message(self, store_id, phone_number=None):
"""向后端发送需要验证码的通知"""
try:
self._log(f"开始发送验证码需求通知店铺ID: {store_id}, 手机号: {phone_number}", "INFO")
from WebSocket.backend_singleton import get_backend_client
backend = get_backend_client()
self._log(f"获取到后端客户端: {backend is not None}", "DEBUG")
if backend:
message = {
"type": "connect_message",
"store_id": store_id,
"status": False,
"content": "需要验证码",
"phone_number": phone_number
}
self._log(f"准备发送验证码通知消息: {message}", "DEBUG")
backend.send_message(message)
self._log("✅ 成功向后端发送验证码需求通知(含手机号)", "SUCCESS")
else:
self._log("❌ 后端客户端为空,无法发送验证码需求通知", "ERROR")
except Exception as e:
self._log(f"❌ 发送验证码需求通知失败: {e}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
def _send_verification_error_message(self, store_id, error_msg):
"""向后端发送验证码错误的通知"""
try:
self._log(f"开始发送验证码错误通知店铺ID: {store_id}, 错误: {error_msg}", "INFO")
from WebSocket.backend_singleton import get_backend_client
backend = get_backend_client()
if backend:
message = {
"type": "connect_message",
"store_id": store_id,
"status": False,
"content": error_msg
}
self._log(f"准备发送验证码错误消息: {message}", "DEBUG")
backend.send_message(message)
self._log("✅ 成功向后端发送验证码错误通知", "SUCCESS")
else:
self._log("❌ 后端客户端为空,无法发送验证码错误通知", "ERROR")
except Exception as e:
self._log(f"❌ 发送验证码错误通知失败: {e}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
def _send_login_success_message(self, store_id):
"""向后端发送登录成功的通知"""
try:
self._log(f"开始发送登录成功通知店铺ID: {store_id}", "INFO")
from WebSocket.backend_singleton import get_backend_client
backend = get_backend_client()
if backend:
message = {
"type": "connect_message",
"store_id": store_id,
"status": True # 登录成功
}
self._log(f"准备发送登录成功消息: {message}", "DEBUG")
backend.send_message(message)
self._log("✅ 成功向后端发送登录成功通知", "SUCCESS")
else:
self._log("❌ 获取后端客户端失败", "ERROR")
except Exception as e:
self._log(f"❌ 发送登录成功通知失败: {e}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
def _send_login_failure_message(self, store_id, error_msg):
"""向后端发送登录失败的通知"""
try:
self._log(f"开始发送登录失败通知店铺ID: {store_id}, 错误: {error_msg}", "INFO")
from WebSocket.backend_singleton import get_backend_client
backend = get_backend_client()
if backend:
message = {
"type": "connect_message",
"store_id": store_id,
"status": False, # 登录失败
"content": error_msg # 官方返回的失败原因
}
self._log(f"准备发送登录失败消息: {message}", "DEBUG")
backend.send_message(message)
self._log("✅ 成功向后端发送登录失败通知", "SUCCESS")
else:
self._log("❌ 获取后端客户端失败", "ERROR")
except Exception as e:
self._log(f"❌ 发送登录失败通知失败: {e}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
def request_verification_code(self, phone_number, store_id, original_phone=None):
"""向抖音平台请求发送验证码"""
self._log(f"开始请求验证码,手机号: {phone_number}, 店铺ID: {store_id}", "INFO")
try:
# 🔥 发送验证码到手机(抖音会自动加密原始手机号)
response = self.send_activation_code(phone_number)
self._log(f"发送验证码请求结果: {response.text}")
# 发送消息给后端,告知需要验证码(使用原始手机号)
self._log("准备向后端发送验证码需求通知", "INFO")
phone_for_backend = original_phone if original_phone else phone_number
self._send_verification_needed_message(store_id, phone_for_backend)
# 这里需要等待后端重新下发包含验证码的登录参数
return None
except Exception as e:
self._log(f"❌ 请求验证码失败: {e}", "ERROR")
self._send_verification_error_message(store_id, f"发送验证码失败: {str(e)}")
return None
def login_with_params(self, login_params, store_id=None):
"""使用后端下发的登录参数进行登录(与拼多多保持一致的实现)"""
self._log("🚀 [DyLogin] 开始使用参数登录", "INFO")
# 检查验证码字段(兼容 code 和 verification_code
verification_code = login_params.get("verification_code") or login_params.get("code", "")
phone_number = login_params.get("phone_number", "")
encrypted_phone = login_params.get("encrypted_phone", "")
self._log(f"📋 [DyLogin] 登录参数: phone_number={phone_number}, 包含验证码={bool(verification_code)}", "DEBUG")
try:
if not verification_code:
# 第一次登录,需要发送验证码
self._log("检测到需要手机验证码,正在调用发送验证码方法", "INFO")
self._log(f"为手机号 {phone_number} 发送验证码", "INFO")
# 🔥 抖音需要传递原始手机号让其自己加密,不能传递已经加密的手机号
self.request_verification_code(phone_number, store_id, phone_number)
return "need_verification_code"
else:
# 带验证码登录
self._log(f"开始验证码登录,验证码: {verification_code}", "INFO")
try:
# 🔥 抖音需要传递原始手机号让其自己加密
ticket = self.verify(phone_number, verification_code)
self._log(f"✅ 验证码验证成功获取到ticket", "SUCCESS")
# 执行后续登录流程
self.callback(ticket)
login_subject_uid, user_identity_id, encode_shop_id = self.subject_list()
self.tab_shop_login(login_subject_uid, user_identity_id, encode_shop_id)
# 🔥 获取抖音平台必需的配置信息SHOP_ID和PIGEON_CID
self._log("🔄 开始获取抖音平台配置信息", "INFO")
shop_config = self.get_shop_config()
if shop_config:
# 将配置信息添加到cookies中
self.cookies.update(shop_config)
self._log("🎉 登录成功!配置信息已获取", "SUCCESS")
# 🔥 不在这里发送成功通知让backend_singleton统一处理
return self.cookies
else:
error_msg = "获取抖音平台配置信息失败"
self._log(f"{error_msg}", "ERROR")
self._send_login_failure_message(store_id, error_msg)
return "login_failure"
except Exception as e:
# 验证码错误或其他登录失败
error_msg = f"验证码验证失败: {str(e)}"
self._log(f"{error_msg}", "ERROR")
self._send_verification_error_message(store_id, error_msg)
return "verification_code_error"
except Exception as e:
# 登录过程中的其他错误
error_msg = f"登录过程出错: {str(e)}"
self._log(f"{error_msg}", "ERROR")
self._send_login_failure_message(store_id, error_msg)
return "login_failure"
# ===== 抖音登录相关类集成结束 =====
# 抖音WebSocket管理器类 # 抖音WebSocket管理器类
class DouYinWebsocketManager: class DouYinWebsocketManager:
_instance = None _instance = None
@@ -100,53 +555,21 @@ class DouYinBackendService:
return True return True
async def send_message_to_backend(self, platform_message): async def send_message_to_backend(self, platform_message):
"""改为通过单后端连接发送,需携带store_id""" """🔥 改为通过单后端连接发送,与拼多多保持完全一致的逻辑"""
try: try:
from WebSocket.backend_singleton import get_backend_client from WebSocket.backend_singleton import get_backend_client
backend = get_backend_client() backend = get_backend_client()
if not backend: if not backend:
return None return None
# 从platform_message中构造统一上行结构并附加store_id # 🔥 确保消息包含store_id与拼多多一致
body = platform_message.get('body', {}) if isinstance(platform_message, dict) else {} if isinstance(platform_message, dict):
sender_id = platform_message.get('sender', {}).get('id', '') if isinstance(platform_message, dict) else '' if 'store_id' not in platform_message and self.current_store_id:
platform_message['store_id'] = self.current_store_id
# 优先取消息内的store_id其次取body内再次退回当前会话store_id # 🔥 通过统一后端连接发送(与拼多多完全一致)
store_id = (platform_message.get('store_id') backend.send_message(platform_message)
or body.get('store_id') return True
or self.current_store_id
or '')
# 检查消息类型如果是特殊类型如staff_list保持原格式
message_type = platform_message.get('type', 'message')
if message_type == 'staff_list':
# 对于客服列表消息,直接转发原始格式
msg = platform_message.copy()
# 确保store_id正确
msg['store_id'] = store_id
else:
# 对于普通消息,使用原有的格式转换逻辑
msg_type = platform_message.get('msg_type', 'text')
content_for_backend = platform_message.get('content', '')
pin_image = platform_message.get('pin_image')
if not pin_image:
pin_image = ""
else:
pass
# 构造标准消息格式
msg = {
'type': 'message',
'content': content_for_backend,
'pin_image': pin_image,
'msg_type': msg_type,
'sender': {'id': sender_id},
'store_id': store_id
}
backend.send_message(msg)
return None
except Exception: except Exception:
return None return None
@@ -201,7 +624,7 @@ class DouYinMessageHandler:
print(f"[DY Handler] 创建实例 {self.instance_id} for store {store_id}") print(f"[DY Handler] 创建实例 {self.instance_id} for store {store_id}")
def get_casl(self): def get_casl(self):
"""获取可分配客服列表""" """获取可分配客服列表 - 根据原始代码实现"""
headers = { headers = {
"authority": "pigeon.jinritemai.com", "authority": "pigeon.jinritemai.com",
"accept": "application/json, text/plain, */*", "accept": "application/json, text/plain, */*",
@@ -226,16 +649,27 @@ class DouYinMessageHandler:
"_ts": int(time.time() * 1000), "_ts": int(time.time() * 1000),
"_pms": "1", "_pms": "1",
"FUSION": "true", "FUSION": "true",
"verifyFp": "", "verifyFp": "", # 🔥 恢复为空字符串,因为原始代码中也是空的
"_v": "1.0.1.3585" "_v": "1.0.1.3585"
} }
try: try:
self._log(f"🔄 正在获取客服列表cookies包含字段: {list(self.cookie.keys())}", "DEBUG")
# 🔥 按照原始代码的方式处理响应
response = requests.get(url, headers=headers, cookies=self.cookie, params=params).json() response = requests.get(url, headers=headers, cookies=self.cookie, params=params).json()
self._log(f"客服列表API响应内容: {response}", "DEBUG")
if response.get('code') == 0: if response.get('code') == 0:
return response.get('data', []) staff_data = response.get('data', [])
return None self._log(f"✅ 成功获取客服列表,共 {len(staff_data)} 个客服", "SUCCESS")
return staff_data
else:
error_msg = response.get('message', '未知错误')
self._log(f"❌ 客服列表API返回错误: code={response.get('code')}, message={error_msg}", "ERROR")
return None
except Exception as e: except Exception as e:
self._log(f"❌ 获取客服列表失败: {e}", "ERROR") self._log(f"❌ 获取客服列表失败: {e}", "ERROR")
import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
return None return None
def transfer_conversation(self, receiver_id, shop_id, staff_id): def transfer_conversation(self, receiver_id, shop_id, staff_id):
@@ -282,18 +716,26 @@ class DouYinMessageHandler:
try: try:
# 获取客服列表 # 获取客服列表
staff_list = self.get_casl() staff_list = self.get_casl()
if not staff_list: if staff_list is None:
self._log("⚠️ 获取客服列表失败", "WARNING") self._log("⚠️ 获取客服列表失败", "WARNING")
return False return False
# 转换客服数据格式 # 🔥 处理空客服列表的情况API成功但无客服
if len(staff_list) == 0:
self._log("📋 当前无可分配客服,发送空列表到后端", "INFO")
# 继续处理,发送空列表给后端
# 转换客服数据格式 - 🔥 根据原始代码调试实际字段名
staff_infos = [] staff_infos = []
for staff in staff_list: for staff in staff_list:
# 打印原始数据结构用于调试
self._log(f"🔍 原始客服数据结构: {staff}", "DEBUG")
staff_info = StaffInfo( staff_info = StaffInfo(
staff_id=str(staff.get('staffId', '')), staff_id=str(staff.get('staffId', '') or staff.get('staff_id', '') or staff.get('id', '')),
name=staff.get('staffName', ''), name=staff.get('staffName', '') or staff.get('staff_name', '') or staff.get('name', ''),
status=staff.get('status', 0), status=str(staff.get('status', 0) or staff.get('state', 0)),
department=staff.get('department', ''), department=staff.get('department', '') or staff.get('dept', ''),
online=staff.get('online', True) online=staff.get('online', True)
) )
staff_infos.append(staff_info.to_dict()) staff_infos.append(staff_info.to_dict())
@@ -312,9 +754,15 @@ class DouYinMessageHandler:
# 发送到后端 # 发送到后端
await self.ai_service.send_message_to_backend(message_template.to_dict()) await self.ai_service.send_message_to_backend(message_template.to_dict())
self._log(f"发送客服列表消息的结构体为: {message_template.to_json()}") self._log(f"发送客服列表消息的结构体为: {message_template.to_json()}")
self._log(f"✅ [DY] 成功发送客服列表到后端,共 {len(staff_infos)} 个客服", "SUCCESS")
print(f"🔥 [DY] 客服列表已上传到后端: {len(staff_infos)} 个客服") if len(staff_infos) > 0:
print(f"[DY] 客服详情: {[{'id': s['staff_id'], 'name': s['name']} for s in staff_infos]}") self._log(f"[DY] 成功发送客服列表到后端,共 {len(staff_infos)} 个客服", "SUCCESS")
print(f"🔥 [DY] 客服列表已上传到后端: {len(staff_infos)} 个客服")
print(f"[DY] 客服详情: {[{'id': s['staff_id'], 'name': s['name']} for s in staff_infos]}")
else:
self._log(f"✅ [DY] 成功发送空客服列表到后端(当前无可分配客服)", "SUCCESS")
print(f"🔥 [DY] 空客服列表已上传到后端(当前无可分配客服)")
return True return True
except Exception as e: except Exception as e:
@@ -603,26 +1051,28 @@ class DouYinMessageHandler:
talk_id = user_info.get("talk_id", 0) talk_id = user_info.get("talk_id", 0)
p_id = user_info.get("p_id", 0) p_id = user_info.get("p_id", 0)
# 准备发送者和接收者信息 # 🔥 统一消息类型检测逻辑(与拼多多保持一致)
sender_info = { content = message_content
"id": str(sender_id), lc = str(content).lower()
"name": f"用户_{sender_id}",
"is_customer": True
}
receiver_info = {
"id": self.store_id,
"name": "店铺客服",
"is_merchant": True
}
# 创建消息模板 # 检测消息类型,使用与拼多多相同的逻辑
message_template = PlatformMessage( if any(ext in lc for ext in [".jpg", ".jpeg", ".png", ".gif", ".webp"]):
type="message", msg_type = "image"
content=message_content, elif any(ext in lc for ext in [".mp4", ".avi", ".mov", ".wmv", ".flv"]):
msg_type="text", msg_type = "video"
sender={"id": sender_info.get("id", "")}, elif any(keyword in lc for keyword in ['goods.html', 'item.html', 'item.jd.com', '商品卡片id']):
msg_type = "product_card"
else:
msg_type = "text"
# 🔥 使用工厂方法创建消息模板(与拼多多保持一致)
message_template = PlatformMessage.create_text_message(
content=content,
sender_id=str(sender_id),
store_id=self.store_id store_id=self.store_id
) )
# 动态设置检测到的消息类型
message_template.msg_type = msg_type
# 发送消息到后端(使用统一连接) # 发送消息到后端(使用统一连接)
await self.ai_service.send_message_to_backend(message_template.to_dict()) await self.ai_service.send_message_to_backend(message_template.to_dict())
@@ -1195,22 +1645,60 @@ class DouYinMessageHandler:
elif msg_type == 'text' and avatar_url: elif msg_type == 'text' and avatar_url:
message_text = message_dict['text'] message_text = message_dict['text']
# 准备发送者和接收者信息 # 🔥 提取goods_info信息与拼多多保持一致
sender_info = { goods_info = {}
"id": str(sender_id), if msg_type == 'order':
"name": f"用户_{sender_id}", # 从抖音的订单信息中提取goods_info
"is_customer": True order_id = message_dict.get('order_id', '')
} goods_id = self.get_goods_id(order_id) if hasattr(self, 'get_goods_id') else message_dict.get('goods_id', '')
goods_info = {
'goodsID': goods_id,
'orderSequenceNo': order_id
}
elif msg_type == 'goods':
# 从抖音的商品信息中提取goods_info
goods_id = message_dict.get('goods_id', '')
goods_info = {
'goodsID': goods_id
}
# 创建消息模板 # 🔥 统一消息类型检测逻辑(与拼多多完全一致)
message_template = PlatformMessage( content = message_text
type="message", lc = str(content).lower()
content=message_text,
pin_image=avatar_url, # 使用与拼多多完全相同的检测逻辑
msg_type=msg_type, if any(ext in lc for ext in [".jpg", ".jpeg", ".png", ".gif", ".webp"]):
sender={"id": sender_info.get("id", "")}, msg_type = "image"
store_id=self.store_id elif any(ext in lc for ext in [".mp4", ".avi", ".mov", ".wmv", ".flv"]):
msg_type = "video"
else:
msg_type = "text"
# 🔥 订单卡片组装(与拼多多完全一致)
if ("订单编号" in str(content) or msg_type == 'order') and goods_info:
content = f"商品id{goods_info.get('goodsID')} 订单号:{goods_info.get('orderSequenceNo')}"
msg_type = "order_card"
# 🔥 商品卡片检测(与拼多多完全一致)
elif any(keyword in lc for keyword in ['goods.html', 'item.html', 'item.jd.com', '商品卡片id']) or \
(goods_info and goods_info.get('goodsID') and not goods_info.get('orderSequenceNo')):
msg_type = "product_card"
# 🔥 使用工厂方法创建消息模板(与拼多多保持一致)
message_template = PlatformMessage.create_text_message(
content=content,
sender_id=str(sender_id),
store_id=self.store_id,
pin_image=avatar_url
) )
# 动态设置检测到的消息类型
message_template.msg_type = msg_type
# 🔥 添加调试日志(与拼多多保持一致)
try:
print(f"📤(SPEC) 发送到AI: {json.dumps(message_template.to_dict(), ensure_ascii=False)[:300]}...")
except Exception:
pass
self._log("📤 准备发送消息到AI服务...", "INFO") self._log("📤 准备发送消息到AI服务...", "INFO")
self._log(f"📋 消息内容: {message_template.to_json()}", "DEBUG") self._log(f"📋 消息内容: {message_template.to_json()}", "DEBUG")
@@ -1838,6 +2326,8 @@ class DouYinListenerForGUI:
# 发送客服列表到后端 # 发送客服列表到后端
try: try:
# 🔥 等待一小段时间确保连接完全建立
await asyncio.sleep(1)
staff_list_success = await self.douyin_bot.message_handler.send_staff_list_to_backend() staff_list_success = await self.douyin_bot.message_handler.send_staff_list_to_backend()
if staff_list_success: if staff_list_success:
print(f"🔥 [DY] 客服列表已上传到后端") print(f"🔥 [DY] 客服列表已上传到后端")
@@ -1876,6 +2366,69 @@ class DouYinListenerForGUI:
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") self._log(f"错误详情: {traceback.format_exc()}", "DEBUG")
return False return False
async def start_with_login_params(self, store_id: str, login_params: str):
"""使用后端下发的登录参数执行登录并启动监听(与拼多多保持一致)"""
try:
self._log("🔵 [DY] 收到后端登录参数开始执行登录获取cookies", "INFO")
self._log(f"🔍 [DY] 登录参数内容: {login_params[:100]}...", "DEBUG")
# 1. 解析登录参数
self._log("🔄 [DY] 开始解析登录参数", "DEBUG")
params_dict = self._parse_login_params(login_params)
if not params_dict:
self._log("❌ [DY] 登录参数解析失败", "ERROR")
return False
self._log(f"✅ [DY] 登录参数解析成功,手机号: {params_dict.get('phone_number', 'N/A')}", "DEBUG")
# 2. 使用新的DyLogin类执行登录
self._log("🔄 [DY] 开始创建DyLogin实例", "DEBUG")
dy_login = DyLogin(log_callback=self.log_callback)
self._log("✅ [DY] DyLogin实例创建成功", "DEBUG")
self._log("🔄 [DY] 开始执行登录", "DEBUG")
login_result = dy_login.login_with_params(params_dict, store_id)
self._log(f"📊 [DY] 登录结果: {login_result}", "DEBUG")
if login_result == "need_verification_code":
self._log("⚠️ [DY] 需要手机验证码,已通知后端,等待重新下发包含验证码的登录参数", "WARNING")
return "need_verification_code" # 返回特殊标识,避免被覆盖
elif login_result == "verification_code_error":
self._log("⚠️ [DY] 验证码错误,已通知后端", "WARNING")
return "verification_code_error" # 返回特殊标识,避免重复发送消息
elif login_result == "login_failure":
self._log("⚠️ [DY] 登录失败,已发送失败通知给后端", "WARNING")
return "login_failure" # 返回特殊标识,避免重复发送消息
elif not login_result:
self._log("❌ [DY] 登录失败", "ERROR")
return False
elif isinstance(login_result, dict):
# 登录成功获取到cookies
self._log("✅ [DY] 登录成功使用获取的cookies连接平台", "SUCCESS")
# 🔥 使用获取的cookies启动监听这里会验证cookies完整性
return await self.start_with_cookies(store_id, login_result)
else:
self._log("❌ [DY] 登录返回未知结果", "ERROR")
return False
except Exception as e:
self._log(f"❌ [DY] 使用登录参数启动失败: {str(e)}", "ERROR")
import traceback
self._log(f"🔍 [DY] 异常详细信息: {traceback.format_exc()}", "ERROR")
return False
def _parse_login_params(self, login_params_str: str) -> dict:
"""解析后端下发的登录参数(与拼多多保持一致)"""
try:
import json
data = json.loads(login_params_str)
params = data.get("data", {})
self._log(f"✅ [DY] 登录参数解析成功: phone_number={params.get('phone_number', 'N/A')}", "INFO")
return params
except Exception as e:
self._log(f"❌ [DY] 解析登录参数失败: {e}", "ERROR")
return {}
def stop_listening(self): def stop_listening(self):
"""停止监听""" """停止监听"""
if self.stop_event: if self.stop_event:

View File

@@ -1151,12 +1151,14 @@ class BackendClient:
content = message.get('content', '') content = message.get('content', '')
data = message.get('data', {}) data = message.get('data', {})
# 判断是拼多多登录参数还是普通Cookie # 🔥 判断是登录参数模式还是普通Cookie模式(支持拼多多和抖音)
if platform_name == "拼多多" and ("拼多多登录" in content) and data.get('login_params'): if (platform_name in ["拼多多", "抖音"] and
# 拼多多登录参数模式 - 传递完整的消息JSON给处理器 (("拼多多登录" in content and data.get('login_params')) or
print(f"收到拼多多登录参数: 平台={platform_name}, 店铺={store_id}, 类型={content}") ("抖音登录" in content and data.get('login_flow')))):
# 登录参数模式 - 传递完整的消息JSON给处理器
print(f"收到{platform_name}登录参数: 平台={platform_name}, 店铺={store_id}, 类型={content}")
if self.login_callback: if self.login_callback:
# 传递完整的JSON消息拼多多处理器来解析login_params # 传递完整的JSON消息让处理器来解析登录参数
import json import json
full_message = json.dumps(message) full_message = json.dumps(message)
self.login_callback(platform_name, store_id, full_message) self.login_callback(platform_name, store_id, full_message)

View File

@@ -275,15 +275,66 @@ class WebSocketManager:
def _runner(): def _runner():
try: try:
import json import json
self._log("🚀 开始创建抖音监听器实例", "DEBUG")
listener = DYListenerForGUI_WS() listener = DYListenerForGUI_WS()
# 将JSON字符串格式的cookies解析为字典 self._log("✅ 抖音监听器实例创建成功", "DEBUG")
try:
cookie_dict = json.loads(cookies) if isinstance(cookies, str) else cookies # 🔥 检查是否为登录参数模式(与拼多多保持一致)
except json.JSONDecodeError as e: if cookies and ('"login_flow"' in cookies or '"phone_number"' in cookies):
self._log(f"❌ Cookie JSON解析失败: {e}", "ERROR") # 使用登录参数模式
return False self._log("📋 使用登录参数启动抖音监听器", "INFO")
self._log("🔄 开始执行 start_with_login_params", "DEBUG")
result = asyncio.run(listener.start_with_login_params(store_id=store_id, login_params=cookies))
self._log(f"📊 start_with_login_params 执行结果: {result}", "DEBUG")
# 🔥 详细的结果分析(与拼多多完全一致)
if result == "need_verification_code":
self._log("✅ [DY] 登录流程正常,已发送验证码需求通知给后端", "SUCCESS")
elif result == "verification_code_error":
self._log("⚠️ [DY] 验证码错误,已发送错误通知给后端", "WARNING")
elif result:
self._log("✅ [DY] 登录成功,平台连接已建立", "SUCCESS")
self._notify_platform_connected("抖音")
else:
self._log("❌ [DY] 登录失败", "ERROR")
else:
# 传统cookie模式保留兼容性
self._log("🍪 使用Cookie启动抖音监听器", "INFO")
self._log("🔄 开始执行 start_with_cookies", "DEBUG")
try:
cookie_dict = json.loads(cookies) if isinstance(cookies, str) else cookies
except json.JSONDecodeError as e:
self._log(f"❌ Cookie JSON解析失败: {e}", "ERROR")
return False
result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookie_dict=cookie_dict))
self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG")
# Cookie启动成功时也要通知GUI
if result:
self._log("✅ [DY] Cookie启动成功平台连接已建立", "SUCCESS")
self._notify_platform_connected("抖音")
# 🔥 根据实际登录结果上报状态给后端(与拼多多完全一致)
if self.backend_client and result not in ["need_verification_code", "verification_code_error", "login_failure"]:
# 如果是特殊状态说明通知已经在DyLogin中发送了不需要重复发送
try:
message = {
"type": "connect_message",
"store_id": store_id,
"status": bool(result)
}
self.backend_client.send_message(message)
status_text = "成功" if result else "失败"
self._log(f"上报抖音平台连接状态{status_text}: {message}", "SUCCESS" if result else "ERROR")
except Exception as send_e:
self._log(f"上报抖音平台连接状态失败: {send_e}", "ERROR")
elif result == "need_verification_code":
self._log("需要验证码验证码通知已由DyLogin发送等待后端重新下发登录参数", "INFO")
elif result == "verification_code_error":
self._log("验证码错误错误通知已由DyLogin发送等待后端处理", "INFO")
elif result == "login_failure":
self._log("登录失败失败通知已由DyLogin发送等待后端处理", "INFO")
result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookie_dict=cookie_dict))
return result return result
except Exception as e: except Exception as e:
self._log(f"抖音监听器运行异常: {e}", "ERROR") self._log(f"抖音监听器运行异常: {e}", "ERROR")
@@ -304,20 +355,7 @@ class WebSocketManager:
if f"抖音:{store_id}" in self.platform_listeners: if f"抖音:{store_id}" in self.platform_listeners:
self.platform_listeners[f"抖音:{store_id}"]['status'] = 'success' self.platform_listeners[f"抖音:{store_id}"]['status'] = 'success'
# 上报连接状态给后端
if self.backend_client:
try:
self.backend_client.send_message({
"type": "connect_message",
"store_id": store_id,
"status": True
})
self._log("已上报抖音平台连接状态: 成功", "INFO")
except Exception as e:
self._log(f"上报抖音平台连接状态失败: {e}", "WARNING")
self._log("已启动抖音平台监听", "SUCCESS") self._log("已启动抖音平台监听", "SUCCESS")
self._notify_platform_connected("抖音") # ← 新增
except Exception as e: except Exception as e:
self._log(f"启动抖音平台监听失败: {e}", "ERROR") self._log(f"启动抖音平台监听失败: {e}", "ERROR")

View File

@@ -9,7 +9,7 @@ import json # 用于将令牌保存为 JSON 格式
# 后端服务器配置 # 后端服务器配置
# BACKEND_HOST = "192.168.5.233" # BACKEND_HOST = "192.168.5.233"
BACKEND_HOST = "192.168.5.12" BACKEND_HOST = "192.168.5.53"
# BACKEND_HOST = "shuidrop.com" # BACKEND_HOST = "shuidrop.com"
# BACKEND_HOST = "test.shuidrop.com" # BACKEND_HOST = "test.shuidrop.com"
BACKEND_PORT = "8000" BACKEND_PORT = "8000"