拆出模型模块

This commit is contained in:
John Smith 2021-12-12 21:54:07 +08:00
parent 0404f97134
commit 104a080167
4 changed files with 461 additions and 288 deletions

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from .blivedm import *
from .models import *

View File

@ -1,17 +1,18 @@
# -*- coding: utf-8 -*-
import asyncio
import collections
import enum
import json
import logging
import ssl as ssl_
import struct
import zlib
from collections import namedtuple
from enum import IntEnum
from typing import *
import aiohttp
from . import models
logger = logging.getLogger('blivedm')
ROOM_INIT_URL = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom'
@ -21,14 +22,14 @@ DEFAULT_DANMAKU_SERVER_LIST = [
]
HEADER_STRUCT = struct.Struct('>I2H2I')
HeaderTuple = namedtuple('HeaderTuple', ('pack_len', 'raw_header_size', 'ver', 'operation', 'seq_id'))
HeaderTuple = collections.namedtuple('HeaderTuple', ('pack_len', 'raw_header_size', 'ver', 'operation', 'seq_id'))
WS_BODY_PROTOCOL_VERSION_INFLATE = 0
WS_BODY_PROTOCOL_VERSION_NORMAL = 1
WS_BODY_PROTOCOL_VERSION_DEFLATE = 2
# go-common\app\service\main\broadcast\model\operation.go
class Operation(IntEnum):
class Operation(enum.IntEnum):
HANDSHAKE = 0
HANDSHAKE_REPLY = 1
HEARTBEAT = 2
@ -56,270 +57,28 @@ class InitError(Exception):
"""初始化失败"""
class DanmakuMessage:
def __init__(self, mode, font_size, color, timestamp, rnd, uid_crc32, msg_type, bubble,
msg,
uid, uname, admin, vip, svip, urank, mobile_verify, uname_color,
medal_level, medal_name, runame, room_id, mcolor, special_medal,
user_level, ulevel_color, ulevel_rank,
old_title, title,
privilege_type):
"""
:param mode: 弹幕显示模式滚动顶部底部
:param font_size: 字体尺寸
:param color: 颜色
:param timestamp: 时间戳
:param rnd: 随机数
:param uid_crc32: 用户ID文本的CRC32
:param msg_type: 是否礼物弹幕节奏风暴
:param bubble: 右侧评论栏气泡
:param msg: 弹幕内容
:param uid: 用户ID
:param uname: 用户名
:param admin: 是否房管
:param vip: 是否月费老爷
:param svip: 是否年费老爷
:param urank: 用户身份用来判断是否正式会员猜测非正式会员为5000正式会员为10000
:param mobile_verify: 是否绑定手机
:param uname_color: 用户名颜色
:param medal_level: 勋章等级
:param medal_name: 勋章名
:param runame: 勋章房间主播名
:param room_id: 勋章房间ID
:param mcolor: 勋章颜色
:param special_medal: 特殊勋章
:param user_level: 用户等级
:param ulevel_color: 用户等级颜色
:param ulevel_rank: 用户等级排名>50000时为'>50000'
:param old_title: 旧头衔
:param title: 头衔
:param privilege_type: 舰队类型0非舰队1总督2提督3舰长
"""
self.mode = mode
self.font_size = font_size
self.color = color
self.timestamp = timestamp
self.rnd = rnd
self.uid_crc32 = uid_crc32
self.msg_type = msg_type
self.bubble = bubble
self.msg = msg
self.uid = uid
self.uname = uname
self.admin = admin
self.vip = vip
self.svip = svip
self.urank = urank
self.mobile_verify = mobile_verify
self.uname_color = uname_color
self.medal_level = medal_level
self.medal_name = medal_name
self.runame = runame
self.room_id = room_id
self.mcolor = mcolor
self.special_medal = special_medal
self.user_level = user_level
self.ulevel_color = ulevel_color
self.ulevel_rank = ulevel_rank
self.old_title = old_title
self.title = title
self.privilege_type = privilege_type
@classmethod
def from_command(cls, info: dict):
return cls(
info[0][1], info[0][2], info[0][3], info[0][4], info[0][5], info[0][7], info[0][9], info[0][10],
info[1],
*info[2][:8],
*(info[3][:6] or (0, '', '', 0, 0, 0)),
info[4][0], info[4][2], info[4][3],
*info[5][:2],
info[7]
)
class GiftMessage:
def __init__(self, gift_name, num, uname, face, guard_level, uid, timestamp, gift_id,
gift_type, action, price, rnd, coin_type, total_coin):
"""
:param gift_name: 礼物名
:param num: 礼物数量
:param uname: 用户名
:param face: 用户头像URL
:param guard_level: 舰队等级0非舰队1总督2提督3舰长
:param uid: 用户ID
:param timestamp: 时间戳
:param gift_id: 礼物ID
:param gift_type: 礼物类型未知
:param action: 目前遇到的有'喂食''赠送'
:param price: 礼物单价瓜子数
:param rnd: 随机数
:param coin_type: 瓜子类型'silver''gold'
:param total_coin: 总瓜子数
"""
self.gift_name = gift_name
self.num = num
self.uname = uname
self.face = face
self.guard_level = guard_level
self.uid = uid
self.timestamp = timestamp
self.gift_id = gift_id
self.gift_type = gift_type
self.action = action
self.price = price
self.rnd = rnd
self.coin_type = coin_type
self.total_coin = total_coin
@classmethod
def from_command(cls, data: dict):
return cls(
data['giftName'], data['num'], data['uname'], data['face'], data['guard_level'],
data['uid'], data['timestamp'], data['giftId'], data['giftType'],
data['action'], data['price'], data['rnd'], data['coin_type'], data['total_coin']
)
class GuardBuyMessage:
def __init__(self, uid, username, guard_level, num, price, gift_id, gift_name,
start_time, end_time):
"""
:param uid: 用户ID
:param username: 用户名
:param guard_level: 舰队等级0非舰队1总督2提督3舰长
:param num: 数量
:param price: 单价金瓜子数
:param gift_id: 礼物ID
:param gift_name: 礼物名
:param start_time: 开始时间戳
:param end_time: 结束时间戳
"""
self.uid = uid
self.username = username
self.guard_level = guard_level
self.num = num
self.price = price
self.gift_id = gift_id
self.gift_name = gift_name
self.start_time = start_time
self.end_time = end_time
@classmethod
def from_command(cls, data: dict):
return cls(
data['uid'], data['username'], data['guard_level'], data['num'], data['price'],
data['gift_id'], data['gift_name'], data['start_time'], data['end_time']
)
class SuperChatMessage:
def __init__(self, price, message, message_jpn, start_time, end_time, time, id_,
gift_id, gift_name, uid, uname, face, guard_level, user_level,
background_bottom_color, background_color, background_icon, background_image,
background_price_color):
"""
:param price: 价格人民币
:param message: 消息
:param message_jpn: 消息日文翻译目前只出现在SUPER_CHAT_MESSAGE_JPN
:param start_time: 开始时间戳
:param end_time: 结束时间戳
:param time: 剩余时间
:param id_: str消息ID删除时用
:param gift_id: 礼物ID
:param gift_name: 礼物名
:param uid: 用户ID
:param uname: 用户名
:param face: 用户头像URL
:param guard_level: 舰队等级0非舰队1总督2提督3舰长
:param user_level: 用户等级
:param background_bottom_color: 底部背景色
:param background_color: 背景色
:param background_icon: 背景图标
:param background_image: 背景图
:param background_price_color: 背景价格颜色
"""
self.price = price
self.message = message
self.message_jpn = message_jpn
self.start_time = start_time
self.end_time = end_time
self.time = time
self.id = id_
self.gift_id = gift_id
self.gift_name = gift_name
self.uid = uid
self.uname = uname
self.face = face
self.guard_level = guard_level
self.user_level = user_level
self.background_bottom_color = background_bottom_color
self.background_color = background_color
self.background_icon = background_icon
self.background_image = background_image
self.background_price_color = background_price_color
@classmethod
def from_command(cls, data: dict):
return cls(
data['price'], data['message'], data['message_trans'], data['start_time'],
data['end_time'], data['time'], data['id'], data['gift']['gift_id'],
data['gift']['gift_name'], data['uid'], data['user_info']['uname'],
data['user_info']['face'], data['user_info']['guard_level'],
data['user_info']['user_level'], data['background_bottom_color'],
data['background_color'], data['background_icon'], data['background_image'],
data['background_price_color']
)
class SuperChatDeleteMessage:
def __init__(self, ids):
"""
:param ids: 消息ID数组
"""
self.ids = ids
@classmethod
def from_command(cls, data: dict):
return cls(
data['ids']
)
class BLiveClient:
_COMMAND_HANDLERS: Dict[str, Optional[Callable[['BLiveClient', dict], Awaitable]]] = {
# 收到弹幕
# go-common\app\service\live\live-dm\service\v1\send.go
'DANMU_MSG': lambda client, command: client._on_receive_danmaku(
DanmakuMessage.from_command(command['info'])
'DANMU_MSG': lambda client, command: client._on_receive_danmaku( # noqa
models.DanmakuMessage.from_command(command['info'])
),
# 有人送礼
'SEND_GIFT': lambda client, command: client._on_receive_gift(
GiftMessage.from_command(command['data'])
'SEND_GIFT': lambda client, command: client._on_receive_gift( # noqa
models.GiftMessage.from_command(command['data'])
),
# 有人上舰
'GUARD_BUY': lambda client, command: client._on_buy_guard(
GuardBuyMessage.from_command(command['data'])
'GUARD_BUY': lambda client, command: client._on_buy_guard( # noqa
models.GuardBuyMessage.from_command(command['data'])
),
# 醒目留言
'SUPER_CHAT_MESSAGE': lambda client, command: client._on_super_chat(
SuperChatMessage.from_command(command['data'])
'SUPER_CHAT_MESSAGE': lambda client, command: client._on_super_chat( # noqa
models.SuperChatMessage.from_command(command['data'])
),
# 删除醒目留言
'SUPER_CHAT_MESSAGE_DELETE': lambda client, command: client._on_super_chat_delete(
SuperChatDeleteMessage.from_command(command['data'])
'SUPER_CHAT_MESSAGE_DELETE': lambda client, command: client._on_super_chat_delete( # noqa
models.SuperChatDeleteMessage.from_command(command['data'])
)
}
# 其他常见命令
@ -332,7 +91,7 @@ class BLiveClient:
_COMMAND_HANDLERS[cmd] = None
del cmd
def __init__(self, room_id, uid=0, session: aiohttp.ClientSession=None,
def __init__(self, room_id, uid=0, session: aiohttp.ClientSession = None,
heartbeat_interval=30, ssl=True, loop=None):
"""
:param room_id: URL中的房间ID可以为短ID
@ -354,8 +113,7 @@ class BLiveClient:
if loop is not None:
self._loop = loop
elif session is not None:
# noinspection PyDeprecation
self._loop = session.loop
self._loop = session.loop # noqa
else:
self._loop = asyncio.get_event_loop()
self._future = None
@ -366,13 +124,11 @@ class BLiveClient:
else:
self._session = session
self._own_session = False
# noinspection PyDeprecation
if self._session.loop is not self._loop:
if self._session.loop is not self._loop: # noqa
raise RuntimeError('BLiveClient and session has to use same event loop')
self._heartbeat_interval = heartbeat_interval
# noinspection PyProtectedMember
self._ssl = ssl if ssl else ssl_._create_unverified_context()
self._ssl = ssl if ssl else ssl_._create_unverified_context() # noqa
self._websocket = None
self._heartbeat_timer_handle = None
@ -556,7 +312,8 @@ class BLiveClient:
)
# 处理消息
async for message in websocket: # type: aiohttp.WSMessage
message: aiohttp.WSMessage
async for message in websocket:
retry_count = 0
if message.type != aiohttp.WSMsgType.BINARY:
logger.warning('room %d 未知的websocket消息type=%s %s', self.room_id,
@ -567,7 +324,7 @@ class BLiveClient:
await self._handle_message(message.data)
except asyncio.CancelledError:
raise
except Exception:
except Exception: # noqa
logger.exception('room %d 处理消息时发生错误:', self.room_id)
except asyncio.CancelledError:
@ -659,31 +416,31 @@ class BLiveClient:
"""
pass
async def _on_receive_danmaku(self, danmaku: DanmakuMessage):
async def _on_receive_danmaku(self, danmaku: models.DanmakuMessage):
"""
收到弹幕
"""
pass
async def _on_receive_gift(self, gift: GiftMessage):
async def _on_receive_gift(self, gift: models.GiftMessage):
"""
收到礼物
"""
pass
async def _on_buy_guard(self, message: GuardBuyMessage):
async def _on_buy_guard(self, message: models.GuardBuyMessage):
"""
有人上舰
"""
pass
async def _on_super_chat(self, message: SuperChatMessage):
async def _on_super_chat(self, message: models.SuperChatMessage):
"""
醒目留言
"""
pass
async def _on_super_chat_delete(self, message: SuperChatDeleteMessage):
async def _on_super_chat_delete(self, message: models.SuperChatDeleteMessage):
"""
删除醒目留言
"""

417
blivedm/models.py Normal file
View File

@ -0,0 +1,417 @@
# -*- coding: utf-8 -*-
from typing import *
__all__ = (
'DanmakuMessage',
'GiftMessage',
'GuardBuyMessage',
'SuperChatMessage',
'SuperChatDeleteMessage',
)
class DanmakuMessage:
"""
弹幕消息
:param mode: 弹幕显示模式滚动顶部底部
:param font_size: 字体尺寸
:param color: 颜色
:param timestamp: 时间戳毫秒
:param rnd: 随机数
:param uid_crc32: 用户ID文本的CRC32
:param msg_type: 是否礼物弹幕节奏风暴
:param bubble: 右侧评论栏气泡
:param msg: 弹幕内容
:param uid: 用户ID
:param uname: 用户名
:param admin: 是否房管
:param vip: 是否月费老爷
:param svip: 是否年费老爷
:param urank: 用户身份用来判断是否正式会员猜测非正式会员为5000正式会员为10000
:param mobile_verify: 是否绑定手机
:param uname_color: 用户名颜色
:param medal_level: 勋章等级
:param medal_name: 勋章名
:param runame: 勋章房间主播名
:param medal_room_id: 勋章房间ID
:param mcolor: 勋章颜色
:param special_medal: 特殊勋章
:param user_level: 用户等级
:param ulevel_color: 用户等级颜色
:param ulevel_rank: 用户等级排名>50000时为'>50000'
:param old_title: 旧头衔
:param title: 头衔
:param privilege_type: 舰队类型0非舰队1总督2提督3舰长
"""
def __init__(
self,
mode: int = None,
font_size: int = None,
color: int = None,
timestamp: int = None,
rnd: int = None,
uid_crc32: str = None,
msg_type: int = None,
bubble: int = None,
msg: str = None,
uid: int = None,
uname: str = None,
admin: int = None,
vip: int = None,
svip: int = None,
urank: int = None,
mobile_verify: int = None,
uname_color: str = None,
medal_level: str = None,
medal_name: str = None,
runame: str = None,
medal_room_id: int = None,
mcolor: int = None,
special_medal: str = None,
user_level: int = None,
ulevel_color: int = None,
ulevel_rank: str = None,
old_title: str = None,
title: str = None,
privilege_type: int = None,
):
self.mode: int = mode
self.font_size: int = font_size
self.color: int = color
self.timestamp: int = timestamp
self.rnd: int = rnd
self.uid_crc32: str = uid_crc32
self.msg_type: int = msg_type
self.bubble: int = bubble
self.msg: str = msg
self.uid: int = uid
self.uname: str = uname
self.admin: int = admin
self.vip: int = vip
self.svip: int = svip
self.urank: int = urank
self.mobile_verify: int = mobile_verify
self.uname_color: str = uname_color
self.medal_level: str = medal_level
self.medal_name: str = medal_name
self.runame: str = runame
self.medal_room_id: int = medal_room_id
self.mcolor: int = mcolor
self.special_medal: str = special_medal
self.user_level: int = user_level
self.ulevel_color: int = ulevel_color
self.ulevel_rank: str = ulevel_rank
self.old_title: str = old_title
self.title: str = title
self.privilege_type: int = privilege_type
@classmethod
def from_command(cls, info: dict):
if len(info[3]) != 0:
medal_level = info[3][0]
medal_name = info[3][1]
runame = info[3][2]
room_id = info[3][3]
mcolor = info[3][4]
special_medal = info[3][5]
else:
medal_level = 0
medal_name = ''
runame = ''
room_id = 0
mcolor = 0
special_medal = 0
return cls(
mode=info[0][1],
font_size=info[0][2],
color=info[0][3],
timestamp=info[0][4],
rnd=info[0][5],
uid_crc32=info[0][7],
msg_type=info[0][9],
bubble=info[0][10],
msg=info[1],
uid=info[2][0],
uname=info[2][1],
admin=info[2][2],
vip=info[2][3],
svip=info[2][4],
urank=info[2][5],
mobile_verify=info[2][6],
uname_color=info[2][7],
medal_level=medal_level,
medal_name=medal_name,
runame=runame,
medal_room_id=room_id,
mcolor=mcolor,
special_medal=special_medal,
user_level=info[4][0],
ulevel_color=info[4][2],
ulevel_rank=info[4][3],
old_title=info[5][0],
title=info[5][1],
privilege_type=info[7],
)
class GiftMessage:
"""
礼物消息
:param gift_name: 礼物名
:param num: 数量
:param uname: 用户名
:param face: 用户头像URL
:param guard_level: 舰队等级0非舰队1总督2提督3舰长
:param uid: 用户ID
:param timestamp: 时间戳
:param gift_id: 礼物ID
:param gift_type: 礼物类型未知
:param action: 目前遇到的有'喂食''赠送'
:param price: 礼物单价瓜子数
:param rnd: 随机数估计是去重用的
:param coin_type: 瓜子类型'silver''gold'
:param total_coin: 总瓜子数
"""
def __init__(
self,
gift_name: str = None,
num: int = None,
uname: str = None,
face: str = None,
guard_level: int = None,
uid: int = None,
timestamp: int = None,
gift_id: int = None,
gift_type: int = None,
action: str = None,
price: int = None,
rnd: str = None,
coin_type: str = None,
total_coin: int = None,
):
self.gift_name = gift_name
self.num = num
self.uname = uname
self.face = face
self.guard_level = guard_level
self.uid = uid
self.timestamp = timestamp
self.gift_id = gift_id
self.gift_type = gift_type
self.action = action
self.price = price
self.rnd = rnd
self.coin_type = coin_type
self.total_coin = total_coin
@classmethod
def from_command(cls, data: dict):
return cls(
gift_name=data['giftName'],
num=data['num'],
uname=data['uname'],
face=data['face'],
guard_level=data['guard_level'],
uid=data['uid'],
timestamp=data['timestamp'],
gift_id=data['giftId'],
gift_type=data['giftType'],
action=data['action'],
price=data['price'],
rnd=data['rnd'],
coin_type=data['coin_type'],
total_coin=data['total_coin'],
)
class GuardBuyMessage:
"""
上舰消息
:param uid: 用户ID
:param username: 用户名
:param guard_level: 舰队等级0非舰队1总督2提督3舰长
:param num: 数量
:param price: 单价金瓜子数
:param gift_id: 礼物ID
:param gift_name: 礼物名
:param start_time: 开始时间戳和结束时间戳一样
:param end_time: 结束时间戳和开始时间戳一样
"""
def __init__(
self,
uid: int = None,
username: str = None,
guard_level: int = None,
num: int = None,
price: int = None,
gift_id: int = None,
gift_name: str = None,
start_time: int = None,
end_time: int = None,
):
self.uid: int = uid
self.username: str = username
self.guard_level: int = guard_level
self.num: int = num
self.price: int = price
self.gift_id: int = gift_id
self.gift_name: str = gift_name
self.start_time: int = start_time
self.end_time: int = end_time
@classmethod
def from_command(cls, data: dict):
return cls(
uid=data['uid'],
username=data['username'],
guard_level=data['guard_level'],
num=data['num'],
price=data['price'],
gift_id=data['gift_id'],
gift_name=data['gift_name'],
start_time=data['start_time'],
end_time=data['end_time'],
)
class SuperChatMessage:
"""
醒目留言消息
:param price: 价格人民币
:param message: 消息
:param message_jpn: 消息日文翻译目前只出现在SUPER_CHAT_MESSAGE_JPN
:param start_time: 开始时间戳
:param end_time: 结束时间戳
:param time: 持续时间结束时间戳 - 开始时间戳
:param id_: str醒目留言ID删除时用
:param gift_id: 礼物ID
:param gift_name: 礼物名
:param uid: 用户ID
:param uname: 用户名
:param face: 用户头像URL
:param guard_level: 舰队等级0非舰队1总督2提督3舰长
:param user_level: 用户等级
:param background_bottom_color: 底部背景色'#rrggbb'
:param background_color: 背景色'#rrggbb'
:param background_icon: 背景图标
:param background_image: 背景图URL
:param background_price_color: 背景价格颜色'#rrggbb'
"""
def __init__(
self,
price: int = None,
message: str = None,
message_jpn: str = None,
start_time: int = None,
end_time: int = None,
time: int = None,
id_: int = None,
gift_id: int = None,
gift_name: str = None,
uid: int = None,
uname: str = None,
face: str = None,
guard_level: int = None,
user_level: int = None,
background_bottom_color: str = None,
background_color: str = None,
background_icon: str = None,
background_image: str = None,
background_price_color: str = None,
):
self.price: int = price
self.message: str = message
self.message_jpn: str = message_jpn
self.start_time: int = start_time
self.end_time: int = end_time
self.time: int = time
self.id: int = id_
self.gift_id: int = gift_id
self.gift_name: str = gift_name
self.uid: int = uid
self.uname: str = uname
self.face: str = face
self.guard_level: int = guard_level
self.user_level: int = user_level
self.background_bottom_color: str = background_bottom_color
self.background_color: str = background_color
self.background_icon: str = background_icon
self.background_image: str = background_image
self.background_price_color: str = background_price_color
@classmethod
def from_command(cls, data: dict):
return cls(
price=data['price'],
message=data['message'],
message_jpn=data['message_trans'],
start_time=data['start_time'],
end_time=data['end_time'],
time=data['time'],
id_=data['id'],
gift_id=data['gift']['gift_id'],
gift_name=data['gift']['gift_name'],
uid=data['uid'],
uname=data['user_info']['uname'],
face=data['user_info']['face'],
guard_level=data['user_info']['guard_level'],
user_level=data['user_info']['user_level'],
background_bottom_color=data['background_bottom_color'],
background_color=data['background_color'],
background_icon=data['background_icon'],
background_image=data['background_image'],
background_price_color=data['background_price_color'],
)
class SuperChatDeleteMessage:
"""
删除醒目留言消息
:param ids: 醒目留言ID数组
"""
def __init__(
self,
ids: List[int] = None,
):
self.ids: List[int] = ids
@classmethod
def from_command(cls, data: dict):
return cls(
ids=data['ids'],
)

View File

@ -1,10 +1,24 @@
# -*- coding: utf-8 -*-
import asyncio
import blivedm
async def main():
# 直播间ID的取值看直播间URL
# 如果SSL验证失败就把ssl设为FalseB站真的有过忘续证书的情况
client = MyBLiveClient(room_id=21224291, ssl=True)
future = client.start()
try:
# 5秒后停止测试用
# await asyncio.sleep(5)
# future = client.stop()
await future
finally:
await client.close()
class MyBLiveClient(blivedm.BLiveClient):
# 演示如何自定义handler
_COMMAND_HANDLERS = blivedm.BLiveClient._COMMAND_HANDLERS.copy()
@ -29,20 +43,5 @@ class MyBLiveClient(blivedm.BLiveClient):
print(f'醒目留言 ¥{message.price} {message.uname}{message.message}')
async def main():
# 直播间ID的取值看直播间URL
# 如果SSL验证失败就把ssl设为FalseB站真的有过忘续证书的情况
client = MyBLiveClient(room_id=21449083, ssl=True)
future = client.start()
try:
# 5秒后停止测试用
# await asyncio.sleep(5)
# future = client.stop()
await future
finally:
await client.close()
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())