1
0
mirror of https://github.com/wbt5/real-url.git synced 2025-07-25 10:01:45 +08:00

新增花椒直播弹幕

This commit is contained in:
wbt5 2020-07-04 08:29:09 +08:00
parent 0ea4fe43e7
commit 4791d0a68f
5 changed files with 1978 additions and 6 deletions

View File

@ -8,6 +8,7 @@ from .huya import Huya
from .kuaishou import KuaiShou from .kuaishou import KuaiShou
from .huomao import HuoMao from .huomao import HuoMao
from .egame import eGame from .egame import eGame
from .huajiao import HuaJiao
__all__ = ['DanmakuClient'] __all__ = ['DanmakuClient']
@ -30,9 +31,11 @@ class DanmakuClient:
'huya.com': Huya, 'huya.com': Huya,
'huomao.com': HuoMao, 'huomao.com': HuoMao,
'kuaishou.com': KuaiShou, 'kuaishou.com': KuaiShou,
'egame.qq.com': eGame}.items(): 'egame.qq.com': eGame,
'huajiao.com': HuaJiao}.items():
if re.match(r'^(?:http[s]?://)?.*?%s/(.+?)$' % u, url): if re.match(r'^(?:http[s]?://)?.*?%s/(.+?)$' % u, url):
self.__site = s self.__site = s
self.__u = u
break break
if self.__site is None: if self.__site is None:
print('Invalid link!') print('Invalid link!')
@ -63,10 +66,31 @@ class DanmakuClient:
await asyncio.sleep(1) await asyncio.sleep(1)
await self.init_ws() await self.init_ws()
await asyncio.sleep(1) await asyncio.sleep(1)
async def init_ws_huajiao(self):
rid = re.search(r'\d+', self.__url).group(0)
s = self.__site(rid)
self.__ws = await self.__hs.ws_connect(self.__site.ws_url)
await self.__ws.send_bytes(s.sendHandshakePack())
count = 0
async for msg in self.__ws:
if count == 0:
await self.__ws.send_bytes(s.sendLoginPack(msg.data))
elif count == 1:
await self.__ws.send_bytes(s.sendJoinChatroomPack(msg.data))
elif count > 2:
ms = s.decode_msg(msg.data)
for m in ms:
await self.__dm_queue.put(m)
count += 1
await self.heartbeats()
async def start(self): async def start(self):
await self.init_ws() if self.__u == 'huajiao.com':
await asyncio.gather( await self.init_ws_huajiao()
self.heartbeats(), else:
self.fetch_danmaku(), await self.init_ws()
) await asyncio.gather(
self.heartbeats(),
self.fetch_danmaku(),
)

174
danmu/danmaku/huajiao.proto Normal file
View File

@ -0,0 +1,174 @@
syntax = "proto2";
package HuaJiaoPack;
message Message {
required uint32 msgid = 1;
required uint64 sn = 2;
optional string sender = 3;
optional string receiver = 4;
optional string receiver_type = 5;
optional Request req = 6;
optional Response resp = 7;
optional Notify notify = 8;
optional string sender_type = 12;
message Request {
optional LoginReq login = 2;
optional InitLoginReq init_login_req = 9;
optional Service_Req service_req = 11;
message LoginReq {
required string mobile_type = 1;
required uint32 net_type = 2;
required string server_ram = 3;
optional bytes secret_ram = 4;
optional uint32 app_id = 5[default = 2000];
optional string platform = 8;
optional string verf_code = 9;
optional bool not_encrypt = 10;
}
message InitLoginReq {
required string client_ram = 1;
optional string sig = 2;
}
message Service_Req {
required uint32 service_id = 1;
required bytes request = 2;
}
}
message Response {
optional LoginResp login = 3;
optional ChatResp chat = 4;
optional InitLoginResp init_login_resp = 10;
optional Service_Resp service_resp = 12;
message LoginResp {
required uint32 timestamp = 1;
required string session_id = 2;
required string session_key = 3;
optional string client_login_ip = 4;
optional string serverip = 5;
}
message InitLoginResp {
required string client_ram = 1;
required string server_ram = 2;
}
message Service_Resp {
required uint32 service_id = 1;
required bytes response = 2;
}
message ChatResp {
required uint32 result = 1;
optional uint32 body_id = 2;
}
}
message Notify {
optional NewMessageNotify newinfo_ntf = 1;
message NewMessageNotify {
required string info_type = 1;
optional bytes info_content = 2;
optional int64 info_id = 3;
optional uint32 query_after_seconds = 4;
}
}
}
message ChatRoomPacket {
required bytes roomid = 1;
optional ChatRoomUpToServer to_server_data = 2;
optional ChatRoomDownToUser to_user_data = 3;
optional string uuid = 4;
optional uint64 client_sn = 5;
optional uint32 appid = 6;
message ChatRoomUpToServer {
required uint32 payloadtype = 1;
optional ApplyJoinChatRoomRequest applyjoinchatroomreq = 4;
message ApplyJoinChatRoomRequest {
required bytes roomid = 1;
optional ChatRoom room = 2;
optional int32 userid_type = 3;
}
}
message ChatRoomDownToUser {
required int32 result = 1;
required uint32 payloadtype = 2;
optional CreateChatRoomResponse createchatroomresp = 3;
optional ApplyJoinChatRoomResponse applyjoinchatroomresp = 5;
optional QuitChatRoomResponse quitchatroomresp = 6;
optional ChatRoomNewMsg newmsgnotify = 13;
optional MemberJoinChatRoomNotify memberjoinnotify = 16;
optional MemberQuitChatRoomNotify memberquitnotify = 17;
repeated ChatRoomMNotify multinotify = 200;
message CreateChatRoomResponse {
optional ChatRoom room = 1;
}
message ApplyJoinChatRoomResponse {
optional ChatRoom room = 1;
}
message QuitChatRoomResponse {
optional ChatRoom room = 1;
}
message ChatRoomNewMsg {
required bytes roomid = 1;
optional CRUser sender = 2;
optional int32 msgtype = 3;
optional bytes msgcontent = 4;
optional int32 regmemcount = 5;
optional int32 memcount = 6;
optional uint32 msgid = 7;
optional uint32 maxid = 8;
optional uint64 timestamp = 9;
}
message MemberJoinChatRoomNotify {
required ChatRoom room = 1;
}
message MemberQuitChatRoomNotify {
required ChatRoom room = 1;
}
message ChatRoomMNotify {
required int32 type = 1;
required bytes data = 2;
optional int32 regmemcount = 3;
optional int32 memcount = 4;
}
}
}
message ChatRoom {
required bytes roomid = 1;
repeated CRPair properties = 8;
repeated CRUser members = 9;
optional bytes partnerdata = 13;
}
message CRUser {
optional bytes userid = 1;
optional string name = 2;
optional bytes userdata = 6;
}
message CRPair {
required string key = 1;
optional bytes value = 2;
}

218
danmu/danmaku/huajiao.py Normal file
View File

@ -0,0 +1,218 @@
from . import huajiao_pb2 as pb
import struct
import hashlib
import random
import string
import json
import time
class HuaJiao:
heartbeats = b'\x00\x00\x00\x00'
ws_url = 'wss://bridge.huajiao.com'
def __init__(self, rid=None):
self.sn = ''
self.tt = str(int(time.time() * 1000))
self.roomId = rid
self.flag = 'qh'
self.protocolVersion = 1
self.clientVersion = 101
self.appId = 2080
self.sender = self.password = '999' + self.tt + self.random_(6, 'n')
self.defaultKey = '3f190210cb1cf32a2378ee57900acf78'
def init_p(self):
p = pb.Message()
p.sn = int(self.random_(10, 'n'))
p.sender = self.sender
p.sender_type = 'jid'
return p
@staticmethod
def random_(num, var):
seq = ''
if var == 's':
seq = string.ascii_letters + string.digits
if var == 'n':
seq = string.digits
result = ''.join([random.choice(seq) for i in range(num)])
return result
@staticmethod
def md5(data):
return hashlib.md5(data.encode('utf-8')).hexdigest()
@staticmethod
def rc4(data, key):
a = [i for i in range(256)]
l = i = 0
while i < 256:
l = (l + a[i] + ord(key[i % len(key)])) % 256
a[i], a[l] = a[l], a[i]
i += 1
i = l = n = 0
s = len(data)
f = []
while n < s:
i = (i + 1) % 256
l = (l + a[i]) % 256
a[i], a[l] = a[l], a[i]
f.append(data[n] ^ a[(a[i] + a[l]) % 256])
n += 1
return bytes(f)
def sendHandshakePack(self):
HandshakePack = struct.pack('!2sbbhih', self.flag.encode(), self.protocolVersion << 4, self.clientVersion,
self.appId, 0, 0)
p = self.init_p()
self.sn = p.sn
p.msgid = 100009
p.req.init_login_req.client_ram = self.random_(10, 's')
p.req.init_login_req.sig = ''
data = p.SerializeToString()
a = self.rc4(data, self.defaultKey)
HandshakePack += struct.pack('!i', len(HandshakePack + a) + 4) + a
return HandshakePack
def processHandShakePack(self, message):
o, = struct.unpack('!2s', message[:2])
if o.decode() != self.flag:
raise Exception('processHandShakePack 服务器响应标识flag有误')
s = self.rc4(message[6:], self.defaultKey)
p = self.init_p()
try:
p.ParseFromString(s)
except:
raise Exception('processHandShakePack 解析消息体异常')
if p.msgid != 200009:
raise Exception('processHandShakePack 响应msgid异常')
if p.sn != self.sn:
raise Exception('processHandShakePack sn验证失败')
return p.resp
def sendLoginPack(self, message):
e = self.processHandShakePack(message)
u = e.init_login_resp.server_ram
secret_ram = self.rc4((u + self.random_(8, 's')).encode(), self.password)
verf_code = self.md5(self.sender + '360tantan@1408$')[24:]
p = self.init_p()
self.sn = p.sn
p.msgid = 100001
p.req.login.app_id = 2080
p.req.login.mobile_type = 'ios'
p.req.login.net_type = 4
p.req.login.not_encrypt = True
p.req.login.platform = 'h5'
p.req.login.server_ram = u
p.req.login.secret_ram = secret_ram
p.req.login.verf_code = verf_code
a = p.SerializeToString()
l = self.rc4(a, self.defaultKey)
LoginPack = struct.pack('!i', 4 + len(l)) + l
return LoginPack
def processLoginPack(self, message):
p = self.init_p()
try:
p.ParseFromString(self.rc4(message[4:], self.password))
except:
try:
p.ParseFromString(self.rc4(message[4:], self.defaultKey))
except:
raise Exception('processLoginPack 解析消息体异常')
if p.msgid != 200001:
raise Exception('processLoginPack 响应msgid异常')
if p.sn != self.sn:
raise Exception('processLoginPack sn验证失败')
return p
def sendJoinChatroomPack(self, message):
p = self.processLoginPack(message)
o = self.roomId.encode()
# crm : ChatroomRequestMessage
crm = pb.ChatRoomPacket()
crm.client_sn = p.sn
crm.roomid = o
crm.appid = self.appId
crm.uuid = self.md5(self.random_(10, 's') + '0000000001' + str(int(time.time() * 1000)))
crm.to_server_data.payloadtype = 102
crm.to_server_data.applyjoinchatroomreq.roomid = o
crm.to_server_data.applyjoinchatroomreq.room.roomid = o
crm.to_server_data.applyjoinchatroomreq.userid_type = 0
p = self.init_p()
self.sn = p.sn
p.msgid = 100011
p.req.service_req.service_id = 10000006
p.req.service_req.request = crm.SerializeToString()
u = p.SerializeToString()
JoinChatroomPack = struct.pack('!i', 4 + len(u)) + u
return JoinChatroomPack
def processMessagePack(self, message):
i, = struct.unpack_from('!i', message, 0)
if len(message) == 4 and i == 0: # HeartbeatPack
return None
p = self.init_p()
p.ParseFromString(message[4:])
o = p.msgid
if o == 200011:
return self.processService_RespMessage(p)
elif o == 300000:
return self.processNewMessageNotifyMessage(p)
else:
return None
def processService_RespMessage(self, p):
if p.sn != self.sn:
raise Exception('processService_RespMessage sn验证失败')
crp = pb.ChatRoomPacket()
crp.ParseFromString(p.resp.service_resp.response)
n = crp.to_user_data
r = i = ''
if n.payloadtype == 102 or n.applyjoinchatroomresp:
if n.result == 0:
r = n.applyjoinchatroomresp.room.properties[1].value
i = n.applyjoinchatroomresp.room.partnerdata
r = r.decode('utf-8')
i = i.decode('utf-8')
return r, i
def processNewMessageNotifyMessage(self, p):
crp = pb.ChatRoomPacket()
crp.ParseFromString(p.notify.newinfo_ntf.info_content)
r = crp.to_user_data
s = i = ''
if r.result == 0:
if r.payloadtype == 1000 and r.newmsgnotify:
s = r.newmsgnotify.memcount
i = r.newmsgnotify.msgcontent
elif r.payloadtype == 1001 and r.memberjoinnotify:
s = r.memberjoinnotify.room.properties[1].value
i = r.memberjoinnotify.room.members[0].userdata
elif r.payloadtype == 1002 and r.memberquitnotify:
s = r.memberquitnotify.room.properties[0].value
i = r.memberquitnotify.room.members[0].userdata
i = i.decode('utf-8')
i = json.loads(i)
return s, i
def decode_msg(self, message):
msgs = []
memcountmsg, msgcontent = self.processMessagePack(message)
if msgcontent.get('type') == 9:
name = msgcontent['extends']['nickname']
content = msgcontent['text']
msg = {'name': name, 'content': content, 'msg_type': 'danmaku'}
msgs.append(msg)
return msgs

1555
danmu/danmaku/huajiao_pb2.py Normal file

File diff suppressed because one or more lines are too long

View File

@ -29,3 +29,4 @@ asyncio.run(main(a))
# 快手https://live.kuaishou.com/u/jjworld126 # 快手https://live.kuaishou.com/u/jjworld126
# 火猫: # 火猫:
# 企鹅电竞https://egame.qq.com/383204988 # 企鹅电竞https://egame.qq.com/383204988
# 花椒直播https://www.huajiao.com/l/303344861?qd=hu