mirror of
https://github.com/wbt5/real-url.git
synced 2025-08-01 14:48:01 +08:00
新增acfun直播弹幕
This commit is contained in:
parent
c75c75713f
commit
3cfbdcd5d0
437
danmu/danmaku/acfun.proto
Normal file
437
danmu/danmaku/acfun.proto
Normal file
@ -0,0 +1,437 @@
|
||||
syntax = "proto2";
|
||||
package AcFunPack;
|
||||
|
||||
message RegisterRequest {
|
||||
optional AppInfo appInfo = 1;
|
||||
optional DeviceInfo deviceInfo = 2;
|
||||
optional EnvInfo envInfo = 3;
|
||||
optional PresenceStatus presenceStatus = 4;
|
||||
optional ActiveStatus appActiveStatus = 5;
|
||||
optional bytes appCustomStatus = 6;
|
||||
optional PushServiceToken pushServiceToken = 7;
|
||||
optional int64 instanceId = 8;
|
||||
repeated PushServiceToken pushServiceTokenList = 9;
|
||||
optional int32 keepaliveIntervalSec = 10;
|
||||
optional ZtCommonInfo ztCommonInfo = 11;
|
||||
|
||||
enum PresenceStatus {
|
||||
kPresenceOffline = 0;
|
||||
kPresenceOnline = 1;
|
||||
}
|
||||
|
||||
enum ActiveStatus {
|
||||
kInvalid = 0;
|
||||
kAppInForeground = 1;
|
||||
kAppInBackground = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message RegisterResponse {
|
||||
optional AccessPointsConfig accessPointsConfig = 1;
|
||||
optional bytes sessKey = 2;
|
||||
optional int64 instanceId = 3;
|
||||
optional SdkOption sdkOption = 4;
|
||||
optional AccessPointsConfig accessPointsConfigIpv6 = 5;
|
||||
}
|
||||
|
||||
message AccessPointsConfig {
|
||||
repeated AccessPoint optimalAps = 1;
|
||||
repeated AccessPoint backupAps = 2;
|
||||
repeated uint32 availablePorts = 3;
|
||||
optional AccessPoint forceLastConnectedAp = 4;
|
||||
}
|
||||
|
||||
message AccessPoint {
|
||||
optional AddressType addressType = 1;
|
||||
optional uint32 port = 2;
|
||||
optional fixed32 ipV4 = 3;
|
||||
optional bytes ipV6 = 4;
|
||||
optional string domain = 5;
|
||||
|
||||
enum AddressType {
|
||||
kIPV4 = 0;
|
||||
kIPV6 = 1;
|
||||
kDomain = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message SdkOption {
|
||||
optional int32 reportIntervalSeconds = 1;
|
||||
optional string reportSecurity = 2;
|
||||
optional int32 lz4CompressionThresholdBytes = 3;
|
||||
repeated string netCheckServers = 4;
|
||||
}
|
||||
|
||||
message ZtLiveCsEnterRoom {
|
||||
optional bool isAuthor = 1;
|
||||
optional uint32 reconnectCount = 2;
|
||||
optional uint32 lastErrorCode = 3;
|
||||
optional string enterRoomAttach = 4;
|
||||
optional string clientLiveSdkVersion = 5;
|
||||
}
|
||||
|
||||
message ZtLiveCsHeartbeat {
|
||||
optional uint64 clientTimestampMs = 1;
|
||||
optional bool sequence = 2;
|
||||
}
|
||||
|
||||
message CsCmd {
|
||||
optional string cmdType = 1;
|
||||
optional bytes payload = 2;
|
||||
optional string ticket = 3;
|
||||
optional string liveId = 4;
|
||||
}
|
||||
|
||||
message AppInfo {
|
||||
optional string appName = 1;
|
||||
optional string appVersion = 2;
|
||||
optional string appChannel = 3;
|
||||
optional string sdkVersion = 4;
|
||||
map < string,
|
||||
string > extensionInfo = 5;
|
||||
}
|
||||
|
||||
message DeviceInfo {
|
||||
optional PlatformType platformType = 1;
|
||||
optional string osVersion = 2;
|
||||
optional string deviceModel = 3;
|
||||
optional bytes imeiMd5 = 4;
|
||||
optional string deviceId = 5;
|
||||
optional string softDid = 6;
|
||||
optional string kwaiDid = 7;
|
||||
optional string manufacturer = 8;
|
||||
optional string deviceName = 9;
|
||||
|
||||
enum PlatformType {
|
||||
kInvalid = 0;
|
||||
kAndroid = 1;
|
||||
kiOS = 2;
|
||||
kWindows = 3;
|
||||
WECHAT_ANDROID = 4;
|
||||
WECHAT_IOS = 5;
|
||||
H5 = 6;
|
||||
H5_ANDROID = 7;
|
||||
H5_IOS = 8;
|
||||
H5_WINDOWS = 9;
|
||||
H5_MAC = 10;
|
||||
kPlatformNum = 11;
|
||||
}
|
||||
}
|
||||
|
||||
message EnvInfo {
|
||||
optional NetworkType networkType = 1;
|
||||
optional bytes apnName = 2;
|
||||
|
||||
enum NetworkType {
|
||||
kInvalid = 0;
|
||||
kWIFI = 1;
|
||||
kCellular = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message PushServiceToken {
|
||||
optional PushType pushType = 1;
|
||||
optional bytes token = 2;
|
||||
optional bool isPassThrough = 3;
|
||||
|
||||
enum PushType {
|
||||
kPushTypeInvalid = 0;
|
||||
kPushTypeAPNS = 1;
|
||||
kPushTypeXmPush = 2;
|
||||
kPushTypeJgPush = 3;
|
||||
kPushTypeGtPush = 4;
|
||||
kPushTypeOpPush = 5;
|
||||
kPushTypeVvPush = 6;
|
||||
kPushTypeHwPush = 7;
|
||||
kPushTypeFcm = 8;
|
||||
}
|
||||
}
|
||||
|
||||
message ZtCommonInfo {
|
||||
optional string kpn = 1;
|
||||
optional string kpf = 2;
|
||||
optional int64 uid = 4;
|
||||
optional string did = 5;
|
||||
}
|
||||
|
||||
message PingResponse {
|
||||
optional sfixed32 serverTimestamp = 1;
|
||||
optional fixed32 clientIp = 2;
|
||||
optional fixed32 redirectIp = 3;
|
||||
optional uint32 redirectPort = 4;
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
optional PingType pingType = 1;
|
||||
optional uint32 pingRound = 2;
|
||||
|
||||
enum PingType {
|
||||
kInvalid = 0;
|
||||
kPriorRegister = 1;
|
||||
kPostRegister = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message UpstreamPayload {
|
||||
optional string command = 1;
|
||||
optional int64 seqId = 2;
|
||||
optional uint32 retryCount = 3;
|
||||
optional bytes payloadData = 4;
|
||||
optional UserInstance userInstance = 5;
|
||||
optional int32 errorCode = 6;
|
||||
optional SettingInfo settingInfo = 7;
|
||||
optional RequsetBasicInfo requestBasicInfo = 8;
|
||||
optional string subBiz = 9;
|
||||
optional FrontendInfo frontendInfo = 10;
|
||||
optional string kpn = 11;
|
||||
optional bool anonymouseUser = 12;
|
||||
}
|
||||
|
||||
message DownstreamPayload {
|
||||
optional string command = 1;
|
||||
optional int64 seqId = 2;
|
||||
optional int32 errorCode = 3;
|
||||
optional bytes payloadData = 4;
|
||||
optional string errorMsg = 5;
|
||||
optional bytes errorData = 6;
|
||||
optional string subBiz = 7;
|
||||
}
|
||||
|
||||
message UserInstance {
|
||||
optional User user = 1;
|
||||
optional int64 instanceId = 2;
|
||||
}
|
||||
|
||||
message User {
|
||||
optional int32 appId = 1;
|
||||
optional int64 uid = 2;
|
||||
}
|
||||
|
||||
message SettingInfo {
|
||||
optional string locale = 1;
|
||||
optional sint32 timezone = 2;
|
||||
}
|
||||
|
||||
message RequsetBasicInfo {
|
||||
optional DeviceInfo.PlatformType clientType = 1;
|
||||
optional string deviceId = 2;
|
||||
optional string clientIp = 3;
|
||||
optional string appVersion = 4;
|
||||
optional string channel = 5;
|
||||
optional AppInfo appInfo = 6;
|
||||
optional DeviceInfo deviceInfo = 7;
|
||||
optional EnvInfo envInfo = 8;
|
||||
optional int32 clientPort = 9;
|
||||
optional string location = 10;
|
||||
optional string kpf = 11;
|
||||
}
|
||||
|
||||
message FrontendInfo {
|
||||
optional string ip = 1;
|
||||
optional int32 port = 2;
|
||||
}
|
||||
|
||||
message PacketHeader {
|
||||
optional int32 appId = 1;
|
||||
optional int64 uid = 2;
|
||||
optional int64 instanceId = 3;
|
||||
optional uint32 flags = 4;
|
||||
optional EncodingType encodingType = 6;
|
||||
optional uint32 decodedPayloadLen = 7;
|
||||
optional EncryptionMode encryptionMode = 8;
|
||||
optional TokenInfo tokenInfo = 9;
|
||||
optional int64 seqId = 10;
|
||||
repeated Feature features = 11;
|
||||
optional string kpn = 12;
|
||||
|
||||
enum Flags {
|
||||
option allow_alias = true;
|
||||
kDirUpstream = 0;
|
||||
kDirDownstream = 1;
|
||||
kDirMask = 1;
|
||||
}
|
||||
|
||||
enum EncodingType {
|
||||
kEncodingNone = 0;
|
||||
kEncodingLz4 = 1;
|
||||
}
|
||||
|
||||
enum EncryptionMode {
|
||||
kEncryptionNone = 0;
|
||||
kEncryptionServiceToken = 1;
|
||||
kEncryptionSessionKey = 2;
|
||||
}
|
||||
|
||||
enum Feature {
|
||||
kReserve = 0;
|
||||
kCompressLz4 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message TokenInfo {
|
||||
optional TokenType tokenType = 1;
|
||||
optional bytes token = 2;
|
||||
|
||||
enum TokenType {
|
||||
kInvalid = 0;
|
||||
kServiceToken = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message KeepAliveRequest {
|
||||
optional RegisterRequest.PresenceStatus presenceStatus = 1;
|
||||
optional RegisterRequest.ActiveStatus appActiveStatus = 2;
|
||||
optional PushServiceToken pushServiceToken = 3;
|
||||
repeated PushServiceToken pushServiceTokenList = 4;
|
||||
repeated int32 keepaliveIntervalSec = 5;
|
||||
}
|
||||
|
||||
message ZtLiveScMessage {
|
||||
optional string messageType = 1;
|
||||
optional int32 compressionType = 2;
|
||||
optional bytes payload = 3;
|
||||
optional string liveId = 4;
|
||||
optional string ticket = 5;
|
||||
optional uint64 serverTimestampMs = 6;
|
||||
}
|
||||
|
||||
message ZtLiveScNotifySignal {
|
||||
repeated ZtLiveNotifySignalItem item = 1;
|
||||
|
||||
message ZtLiveNotifySignalItem {
|
||||
optional string signalType = 1;
|
||||
repeated bytes payload = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ZtLiveScActionSignal {
|
||||
repeated ZtLiveActionSignalItem item = 1;
|
||||
|
||||
message ZtLiveActionSignalItem {
|
||||
optional string signalType = 1;
|
||||
repeated bytes payload = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ZtLiveScStateSignal {
|
||||
repeated ZtLiveStateSignalItem item = 1;
|
||||
|
||||
message ZtLiveStateSignalItem {
|
||||
optional string signalType = 1;
|
||||
repeated bytes payload = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ZtLiveScStatusChanged {
|
||||
optional int32 type = 1;
|
||||
optional uint64 maxRandomDelayMs = 2;
|
||||
optional BannedInfo bannedInfo = 3;
|
||||
|
||||
message BannedInfo {
|
||||
optional string banReason = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message CommonActionSignalComment {
|
||||
optional string content = 1;
|
||||
optional uint64 sendTimeMs = 2;
|
||||
optional ZtLiveUserInfo userInfo = 3;
|
||||
}
|
||||
|
||||
message CommonActionSignalLike {
|
||||
optional ZtLiveUserInfo userInfo = 1;
|
||||
optional uint64 sendTimeMs = 2;
|
||||
}
|
||||
|
||||
message CommonActionSignalGift {
|
||||
optional ZtLiveUserInfo userInfo = 1;
|
||||
optional uint64 sendTimeMs = 2;
|
||||
optional uint64 giftId = 3;
|
||||
optional uint32 batchSize = 4;
|
||||
optional uint32 comboCount = 5;
|
||||
optional uint64 rank = 6;
|
||||
optional string comboKey = 7;
|
||||
optional uint64 slotDisplayDurationMs = 8;
|
||||
optional uint64 expireDurationMs = 9;
|
||||
}
|
||||
|
||||
message CommonStateSignalDisplayInfo {
|
||||
optional string watchingCount = 1;
|
||||
optional string likeCount = 2;
|
||||
optional uint32 likeDelta = 3;
|
||||
}
|
||||
|
||||
message CommonStateSignalTopUsers {
|
||||
repeated TopUser topUser = 1;
|
||||
|
||||
message TopUser {
|
||||
optional ZtLiveUserInfo userInfo = 1;
|
||||
optional string customWatchingListData = 2;
|
||||
optional string displaySendAmount = 3;
|
||||
optional bool anonymousUser = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message CommonActionSignalUserEnterRoom {
|
||||
optional ZtLiveUserInfo userInfo = 1;
|
||||
optional uint64 sendTimeMs = 2;
|
||||
}
|
||||
|
||||
message CommonActionSignalUserFollowAuthor {
|
||||
optional ZtLiveUserInfo userInfo = 1;
|
||||
optional uint64 sendTimeMs = 2;
|
||||
}
|
||||
|
||||
message CommonNotifySignalKickedOut {
|
||||
optional string reason = 1;
|
||||
}
|
||||
|
||||
message CommonNotifySignalViolationAlert {
|
||||
optional string violationContent = 1;
|
||||
}
|
||||
|
||||
message CommonStateSignalCurrentRedpackList {
|
||||
|
||||
}
|
||||
|
||||
message CommonStateSignalRecentComment {
|
||||
optional CommonActionSignalComment comment = 1;
|
||||
}
|
||||
|
||||
message CommonStateSignalChatReady {
|
||||
optional string chatId = 1;
|
||||
optional ZtLiveUserInfo guestUserInfo = 2;
|
||||
optional int32 mediaType = 3;
|
||||
}
|
||||
|
||||
message CommonStateSignalChatEnd {
|
||||
optional string chatId = 1;
|
||||
optional int32 endType = 2;
|
||||
}
|
||||
|
||||
message AcfunActionSignalThrowBanana {
|
||||
optional UserInfo visitor = 1;
|
||||
optional int32 count = 2;
|
||||
optional uint64 sendTimeMs = 3;
|
||||
}
|
||||
|
||||
message AcfunStateSignalDisplayInfo {
|
||||
optional string bananaCount = 1;
|
||||
}
|
||||
|
||||
message ZtLiveUserInfo {
|
||||
optional uint64 userId = 1;
|
||||
optional string nickname = 2;
|
||||
optional ImageCdnNode avatar = 3;
|
||||
}
|
||||
|
||||
message ImageCdnNode {
|
||||
optional string cdn = 1;
|
||||
optional string url = 2;
|
||||
optional string urlPattern = 3;
|
||||
}
|
||||
|
||||
message UserInfo {
|
||||
optional uint64 userId = 1;
|
||||
optional string name = 2;
|
||||
}
|
310
danmu/danmaku/acfun.py
Normal file
310
danmu/danmaku/acfun.py
Normal file
@ -0,0 +1,310 @@
|
||||
# AcFun直播现在属于快手旗下了,其JS源码中也可以看到很多名为'kuaishou'的变量,所以和快手直播弹幕的获取方法有点类似,都使用了protobuf压缩数据。
|
||||
# 在mplayer-live.xxx.js中可以看到所有websocket过程。奇怪的是,其序列化和反序列化时代码并不统一,可能有啥"特殊意义"我没看懂。
|
||||
# ws请求过程:Register--EnterRoom--Heartbeat,其中Register后返回的第一条数据里有sessionkey和instanceid。
|
||||
# 在Chrome调试时发现客户端一直会向服务器发送:每1秒一次ping;每10秒一次keepalive,模拟实现时,不发送也正常。
|
||||
import base64
|
||||
import gzip
|
||||
import struct
|
||||
import time
|
||||
|
||||
import requests
|
||||
from Crypto import Random
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad
|
||||
|
||||
from . import acfun_pb2 as pb
|
||||
|
||||
|
||||
class AcFun:
|
||||
ws_url = 'wss://link.xiatou.com/'
|
||||
|
||||
def __init__(self, rid):
|
||||
self.encryptionmode = 0 # 加密模式,0不加密,1时key=ssecurity,2时key=sessionkey
|
||||
self.seqId = 0 # 客户端发送请求时都会加1
|
||||
self.payload_len = 0
|
||||
self.sessionkey = b''
|
||||
self.instanceid = 0 # 初始值0
|
||||
self.gift = { # 有些直播间的礼物编号不一样,实际打开直播间页面会有一次XHR请求
|
||||
1: '香蕉',
|
||||
17: '快乐水',
|
||||
9: '告白',
|
||||
35: '氧气瓶',
|
||||
4: '牛啤',
|
||||
33: '情书',
|
||||
8: '星蕉雨',
|
||||
31: '金坷垃',
|
||||
34: '狗粮',
|
||||
2: '吃瓜',
|
||||
12: '打Call',
|
||||
32: '变身腰带',
|
||||
15: 'AC机娘',
|
||||
16: '猴岛',
|
||||
10: '666',
|
||||
11: '菜鸡',
|
||||
30: '鸽鸽',
|
||||
13: '立FLAG',
|
||||
6: '魔法棒',
|
||||
7: '好人卡',
|
||||
14: '窜天猴',
|
||||
21: '生日快乐',
|
||||
5: '手柄',
|
||||
29: '大触'
|
||||
}
|
||||
# 下面获取一些加入房间所需注册参数
|
||||
headers = {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'cookie': '_did=H5_',
|
||||
'referer': 'https://m.acfun.cn/'
|
||||
}
|
||||
url = 'https://id.app.acfun.cn/rest/app/visitor/login'
|
||||
form_data = 'sid=acfun.api.visitor'
|
||||
with requests.Session() as s:
|
||||
res = s.post(url, data=form_data, headers=headers).json()
|
||||
status, *d = res.values()
|
||||
if status == 0:
|
||||
# 未登陆访客获取的3个参数
|
||||
acsecurity, self.uid, visitor_st = d
|
||||
self.ssecurity = base64.b64decode(acsecurity)
|
||||
self.token = visitor_st.encode()
|
||||
else:
|
||||
raise Exception('token 获取错误')
|
||||
url = 'https://api.kuaishouzt.com/rest/zt/live/web/startPlay'
|
||||
params = {
|
||||
'subBiz': 'mainApp',
|
||||
'kpn': 'ACFUN_APP',
|
||||
'kpf': 'OUTSIDE_IOS_H5',
|
||||
'userId': self.uid,
|
||||
'did': 'H5_',
|
||||
'acfun.api.visitor_st': visitor_st
|
||||
}
|
||||
data = 'authorId={}&pullStreamType=SINGLE_HLS'.format(rid)
|
||||
res = s.post(url, params=params, data=data, headers=headers).json()
|
||||
if res['result'] == 1:
|
||||
data = res['data']
|
||||
# 获取直播间的几个参数
|
||||
self.availabletickets, *_ = data['availableTickets']
|
||||
self.enterroomattach = data['enterRoomAttach']
|
||||
self.liveid = data['liveId']
|
||||
else:
|
||||
raise Exception('直播已关闭')
|
||||
|
||||
@staticmethod
|
||||
# aes加密,iv随机;key值取决去encryptionmode
|
||||
def aes_encode(t, key):
|
||||
t = pad(t, AES.block_size)
|
||||
iv = Random.new().read(AES.block_size)
|
||||
mode = AES.MODE_CBC
|
||||
c = AES.new(key, mode, iv)
|
||||
res = c.encrypt(t)
|
||||
return iv + res # 把iv加到头部
|
||||
|
||||
@staticmethod
|
||||
# 这里传入的待解密数据不用补全,一直都是16的整数倍。key值取决去encryptionmode。pkcs7模式去掉最后的填充padding。
|
||||
def aes_decode(t, key):
|
||||
iv = t[:16]
|
||||
n = t[16:]
|
||||
# n = pad(n, AES.block_size)
|
||||
mode = AES.MODE_CBC
|
||||
c = AES.new(key, mode, iv)
|
||||
res = c.decrypt(n)
|
||||
length = len(res)
|
||||
padding = res[length - 1]
|
||||
res = res[0:length - padding]
|
||||
return res
|
||||
|
||||
def register(self):
|
||||
# 注册
|
||||
p = pb.RegisterRequest()
|
||||
p.appActiveStatus = 1
|
||||
p.appInfo.appName = 'link-sdk'
|
||||
p.appInfo.sdkVersion = '1.2.1'
|
||||
p.deviceInfo.deviceModel = 'h5'
|
||||
p.deviceInfo.platformType = 6
|
||||
p.instanceId = 0
|
||||
p.presenceStatus = 1
|
||||
p.ztCommonInfo.kpn = 'ACFUN_APP'
|
||||
p.ztCommonInfo.kpf = 'OUTSIDE_IOS_H5'
|
||||
p.ztCommonInfo.uid = self.uid
|
||||
register_data = p.SerializeToString()
|
||||
self.encryptionmode = 1
|
||||
self.seqId = 1
|
||||
return register_data
|
||||
|
||||
def keepalive(self):
|
||||
# keepalive
|
||||
p = pb.KeepAliveRequest()
|
||||
p.appActiveStatus = 1
|
||||
p.presenceStatus = 1
|
||||
keepalive_data = p.SerializeToString()
|
||||
self.encryptionmode = 2
|
||||
self.seqId += 1
|
||||
return keepalive_data
|
||||
|
||||
def ping(self):
|
||||
# ping
|
||||
p = pb.PingRequest()
|
||||
# p.pingType = 2
|
||||
ping_data = p.SerializeToString()
|
||||
self.seqId += 1
|
||||
self.encryptionmode = 2
|
||||
return ping_data
|
||||
|
||||
def ztlivecsenterroom(self):
|
||||
return self.cscmd('ZtLiveCsEnterRoom')
|
||||
|
||||
def ztlivecsheartbeat(self):
|
||||
# 心跳数据
|
||||
return self.cscmd('ZtLiveCsHeartbeat')
|
||||
|
||||
def cscmd(self, payload_type):
|
||||
p = getattr(pb, payload_type)()
|
||||
|
||||
if payload_type == 'ZtLiveCsEnterRoom':
|
||||
p.isAuthor = False
|
||||
p.reconnectCount = 0
|
||||
p.enterRoomAttach = self.enterroomattach
|
||||
p.clientLiveSdkVersion = 'kwai-acfun-live-link'
|
||||
elif payload_type == 'ZtLiveCsHeartbeat':
|
||||
p.sequence = self.seqId
|
||||
p.clientTimestampMs = int(time.time() * 1000)
|
||||
|
||||
payload = p.SerializeToString()
|
||||
p = pb.CsCmd()
|
||||
p.cmdType = payload_type
|
||||
p.ticket = self.availabletickets
|
||||
p.payload = payload
|
||||
p.liveId = self.liveid
|
||||
cscmd_data = p.SerializeToString()
|
||||
|
||||
self.seqId += 1
|
||||
self.encryptionmode = 2
|
||||
return cscmd_data
|
||||
|
||||
def encode_payload(self, payload_type):
|
||||
# 要发送的原始数据都是先序列化再拼接
|
||||
c = {
|
||||
'keepalive': 'Basic.KeepAlive',
|
||||
'register': 'Basic.Register',
|
||||
'ping': 'Basic.Ping',
|
||||
'ztlivecsenterroom': 'Global.ZtLiveInteractive.CsCmd',
|
||||
'ztlivecsheartbeat': 'Global.ZtLiveInteractive.CsCmd'
|
||||
}
|
||||
p = pb.UpstreamPayload()
|
||||
p.command = c[payload_type]
|
||||
p.retryCount = 1
|
||||
p.payloadData = getattr(self, payload_type)()
|
||||
p.seqId = self.seqId
|
||||
p.subBiz = 'mainApp'
|
||||
e = p.SerializeToString()
|
||||
key = self.ssecurity if self.encryptionmode == 1 else self.sessionkey
|
||||
payload_data = AcFun.aes_encode(e, key) if self.encryptionmode != 0 else e
|
||||
self.payload_len = len(e)
|
||||
return payload_data
|
||||
|
||||
def encode_head(self):
|
||||
# 头部数据,里面有原始数据长度
|
||||
p = pb.PacketHeader()
|
||||
p.appId = 13
|
||||
p.decodedPayloadLen = self.payload_len
|
||||
p.encryptionMode = self.encryptionmode
|
||||
p.instanceId = self.instanceid
|
||||
p.kpn = 'ACFUN_APP'
|
||||
p.seqId = self.seqId
|
||||
if self.encryptionmode == 1:
|
||||
p.tokenInfo.tokenType = 1
|
||||
p.tokenInfo.token = self.token
|
||||
p.uid = self.uid
|
||||
head = p.SerializeToString()
|
||||
return head
|
||||
|
||||
def encode_packet(self, payload_type):
|
||||
# 数据组成:固定数据 + 头部长度 + 原始数据长度 + 头部 + 原始数据
|
||||
body = self.encode_payload(payload_type)
|
||||
head = self.encode_head()
|
||||
data = struct.pack('!HHII', 43981, 1, len(head), len(body))
|
||||
data += head + body
|
||||
return data
|
||||
|
||||
def decode_packet(self, data):
|
||||
# 数据解包
|
||||
msgs = [{'name': '', 'content': '', 'msg_type': 'other'}]
|
||||
|
||||
head_length, body_length = struct.unpack('!II', data[4:12])
|
||||
|
||||
if 12 + head_length + body_length != len(data):
|
||||
raise Exception('downstream message size is not correct')
|
||||
|
||||
# 头部解包
|
||||
e = data[12:head_length + 12]
|
||||
c = pb.PacketHeader()
|
||||
c.ParseFromString(e)
|
||||
encryptionmode = c.encryptionMode # 根据加密模式确定解密的key
|
||||
|
||||
# body解包
|
||||
h = data[head_length + 12:]
|
||||
key = self.ssecurity if encryptionmode == 1 else self.sessionkey
|
||||
n = AcFun.aes_decode(h, key) if encryptionmode != 0 else h
|
||||
u = pb.DownstreamPayload()
|
||||
u.ParseFromString(n)
|
||||
|
||||
# header = c
|
||||
# body = u
|
||||
payload = u.payloadData
|
||||
command = u.command # 根据command确定返回数据类型
|
||||
# print(command)
|
||||
|
||||
if command == 'Basic.Register':
|
||||
# websocket第一次返回数据,确定sessKey和instanceId
|
||||
p = pb.RegisterResponse()
|
||||
p.ParseFromString(payload)
|
||||
self.sessionkey = p.sessKey
|
||||
self.instanceid = p.instanceId
|
||||
|
||||
elif command == 'Push.ZtLiveInteractive.Message': # 'Push.ZtLiveInteractive.Message'
|
||||
a = pb.ZtLiveScMessage()
|
||||
a.ParseFromString(payload)
|
||||
o = a.messageType
|
||||
s = a.payload
|
||||
|
||||
if a.compressionType == 2:
|
||||
s = gzip.decompress(s)
|
||||
|
||||
if o == 'ZtLiveScTicketInvalid':
|
||||
raise Exception('ZtLiveScTicketInvalid')
|
||||
|
||||
# o可为'ZtLiveScNotifySignal' 'ZtLiveScActionSignal' 等,弹幕礼物入场点赞等在ZtLiveScActionSignal中
|
||||
elif o == 'ZtLiveScActionSignal':
|
||||
p = getattr(pb, o)()
|
||||
p.ParseFromString(s)
|
||||
|
||||
for i in p.item:
|
||||
p = getattr(pb, i.signalType)()
|
||||
# signalType:
|
||||
# CommonActionSignalComment 评论
|
||||
# CommonActionSignalGift 礼物
|
||||
# CommonActionSignalUserEnterRoom 入场
|
||||
# CommonActionSignalLike 点赞
|
||||
# CommonActionSignalUserFollowAuthor 关注主播
|
||||
# 等等等
|
||||
u = {
|
||||
'CommonActionSignalUserEnterRoom': '进入直播间',
|
||||
'CommonActionSignalUserFollowAuthor': '关注了主播',
|
||||
'CommonActionSignalComment': '',
|
||||
'CommonActionSignalLike': '点赞了❤',
|
||||
'CommonActionSignalGift': ''
|
||||
}
|
||||
|
||||
for a_payload in i.payload: # i.payload 是 repeated 类型
|
||||
p.ParseFromString(a_payload)
|
||||
|
||||
if i.signalType in u.keys():
|
||||
user = p.userInfo.nickname
|
||||
content = u[i.signalType]
|
||||
if i.signalType == 'CommonActionSignalComment':
|
||||
content = p.content
|
||||
elif i.signalType == 'CommonActionSignalGift':
|
||||
content = '送出 ' + self.gift.get(p.giftId, '')
|
||||
|
||||
msg = {'name': user, 'content': content, 'msg_type': 'danmaku'}
|
||||
msgs.append(msg.copy())
|
||||
return msgs
|
3650
danmu/danmaku/acfun_pb2.py
Normal file
3650
danmu/danmaku/acfun_pb2.py
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user