mirror of
https://github.com/wbt5/real-url.git
synced 2025-08-02 15:44:49 +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