1
0
mirror of https://github.com/wbt5/real-url.git synced 2025-07-27 11:00:32 +08:00

更新快手直播弹幕

This commit is contained in:
wbt5 2020-08-18 21:50:19 +08:00
parent 3cfbdcd5d0
commit 0c7b9b7bcd
3 changed files with 1745 additions and 344 deletions

View File

@ -0,0 +1,204 @@
syntax = "proto2";
package KuaiShouPack;
message CSWebHeartbeat {
optional uint64 timestamp = 1;
}
message SocketMessage {
optional PayloadType payloadType = 1;
optional CompressionType compressionType = 2;
optional bytes payload = 3;
enum CompressionType {
UNKNOWN = 0;
NONE = 1;
GZIP = 2;
AES = 3;
}
}
enum PayloadType {
UNKNOWN = 0;
CS_HEARTBEAT = 1;
CS_ERROR = 3;
CS_PING = 4;
PS_HOST_INFO = 51;
SC_HEARTBEAT_ACK = 101;
SC_ECHO = 102;
SC_ERROR = 103;
SC_PING_ACK = 104;
SC_INFO = 105;
CS_ENTER_ROOM = 200;
CS_USER_PAUSE = 201;
CS_USER_EXIT = 202;
CS_AUTHOR_PUSH_TRAFFIC_ZERO = 203;
CS_HORSE_RACING = 204;
CS_RACE_LOSE = 205;
CS_VOIP_SIGNAL = 206;
SC_ENTER_ROOM_ACK = 300;
SC_AUTHOR_PAUSE = 301;
SC_AUTHOR_RESUME = 302;
SC_AUTHOR_PUSH_TRAFFIC_ZERO = 303;
SC_AUTHOR_HEARTBEAT_MISS = 304;
SC_PIP_STARTED = 305;
SC_PIP_ENDED = 306;
SC_HORSE_RACING_ACK = 307;
SC_VOIP_SIGNAL = 308;
SC_FEED_PUSH = 310;
SC_ASSISTANT_STATUS = 311;
SC_REFRESH_WALLET = 312;
SC_LIVE_CHAT_CALL = 320;
SC_LIVE_CHAT_CALL_ACCEPTED = 321;
SC_LIVE_CHAT_CALL_REJECTED = 322;
SC_LIVE_CHAT_READY = 323;
SC_LIVE_CHAT_GUEST_END = 324;
SC_LIVE_CHAT_ENDED = 325;
SC_RENDERING_MAGIC_FACE_DISABLE = 326;
SC_RENDERING_MAGIC_FACE_ENABLE = 327;
SC_RED_PACK_FEED = 330;
SC_LIVE_WATCHING_LIST = 340;
SC_LIVE_QUIZ_QUESTION_ASKED = 350;
SC_LIVE_QUIZ_QUESTION_REVIEWED = 351;
SC_LIVE_QUIZ_SYNC = 352;
SC_LIVE_QUIZ_ENDED = 353;
SC_LIVE_QUIZ_WINNERS = 354;
SC_SUSPECTED_VIOLATION = 355;
SC_SHOP_OPENED = 360;
SC_SHOP_CLOSED = 361;
SC_GUESS_OPENED = 370;
SC_GUESS_CLOSED = 371;
SC_PK_INVITATION = 380;
SC_PK_STATISTIC = 381;
SC_RIDDLE_OPENED = 390;
SC_RIDDLE_CLOESED = 391;
SC_RIDE_CHANGED = 412;
SC_BET_CHANGED = 441;
SC_BET_CLOSED = 442;
SC_LIVE_SPECIAL_ACCOUNT_CONFIG_STATE = 645;
}
message CSWebEnterRoom {
optional string token = 1;
optional string liveStreamId = 2;
optional uint32 reconnectCount = 3;
optional uint32 lastErrorCode = 4;
optional string expTag = 5;
optional string attach = 6;
optional string pageId = 7;
}
message SCWebFeedPush {
optional string displayWatchingCount = 1;
optional string displayLikeCount = 2;
optional uint64 pendingLikeCount = 3;
optional uint64 pushInterval = 4;
repeated WebCommentFeed commentFeeds = 5;
optional string commentCursor = 6;
repeated WebComboCommentFeed comboCommentFeed = 7;
repeated WebLikeFeed likeFeeds = 8;
repeated WebGiftFeed giftFeeds = 9;
optional string giftCursor = 10;
repeated WebSystemNoticeFeed systemNoticeFeeds = 11;
repeated WebShareFeed shareFeeds = 12;
}
message WebCommentFeed {
optional string id = 1;
optional SimpleUserInfo user = 2;
optional string content = 3;
optional string deviceHash = 4;
optional uint64 sortRank = 5;
optional string color = 6;
optional WebCommentFeedShowType showType = 7;
}
message SimpleUserInfo {
optional string principalId = 1;
optional string userName = 2;
optional string headUrl = 3;
}
enum WebCommentFeedShowType {
FEED_SHOW_UNKNOWN = 0;
FEED_SHOW_NORMAL = 1;
FEED_HIDDEN = 2;
}
message WebComboCommentFeed {
optional string id = 1;
optional string content = 2;
optional uint32 comboCount = 3;
}
message WebLikeFeed {
optional string id = 1;
optional SimpleUserInfo user = 2;
optional uint64 sortRank = 3;
optional string deviceHash = 4;
}
message WebGiftFeed {
optional string id = 1;
optional SimpleUserInfo user = 2;
optional uint64 time = 3;
optional uint32 giftId = 4;
optional uint64 sortRank = 5;
optional string mergeKey = 6;
optional uint32 batchSize = 7;
optional uint32 comboCount = 8;
optional uint32 rank = 9;
optional uint64 expireDuration = 10;
optional uint64 clientTimestamp = 11;
optional uint64 slotDisplayDuration = 12;
optional uint32 starLevel = 13;
optional StyleType styleType = 14;
optional WebLiveAssistantType liveAssistantType = 15;
optional string deviceHash = 16;
optional bool danmakuDisplay = 17;
enum StyleType {
UNKNOWN_STYLE = 0;
BATCH_STAR_0 = 1;
BATCH_STAR_1 = 2;
BATCH_STAR_2 = 3;
BATCH_STAR_3 = 4;
BATCH_STAR_4 = 5;
BATCH_STAR_5 = 6;
BATCH_STAR_6 = 7;
}
}
enum WebLiveAssistantType {
UNKNOWN_ASSISTANT_TYPE = 0;
SUPER = 1;
JUNIOR = 2;
}
message WebSystemNoticeFeed {
optional string id = 1;
optional SimpleUserInfo user = 2;
optional uint64 time = 3;
optional string content = 4;
optional uint64 displayDuration = 5;
optional uint64 sortRank = 6;
optional DisplayType displayType = 7;
enum DisplayType {
UNKNOWN_DISPLAY_TYPE = 0;
COMMENT = 1;
ALERT = 2;
TOAST = 3;
}
}
message WebShareFeed {
optional string id = 1;
optional SimpleUserInfo user = 2;
optional uint64 time = 3;
optional uint32 thirdPartyPlatform = 4;
optional uint64 sortRank = 5;
optional WebLiveAssistantType liveAssistantType = 6;
optional string deviceHash = 7;
}

View File

@ -1,14 +1,12 @@
# 快手代码来源及思路https://github.com/py-wuhao/ks_barrage from . import kuaishou_pb2 as pb
import aiohttp import aiohttp
import random
import time
import json
import re import re
import json
import time
import random
class KuaiShou: class KuaiShou:
heartbeat = b'\x08\x01\x1A\x07\x08' # 发送心跳可固定
heartbeatInterval = 20 heartbeatInterval = 20
@staticmethod @staticmethod
@ -18,7 +16,7 @@ class KuaiShou:
直播间完整地址 直播间完整地址
Returns: Returns:
webSocketUrls:wss地址 webSocketUrls:wss地址
reg_datas:第一次send数据 data:第一次send数据
liveStreamId: liveStreamId:
token: token:
page_id: page_id:
@ -29,27 +27,33 @@ class KuaiShou:
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, ' 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, '
'like Gecko) Version/11.0 Mobile/15A372 Safari/604.1', 'like Gecko) Version/11.0 Mobile/15A372 Safari/604.1',
'Cookie': 'did=web_e8436e86a8ec476c801c1d534f56db0c'} # 请求失败则更换cookie中的did字段 'Cookie': 'did=web_d563dca728d28b00336877723e0359ed'} # 请求失败则更换cookie中的did字段
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as resp: async with session.get(url, headers=headers) as resp:
wsFeedInfo = re.findall(r'wsFeedInfo":(.*),"liveExist', await resp.text()) res = await resp.text()
wsFeedInfo = json.loads(wsFeedInfo[0])
liveStreamId = wsFeedInfo['liveStreamId']
token = wsFeedInfo['token']
webSocketUrls = wsFeedInfo['webSocketUrls'][0]
reg_datas = [] wsfeedinfo = re.search(r'wsFeedInfo":(.*),"liveExist', res)
part1 = b'\x08\xC8\x01\x1A\xC8\x01\x0A\x98\x01' if wsfeedinfo:
part2 = token.encode() wsfeedinfo = json.loads(wsfeedinfo.group(1))
part3 = b'\x12\x0B' else:
part4 = liveStreamId.encode() raise Exception('找不到 wsFeedInfo可能链接错误或 Cookie 过期')
part5 = b'\x3A\x1E'
page_id = KuaiShou.get_page_id()
part6 = page_id.encode()
s = part1 + part2 + part3 + part4 + part5 + part6
reg_datas.append(s)
return webSocketUrls, reg_datas livestreamid, [websocketurls], token = wsfeedinfo.values()
page_id = KuaiShou.get_page_id()
p, s = pb.SocketMessage(), pb.CSWebEnterRoom()
s.liveStreamId, s.pageId, s.token = livestreamid, page_id, token
p.payload = s.SerializeToString()
p.payloadType = 200
reg_data = p.SerializeToString()
t = pb.CSWebHeartbeat()
t.timestamp = int(time.time() * 1000)
p.payload = t.SerializeToString()
p.payloadType = 1
KuaiShou.heartbeat = p.SerializeToString() # 心跳可固定
return websocketurls, [reg_data]
@staticmethod @staticmethod
def get_page_id(): def get_page_id():
@ -62,324 +66,48 @@ class KuaiShou:
return page_id return page_id
@staticmethod @staticmethod
def decode_msg(data): def decode_msg(message):
msgs = [] msgs = [{'name': '', 'content': '', 'msg_type': 'other'}]
msg = {}
s = MessageDecode(data) p, s = pb.SocketMessage(), pb.SCWebFeedPush()
c = s.decode() p.ParseFromString(message)
if c.get('payloadType', 0) == 310: # SC_FEED_PUSH = 310 时有弹幕数据 if p.payloadType == 310:
m = s.feed_decode(c['payload']) # 弹幕解码方法 s.ParseFromString(p.payload)
if m.get('comment'):
for user in m.get('comment'): def f(*feeds):
msg['name'] = user.get('user').get('userName').encode('utf-16', 'surrogatepass').decode('utf-16') gift = {
msg['content'] = user.get('content').encode('utf-16', 'surrogatepass').decode('utf-16') 1: '荧光棒', 2: '棒棒糖', 3: '荧光棒', 4: 'PCL加油', 7: '么么哒', 9: '啤酒', 10: '甜甜圈',
msg['msg_type'] = 'danmaku' 14: '钻戒', 16: '皇冠', 25: '凤冠', 33: '烟花', 41: '跑车', 56: '', 113: '火箭',
msgs.append(msg.copy()) 114: '玫瑰', 132: '绷带', 133: '平底锅', 135: '红爸爸', 136: '蓝爸爸', 137: '铭文碎片',
return msgs 143: '太阳女神', 147: '', 149: '血瓶', 150: 'carry全场', 152: '大红灯笼', 156: '穿云箭',
else: 159: '膨胀了', 160: '秀你一脸', 161: 'MVP', 163: '加油', 164: '猫粮', 165: '小可爱',
msg = {'name': '', 'content': '', 'msg_type': 'other'} 169: '男神', 172: '联盟金猪', 173: '有钱花', 193: '蛋糕', 197: '棒棒糖', 198: '',
else: 199: '小可爱', 201: '', 207: '快手卡', 208: '灵狐姐', 216: 'LPL加油', 218: '烟花',
msg = {'name': '', 'content': '', 'msg_type': 'other'} 219: '告白气球', 220: '大红灯笼', 221: '怦然心动', 222: '凤冠', 223: '火箭', 224: '跑车',
msgs.append(msg) 225: '穿云箭', 226: '金话筒', 227: 'IG冲鸭', 228: 'GRF冲鸭', 229: 'FPX冲鸭', 230: 'FNC冲鸭',
231: 'SKT冲鸭', 232: 'SPY冲鸭', 233: 'DWG冲鸭', 234: 'G2冲鸭', 235: '爆单', 236: '入团券',
237: '陪着你540', 238: '支持牌', 239: '陪着你', 242: '金龙', 243: '豪车幻影', 244: '超级6',
245: '水晶', 246: '金莲', 247: '福袋', 248: '铃铛', 249: '巧克力', 250: '感恩的心',
254: '武汉加油', 256: '金龙', 257: '财神', 258: '金龙', 259: '天鹅湖', 260: '珍珠',
261: '金莲', 262: '招财猫', 263: '铃铛', 264: '巧克力', 266: '幸运魔盒', 267: '吻你',
268: '梦幻城堡', 269: '游乐园', 271: '萌宠', 272: '小雪豹', 275: '喜欢你', 276: '三级头',
277: '喜欢你', 278: '财神', 279: '锦鲤', 281: '廉颇', 282: '开黑卡', 283: '付费直播门票(不下线)',
285: '喜欢你呀', 286: '629', 287: '真爱大炮', 289: '玫瑰花园', 290: '珠峰', 292: '鹿角',
296: '666', 297: '超跑车队', 298: '奥利给', 302: '互粉', 303: '冰棒', 304: '龙之谷',
306: '浪漫游轮', 307: '壁咚', 308: '壁咚', 309: '鹿角', 310: '么么哒', 311: '私人飞机',
312: '巅峰票', 313: '巅峰王者', 315: '莫吉托', 316: '地表最强', 318: '阳光海滩', 319: '12号唱片'
}
infos = [{'name': '', 'content': '', 'msg_type': 'other'}]
for feed in feeds:
if feed:
for i in feed:
name = i.user.userName
content = i.content if hasattr(i, 'content') else '' + gift.get(i.giftId, '') \
if hasattr(i, 'giftId') else '点亮了 ❤'
info = {'name': name, 'content': content, 'msg_type': 'danmaku'}
infos.append(info.copy())
return infos
msgs = f(s.commentFeeds, s.giftFeeds, s.likeFeeds)
return msgs return msgs
class MessageDecode:
"""
返回的数据流解码
"""
def __init__(self, buf):
self.buf = buf
self.pos = 0
self.message = {}
def __len__(self):
return len(self.buf)
def int_(self):
res = 0
i = 0
while self.buf[self.pos] >= 128:
res = res | (127 & self.buf[self.pos]) << 7 * i
self.pos += 1
i += 1
res = res | self.buf[self.pos] << 7 * i
self.pos += 1
return res
def decode(self):
"""
服务器返回数据第一次解码
Return:m
payloadType:
101: "SC_HEARTBEAT_ACK",
103: "SC_ERROR",
105: "SC_INFO",
300: "SC_ENTER_ROOM_ACK",
310: "SC_FEED_PUSH", # 310是弹幕信息
330: "SC_RED_PACK_FEED",
340: "SC_LIVE_WATCHING_LIST",
370: "SC_GUESS_OPENED",
371: "SC_GUESS_CLOSED",
412: "SC_RIDE_CHANGED",
441: "SC_BET_CHANGED",
442: "SC_BET_CLOSED",
645: "SC_LIVE_SPECIAL_ACCOUNT_CONFIG_STATE"
"""
m = {}
self.pos = 0
length = len(self)
while self.pos < length:
t = self.int_()
tt = t >> 3
if tt == 1:
m['payloadType'] = self.int_()
elif tt == 2:
m['compressionType'] = self.int_()
elif tt == 3:
m['payload'] = self.bytes()
else:
self.skipType(t & 7)
return m
def skipType(self, e):
if e == 0:
self.skip()
elif e == 1:
self.skip(8)
elif e == 2:
self.skip(self.int_())
elif e == 3:
while True:
e = 7 & self.int_()
if 4 != e:
self.skipType(e)
elif e == 5:
self.skip(4)
else:
raise Exception('跳过类型错误')
def bytes(self):
e = self.int_()
if e + self.pos > len(self.buf):
raise Exception('index out of range')
res = self.buf[self.pos: (e + self.pos)]
self.pos += e
return res
def skip(self, e=None):
"""跳过多少字节"""
if e is None:
while self.pos < len(self.buf):
if 128 & self.buf[self.pos] == 0:
self.pos += 1
return
self.pos += 1
return
self.pos += e
if self.pos >= len(self.buf):
self.pos -= 1
def feed_decode(self, payload):
"""
payload解码,即还原JS中的function SCWebFeedPush$decode(r, l)
Args:
decode函数返回的paylod
Returns:
m解码后的数据
"""
self.pos = 0
self.buf = payload
m = {}
length = len(self.buf)
while self.pos < length:
t = self.int_()
tt = t >> 3
if tt == 1:
m['displayWatchingCount'] = self.string()
elif tt == 2:
m['displayLikeCount'] = self.string()
elif tt == 3:
m['pendingLikeCount'] = self.int_()
elif tt == 4:
m['pushInterval'] = self.int_()
elif tt == 5:
if not m.get('comment'):
m['comment'] = []
m['comment'].append(self.comment_decode(self.buf, self.int_()))
elif tt == 6:
m['commentCursor'] = self.string()
elif tt == 7:
if not m.get('comboComment'):
m['comboComment'] = []
m['comboComment'].append(self.comboComment_decode(self.buf, self.int_()))
elif tt == 8:
if not m.get('like'):
m['like'] = []
m['like'].append(self.web_like_feed_decode(self.buf, self.int_()))
elif tt == 9: # 礼物
if not m.get('gift'):
m['gift'] = []
m['gift'].append(self.gift_decode(self.buf, self.int_()))
elif tt == 10:
m['giftCursor'] = self.string()
elif tt == 11:
if not m.get('systemNotice'):
m['systemNotice'] = []
m['systemNotice'].append(self.systemNotice_decode(self.buf, self.int_()))
elif tt == 12:
if not m.get('share'):
m['share'] = []
m['share'].append(self.share_decode(self.buf, self.int_()))
else:
self.skipType(t & 7)
return m
def comment_decode(self, r, l):
c = self.pos + l
m = {}
while self.pos < c:
t = self.int_()
tt = t >> 3
if tt == 1:
m['id'] = self.string()
elif tt == 2:
m['user'] = self.user_info_decode(self.buf, self.int_())
elif tt == 3:
m['content'] = self.string()
elif tt == 4:
m['deviceHash'] = self.string()
elif tt == 5:
m['sortRank'] = self.int_()
elif tt == 6:
m['color'] = self.string()
elif tt == 7:
m['showType'] = self.int_()
else:
self.skipType(t & 7)
return m
def comboComment_decode(self, r, l):
pass
def systemNotice_decode(self, r, l):
pass
def share_decode(self, r, l):
pass
def user_info_decode(self, r, l):
c = self.pos + l
m = {}
while self.pos < c:
t = self.int_()
tt = t >> 3
if tt == 1:
m['principalId'] = self.string()
elif tt == 2:
m['userName'] = self.string()
elif tt == 3:
m['headUrl'] = self.string()
else:
self.skipType(t & 7)
return m
def web_like_feed_decode(self, r, l):
c = self.pos + l
m = {}
while self.pos < c:
t = self.int_()
tt = t >> 3
if tt == 1:
m['id'] = self.string()
elif tt == 2:
m['user'] = self.user_info_decode(self.buf, self.int_())
elif tt == 3:
m['sortRank'] = self.int_()
elif tt == 4:
m['deviceHash'] = self.string()
else:
self.skipType(t & 7)
return m
def gift_decode(self, r, l):
c = self.pos + l
m = {}
while self.pos < c:
t = self.int_()
tt = t >> 3
if tt == 1:
m['id'] = self.string()
elif tt == 2:
m['user'] = self.user_info_decode(self.buf, self.int_())
elif tt == 3:
m['time'] = self.int_()
elif tt == 4:
m['giftId'] = self.int_()
elif tt == 5:
m['sortRank'] = self.int_()
elif tt == 6:
m['mergeKey'] = self.string()
elif tt == 7:
m['batchSize'] = self.int_()
elif tt == 8:
m['comboCount'] = self.int_()
elif tt == 9:
m['rank'] = self.int_()
elif tt == 10:
m['expireDuration'] = self.int_()
elif tt == 11:
m['clientTimestamp'] = self.int_()
elif tt == 12:
m['slotDisplayDuration'] = self.int_()
elif tt == 13:
m['starLevel'] = self.int_()
elif tt == 14:
m['styleType'] = self.int_()
elif tt == 15:
m['liveAssistantType'] = self.int_()
elif tt == 16:
m['deviceHash'] = self.string()
elif tt == 17:
m['danmakuDisplay'] = self.int_()
else:
self.skipType(t & 7)
return m
def string(self):
e = self.bytes()
n = len(e)
if n < 1:
return ""
s = []
t = 0
while t < n:
r = e[t]
t += 1
if r < 128:
s.append(r)
elif 191 < r < 224:
s.append((31 & r) << 6 | 63 & e[t])
t += 1
elif 239 < r < 365:
x = (7 & r) << 18 | (63 & e[t]) << 12
t += 1
y = (63 & e[t]) << 6
t += 1
z = 63 & e[t]
t += 1
r = (x | y | z) - 65536
s.append(55296 + (r >> 10))
s.append(56320 + (1023 & r))
else:
x = (15 & r) << 12
y = (63 & e[t]) << 6
t += 1
z = 63 & e[t]
t += 1
s.append(x | y | z)
string = ''
for w in s:
string += chr(w)
return string

File diff suppressed because one or more lines are too long