mirror of
https://github.com/wbt5/real-url.git
synced 2025-07-24 01:11:43 +08:00
新增花椒直播弹幕
This commit is contained in:
parent
0ea4fe43e7
commit
4791d0a68f
@ -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
174
danmu/danmaku/huajiao.proto
Normal 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
218
danmu/danmaku/huajiao.py
Normal 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
1555
danmu/danmaku/huajiao_pb2.py
Normal file
File diff suppressed because one or more lines are too long
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user