first commit

main
Zhangzhichao@123 2 months ago
commit 0dc332397b

2
.gitignore vendored

@ -0,0 +1,2 @@
./.vscode/
./venv/*

@ -0,0 +1,111 @@
import sqlite3
from multiprocessing import Queue
from settings import sqlite_file
from queue import Empty
class LiveChatConfig:
def __init__(self):
self.conn = sqlite3.connect(sqlite_file)
self.conn.execute("PRAGMA journal_mode=WAL;")
def _query_config(self, key):
cursor = self.conn.cursor()
cursor.execute('select value from config where key=?', (key,))
result = cursor.fetchone()
result = result[0] if result is not None else None
cursor.close()
return result
@property
def enter_live_room_prob(self):
result = self._query_config('reply_prob_enter_live_room')
return int(result) if result is not None else None
@property
def follow_prob(self):
result = self._query_config('reply_prob_follow')
return int(result) if result is not None else None
@property
def like_prob(self):
result = self._query_config('reply_prob_like')
return int(result) if result is not None else None
@property
def gift_prob(self):
result = self._query_config('reply_prob_gift')
return int(result) if result is not None else None
@property
def chat_prob(self):
result = self._query_config('reply_prob_chat')
return int(result) if result is not None else None
@property
def enter_live_room_prompt(self):
return self._query_config('enter_live_room_prompt')
@property
def follow_prompt(self):
return self._query_config('follow_prompt')
@property
def like_prompt(self):
return self._query_config('like_prompt')
@property
def gift_prompt(self):
return self._query_config('gift_prompt')
@property
def chat_prompt(self):
return self._query_config('chat_prompt')
@property
def product_related_prompt(self):
return self._query_config('product_related_prompt')
@property
def backend_token(self):
return self._query_config('backend_token')
@property
def system_messages(self) -> list:
results = []
cursor = self.conn.cursor()
cursor.execute('select message from system_message')
rows = cursor.fetchall()
for message in rows:
results.append(message[0])
cursor.close()
return results
class PromptQueue:
def __init__(self, maxsize=0):
self.queue = Queue(maxsize)
self.maxsize = maxsize
def put(self, item):
if self.queue.full():
try:
self.queue.get_nowait() # 丢掉最旧的数据
except:
pass
self.queue.put(item)
def get(self, block=True, timeout=None):
try:
return self.queue.get(block, timeout)
except Empty:
return None
def qsize(self):
return self.queue.qsize()
def empty(self):
return self.queue.empty()
def full(self):
return self.queue.full()

@ -0,0 +1,416 @@
import asyncio
import codecs
import gzip
import hashlib
import json
import re
import string
import subprocess
import threading
import time
import traceback
import urllib.parse
from contextlib import contextmanager
from unittest.mock import patch
import httpx
import requests
import websocket
from py_mini_racer import MiniRacer
from protobuf.douyin import *
from message_processor import *
from helper import PromptQueue
from settings import live_talking_host, Backend
@contextmanager
def patched_popen_encoding(encoding='utf-8'):
original_popen_init = subprocess.Popen.__init__
def new_popen_init(self, *args, **kwargs):
kwargs['encoding'] = encoding
original_popen_init(self, *args, **kwargs)
with patch.object(subprocess.Popen, '__init__', new_popen_init):
yield
def generateSignature(wss, script_file='sign.js'):
"""
出现gbk编码问题则修改 python模块subprocess.py的源码中Popen类的__init__函数参数encoding值为 "utf-8"
"""
params = ("live_id,aid,version_code,webcast_sdk_version,"
"room_id,sub_room_id,sub_channel_id,did_rule,"
"user_unique_id,device_platform,device_type,ac,"
"identity").split(',')
wss_params = urllib.parse.urlparse(wss).query.split('&')
wss_maps = {i.split('=')[0]: i.split("=")[-1] for i in wss_params}
tpl_params = [f"{i}={wss_maps.get(i, '')}" for i in params]
param = ','.join(tpl_params)
md5 = hashlib.md5()
md5.update(param.encode())
md5_param = md5.hexdigest()
with codecs.open(script_file, 'r', encoding='utf8') as f:
script = f.read()
ctx = MiniRacer()
ctx.eval(script)
try:
signature = ctx.call("get_sign", md5_param)
return signature
except Exception as e:
logger.error(e)
# 以下代码对应js脚本为sign_v0.js
# context = execjs.compile(script)
# with patched_popen_encoding(encoding='utf-8'):
# ret = context.call('getSign', {'X-MS-STUB': md5_param})
# return ret.get('X-Bogus')
def generateMsToken(length=107):
"""
产生请求头部cookie中的msToken字段其实为随机的107位字符
:param length:字符位数
:return:msToken
"""
random_str = ''
base_str = string.ascii_letters + string.digits + '=_'
_len = len(base_str) - 1
for _ in range(length):
random_str += base_str[random.randint(0, _len)]
return random_str
class DouyinLiveWebFetcher:
def __init__(self, live_id, ws_open_event, queue: PromptQueue):
"""
直播间弹幕抓取对象
:param live_id: 直播间的直播id打开直播间web首页的链接如https://live.douyin.com/261378947940
其中的261378947940即是live_id
"""
self.__ttwid = None
self.__room_id = None
self.live_id = live_id
self.live_url = "https://live.douyin.com/"
self.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"
self.queue = queue
self.ws_open_event = ws_open_event
def start(self):
self._connectWebSocket()
def stop(self):
self.ws.close()
@property
def ttwid(self):
"""
产生请求头部cookie中的ttwid字段访问抖音网页版直播间首页可以获取到响应cookie中的ttwid
:return: ttwid
"""
if self.__ttwid:
return self.__ttwid
headers = {
"User-Agent": self.user_agent,
}
try:
response = requests.get(self.live_url, headers=headers)
response.raise_for_status()
except Exception as err:
logger.error("【X】Request the live url error: ", err)
else:
self.__ttwid = response.cookies.get('ttwid')
return self.__ttwid
@property
def room_id(self):
"""
根据直播间的地址获取到真正的直播间roomId有时会有错误可以重试请求解决
:return:room_id
"""
if self.__room_id:
return self.__room_id
url = self.live_url + self.live_id
headers = {
"User-Agent": self.user_agent,
"cookie": f"ttwid={self.ttwid}&msToken={generateMsToken()}; __ac_nonce=0123407cc00a9e438deb4",
}
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
except Exception as err:
logger.error("【X】Request the live room url error: ", err)
else:
match = re.search(r'roomId\\":\\"(\d+)\\"', response.text)
if match is None or len(match.groups()) < 1:
logger.error("【X】No match found for roomId")
self.__room_id = match.group(1)
return self.__room_id
def get_room_status(self):
"""
获取直播间开播状态:
room_status: 2 直播已结束
room_status: 0 直播进行中
"""
url = ('https://live.douyin.com/webcast/room/web/enter/?aid=6383'
'&app_name=douyin_web&live_id=1&device_platform=web&language=zh-CN&enter_from=web_live'
'&cookie_enabled=true&screen_width=1536&screen_height=864&browser_language=zh-CN&browser_platform=Win32'
'&browser_name=Edge&browser_version=133.0.0.0'
f'&web_rid={self.live_id}'
f'&room_id_str={self.room_id}'
'&enter_source=&is_need_double_stream=false&insert_task_id=&live_reason='
'&msToken=&a_bogus=')
resp = requests.get(url, headers={
'User-Agent': self.user_agent,
'Cookie': f'ttwid={self.ttwid};'
})
data = resp.json().get('data')
if data:
room_status = data.get('room_status')
user = data.get('user')
user_id = user.get('id_str')
nickname = user.get('nickname')
logger.info(f"{nickname}】[{user_id}]直播间:{['正在直播', '已结束'][bool(room_status)]}.")
def _connectWebSocket(self):
"""
连接抖音直播间websocket服务器请求直播间数据
"""
wss = ("wss://webcast100-ws-web-lq.douyin.com/webcast/im/push/v2/?app_name=douyin_web"
"&version_code=180800&webcast_sdk_version=1.0.14-beta.0"
"&update_version_code=1.0.14-beta.0&compress=gzip&device_platform=web&cookie_enabled=true"
"&screen_width=1536&screen_height=864&browser_language=zh-CN&browser_platform=Win32"
"&browser_name=Mozilla"
"&browser_version=5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,"
"%20like%20Gecko)%20Chrome/126.0.0.0%20Safari/537.36"
"&browser_online=true&tz_name=Asia/Shanghai"
"&cursor=d-1_u-1_fh-7392091211001140287_t-1721106114633_r-1"
f"&internal_ext=internal_src:dim|wss_push_room_id:{self.room_id}|wss_push_did:7319483754668557238"
f"|first_req_ms:1721106114541|fetch_time:1721106114633|seq:1|wss_info:0-1721106114633-0-0|"
f"wrds_v:7392094459690748497"
f"&host=https://live.douyin.com&aid=6383&live_id=1&did_rule=3&endpoint=live_pc&support_wrds=1"
f"&user_unique_id=7319483754668557238&im_path=/webcast/im/fetch/&identity=audience"
f"&need_persist_msg_count=15&insert_task_id=&live_reason=&room_id={self.room_id}&heartbeatDuration=0")
signature = generateSignature(wss)
wss += f"&signature={signature}"
headers = {
"cookie": f"ttwid={self.ttwid}",
'user-agent': self.user_agent,
}
self.ws = websocket.WebSocketApp(wss,
header=headers,
on_open=self._wsOnOpen,
on_message=self._wsOnMessage,
on_error=self._wsOnError,
on_close=self._wsOnClose)
try:
self.ws.run_forever()
except Exception:
self.stop()
raise
def _sendHeartbeat(self):
"""
发送心跳包
"""
while True:
try:
heartbeat = PushFrame(payload_type='hb').SerializeToString()
self.ws.send(heartbeat, websocket.ABNF.OPCODE_PING)
# print("【√】发送心跳包")
except Exception as e:
logger.error("【X】心跳包检测错误: ", e)
break
else:
time.sleep(5)
def _wsOnOpen(self, ws):
"""
连接建立成功
"""
logger.info("【√】WebSocket连接成功.")
self.ws_open_event.set()
threading.Thread(target=self._sendHeartbeat).start()
def _wsOnMessage(self, ws, message):
"""
接收到数据
:param ws: websocket实例
:param message: 数据
"""
# 根据proto结构体解析对象
package = PushFrame().parse(message)
response = Response().parse(gzip.decompress(package.payload))
# 返回直播间服务器链接存活确认消息,便于持续获取数据
if response.need_ack:
ack = PushFrame(log_id=package.log_id,
payload_type='ack',
payload=response.internal_ext.encode('utf-8')
).SerializeToString()
ws.send(ack, websocket.ABNF.OPCODE_BINARY)
# 根据消息类别解析消息体
for msg in response.messages_list:
method = msg.method
try:
funcs = {
'WebcastChatMessage': parse_chat_msg, # 聊天消息
'WebcastGiftMessage': parse_gif_msg, # 礼物消息
'WebcastLikeMessage': parse_like_msg, # 点赞消息
'WebcastMemberMessage': parse_member_msg, # 进入直播间消息
'WebcastSocialMessage': parse_social_msg, # 关注消息
# 'WebcastRoomUserSeqMessage': parse_room_user_seq_msg, # 直播间统计
# 'WebcastFansclubMessage': parse_fansclub_msg, # 粉丝团消息
'WebcastControlMessage': self.parse_control_msg, # 直播间状态消息
# 'WebcastEmojiChatMessage': parse_emoji_chat_msg, # 聊天表情包消息
# 'WebcastRoomStatsMessage': parse_room_stats_msg, # 直播间统计信息
# 'WebcastRoomMessage': parse_room_msg, # 直播间信息
# 'WebcastRoomRankMessage': parse_rank_msg, # 直播间排行榜信息
# 'WebcastRoomStreamAdaptationMessage': parse_room_stream_adaptation_msg, # 直播间流配置
}
if method in funcs:
funcs[method](msg.payload, self.queue)
except Exception:
logger.error(traceback.format_exc())
def parse_control_msg(self, payload):
'''直播间状态消息'''
message = ControlMessage().parse(payload)
if message.status == 3:
logger.info("直播间已结束")
self.stop()
def _wsOnError(self, ws, error):
logger.error("WebSocket error: ", error)
def _wsOnClose(self, ws, *args):
self.get_room_status()
logger.info("WebSocket connection closed.")
class DouyinLiveWebReply:
def __init__(self, queue: PromptQueue):
self.queue = queue
self.system_text_list = []
self.session_id = 0
self.backend_token = ''
self.live_chat_config = LiveChatConfig()
self.punctuation = ",.!;:,。!?:;"
def _llm(self, prompt, stream=False):
payload = {
"model": "qwen3:30b-a3b",
"messages": [
{
"role": "user",
"content": f"{prompt}/no_think"
}
],
"options": {
"temperature": 0.5
},
"stream": False,
"filterThink": True
}
def _gen():
response = requests.post(f'{Backend.backend_host}{Backend.ollama_uri}', json=payload,
headers={'Authorization': f'Bearer {self.live_chat_config.backend_token}'},
stream=True)
buffer = ''
for line in response.iter_lines():
if not line:
continue
logger.info(f'llm output -> {line.decode()}')
data = json.loads(line.decode()[5:])
content = data['message']['content']
for char in content:
buffer += char
if char in self.punctuation:
if len(buffer.strip()) < 10:
continue # 不够长,继续缓冲
yield buffer.strip()
buffer = ''
if buffer.strip():
yield buffer.strip()
if stream:
return _gen()
else:
response = requests.post(f'{Backend.backend_host}{Backend.ollama_uri}', json=payload,
headers={'Authorization': f'Bearer {self.live_chat_config.backend_token}'},
timeout=10).content.decode()[5:]
response = json.loads(response)
return response['message']['content']
async def post_to_human(self, text: str):
async with httpx.AsyncClient() as client:
await client.post(
f'{live_talking_host}/human',
json={
"type": "echo",
"sessionid": self.session_id,
"text": text
},
timeout=5
)
def __call__(self):
"""
优先从用户交互队列中取提示词如果没有用户交互的数据则输出系统提示词
"""
while True:
try:
is_speaking = requests.post(f'{live_talking_host}/is_speaking', json={'sessionid': self.session_id},
timeout=5).json()['data']
if is_speaking:
time.sleep(0.1)
continue
prompt_data = self.queue.get(False)
print(self.queue.qsize())
if prompt_data is not None:
# live_chat: 弹幕
prompt, live_chat = prompt_data
# logger.info(f'处理提示词: {prompt}')
if live_chat is not None:
llm_output = self._llm(self.live_chat_config.product_related_prompt.format(content=live_chat))
logger.info(f'判断弹幕是否和商品有关: {llm_output}')
if llm_output != '':
continue
reply_messages = self._llm(prompt, True)
for reply_message in reply_messages:
asyncio.run(self.post_to_human(reply_message))
logger.info(f'输出回复: {reply_message}')
# is_speaking此时是False需要等一段时间再查询
time.sleep(0.5)
else:
# 用户交互队列为空,输出系统文案
# time.sleep(1)
system_messages = self.live_chat_config.system_messages
reply_message = system_messages[random.randint(0, len(system_messages) - 1)]
asyncio.run(self.post_to_human(reply_message))
logger.info(f'输出系统文案: {reply_message}')
time.sleep(1)
except Exception:
# 发生异常,输出系统文案
logger.error(traceback.format_exc())
time.sleep(5)
system_messages = self.live_chat_config.system_messages
reply_message = system_messages[random.randint(0, len(system_messages) - 1)]
asyncio.run(self.post_to_human(reply_message))

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,36 @@
from liveMan import DouyinLiveWebFetcher, DouyinLiveWebReply
from argparse import ArgumentParser
from helper import PromptQueue
from multiprocessing import Process, Event
def parse_args():
parser = ArgumentParser()
parser.add_argument('--live_id', type=str, required=True, help='直播间id')
args = parser.parse_args()
return args
def fetch_user_chat_content(live_id, ws_open_event, queue):
fetcher = DouyinLiveWebFetcher(live_id, ws_open_event, queue)
fetcher.start()
def reply_user_chat_content(queue):
reply = DouyinLiveWebReply(queue)
reply()
if __name__ == '__main__':
args = parse_args()
queue = PromptQueue(1000)
ws_open_event = Event()
fetch_process = Process(target=fetch_user_chat_content, args=(args.live_id, ws_open_event, queue))
reply_process = Process(target=reply_user_chat_content, args=(queue,))
fetch_process.start()
ws_open_event.wait()
reply_process.start()
fetch_process.join()
reply_process.join()

@ -0,0 +1,126 @@
from protobuf.douyin import *
import random
from loguru import logger
from helper import LiveChatConfig
live_chat_config = LiveChatConfig()
def parse_chat_msg(payload, queue):
"""聊天消息"""
if not random.random() < live_chat_config.chat_prob / 100:
return
message = ChatMessage().parse(payload)
user_name = message.user.nick_name
user_id = message.user.id
content = message.content
prompt = live_chat_config.chat_prompt.format(content=content)
queue.put((prompt, content))
# logger.info(f"【聊天msg】[{user_id}]{user_name}: {content}")
# logger.info(f"队列数量: {queue.qsize()}")
def parse_gif_msg(payload, queue):
"""礼物消息"""
if not random.random() < live_chat_config.gift_prob / 100:
return
message = GiftMessage().parse(payload)
user_name = message.user.nick_name
gift_name = message.gift.name
gift_count = message.combo_count
prompt = live_chat_config.gift_prompt.format(user_name=user_name, gift_count=gift_count, gift_name=gift_name)
queue.put((prompt, None))
# logger.info(f"【礼物msg】{user_name} 送出了 {gift_name}x{gift_count}")
# logger.info(f"队列数量: {queue.qsize()}")
def parse_like_msg(payload, queue):
'''点赞消息'''
if not random.random() < live_chat_config.like_prob / 100:
return
message = LikeMessage().parse(payload)
user_name = message.user.nick_name
count = message.count
prompt = live_chat_config.like_prompt.format(user_name=user_name, count=count)
queue.put((prompt, None))
# logger.info(f"【点赞msg】{user_name} 点了{count}个赞")
# logger.info(f"队列数量: {queue.qsize()}")
def parse_member_msg(payload, queue):
'''进入直播间消息'''
if not random.random() < live_chat_config.enter_live_room_prob / 100:
return
message = MemberMessage().parse(payload)
user_name = message.user.nick_name
user_id = message.user.id
gender = message.user.gender
if gender in (0, 1):
gender = ["", ""][gender]
prompt = live_chat_config.enter_live_room_prompt.format(user_name=user_name)
queue.put((prompt, None))
# logger.info(f"【进场msg】[{user_id}][{gender}]{user_name} 进入了直播间")
# logger.info(f"队列数量: {queue.qsize()}")
def parse_social_msg(payload, queue):
'''关注消息'''
if not random.random() < live_chat_config.follow_prob / 100:
return
message = SocialMessage().parse(payload)
user_name = message.user.nick_name
user_id = message.user.id
prompt = live_chat_config.follow_prompt.format(user_name=user_name)
queue.put((prompt, None))
# logger.info(f"【关注msg】[{user_id}]{user_name} 关注了主播")
# logger.info(f"队列数量: {queue.qsize()}")
def parse_room_user_seq_msg(payload):
'''直播间统计'''
message = RoomUserSeqMessage().parse(payload)
current = message.total
total = message.total_pv_for_anchor
# print(f"【统计msg】当前观看人数: {current}, 累计观看人数: {total}")
def parse_fansclub_msg(payload):
'''粉丝团消息'''
message = FansclubMessage().parse(payload)
content = message.content
# print(f"【粉丝团msg】 {content}")
def parse_emoji_chat_msg(payload):
'''聊天表情包消息'''
message = EmojiChatMessage().parse(payload)
emoji_id = message.emoji_id
user = message.user
common = message.common
default_content = message.default_content
# print(f"【聊天表情包id】 {emoji_id},user{user},common:{common},default_content:{default_content}")
def parse_room_msg(payload):
message = RoomMessage().parse(payload)
common = message.common
room_id = common.room_id
# print(f"【直播间msg】直播间id:{room_id}")
def parse_room_stats_msg(payload):
message = RoomStatsMessage().parse(payload)
display_long = message.display_long
# print(f"【直播间统计msg】{display_long}")
def parse_rank_msg(payload):
message = RoomRankMessage().parse(payload)
ranks_list = message.ranks_list
# print(f"【直播间排行榜msg】{ranks_list}")
def parse_room_stream_adaptation_msg(payload):
message = RoomStreamAdaptationMessage().parse(payload)
adaptationType = message.adaptation_type
# print(f'直播间adaptation: {adaptationType}')

@ -0,0 +1,753 @@
syntax = "proto3";
package douyin;
message Response {
repeated Message messagesList = 1;
string cursor = 2;
uint64 fetchInterval = 3;
uint64 now = 4;
string internalExt = 5;
uint32 fetchType = 6;
map<string, string> routeParams = 7;
uint64 heartbeatDuration = 8;
bool needAck = 9;
string pushServer = 10;
string liveCursor = 11;
bool historyNoMore = 12;
}
message Message{
string method = 1;
bytes payload = 2;
int64 msgId = 3;
int32 msgType = 4;
int64 offset = 5;
bool needWrdsStore = 6;
int64 wrdsVersion = 7;
string wrdsSubKey = 8;
}
message EmojiChatMessage {
Common common = 1;
User user = 2;
int64 emojiId = 3;
Text emojiContent = 4;
string defaultContent = 5;
Image backgroundImage = 6;
bool fromIntercom = 7;
bool intercomHideUserCard = 8;
}
//
message ChatMessage {
Common common = 1;
User user = 2;
string content = 3;
bool visibleToSender = 4;
Image backgroundImage = 5;
string fullScreenTextColor = 6;
Image backgroundImageV2 = 7;
PublicAreaCommon publicAreaCommon = 9;
Image giftImage = 10;
uint64 agreeMsgId = 11;
uint32 priorityLevel = 12;
LandscapeAreaCommon landscapeAreaCommon = 13;
uint64 eventTime = 15;
bool sendReview = 16;
bool fromIntercom = 17;
bool intercomHideUserCard = 18;
// repeated chatTagsList = 19;
string chatBy = 20;
uint32 individualChatPriority = 21;
Text rtfContent = 22;
}
message LandscapeAreaCommon {
bool showHead = 1;
bool showNickname = 2;
bool showFontColor = 3;
repeated string colorValueList = 4;
repeated CommentTypeTag commentTypeTagsList = 5;
}
message RoomUserSeqMessage {
Common common = 1;
repeated RoomUserSeqMessageContributor ranksList = 2;
int64 total = 3;
string popStr = 4;
repeated RoomUserSeqMessageContributor seatsList = 5;
int64 popularity = 6;
int64 totalUser = 7;
string totalUserStr = 8;
string totalStr = 9;
string onlineUserForAnchor = 10;
string totalPvForAnchor = 11;
string upRightStatsStr = 12;
string upRightStatsStrComplete = 13;
}
message CommonTextMessage {
Common common = 1;
User user = 2;
string scene = 3;
}
message UpdateFanTicketMessage {
Common common = 1;
string roomFanTicketCountText = 2;
uint64 roomFanTicketCount = 3;
bool forceUpdate = 4;
}
message RoomUserSeqMessageContributor {
uint64 score = 1;
User user = 2;
uint64 rank = 3;
uint64 delta = 4;
bool isHidden = 5;
string scoreDescription = 6;
string exactlyScore = 7;
}
//
message GiftMessage {
Common common = 1;
uint64 giftId = 2;
uint64 fanTicketCount = 3;
uint64 groupCount = 4;
uint64 repeatCount = 5;
uint64 comboCount = 6;
User user = 7;
User toUser = 8;
uint32 repeatEnd = 9;
TextEffect textEffect = 10;
uint64 groupId = 11;
uint64 incomeTaskgifts = 12;
uint64 roomFanTicketCount = 13;
GiftIMPriority priority = 14;
GiftStruct gift = 15;
string logId = 16;
uint64 sendType = 17;
PublicAreaCommon publicAreaCommon = 18;
Text trayDisplayText = 19;
uint64 bannedDisplayEffects = 20;
// GiftTrayInfo trayInfo = 21;
// AssetEffectMixInfo assetEffectMixInfo = 22;
bool displayForSelf = 25;
string interactGiftInfo = 26;
string diyItemInfo = 27;
repeated uint64 minAssetSetList = 28;
uint64 totalCount = 29;
uint32 clientGiftSource = 30;
// AnchorGiftData anchorGift = 31;
repeated uint64 toUserIdsList = 32;
uint64 sendTime = 33;
uint64 forceDisplayEffects = 34;
string traceId = 35;
uint64 effectDisplayTs = 36;
}
message GiftStruct {
Image image = 1;
string describe = 2;
bool notify = 3;
uint64 duration = 4;
uint64 id = 5;
// GiftStructFansClubInfo fansclubInfo = 6;
bool forLinkmic = 7;
bool doodle = 8;
bool forFansclub = 9;
bool combo = 10;
uint32 type = 11;
uint32 diamondCount = 12;
bool isDisplayedOnPanel = 13;
uint64 primaryEffectId = 14;
Image giftLabelIcon = 15;
string name = 16;
string region = 17;
string manual = 18;
bool forCustom = 19;
// specialEffectsMap = 20;
Image icon = 21;
uint32 actionType = 22;
// fixme
}
message GiftIMPriority {
repeated uint64 queueSizesList = 1;
uint64 selfQueuePriority = 2;
uint64 priority = 3;
}
message TextEffect {
TextEffectDetail portrait = 1;
TextEffectDetail landscape = 2;
}
message TextEffectDetail {
Text text = 1;
uint32 textFontSize = 2;
Image background = 3;
uint32 start = 4;
uint32 duration = 5;
uint32 x = 6;
uint32 y = 7;
uint32 width = 8;
uint32 height = 9;
uint32 shadowDx = 10;
uint32 shadowDy = 11;
uint32 shadowRadius = 12;
string shadowColor = 13;
string strokeColor = 14;
uint32 strokeWidth = 15;
}
//
message MemberMessage {
Common common = 1;
User user = 2;
uint64 memberCount = 3;
User operator = 4;
bool isSetToAdmin = 5;
bool isTopUser = 6;
uint64 rankScore = 7;
uint64 topUserNo = 8;
uint64 enterType = 9;
uint64 action = 10;
string actionDescription = 11;
uint64 userId = 12;
EffectConfig effectConfig = 13;
string popStr = 14;
EffectConfig enterEffectConfig = 15;
Image backgroundImage = 16;
Image backgroundImageV2 = 17;
Text anchorDisplayText = 18;
PublicAreaCommon publicAreaCommon = 19;
uint64 userEnterTipType = 20;
uint64 anchorEnterTipType = 21;
}
message PublicAreaCommon {
Image userLabel = 1;
uint64 userConsumeInRoom = 2;
uint64 userSendGiftCntInRoom = 3;
}
message EffectConfig {
uint64 type = 1;
Image icon = 2;
uint64 avatarPos = 3;
Text text = 4;
Image textIcon = 5;
uint32 stayTime = 6;
uint64 animAssetId = 7;
Image badge = 8;
repeated uint64 flexSettingArrayList = 9;
Image textIconOverlay = 10;
Image animatedBadge = 11;
bool hasSweepLight = 12;
repeated uint64 textFlexSettingArrayList = 13;
uint64 centerAnimAssetId = 14;
Image dynamicImage = 15;
map<string, string> extraMap = 16;
uint64 mp4AnimAssetId = 17;
uint64 priority = 18;
uint64 maxWaitTime = 19;
string dressId = 20;
uint64 alignment = 21;
uint64 alignmentOffset = 22;
}
message Text {
string key = 1;
string defaultPatter = 2;
TextFormat defaultFormat = 3;
repeated TextPiece piecesList = 4;
}
message TextPiece {
bool type = 1;
TextFormat format = 2;
string stringValue = 3;
TextPieceUser userValue = 4;
TextPieceGift giftValue = 5;
TextPieceHeart heartValue = 6;
TextPiecePatternRef patternRefValue = 7;
TextPieceImage imageValue = 8;
}
message TextPieceImage {
Image image = 1;
float scalingRate = 2;
}
message TextPiecePatternRef {
string key = 1;
string defaultPattern = 2;
}
message TextPieceHeart {
string color = 1;
}
message TextPieceGift {
uint64 giftId = 1;
PatternRef nameRef = 2;
}
message PatternRef {
string key = 1;
string defaultPattern = 2;
}
message TextPieceUser {
User user = 1;
bool withColon = 2;
}
message TextFormat {
string color = 1;
bool bold = 2;
bool italic = 3;
uint32 weight = 4;
uint32 italicAngle = 5;
uint32 fontSize = 6;
bool useHeighLightColor = 7;
bool useRemoteClor = 8;
}
//
message LikeMessage {
Common common = 1;
uint64 count = 2;
uint64 total = 3;
uint64 color = 4;
User user = 5;
string icon = 6;
DoubleLikeDetail doubleLikeDetail = 7;
DisplayControlInfo displayControlInfo = 8;
uint64 linkmicGuestUid = 9;
string scene = 10;
PicoDisplayInfo picoDisplayInfo = 11;
}
message SocialMessage {
Common common = 1;
User user = 2;
uint64 shareType = 3;
uint64 action = 4;
string shareTarget = 5;
uint64 followCount = 6;
PublicAreaCommon publicAreaCommon = 7;
}
message PicoDisplayInfo {
uint64 comboSumCount = 1;
string emoji = 2;
Image emojiIcon = 3;
string emojiText = 4;
}
message DoubleLikeDetail {
bool doubleFlag = 1;
uint32 seqId = 2;
uint32 renewalsNum = 3;
uint32 triggersNum = 4;
}
message DisplayControlInfo {
bool showText = 1;
bool showIcons = 2;
}
message EpisodeChatMessage {
Message common = 1;
User user = 2;
string content = 3;
bool visibleToSende = 4;
// BackgroundImage backgroundImage = 5;
// PublicAreaCommon publicAreaCommon = 6;
Image giftImage = 7;
uint64 agreeMsgId = 8;
repeated string colorValueList = 9;
}
message MatchAgainstScoreMessage {
Common common = 1;
Against against = 2;
uint32 matchStatus = 3;
uint32 displayStatus = 4;
}
message Against {
string leftName = 1;
Image leftLogo = 2;
string leftGoal = 3;
// LeftPlayersList leftPlayersList = 4;
// LeftGoalStageDetail leftGoalStageDetail = 5;
string rightName = 6;
Image rightLogo = 7;
string rightGoal = 8;
// RightPlayersList rightPlayersList = 9;
// RightGoalStageDetail rightGoalStageDetail = 10;
uint64 timestamp = 11;
uint64 version = 12;
uint64 leftTeamId = 13;
uint64 rightTeamId = 14;
uint64 diffSei2absSecond = 15;
uint32 finalGoalStage = 16;
uint32 currentGoalStage = 17;
uint32 leftScoreAddition = 18;
uint32 rightScoreAddition = 19;
uint64 leftGoalInt = 20;
uint64 rightGoalInt = 21;
}
message Common {
string method = 1;
uint64 msgId = 2;
uint64 roomId = 3;
uint64 createTime = 4;
uint32 monitor = 5;
bool isShowMsg = 6;
string describe = 7;
// DisplayText displayText = 8;
uint64 foldType = 9;
uint64 anchorFoldType = 10;
uint64 priorityScore = 11;
string logId = 12;
string msgProcessFilterK = 13;
string msgProcessFilterV = 14;
User user = 15;
// Room room = 16;
uint64 anchorFoldTypeV2 = 17;
uint64 processAtSeiTimeMs = 18;
uint64 randomDispatchMs = 19;
bool isDispatch = 20;
uint64 channelId = 21;
uint64 diffSei2absSecond = 22;
uint64 anchorFoldDuration = 23;
}
message User {
uint64 id = 1;
uint64 shortId = 2;
string nickName = 3;
uint32 gender = 4;
string Signature = 5;
uint32 Level = 6;
uint64 Birthday = 7;
string Telephone = 8;
Image AvatarThumb = 9;
Image AvatarMedium = 10;
Image AvatarLarge = 11;
bool Verified = 12;
uint32 Experience = 13;
string city = 14;
int32 Status = 15;
uint64 CreateTime = 16;
uint64 ModifyTime = 17;
uint32 Secret = 18;
string ShareQrcodeUri = 19;
uint32 IncomeSharePercent = 20;
repeated Image BadgeImageList = 21;
FollowInfo FollowInfo = 22;
PayGrade PayGrade = 23;
FansClub FansClub = 24;
// Border Border = 25;
string SpecialId = 26;
Image AvatarBorder = 27;
Image Medal = 28;
repeated Image RealTimeIconsList = 29;
string displayId = 38;
string secUid = 46;
uint64 fanTicketCount = 1022;
string idStr = 1028;
uint32 ageRange = 1045;
}
message PayGrade {
int64 totalDiamondCount = 1;
Image diamondIcon = 2;
string name = 3;
Image icon = 4;
string nextName = 5;
int64 level = 6;
Image nextIcon = 7;
int64 nextDiamond = 8;
int64 nowDiamond = 9;
int64 thisGradeMinDiamond = 10;
int64 thisGradeMaxDiamond = 11;
int64 payDiamondBak = 12;
string gradeDescribe = 13;
repeated GradeIcon gradeIconList = 14;
int64 screenChatType = 15;
Image imIcon = 16;
Image imIconWithLevel = 17;
Image liveIcon = 18;
Image newImIconWithLevel = 19;
Image newLiveIcon = 20;
int64 upgradeNeedConsume = 21;
string nextPrivileges = 22;
Image background = 23;
Image backgroundBack = 24;
int64 score = 25;
GradeBuffInfo buffInfo = 26;
string gradeBanner = 1001;
Image profileDialogBg = 1002;
Image profileDialogBgBack = 1003;
}
message FansClub{
FansClubData data = 1;
map<int32, FansClubData> preferData = 2;
}
message FansClubData {
string clubName = 1;
int32 level = 2;
int32 userFansClubStatus = 3;
UserBadge badge = 4;
repeated int64 availableGiftIds = 5;
int64 anchorId = 6;
}
message UserBadge {
map<int32, Image> icons = 1;
string title = 2;
}
message GradeBuffInfo {
}
message Border{
}
message GradeIcon{
Image icon = 1;
int64 iconDiamond = 2;
int64 level = 3;
string levelStr = 4;
}
message FollowInfo {
uint64 followingCount = 1;
uint64 followerCount = 2;
uint64 followStatus = 3;
uint64 pushStatus = 4;
string remarkName = 5;
string followerCountStr = 6;
string followingCountStr = 7;
}
message Image {
repeated string urlListList = 1;
string uri = 2;
uint64 height = 3;
uint64 width = 4;
string avgColor = 5;
uint32 imageType = 6;
string openWebUrl = 7;
ImageContent content = 8;
bool isAnimated = 9;
NinePatchSetting FlexSettingList = 10;
NinePatchSetting TextSettingList = 11;
}
message NinePatchSetting {
repeated string settingListList = 1;
}
message ImageContent {
string name = 1;
string fontColor = 2;
uint64 level = 3;
string alternativeText = 4;
}
message PushFrame {
uint64 seqId = 1;
uint64 logId = 2;
uint64 service = 3;
uint64 method = 4;
repeated HeadersList headersList = 5;
string payloadEncoding = 6;
string payloadType = 7;
bytes payload = 8;
}
message kk {
uint32 k = 14;
}
message SendMessageBody {
string conversationId = 1;
uint32 conversationType = 2;
uint64 conversationShortId = 3;
string content = 4;
repeated ExtList ext = 5;
uint32 messageType = 6;
string ticket = 7;
string clientMessageId = 8;
}
message ExtList {
string key = 1;
string value = 2;
}
message Rsp{
int32 a = 1;
int32 b = 2;
int32 c = 3;
string d = 4;
int32 e = 5;
message F {
uint64 q1 = 1;
uint64 q3 = 3;
string q4 = 4;
uint64 q5 = 5;
}
F f = 6;
string g = 7;
uint64 h = 10;
uint64 i = 11;
uint64 j = 13;
}
message PreMessage {
uint32 cmd = 1;
uint32 sequenceId = 2;
string sdkVersion = 3;
string token = 4;
uint32 refer = 5;
uint32 inboxType = 6;
string buildNumber = 7;
SendMessageBody sendMessageBody = 8;
//
string aa = 9;
string devicePlatform = 11;
repeated HeadersList headers = 15;
uint32 authType = 18;
string biz = 21;
string access = 22;
}
message HeadersList {
string key = 1;
string value = 2;
}
message LiveShoppingMessage {
Common common = 1;
int32 msgType = 2;
int64 promotionId = 4;
}
message RoomStatsMessage {
Common common = 1;
string displayShort = 2;
string displayMiddle = 3;
string displayLong = 4;
int64 displayValue = 5;
int64 displayVersion = 6;
bool incremental = 7;
bool isHidden = 8;
int64 total = 9;
int64 displayType = 10;
}
enum CommentTypeTag {
COMMENTTYPETAGUNKNOWN = 0;
COMMENTTYPETAGSTAR = 1;
}
message ProductInfo {
int64 promotionId = 1;
int32 index = 2;
repeated int64 targetFlashUidsList = 3;
int64 explainType = 4;
}
message CategoryInfo {
int32 id = 1;
string name = 2;
repeated int64 promotionIdsList = 3;
string type = 4;
string uniqueIndex = 5;
}
message ProductChangeMessage {
Common common = 1;
int64 updateTimestamp = 2;
string updateToast = 3;
repeated ProductInfo updateProductInfoList = 4;
int64 total = 5;
repeated CategoryInfo updateCategoryInfoList = 8;
}
// from https://github.com/HaoDong108/DouyinBarrageGrab/blob/main/BarrageGrab/proto/message.proto
// status = 3
message ControlMessage {
Common common = 1;
int32 status = 2;
}
// from https://github.com/HaoDong108/DouyinBarrageGrab/blob/main/BarrageGrab/proto/message.proto
message FansclubMessage {
Common commonInfo = 1;
// 12
int32 type = 2;
string content = 3;
User user = 4;
}
// from https://github.com/scx567888/live-room-watcher/blob/master/src/main/proto/douyin_hack/webcast/im/RoomRankMessage.proto
//
message RoomRankMessage {
Common common = 1;
repeated RoomRank ranksList = 2;
message RoomRank{
User user = 1;
string scoreStr = 2;
bool profileHidden = 3;
}
}
// from https://github.com/scx567888/live-room-watcher/blob/master/src/main/proto/douyin_hack/webcast/im/RoomMsgTypeEnum.proto
enum RoomMsgTypeEnum{
DEFAULTROOMMSG = 0;
ECOMLIVEREPLAYSAVEROOMMSG = 1;
CONSUMERRELATIONROOMMSG = 2;
JUMANJIDATAAUTHNOTIFYMSG = 3;
VSWELCOMEMSG = 4;
MINORREFUNDMSG = 5;
PAIDLIVEROOMNOTIFYANCHORMSG = 6;
HOSTTEAMSYSTEMMSG = 7;
}
// from https://github.com/scx567888/live-room-watcher/blob/master/src/main/proto/douyin_hack/webcast/im/RoomMessage.proto
message RoomMessage{
Common common = 1;
string content = 2;
bool supprotLandscape = 3;
RoomMsgTypeEnum roommessagetype = 4;
bool systemTopMsg = 5;
bool forcedGuarantee = 6;
string bizScene = 20;
map<string, string> buriedPointMap = 30;
}
// from https://github.com/jwwsjlm/douyinLive/tree/main/protobuf/douyin.proto
message RoomStreamAdaptationMessage {
Common common = 1;
int32 adaptationType = 2;
float adaptationHeightRatio = 3;
float adaptationBodyCenterRatio = 4;
}

@ -0,0 +1,855 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# sources: douyin.proto
# plugin: python-betterproto
from dataclasses import dataclass
from typing import Dict, List
import betterproto
class CommentTypeTag(betterproto.Enum):
COMMENTTYPETAGUNKNOWN = 0
COMMENTTYPETAGSTAR = 1
class RoomMsgTypeEnum(betterproto.Enum):
"""
from https://github.com/scx567888/live-room-watcher/blob/master/src/main/pr
oto/douyin_hack/webcast/im/RoomMsgTypeEnum.proto
"""
DEFAULTROOMMSG = 0
ECOMLIVEREPLAYSAVEROOMMSG = 1
CONSUMERRELATIONROOMMSG = 2
JUMANJIDATAAUTHNOTIFYMSG = 3
VSWELCOMEMSG = 4
MINORREFUNDMSG = 5
PAIDLIVEROOMNOTIFYANCHORMSG = 6
HOSTTEAMSYSTEMMSG = 7
@dataclass
class Response(betterproto.Message):
messages_list: List["Message"] = betterproto.message_field(1)
cursor: str = betterproto.string_field(2)
fetch_interval: int = betterproto.uint64_field(3)
now: int = betterproto.uint64_field(4)
internal_ext: str = betterproto.string_field(5)
fetch_type: int = betterproto.uint32_field(6)
route_params: Dict[str, str] = betterproto.map_field(
7, betterproto.TYPE_STRING, betterproto.TYPE_STRING
)
heartbeat_duration: int = betterproto.uint64_field(8)
need_ack: bool = betterproto.bool_field(9)
push_server: str = betterproto.string_field(10)
live_cursor: str = betterproto.string_field(11)
history_no_more: bool = betterproto.bool_field(12)
@dataclass
class Message(betterproto.Message):
method: str = betterproto.string_field(1)
payload: bytes = betterproto.bytes_field(2)
msg_id: int = betterproto.int64_field(3)
msg_type: int = betterproto.int32_field(4)
offset: int = betterproto.int64_field(5)
need_wrds_store: bool = betterproto.bool_field(6)
wrds_version: int = betterproto.int64_field(7)
wrds_sub_key: str = betterproto.string_field(8)
@dataclass
class EmojiChatMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
user: "User" = betterproto.message_field(2)
emoji_id: int = betterproto.int64_field(3)
emoji_content: "Text" = betterproto.message_field(4)
default_content: str = betterproto.string_field(5)
background_image: "Image" = betterproto.message_field(6)
from_intercom: bool = betterproto.bool_field(7)
intercom_hide_user_card: bool = betterproto.bool_field(8)
@dataclass
class ChatMessage(betterproto.Message):
"""聊天"""
common: "Common" = betterproto.message_field(1)
user: "User" = betterproto.message_field(2)
content: str = betterproto.string_field(3)
visible_to_sender: bool = betterproto.bool_field(4)
background_image: "Image" = betterproto.message_field(5)
full_screen_text_color: str = betterproto.string_field(6)
background_image_v2: "Image" = betterproto.message_field(7)
public_area_common: "PublicAreaCommon" = betterproto.message_field(9)
gift_image: "Image" = betterproto.message_field(10)
agree_msg_id: int = betterproto.uint64_field(11)
priority_level: int = betterproto.uint32_field(12)
landscape_area_common: "LandscapeAreaCommon" = betterproto.message_field(13)
event_time: int = betterproto.uint64_field(15)
send_review: bool = betterproto.bool_field(16)
from_intercom: bool = betterproto.bool_field(17)
intercom_hide_user_card: bool = betterproto.bool_field(18)
# repeated chatTagsList = 19;
chat_by: str = betterproto.string_field(20)
individual_chat_priority: int = betterproto.uint32_field(21)
rtf_content: "Text" = betterproto.message_field(22)
@dataclass
class LandscapeAreaCommon(betterproto.Message):
show_head: bool = betterproto.bool_field(1)
show_nickname: bool = betterproto.bool_field(2)
show_font_color: bool = betterproto.bool_field(3)
color_value_list: List[str] = betterproto.string_field(4)
comment_type_tags_list: List["CommentTypeTag"] = betterproto.enum_field(5)
@dataclass
class RoomUserSeqMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
ranks_list: List["RoomUserSeqMessageContributor"] = betterproto.message_field(2)
total: int = betterproto.int64_field(3)
pop_str: str = betterproto.string_field(4)
seats_list: List["RoomUserSeqMessageContributor"] = betterproto.message_field(5)
popularity: int = betterproto.int64_field(6)
total_user: int = betterproto.int64_field(7)
total_user_str: str = betterproto.string_field(8)
total_str: str = betterproto.string_field(9)
online_user_for_anchor: str = betterproto.string_field(10)
total_pv_for_anchor: str = betterproto.string_field(11)
up_right_stats_str: str = betterproto.string_field(12)
up_right_stats_str_complete: str = betterproto.string_field(13)
@dataclass
class CommonTextMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
user: "User" = betterproto.message_field(2)
scene: str = betterproto.string_field(3)
@dataclass
class UpdateFanTicketMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
room_fan_ticket_count_text: str = betterproto.string_field(2)
room_fan_ticket_count: int = betterproto.uint64_field(3)
force_update: bool = betterproto.bool_field(4)
@dataclass
class RoomUserSeqMessageContributor(betterproto.Message):
score: int = betterproto.uint64_field(1)
user: "User" = betterproto.message_field(2)
rank: int = betterproto.uint64_field(3)
delta: int = betterproto.uint64_field(4)
is_hidden: bool = betterproto.bool_field(5)
score_description: str = betterproto.string_field(6)
exactly_score: str = betterproto.string_field(7)
@dataclass
class GiftMessage(betterproto.Message):
"""礼物消息"""
common: "Common" = betterproto.message_field(1)
gift_id: int = betterproto.uint64_field(2)
fan_ticket_count: int = betterproto.uint64_field(3)
group_count: int = betterproto.uint64_field(4)
repeat_count: int = betterproto.uint64_field(5)
combo_count: int = betterproto.uint64_field(6)
user: "User" = betterproto.message_field(7)
to_user: "User" = betterproto.message_field(8)
repeat_end: int = betterproto.uint32_field(9)
text_effect: "TextEffect" = betterproto.message_field(10)
group_id: int = betterproto.uint64_field(11)
income_taskgifts: int = betterproto.uint64_field(12)
room_fan_ticket_count: int = betterproto.uint64_field(13)
priority: "GiftIMPriority" = betterproto.message_field(14)
gift: "GiftStruct" = betterproto.message_field(15)
log_id: str = betterproto.string_field(16)
send_type: int = betterproto.uint64_field(17)
public_area_common: "PublicAreaCommon" = betterproto.message_field(18)
tray_display_text: "Text" = betterproto.message_field(19)
banned_display_effects: int = betterproto.uint64_field(20)
# GiftTrayInfo trayInfo = 21; AssetEffectMixInfo assetEffectMixInfo = 22;
display_for_self: bool = betterproto.bool_field(25)
interact_gift_info: str = betterproto.string_field(26)
diy_item_info: str = betterproto.string_field(27)
min_asset_set_list: List[int] = betterproto.uint64_field(28)
total_count: int = betterproto.uint64_field(29)
client_gift_source: int = betterproto.uint32_field(30)
# AnchorGiftData anchorGift = 31;
to_user_ids_list: List[int] = betterproto.uint64_field(32)
send_time: int = betterproto.uint64_field(33)
force_display_effects: int = betterproto.uint64_field(34)
trace_id: str = betterproto.string_field(35)
effect_display_ts: int = betterproto.uint64_field(36)
@dataclass
class GiftStruct(betterproto.Message):
image: "Image" = betterproto.message_field(1)
describe: str = betterproto.string_field(2)
notify: bool = betterproto.bool_field(3)
duration: int = betterproto.uint64_field(4)
id: int = betterproto.uint64_field(5)
# GiftStructFansClubInfo fansclubInfo = 6;
for_linkmic: bool = betterproto.bool_field(7)
doodle: bool = betterproto.bool_field(8)
for_fansclub: bool = betterproto.bool_field(9)
combo: bool = betterproto.bool_field(10)
type: int = betterproto.uint32_field(11)
diamond_count: int = betterproto.uint32_field(12)
is_displayed_on_panel: bool = betterproto.bool_field(13)
primary_effect_id: int = betterproto.uint64_field(14)
gift_label_icon: "Image" = betterproto.message_field(15)
name: str = betterproto.string_field(16)
region: str = betterproto.string_field(17)
manual: str = betterproto.string_field(18)
for_custom: bool = betterproto.bool_field(19)
# specialEffectsMap = 20;
icon: "Image" = betterproto.message_field(21)
action_type: int = betterproto.uint32_field(22)
@dataclass
class GiftIMPriority(betterproto.Message):
queue_sizes_list: List[int] = betterproto.uint64_field(1)
self_queue_priority: int = betterproto.uint64_field(2)
priority: int = betterproto.uint64_field(3)
@dataclass
class TextEffect(betterproto.Message):
portrait: "TextEffectDetail" = betterproto.message_field(1)
landscape: "TextEffectDetail" = betterproto.message_field(2)
@dataclass
class TextEffectDetail(betterproto.Message):
text: "Text" = betterproto.message_field(1)
text_font_size: int = betterproto.uint32_field(2)
background: "Image" = betterproto.message_field(3)
start: int = betterproto.uint32_field(4)
duration: int = betterproto.uint32_field(5)
x: int = betterproto.uint32_field(6)
y: int = betterproto.uint32_field(7)
width: int = betterproto.uint32_field(8)
height: int = betterproto.uint32_field(9)
shadow_dx: int = betterproto.uint32_field(10)
shadow_dy: int = betterproto.uint32_field(11)
shadow_radius: int = betterproto.uint32_field(12)
shadow_color: str = betterproto.string_field(13)
stroke_color: str = betterproto.string_field(14)
stroke_width: int = betterproto.uint32_field(15)
@dataclass
class MemberMessage(betterproto.Message):
"""成员消息"""
common: "Common" = betterproto.message_field(1)
user: "User" = betterproto.message_field(2)
member_count: int = betterproto.uint64_field(3)
operator: "User" = betterproto.message_field(4)
is_set_to_admin: bool = betterproto.bool_field(5)
is_top_user: bool = betterproto.bool_field(6)
rank_score: int = betterproto.uint64_field(7)
top_user_no: int = betterproto.uint64_field(8)
enter_type: int = betterproto.uint64_field(9)
action: int = betterproto.uint64_field(10)
action_description: str = betterproto.string_field(11)
user_id: int = betterproto.uint64_field(12)
effect_config: "EffectConfig" = betterproto.message_field(13)
pop_str: str = betterproto.string_field(14)
enter_effect_config: "EffectConfig" = betterproto.message_field(15)
background_image: "Image" = betterproto.message_field(16)
background_image_v2: "Image" = betterproto.message_field(17)
anchor_display_text: "Text" = betterproto.message_field(18)
public_area_common: "PublicAreaCommon" = betterproto.message_field(19)
user_enter_tip_type: int = betterproto.uint64_field(20)
anchor_enter_tip_type: int = betterproto.uint64_field(21)
@dataclass
class PublicAreaCommon(betterproto.Message):
user_label: "Image" = betterproto.message_field(1)
user_consume_in_room: int = betterproto.uint64_field(2)
user_send_gift_cnt_in_room: int = betterproto.uint64_field(3)
@dataclass
class EffectConfig(betterproto.Message):
type: int = betterproto.uint64_field(1)
icon: "Image" = betterproto.message_field(2)
avatar_pos: int = betterproto.uint64_field(3)
text: "Text" = betterproto.message_field(4)
text_icon: "Image" = betterproto.message_field(5)
stay_time: int = betterproto.uint32_field(6)
anim_asset_id: int = betterproto.uint64_field(7)
badge: "Image" = betterproto.message_field(8)
flex_setting_array_list: List[int] = betterproto.uint64_field(9)
text_icon_overlay: "Image" = betterproto.message_field(10)
animated_badge: "Image" = betterproto.message_field(11)
has_sweep_light: bool = betterproto.bool_field(12)
text_flex_setting_array_list: List[int] = betterproto.uint64_field(13)
center_anim_asset_id: int = betterproto.uint64_field(14)
dynamic_image: "Image" = betterproto.message_field(15)
extra_map: Dict[str, str] = betterproto.map_field(
16, betterproto.TYPE_STRING, betterproto.TYPE_STRING
)
mp4_anim_asset_id: int = betterproto.uint64_field(17)
priority: int = betterproto.uint64_field(18)
max_wait_time: int = betterproto.uint64_field(19)
dress_id: str = betterproto.string_field(20)
alignment: int = betterproto.uint64_field(21)
alignment_offset: int = betterproto.uint64_field(22)
@dataclass
class Text(betterproto.Message):
key: str = betterproto.string_field(1)
default_patter: str = betterproto.string_field(2)
default_format: "TextFormat" = betterproto.message_field(3)
pieces_list: List["TextPiece"] = betterproto.message_field(4)
@dataclass
class TextPiece(betterproto.Message):
type: bool = betterproto.bool_field(1)
format: "TextFormat" = betterproto.message_field(2)
string_value: str = betterproto.string_field(3)
user_value: "TextPieceUser" = betterproto.message_field(4)
gift_value: "TextPieceGift" = betterproto.message_field(5)
heart_value: "TextPieceHeart" = betterproto.message_field(6)
pattern_ref_value: "TextPiecePatternRef" = betterproto.message_field(7)
image_value: "TextPieceImage" = betterproto.message_field(8)
@dataclass
class TextPieceImage(betterproto.Message):
image: "Image" = betterproto.message_field(1)
scaling_rate: float = betterproto.float_field(2)
@dataclass
class TextPiecePatternRef(betterproto.Message):
key: str = betterproto.string_field(1)
default_pattern: str = betterproto.string_field(2)
@dataclass
class TextPieceHeart(betterproto.Message):
color: str = betterproto.string_field(1)
@dataclass
class TextPieceGift(betterproto.Message):
gift_id: int = betterproto.uint64_field(1)
name_ref: "PatternRef" = betterproto.message_field(2)
@dataclass
class PatternRef(betterproto.Message):
key: str = betterproto.string_field(1)
default_pattern: str = betterproto.string_field(2)
@dataclass
class TextPieceUser(betterproto.Message):
user: "User" = betterproto.message_field(1)
with_colon: bool = betterproto.bool_field(2)
@dataclass
class TextFormat(betterproto.Message):
color: str = betterproto.string_field(1)
bold: bool = betterproto.bool_field(2)
italic: bool = betterproto.bool_field(3)
weight: int = betterproto.uint32_field(4)
italic_angle: int = betterproto.uint32_field(5)
font_size: int = betterproto.uint32_field(6)
use_heigh_light_color: bool = betterproto.bool_field(7)
use_remote_clor: bool = betterproto.bool_field(8)
@dataclass
class LikeMessage(betterproto.Message):
"""点赞"""
common: "Common" = betterproto.message_field(1)
count: int = betterproto.uint64_field(2)
total: int = betterproto.uint64_field(3)
color: int = betterproto.uint64_field(4)
user: "User" = betterproto.message_field(5)
icon: str = betterproto.string_field(6)
double_like_detail: "DoubleLikeDetail" = betterproto.message_field(7)
display_control_info: "DisplayControlInfo" = betterproto.message_field(8)
linkmic_guest_uid: int = betterproto.uint64_field(9)
scene: str = betterproto.string_field(10)
pico_display_info: "PicoDisplayInfo" = betterproto.message_field(11)
@dataclass
class SocialMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
user: "User" = betterproto.message_field(2)
share_type: int = betterproto.uint64_field(3)
action: int = betterproto.uint64_field(4)
share_target: str = betterproto.string_field(5)
follow_count: int = betterproto.uint64_field(6)
public_area_common: "PublicAreaCommon" = betterproto.message_field(7)
@dataclass
class PicoDisplayInfo(betterproto.Message):
combo_sum_count: int = betterproto.uint64_field(1)
emoji: str = betterproto.string_field(2)
emoji_icon: "Image" = betterproto.message_field(3)
emoji_text: str = betterproto.string_field(4)
@dataclass
class DoubleLikeDetail(betterproto.Message):
double_flag: bool = betterproto.bool_field(1)
seq_id: int = betterproto.uint32_field(2)
renewals_num: int = betterproto.uint32_field(3)
triggers_num: int = betterproto.uint32_field(4)
@dataclass
class DisplayControlInfo(betterproto.Message):
show_text: bool = betterproto.bool_field(1)
show_icons: bool = betterproto.bool_field(2)
@dataclass
class EpisodeChatMessage(betterproto.Message):
common: "Message" = betterproto.message_field(1)
user: "User" = betterproto.message_field(2)
content: str = betterproto.string_field(3)
visible_to_sende: bool = betterproto.bool_field(4)
# BackgroundImage backgroundImage = 5; PublicAreaCommon publicAreaCommon =
# 6;
gift_image: "Image" = betterproto.message_field(7)
agree_msg_id: int = betterproto.uint64_field(8)
color_value_list: List[str] = betterproto.string_field(9)
@dataclass
class MatchAgainstScoreMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
against: "Against" = betterproto.message_field(2)
match_status: int = betterproto.uint32_field(3)
display_status: int = betterproto.uint32_field(4)
@dataclass
class Against(betterproto.Message):
left_name: str = betterproto.string_field(1)
left_logo: "Image" = betterproto.message_field(2)
left_goal: str = betterproto.string_field(3)
# LeftPlayersList leftPlayersList = 4; LeftGoalStageDetail
# leftGoalStageDetail = 5;
right_name: str = betterproto.string_field(6)
right_logo: "Image" = betterproto.message_field(7)
right_goal: str = betterproto.string_field(8)
# RightPlayersList rightPlayersList = 9; RightGoalStageDetail
# rightGoalStageDetail = 10;
timestamp: int = betterproto.uint64_field(11)
version: int = betterproto.uint64_field(12)
left_team_id: int = betterproto.uint64_field(13)
right_team_id: int = betterproto.uint64_field(14)
diff_sei2abs_second: int = betterproto.uint64_field(15)
final_goal_stage: int = betterproto.uint32_field(16)
current_goal_stage: int = betterproto.uint32_field(17)
left_score_addition: int = betterproto.uint32_field(18)
right_score_addition: int = betterproto.uint32_field(19)
left_goal_int: int = betterproto.uint64_field(20)
right_goal_int: int = betterproto.uint64_field(21)
@dataclass
class Common(betterproto.Message):
method: str = betterproto.string_field(1)
msg_id: int = betterproto.uint64_field(2)
room_id: int = betterproto.uint64_field(3)
create_time: int = betterproto.uint64_field(4)
monitor: int = betterproto.uint32_field(5)
is_show_msg: bool = betterproto.bool_field(6)
describe: str = betterproto.string_field(7)
# DisplayText displayText = 8;
fold_type: int = betterproto.uint64_field(9)
anchor_fold_type: int = betterproto.uint64_field(10)
priority_score: int = betterproto.uint64_field(11)
log_id: str = betterproto.string_field(12)
msg_process_filter_k: str = betterproto.string_field(13)
msg_process_filter_v: str = betterproto.string_field(14)
user: "User" = betterproto.message_field(15)
# Room room = 16;
anchor_fold_type_v2: int = betterproto.uint64_field(17)
process_at_sei_time_ms: int = betterproto.uint64_field(18)
random_dispatch_ms: int = betterproto.uint64_field(19)
is_dispatch: bool = betterproto.bool_field(20)
channel_id: int = betterproto.uint64_field(21)
diff_sei2abs_second: int = betterproto.uint64_field(22)
anchor_fold_duration: int = betterproto.uint64_field(23)
@dataclass
class User(betterproto.Message):
id: int = betterproto.uint64_field(1)
short_id: int = betterproto.uint64_field(2)
nick_name: str = betterproto.string_field(3)
gender: int = betterproto.uint32_field(4)
signature: str = betterproto.string_field(5)
level: int = betterproto.uint32_field(6)
birthday: int = betterproto.uint64_field(7)
telephone: str = betterproto.string_field(8)
avatar_thumb: "Image" = betterproto.message_field(9)
avatar_medium: "Image" = betterproto.message_field(10)
avatar_large: "Image" = betterproto.message_field(11)
verified: bool = betterproto.bool_field(12)
experience: int = betterproto.uint32_field(13)
city: str = betterproto.string_field(14)
status: int = betterproto.int32_field(15)
create_time: int = betterproto.uint64_field(16)
modify_time: int = betterproto.uint64_field(17)
secret: int = betterproto.uint32_field(18)
share_qrcode_uri: str = betterproto.string_field(19)
income_share_percent: int = betterproto.uint32_field(20)
badge_image_list: List["Image"] = betterproto.message_field(21)
follow_info: "FollowInfo" = betterproto.message_field(22)
pay_grade: "PayGrade" = betterproto.message_field(23)
fans_club: "FansClub" = betterproto.message_field(24)
# Border Border = 25;
special_id: str = betterproto.string_field(26)
avatar_border: "Image" = betterproto.message_field(27)
medal: "Image" = betterproto.message_field(28)
real_time_icons_list: List["Image"] = betterproto.message_field(29)
display_id: str = betterproto.string_field(38)
sec_uid: str = betterproto.string_field(46)
fan_ticket_count: int = betterproto.uint64_field(1022)
id_str: str = betterproto.string_field(1028)
age_range: int = betterproto.uint32_field(1045)
@dataclass
class PayGrade(betterproto.Message):
total_diamond_count: int = betterproto.int64_field(1)
diamond_icon: "Image" = betterproto.message_field(2)
name: str = betterproto.string_field(3)
icon: "Image" = betterproto.message_field(4)
next_name: str = betterproto.string_field(5)
level: int = betterproto.int64_field(6)
next_icon: "Image" = betterproto.message_field(7)
next_diamond: int = betterproto.int64_field(8)
now_diamond: int = betterproto.int64_field(9)
this_grade_min_diamond: int = betterproto.int64_field(10)
this_grade_max_diamond: int = betterproto.int64_field(11)
pay_diamond_bak: int = betterproto.int64_field(12)
grade_describe: str = betterproto.string_field(13)
grade_icon_list: List["GradeIcon"] = betterproto.message_field(14)
screen_chat_type: int = betterproto.int64_field(15)
im_icon: "Image" = betterproto.message_field(16)
im_icon_with_level: "Image" = betterproto.message_field(17)
live_icon: "Image" = betterproto.message_field(18)
new_im_icon_with_level: "Image" = betterproto.message_field(19)
new_live_icon: "Image" = betterproto.message_field(20)
upgrade_need_consume: int = betterproto.int64_field(21)
next_privileges: str = betterproto.string_field(22)
background: "Image" = betterproto.message_field(23)
background_back: "Image" = betterproto.message_field(24)
score: int = betterproto.int64_field(25)
buff_info: "GradeBuffInfo" = betterproto.message_field(26)
grade_banner: str = betterproto.string_field(1001)
profile_dialog_bg: "Image" = betterproto.message_field(1002)
profile_dialog_bg_back: "Image" = betterproto.message_field(1003)
@dataclass
class FansClub(betterproto.Message):
data: "FansClubData" = betterproto.message_field(1)
prefer_data: Dict[int, "FansClubData"] = betterproto.map_field(
2, betterproto.TYPE_INT32, betterproto.TYPE_MESSAGE
)
@dataclass
class FansClubData(betterproto.Message):
club_name: str = betterproto.string_field(1)
level: int = betterproto.int32_field(2)
user_fans_club_status: int = betterproto.int32_field(3)
badge: "UserBadge" = betterproto.message_field(4)
available_gift_ids: List[int] = betterproto.int64_field(5)
anchor_id: int = betterproto.int64_field(6)
@dataclass
class UserBadge(betterproto.Message):
icons: Dict[int, "Image"] = betterproto.map_field(
1, betterproto.TYPE_INT32, betterproto.TYPE_MESSAGE
)
title: str = betterproto.string_field(2)
@dataclass
class GradeBuffInfo(betterproto.Message):
pass
@dataclass
class Border(betterproto.Message):
pass
@dataclass
class GradeIcon(betterproto.Message):
icon: "Image" = betterproto.message_field(1)
icon_diamond: int = betterproto.int64_field(2)
level: int = betterproto.int64_field(3)
level_str: str = betterproto.string_field(4)
@dataclass
class FollowInfo(betterproto.Message):
following_count: int = betterproto.uint64_field(1)
follower_count: int = betterproto.uint64_field(2)
follow_status: int = betterproto.uint64_field(3)
push_status: int = betterproto.uint64_field(4)
remark_name: str = betterproto.string_field(5)
follower_count_str: str = betterproto.string_field(6)
following_count_str: str = betterproto.string_field(7)
@dataclass
class Image(betterproto.Message):
url_list_list: List[str] = betterproto.string_field(1)
uri: str = betterproto.string_field(2)
height: int = betterproto.uint64_field(3)
width: int = betterproto.uint64_field(4)
avg_color: str = betterproto.string_field(5)
image_type: int = betterproto.uint32_field(6)
open_web_url: str = betterproto.string_field(7)
content: "ImageContent" = betterproto.message_field(8)
is_animated: bool = betterproto.bool_field(9)
flex_setting_list: "NinePatchSetting" = betterproto.message_field(10)
text_setting_list: "NinePatchSetting" = betterproto.message_field(11)
@dataclass
class NinePatchSetting(betterproto.Message):
setting_list_list: List[str] = betterproto.string_field(1)
@dataclass
class ImageContent(betterproto.Message):
name: str = betterproto.string_field(1)
font_color: str = betterproto.string_field(2)
level: int = betterproto.uint64_field(3)
alternative_text: str = betterproto.string_field(4)
@dataclass
class PushFrame(betterproto.Message):
seq_id: int = betterproto.uint64_field(1)
log_id: int = betterproto.uint64_field(2)
service: int = betterproto.uint64_field(3)
method: int = betterproto.uint64_field(4)
headers_list: List["HeadersList"] = betterproto.message_field(5)
payload_encoding: str = betterproto.string_field(6)
payload_type: str = betterproto.string_field(7)
payload: bytes = betterproto.bytes_field(8)
@dataclass
class Kk(betterproto.Message):
k: int = betterproto.uint32_field(14)
@dataclass
class SendMessageBody(betterproto.Message):
conversation_id: str = betterproto.string_field(1)
conversation_type: int = betterproto.uint32_field(2)
conversation_short_id: int = betterproto.uint64_field(3)
content: str = betterproto.string_field(4)
ext: List["ExtList"] = betterproto.message_field(5)
message_type: int = betterproto.uint32_field(6)
ticket: str = betterproto.string_field(7)
client_message_id: str = betterproto.string_field(8)
@dataclass
class ExtList(betterproto.Message):
key: str = betterproto.string_field(1)
value: str = betterproto.string_field(2)
@dataclass
class Rsp(betterproto.Message):
a: int = betterproto.int32_field(1)
b: int = betterproto.int32_field(2)
c: int = betterproto.int32_field(3)
d: str = betterproto.string_field(4)
e: int = betterproto.int32_field(5)
f: "RspF" = betterproto.message_field(6)
g: str = betterproto.string_field(7)
h: int = betterproto.uint64_field(10)
i: int = betterproto.uint64_field(11)
j: int = betterproto.uint64_field(13)
@dataclass
class RspF(betterproto.Message):
q1: int = betterproto.uint64_field(1)
q3: int = betterproto.uint64_field(3)
q4: str = betterproto.string_field(4)
q5: int = betterproto.uint64_field(5)
@dataclass
class PreMessage(betterproto.Message):
cmd: int = betterproto.uint32_field(1)
sequence_id: int = betterproto.uint32_field(2)
sdk_version: str = betterproto.string_field(3)
token: str = betterproto.string_field(4)
refer: int = betterproto.uint32_field(5)
inbox_type: int = betterproto.uint32_field(6)
build_number: str = betterproto.string_field(7)
send_message_body: "SendMessageBody" = betterproto.message_field(8)
# 字段名待定
aa: str = betterproto.string_field(9)
device_platform: str = betterproto.string_field(11)
headers: List["HeadersList"] = betterproto.message_field(15)
auth_type: int = betterproto.uint32_field(18)
biz: str = betterproto.string_field(21)
access: str = betterproto.string_field(22)
@dataclass
class HeadersList(betterproto.Message):
key: str = betterproto.string_field(1)
value: str = betterproto.string_field(2)
@dataclass
class LiveShoppingMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
msg_type: int = betterproto.int32_field(2)
promotion_id: int = betterproto.int64_field(4)
@dataclass
class RoomStatsMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
display_short: str = betterproto.string_field(2)
display_middle: str = betterproto.string_field(3)
display_long: str = betterproto.string_field(4)
display_value: int = betterproto.int64_field(5)
display_version: int = betterproto.int64_field(6)
incremental: bool = betterproto.bool_field(7)
is_hidden: bool = betterproto.bool_field(8)
total: int = betterproto.int64_field(9)
display_type: int = betterproto.int64_field(10)
@dataclass
class ProductInfo(betterproto.Message):
promotion_id: int = betterproto.int64_field(1)
index: int = betterproto.int32_field(2)
target_flash_uids_list: List[int] = betterproto.int64_field(3)
explain_type: int = betterproto.int64_field(4)
@dataclass
class CategoryInfo(betterproto.Message):
id: int = betterproto.int32_field(1)
name: str = betterproto.string_field(2)
promotion_ids_list: List[int] = betterproto.int64_field(3)
type: str = betterproto.string_field(4)
unique_index: str = betterproto.string_field(5)
@dataclass
class ProductChangeMessage(betterproto.Message):
common: "Common" = betterproto.message_field(1)
update_timestamp: int = betterproto.int64_field(2)
update_toast: str = betterproto.string_field(3)
update_product_info_list: List["ProductInfo"] = betterproto.message_field(4)
total: int = betterproto.int64_field(5)
update_category_info_list: List["CategoryInfo"] = betterproto.message_field(8)
@dataclass
class ControlMessage(betterproto.Message):
"""
from https://github.com/HaoDong108/DouyinBarrageGrab/blob/main/BarrageGrab/
proto/message.proto status = 3 下播
"""
common: "Common" = betterproto.message_field(1)
status: int = betterproto.int32_field(2)
@dataclass
class FansclubMessage(betterproto.Message):
"""
from https://github.com/HaoDong108/DouyinBarrageGrab/blob/main/BarrageGrab/
proto/message.proto
"""
common_info: "Common" = betterproto.message_field(1)
# 升级是1加入是2
type: int = betterproto.int32_field(2)
content: str = betterproto.string_field(3)
user: "User" = betterproto.message_field(4)
@dataclass
class RoomRankMessage(betterproto.Message):
"""
from https://github.com/scx567888/live-room-watcher/blob/master/src/main/pr
oto/douyin_hack/webcast/im/RoomRankMessage.proto 直播间排行榜
"""
common: "Common" = betterproto.message_field(1)
ranks_list: List["RoomRankMessageRoomRank"] = betterproto.message_field(2)
@dataclass
class RoomRankMessageRoomRank(betterproto.Message):
user: "User" = betterproto.message_field(1)
score_str: str = betterproto.string_field(2)
profile_hidden: bool = betterproto.bool_field(3)
@dataclass
class RoomMessage(betterproto.Message):
"""
from https://github.com/scx567888/live-room-
watcher/blob/master/src/main/proto/douyin_hack/webcast/im/RoomMessage.proto
"""
common: "Common" = betterproto.message_field(1)
content: str = betterproto.string_field(2)
supprot_landscape: bool = betterproto.bool_field(3)
roommessagetype: "RoomMsgTypeEnum" = betterproto.enum_field(4)
system_top_msg: bool = betterproto.bool_field(5)
forced_guarantee: bool = betterproto.bool_field(6)
biz_scene: str = betterproto.string_field(20)
buried_point_map: Dict[str, str] = betterproto.map_field(
30, betterproto.TYPE_STRING, betterproto.TYPE_STRING
)
@dataclass
class RoomStreamAdaptationMessage(betterproto.Message):
"""
from https://github.com/jwwsjlm/douyinLive/tree/main/protobuf/douyin.proto
"""
common: "Common" = betterproto.message_field(1)
adaptation_type: int = betterproto.int32_field(2)
adaptation_height_ratio: float = betterproto.float_field(3)
adaptation_body_center_ratio: float = betterproto.float_field(4)

Binary file not shown.

@ -0,0 +1,18 @@
# 生成protobuf的Python版结构体脚本
## 声明:本代码库所有代码均只用于学习研究交流,严禁用于包括但不限于商业谋利、破坏系统、盗取个人信息等不良不法行为,违反此声明使用所产生的一切后果均由违反声明使用者承担。
## 侵权或涉及相关利益请联系作者:[微博](https://weibo.com/u/7751075499)、[B站](https://space.bilibili.com/4690313)、[邮箱](mailto:kukushka@126.com)
> 2024年1月2日
## 0.安装[betterproto](https://github.com/danielgtaylor/python-betterproto)
```shell
pip install betterproto
```
注意`betterproto`版本为`2.0.0b6`必须为2.0以上版本
## 1.在当前目录下打开终端,输入:
```shell
protoc -I . --python_betterproto_out=. douyin.proto
```
当前目录下生成文件`douyin.py`和`__init__.py`即为成功(此程序已经生成可用)。
## Done

@ -0,0 +1,6 @@
requests==2.31.0
betterproto==2.0.0b6
websocket-client==1.7.0
PyExecJS==1.5.1
mini_racer==0.12.4
loguru==0.7.3

@ -0,0 +1,8 @@
sqlite_file = 'live_chat.db'
live_talking_host = 'http://192.168.10.96:8020'
class Backend:
backend_host = 'http://192.168.10.25:9909'
ollama_uri = '/live-digital-avatar-manage/ollama/generate'
login_uri = '/live-digital-avatar-manage/auth/login'

7011
sign.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save