1
1
package huya
2
2
3
3
import (
4
- "bytes"
5
4
"crypto/md5"
6
- "encoding/base64"
7
5
"encoding/hex"
8
6
"fmt"
9
- "html/template"
10
- "math/rand"
11
- "net/http"
12
- "net/url"
13
- "strconv"
14
- "strings"
15
- "time"
16
-
17
- "github.com/hr3lxphr6j/requests"
18
- "github.com/tidwall/gjson"
19
-
20
7
"github.com/hr3lxphr6j/bililive-go/src/live"
21
8
"github.com/hr3lxphr6j/bililive-go/src/live/internal"
22
9
"github.com/hr3lxphr6j/bililive-go/src/pkg/utils"
10
+ "github.com/hr3lxphr6j/requests"
11
+ "github.com/tidwall/gjson"
12
+ "net/http"
13
+ "net/url"
23
14
)
24
15
25
16
const (
26
17
domain = "www.huya.com"
27
18
cnName = "虎牙"
28
19
userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
20
+ uaApp = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.49(0x18003137) NetType/WIFI Language/zh_CN WeChat/8.0.49.33 CFNetwork/1474 Darwin/23.0.0"
29
21
)
30
22
31
23
func init () {
@@ -45,8 +37,35 @@ type Live struct {
45
37
lastCdnIndex int
46
38
}
47
39
48
- func (l * Live ) GetInfo () (info * live.Info , err error ) {
49
- resp , err := requests .Get (l .Url .String (), live .CommonUserAgent )
40
+ func (l * Live ) getDate () (result * gjson.Result , err error ) {
41
+ html , err := requests .Get (l .Url .String (), live .CommonUserAgent )
42
+ if err != nil {
43
+ return nil , err
44
+ }
45
+ if html .StatusCode != http .StatusOK {
46
+ return nil , live .ErrRoomNotExist
47
+ }
48
+ htmlBody , err := html .Text ()
49
+ if err != nil {
50
+ return nil , err
51
+ }
52
+ strFilter := utils .NewStringFilterChain (utils .ParseUnicode , utils .UnescapeHTMLEntity )
53
+ rjson := strFilter .Do (utils .Match1 (`stream: (\{"data".*?),"iWebDefaultBitRate"` , htmlBody )) + "}"
54
+ gj := gjson .Parse (rjson )
55
+
56
+ roomId := gj .Get ("data.0.gameLiveInfo.profileRoom" ).String ()
57
+ params := make (map [string ]string )
58
+ params ["m" ] = "Live"
59
+ params ["do" ] = "profileRoom"
60
+ params ["roomid" ] = roomId
61
+ params ["showSecret" ] = "1"
62
+
63
+ headers := make (map [string ]interface {})
64
+ headers ["User-Agent" ] = uaApp
65
+ headers ["xweb_xhr" ] = "1"
66
+ headers ["referer" ] = "https://servicewechat.com/wx74767bf0b684f7d3/301/page-frame.html"
67
+ headers ["accept-language" ] = "zh-CN,zh;q=0.9"
68
+ resp , err := requests .Get ("https://mp.huya.com/cache.php" , requests .Headers (headers ), requests .Queries (params ), requests .UserAgent (uaApp ))
50
69
if err != nil {
51
70
return nil , err
52
71
}
@@ -57,25 +76,32 @@ func (l *Live) GetInfo() (info *live.Info, err error) {
57
76
if err != nil {
58
77
return nil , err
59
78
}
79
+ res := gjson .Parse (body )
80
+ return & res , nil
81
+ }
60
82
61
- if res := utils .Match1 ("哎呀,虎牙君找不到这个主播,要不搜索看看?" , body ); res != "" {
83
+ func (l * Live ) GetInfo () (info * live.Info , err error ) {
84
+ res , err := l .getDate ()
85
+ if err != nil {
86
+ return nil , err
87
+ }
88
+ if res := utils .Match1 ("该主播不存在!" , res .String ()); res != "" {
62
89
return nil , live .ErrRoomNotExist
63
90
}
64
91
65
- if strings .Contains (body , "该主播涉嫌违规,正在整改中" ) {
66
- return & live.Info {
67
- Live : l ,
68
- HostName : "该主播涉嫌违规,正在整改中" ,
69
- RoomName : "该主播涉嫌违规,正在整改中" ,
70
- Status : false ,
71
- }, nil
72
- }
92
+ // if strings.Contains(body, "该主播涉嫌违规,正在整改中") {
93
+ // return &live.Info{
94
+ // Live: l,
95
+ // HostName: "该主播涉嫌违规,正在整改中",
96
+ // RoomName: "该主播涉嫌违规,正在整改中",
97
+ // Status: false,
98
+ // }, nil
99
+ // }
73
100
74
101
var (
75
- strFilter = utils .NewStringFilterChain (utils .ParseUnicode , utils .UnescapeHTMLEntity )
76
- hostName = strFilter .Do (utils .Match1 (`"nick":"([^"]*)"` , body ))
77
- roomName = strFilter .Do (utils .Match1 (`"introduction":"([^"]*)"` , body ))
78
- status = strFilter .Do (utils .Match1 (`"isOn":([^,]*),` , body ))
102
+ hostName = res .Get ("data.liveData.nick" ).String ()
103
+ roomName = res .Get ("data.liveData.introduction" ).String ()
104
+ status = res .Get ("data.realLiveStatus" ).String ()
79
105
)
80
106
81
107
if hostName == "" || roomName == "" || status == "" {
@@ -86,7 +112,7 @@ func (l *Live) GetInfo() (info *live.Info, err error) {
86
112
Live : l ,
87
113
HostName : hostName ,
88
114
RoomName : roomName ,
89
- Status : status == "true " ,
115
+ Status : status == "ON " ,
90
116
}
91
117
return info , nil
92
118
}
@@ -96,133 +122,23 @@ func GetMD5Hash(text string) string {
96
122
return hex .EncodeToString (hash [:])
97
123
}
98
124
99
- type urlQueryParams struct {
100
- WsSecret string
101
- WsTime string
102
- Seqid string
103
- Ctype string
104
- Ver string
105
- Fs string
106
- U string
107
- T string
108
- Sv string
109
- Sdk_sid string
110
- Codec string
111
- }
112
-
113
- func parseAntiCode (anticode string , uid int64 , streamName string ) (string , error ) {
114
- qr , err := url .ParseQuery (anticode )
115
- if err != nil {
116
- return "" , err
117
- }
118
- resultTemplate := template .Must (template .New ("urlQuery" ).Parse (
119
- "wsSecret={{.WsSecret}}" +
120
- "&wsTime={{.WsTime}}" +
121
- "&seqid={{.Seqid}}" +
122
- "&ctype={{.Ctype}}" +
123
- "&ver={{.Ver}}" +
124
- "&fs={{.Fs}}" +
125
- "&u={{.U}}" +
126
- "&t={{.T}}" +
127
- "&sv={{.Sv}}" +
128
- "&sdk_sid={{.Sdk_sid}}" +
129
- "&codec={{.Codec}}" ,
130
- ))
131
- timeNow := time .Now ().Unix () * 1000
132
- resultParams := urlQueryParams {
133
- WsSecret : "" ,
134
- WsTime : qr .Get ("wsTime" ),
135
- Seqid : strconv .FormatInt (timeNow + uid , 10 ),
136
- Ctype : qr .Get ("ctype" ),
137
- Ver : "1" ,
138
- Fs : qr .Get ("fs" ),
139
- U : strconv .FormatInt (uid , 10 ),
140
- T : "100" ,
141
- Sv : "2405220949" ,
142
- Sdk_sid : strconv .FormatInt (uid , 10 ),
143
- Codec : "264" ,
144
- }
145
- ss := GetMD5Hash (fmt .Sprintf ("%s|%s|%s" , resultParams .Seqid , resultParams .Ctype , resultParams .T ))
146
-
147
- decodeString , _ := base64 .StdEncoding .DecodeString (qr .Get ("fm" ))
148
- fm := string (decodeString )
149
- fm = strings .ReplaceAll (fm , "$0" , resultParams .U )
150
- fm = strings .ReplaceAll (fm , "$1" , streamName )
151
- fm = strings .ReplaceAll (fm , "$2" , ss )
152
- fm = strings .ReplaceAll (fm , "$3" , resultParams .WsTime )
153
-
154
- resultParams .WsSecret = GetMD5Hash (fm )
155
- var buf bytes.Buffer
156
- if err := resultTemplate .Execute (& buf , resultParams ); err != nil {
157
- return "" , err
158
- }
159
- return buf .String (), nil
160
- }
161
-
162
125
func (l * Live ) GetStreamUrls () (us []* url.URL , err error ) {
163
- resp , err := requests .Get (l .Url .String (), requests .UserAgent (userAgent ))
164
- if err != nil {
165
- return nil , err
166
- }
167
- if resp .StatusCode != http .StatusOK {
168
- return nil , fmt .Errorf ("status code: %d" , resp .StatusCode )
169
- }
170
- body , err := resp .Text ()
126
+ data , err := l .getDate ()
171
127
if err != nil {
172
128
return nil , err
173
129
}
130
+ sFlvUrl := data .Get ("data.stream.baseSteamInfoList.0.sFlvUrl" ).String ()
131
+ sStreamName := data .Get ("data.stream.baseSteamInfoList.0.sStreamName" ).String ()
132
+ sFlvUrlSuffix := data .Get ("data.stream.baseSteamInfoList.0.sFlvUrlSuffix" ).String ()
133
+ sFlvAntiCode := data .Get ("data.stream.baseSteamInfoList.0.sFlvAntiCode" ).String ()
134
+ streamUrl := fmt .Sprintf ("%s/%s.%s?%s" , sFlvUrl , sStreamName , sFlvUrlSuffix , sFlvAntiCode )
174
135
175
- tmpStrings := strings .Split (body , `stream: ` )
176
- if len (tmpStrings ) < 2 {
177
- return nil , fmt .Errorf ("stream json info not found" )
178
- }
179
- tmpStreamJsonRawString := strings .Split (tmpStrings [1 ], `};` )
180
- if len (tmpStreamJsonRawString ) < 1 {
181
- return nil , fmt .Errorf ("stream json info end not found. stream text: %s" , tmpStrings [1 ])
182
- }
183
- streamJsonRawString := tmpStreamJsonRawString [0 ]
184
- if ! gjson .Valid (streamJsonRawString ) {
185
- return nil , fmt .Errorf ("streamJsonRawString not valid" )
186
- }
187
- streamJson := gjson .Parse (streamJsonRawString )
188
- vMultiStreamInfoJson := streamJson .Get ("vMultiStreamInfo" ).Array ()
189
- if len (vMultiStreamInfoJson ) == 0 {
190
- return nil , fmt .Errorf ("vMultiStreamInfo not found" )
191
- }
192
-
193
- streamInfoJsons := streamJson .Get ("data.0.gameStreamInfoList" ).Array ()
194
- if len (streamInfoJsons ) == 0 {
195
- return nil , fmt .Errorf ("gameStreamInfoList not found" )
196
- }
197
- index := l .lastCdnIndex
198
- if index >= len (streamInfoJsons ) {
199
- index = 0
200
- }
201
- l .lastCdnIndex = index + 1
202
- gameStreamInfoJson := streamInfoJsons [index ]
203
- return getStreamUrlsFromGameStreamInfoJson (gameStreamInfoJson )
204
- }
205
-
206
- func getStreamUrlsFromGameStreamInfoJson (gameStreamInfoJson gjson.Result ) (us []* url.URL , err error ) {
207
- // get streamName
208
- sStreamName := gameStreamInfoJson .Get ("sStreamName" ).String ()
209
- // get sFlvAntiCode
210
- sFlvAntiCode := gameStreamInfoJson .Get ("sFlvAntiCode" ).String ()
211
- // get sFlvUrl
212
- sFlvUrl := gameStreamInfoJson .Get ("sFlvUrl" ).String ()
213
- // get random uid
214
- uid := rand .Int63n (99999999999 ) + 1200000000000
215
-
216
- query , err := parseAntiCode (sFlvAntiCode , uid , sStreamName )
217
- if err != nil {
218
- return nil , err
219
- }
220
- tmpUrlString := fmt .Sprintf ("%s/%s.flv?%s" , sFlvUrl , sStreamName , query )
221
- u , err := url .Parse (tmpUrlString )
136
+ res , err := utils .GenUrls (streamUrl )
222
137
if err != nil {
223
138
return nil , err
224
139
}
225
- return []* url.URL {u }, nil
140
+ return res , nil
141
+
226
142
}
227
143
228
144
func (l * Live ) GetPlatformCNName () string {
@@ -231,7 +147,7 @@ func (l *Live) GetPlatformCNName() string {
231
147
232
148
func (l * Live ) GetHeadersForDownloader () map [string ]string {
233
149
return map [string ]string {
234
- "User-Agent" : userAgent ,
150
+ "User-Agent" : uaApp ,
235
151
"Accept" : `text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8` ,
236
152
"Accept-Encoding" : `gzip, deflate` ,
237
153
"Accept-Language" : `zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3` ,
0 commit comments