1
0
mirror of https://github.com/wbt5/real-url.git synced 2025-07-24 01:11:43 +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 .huomao import HuoMao
from .egame import eGame
from .huajiao import HuaJiao
__all__ = ['DanmakuClient']
@ -30,9 +31,11 @@ class DanmakuClient:
'huya.com': Huya,
'huomao.com': HuoMao,
'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):
self.__site = s
self.__u = u
break
if self.__site is None:
print('Invalid link!')
@ -63,10 +66,31 @@ class DanmakuClient:
await asyncio.sleep(1)
await self.init_ws()
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):
await self.init_ws()
await asyncio.gather(
self.heartbeats(),
self.fetch_danmaku(),
)
if self.__u == 'huajiao.com':
await self.init_ws_huajiao()
else:
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://egame.qq.com/383204988
# 花椒直播https://www.huajiao.com/l/303344861?qd=hu