├── package.json ├── .github └── asserts │ ├── env.png │ ├── nodejs.png │ ├── region.png │ └── envDemo.png ├── README.md ├── vercel.json ├── go.mod ├── LICENSE ├── list ├── yylunbo.go ├── douyuyqk.go ├── huyayqk.go └── tvm3u.go ├── utils ├── jsRun.go └── http.go ├── liveurls ├── douyin.go ├── yy.go ├── youtube.go ├── bilibili.go ├── douyu.go ├── ysptp.go ├── huya.go └── itv.go └── api ├── yqk └── yqk.go ├── live.go └── index.go /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": "18.x" 4 | } 5 | } -------------------------------------------------------------------------------- /.github/asserts/env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papagaye744/iptv-go/HEAD/.github/asserts/env.png -------------------------------------------------------------------------------- /.github/asserts/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papagaye744/iptv-go/HEAD/.github/asserts/nodejs.png -------------------------------------------------------------------------------- /.github/asserts/region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papagaye744/iptv-go/HEAD/.github/asserts/region.png -------------------------------------------------------------------------------- /.github/asserts/envDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/papagaye744/iptv-go/HEAD/.github/asserts/envDemo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 源仓库已闭源 2 | 3 | 建议自己部署docker的allinone使用,[https://hub.docker.com/r/youshandefeiyang/allinone](https://hub.docker.com/r/youshandefeiyang/allinone) 4 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [ 3 | { 4 | "src": "/live/.*", 5 | "dest": "/api/live.go" 6 | }, 7 | { 8 | "src": "/yqk/.*", 9 | "dest": "/api/yqk/yqk.go" 10 | }, 11 | { 12 | "src": "/favicon.ico", 13 | "dest": "https://assets.vercel.com/image/upload/front/favicon/vercel/favicon.ico" 14 | }, 15 | { 16 | "src": "/.*", 17 | "dest": "/api/index.go" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module Golang 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32 7 | github.com/etherlabsio/go-m3u8 v1.0.0 8 | github.com/forgoer/openssl v1.5.0 9 | github.com/gin-gonic/gin v1.8.2 10 | github.com/hr3lxphr6j/bililive-go v0.7.23 11 | github.com/hr3lxphr6j/requests v0.0.2 12 | github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b 13 | github.com/tidwall/gjson v1.14.4 14 | ) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 papagaye744 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /list/yylunbo.go: -------------------------------------------------------------------------------- 1 | // Package list 2 | // @Time:2023/06/03 20:35 3 | // @File:yylunbo.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package list 9 | 10 | import ( 11 | "io" 12 | "net/http" 13 | ) 14 | 15 | type Yylist struct { 16 | } 17 | 18 | type DataElement struct { 19 | Avatar string `json:"avatar"` 20 | Biz string `json:"biz"` 21 | Desc string `json:"desc"` 22 | Sid int `json:"sid"` 23 | } 24 | 25 | type ApiResponse struct { 26 | Data struct { 27 | IsLastPage int `json:"isLastPage"` 28 | Data []DataElement `json:"data"` 29 | } `json:"data"` 30 | } 31 | 32 | func (y *Yylist) Yylb(requesturl string) string { 33 | client := &http.Client{} 34 | req, _ := http.NewRequest("GET", requesturl, nil) 35 | req.Header.Set("Upgrade-Insecure-Requests", "1") 36 | req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36") 37 | res, err := client.Do(req) 38 | if err != nil { 39 | panic(err) 40 | } 41 | defer res.Body.Close() 42 | body, _ := io.ReadAll(res.Body) 43 | return string(body) 44 | } -------------------------------------------------------------------------------- /utils/jsRun.go: -------------------------------------------------------------------------------- 1 | // Package utils 2 | // @Time:2023/08/24 06:36 3 | // @File:jsRun.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package utils 9 | 10 | import ( 11 | "fmt" 12 | js "github.com/dop251/goja" 13 | "sync" 14 | ) 15 | 16 | type JsUtil struct { 17 | pool sync.Pool 18 | } 19 | 20 | func (j *JsUtil) getVm() *js.Runtime { 21 | v := j.pool.Get() 22 | if v != nil { 23 | return v.(*js.Runtime) 24 | } 25 | return js.New() 26 | } 27 | 28 | func (j *JsUtil) putVm(vm *js.Runtime) { 29 | vm.Set("global", nil) // 清除全局对象 30 | j.pool.Put(vm) 31 | } 32 | 33 | func (j *JsUtil) JsRun(funcContent []string, params ...any) any { 34 | vm := j.getVm() 35 | defer j.putVm(vm) 36 | _, err := vm.RunString(funcContent[0]) 37 | if err != nil { 38 | return err 39 | } 40 | jsfn, ok := js.AssertFunction(vm.Get(funcContent[1])) 41 | if !ok { 42 | return fmt.Errorf("执行函数失败") 43 | } 44 | jsValues := make([]js.Value, 0, len(params)) 45 | for _, v := range params { 46 | jsValues = append(jsValues, vm.ToValue(v)) 47 | } 48 | result, err := jsfn( 49 | js.Undefined(), 50 | jsValues..., 51 | ) 52 | if err != nil { 53 | return err 54 | } 55 | return result 56 | } -------------------------------------------------------------------------------- /list/douyuyqk.go: -------------------------------------------------------------------------------- 1 | // Package list 2 | // @Time:2023/06/02 10:00 3 | // @File:mian.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package list 9 | 10 | import ( 11 | "io" 12 | "net/http" 13 | ) 14 | 15 | type DouYuYqk struct { 16 | } 17 | 18 | type DouYuResponse struct { 19 | Data struct { 20 | Pgcnt int `json:"pgcnt"` 21 | Rl []struct { 22 | Av string `json:"av"` 23 | C2name string `json:"c2name"` 24 | Nn string `json:"nn"` 25 | Rid int `json:"rid"` 26 | } `json:"rl"` 27 | } `json:"data"` 28 | } 29 | 30 | func (dy *DouYuYqk) Douyuyqk(requestURL string) ([]byte, error) { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", requestURL, nil) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | req.Header.Set("upgrade-insecure-requests", "1") 39 | req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36") 40 | 41 | resp, err := client.Do(req) 42 | if err != nil { 43 | return nil, err 44 | } 45 | defer resp.Body.Close() 46 | 47 | return io.ReadAll(resp.Body) 48 | } -------------------------------------------------------------------------------- /list/huyayqk.go: -------------------------------------------------------------------------------- 1 | // Package list 2 | // @Time:2023/06/02 10:00 3 | // @File:mian.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package list 9 | 10 | import ( 11 | "io" 12 | "net/http" 13 | ) 14 | 15 | type HuyaYqk struct { 16 | } 17 | 18 | type YaResponse struct { 19 | ITotalPage int `json:"iTotalPage"` 20 | IPageSize int `json:"iPageSize"` 21 | VList []struct { 22 | SAvatar180 string `json:"sAvatar180"` 23 | SGameFullName string `json:"sGameFullName"` 24 | SNick string `json:"sNick"` 25 | LProfileRoom int `json:"lProfileRoom"` 26 | } `json:"vList"` 27 | } 28 | 29 | func (hy *HuyaYqk) HuYaYqk(requestURL string) ([]byte, error) { 30 | client := &http.Client{} 31 | req, err := http.NewRequest("GET", requestURL, nil) 32 | if err != nil { 33 | return nil, err 34 | } 35 | req.Header.Set("upgrade-insecure-requests", "1") 36 | req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36") 37 | resp, err := client.Do(req) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer resp.Body.Close() 42 | return io.ReadAll(resp.Body) 43 | } -------------------------------------------------------------------------------- /utils/http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "fmt" 6 | "time" 7 | "net/http" 8 | "net/url" 9 | // "log" 10 | ) 11 | 12 | func GetTestVideoUrl(w http.ResponseWriter) { 13 | TimeLocation, err := time.LoadLocation("Asia/Shanghai") 14 | if err != nil { 15 | TimeLocation = time.FixedZone("CST", 8*60*60) 16 | } 17 | str_time := time.Now().In(TimeLocation).Format("2006-01-02 15:04:05") 18 | fmt.Fprintln(w, "#EXTM3U") 19 | fmt.Fprintln(w, "#EXTINF:-1 tvg-name=\""+str_time+"\" tvg-logo=\"https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/tg.jpg\" group-title=\"列表更新时间\","+str_time) 20 | fmt.Fprintln(w, "https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/time/time.mp4") 21 | fmt.Fprintln(w, "#EXTINF:-1 tvg-name=\"4K60PSDR-H264-AAC测试\" tvg-logo=\"https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/tg.jpg\" group-title=\"4K频道\",4K60PSDR-H264-AAC测试") 22 | fmt.Fprintln(w, "https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/sdr4kvideo/index.m3u8") 23 | fmt.Fprintln(w, "#EXTINF:-1 tvg-name=\"4K60PHLG-HEVC-EAC3测试\" tvg-logo=\"https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/tg.jpg\" group-title=\"4K频道\",4K60PHLG-HEVC-EAC3测试") 24 | fmt.Fprintln(w, "https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/hlg4kvideo/index.m3u8") 25 | } 26 | 27 | func GetLivePrefix(r *http.Request) string { 28 | // 尝试从环境变量读取url 29 | envUrl := os.Getenv("LIVE_URL") 30 | // log.Println("env url:", envUrl) 31 | if envUrl == "" { 32 | // 默认url 33 | envUrl = "https://www.goodiptv.club" 34 | } 35 | firstUrl := DefaultQuery(r, "url", envUrl) 36 | realUrl, _ := url.QueryUnescape(firstUrl) 37 | return realUrl 38 | } 39 | 40 | func DefaultQuery(r *http.Request, name string, defaultValue string) string { 41 | param := r.URL.Query().Get(name) 42 | if param == "" { 43 | return defaultValue 44 | } 45 | return param 46 | } 47 | 48 | func Duanyan(adurl string, realurl any) string { 49 | var liveurl string 50 | if str, ok := realurl.(string); ok { 51 | liveurl = str 52 | } else { 53 | liveurl = adurl 54 | } 55 | // log.Println("Redirect url:", liveurl) 56 | return liveurl 57 | } -------------------------------------------------------------------------------- /liveurls/douyin.go: -------------------------------------------------------------------------------- 1 | // Package liveurls 2 | // @Time:2023/02/03 01:59 3 | // @File:douyin.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package liveurls 9 | 10 | import ( 11 | "fmt" 12 | "github.com/tidwall/gjson" 13 | "io" 14 | "net/http" 15 | "regexp" 16 | "strconv" 17 | ) 18 | 19 | type Douyin struct { 20 | Stream string 21 | Rid string 22 | } 23 | 24 | func (d *Douyin) GetDouYinUrl() any { 25 | liveurl := "https://live.douyin.com/" + d.Rid 26 | client := &http.Client{} 27 | re, _ := http.NewRequest("GET", liveurl, nil) 28 | re.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36") 29 | re.Header.Add("upgrade-insecure-requests", "1") 30 | oresp, _ := client.Do(re) 31 | defer oresp.Body.Close() 32 | oreg := regexp.MustCompile(`(?i)__ac_nonce=(.*?);`) 33 | ores := oreg.FindStringSubmatch(oresp.Header["Set-Cookie"][0]) 34 | r, _ := http.NewRequest("GET", liveurl, nil) 35 | cookie := &http.Cookie{Name: "__ac_nonce", Value: ores[1]} 36 | r.AddCookie(cookie) 37 | r.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36") 38 | r.Header.Add("upgrade-insecure-requests", "1") 39 | resp, _ := client.Do(r) 40 | defer resp.Body.Close() 41 | reg := regexp.MustCompile(`(?i)ttwid=.*?;`) 42 | res := reg.FindStringSubmatch(fmt.Sprintf("%s", resp.Cookies()))[0] 43 | url := "https://live.douyin.com/webcast/room/web/enter/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&language=zh-CN&enter_from=web_live&cookie_enabled=true&screen_width=1728&screen_height=1117&browser_language=zh-CN&browser_platform=MacIntel&browser_name=Chrome&browser_version=116.0.0.0&web_rid=" + d.Rid 44 | req, _ := http.NewRequest("GET", url, nil) 45 | req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36") 46 | req.Header.Add("Cookie", res) 47 | req.Header.Add("Accept", "*/*") 48 | req.Header.Add("Host", "live.douyin.com") 49 | req.Header.Add("Connection", "keep-alive") 50 | ress, _ := client.Do(req) 51 | defer ress.Body.Close() 52 | body, _ := io.ReadAll(ress.Body) 53 | var json = string(body) 54 | status, _ := strconv.Atoi(fmt.Sprintf("%s", gjson.Get(json, "data.data.0.status"))) 55 | if status != 2 { 56 | return nil 57 | } 58 | var realurl string 59 | value := gjson.Get(json, "data.data.0.stream_url.live_core_sdk_data.pull_data.stream_data") 60 | value.ForEach(func(key, value gjson.Result) bool { 61 | if gjson.Get(value.String(), "data.origin").Exists() { 62 | switch d.Stream { 63 | case "flv": 64 | realurl = fmt.Sprintf("%s", gjson.Get(value.String(), "data.origin.main.flv")) 65 | case "hls": 66 | realurl = fmt.Sprintf("%s", gjson.Get(value.String(), "data.origin.main.hls")) 67 | } 68 | } 69 | return true 70 | }) 71 | return realurl 72 | } 73 | -------------------------------------------------------------------------------- /liveurls/yy.go: -------------------------------------------------------------------------------- 1 | // Package liveurls 2 | // @Time:2023/06/03 05:40 3 | // @File:yy.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package liveurls 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "fmt" 14 | "io" 15 | "net/http" 16 | "regexp" 17 | "strconv" 18 | "time" 19 | ) 20 | 21 | type Yy struct { 22 | Rid string 23 | Quality string 24 | } 25 | 26 | type StreamLineAddr struct { 27 | CdnInfo struct { 28 | Url string `json:"url"` 29 | } `json:"cdn_info"` 30 | } 31 | 32 | type Result struct { 33 | AvpInfoRes struct { 34 | StreamLineAddr map[string]StreamLineAddr `json:"stream_line_addr"` 35 | } `json:"avp_info_res"` 36 | } 37 | 38 | func (y *Yy) GetLiveUrl() any { 39 | firstrid := y.Rid 40 | quality := y.Quality 41 | var rid string 42 | checkUrl := "https://wap.yy.com/mobileweb/" + firstrid 43 | client := &http.Client{} 44 | req, _ := http.NewRequest("GET", checkUrl, nil) 45 | req.Header.Set("Referer", "https://wap.yy.com") 46 | req.Header.Set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Mobile/15E148 Safari/604.1") 47 | res, _ := client.Do(req) 48 | defer res.Body.Close() 49 | body, _ := io.ReadAll(res.Body) 50 | re := regexp.MustCompile(`md5Hash[\s\S]*?sid.*'(.*)'.*?getQuery`) 51 | realdata := re.FindStringSubmatch(string(body)) 52 | 53 | if len(realdata) > 0 { 54 | rid = realdata[1] 55 | } else { 56 | return nil 57 | } 58 | millis_13 := time.Now().UnixNano() / int64(time.Millisecond) 59 | millis_10 := time.Now().Unix() 60 | data := fmt.Sprintf(`{"head":{"seq":%d,"appidstr":"0","bidstr":"0","cidstr":"%s","sidstr":"%s","uid64":0,"client_type":108,"client_ver":"5.14.13","stream_sys_ver":1,"app":"yylive_web","playersdk_ver":"5.14.13","thundersdk_ver":"0","streamsdk_ver":"5.14.13"},"client_attribute":{"client":"web","model":"","cpu":"","graphics_card":"","os":"chrome","osversion":"118.0.0.0","vsdk_version":"","app_identify":"","app_version":"","business":"","width":"1728","height":"1117","scale":"","client_type":8,"h265":0},"avp_parameter":{"version":1,"client_type":8,"service_type":0,"imsi":0,"send_time":%d,"line_seq":-1,"gear":%s,"ssl":1,"stream_format":0}}`, millis_13, rid, rid, millis_10, quality) 61 | url := "https://stream-manager.yy.com/v3/channel/streams?uid=0&cid=" + rid + "&sid=" + rid + "&appid=0&sequence=" + strconv.FormatInt(millis_13, 10) + "&encode=json" 62 | req, _ = http.NewRequest("POST", url, bytes.NewBuffer([]byte(data))) 63 | req.Header.Set("Content-Type", "text/plain;charset=UTF-8") 64 | req.Header.Set("Referer", "https://www.yy.com/") 65 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42") 66 | 67 | res, _ = client.Do(req) 68 | defer res.Body.Close() 69 | body, _ = io.ReadAll(res.Body) 70 | var result Result 71 | json.Unmarshal(body, &result) 72 | if len(result.AvpInfoRes.StreamLineAddr) > 0 { 73 | var arr []string 74 | for k := range result.AvpInfoRes.StreamLineAddr { 75 | arr = append(arr, k) 76 | } 77 | return result.AvpInfoRes.StreamLineAddr[arr[0]].CdnInfo.Url 78 | } else { 79 | return nil 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /api/yqk/yqk.go: -------------------------------------------------------------------------------- 1 | package yqk 2 | 3 | import ( 4 | "Golang/list" 5 | "Golang/utils" 6 | "fmt" 7 | "encoding/json" 8 | "net/http" 9 | "strconv" 10 | "log" 11 | ) 12 | 13 | // vercel 平台会将请求传递给该函数,这个函数名随意,但函数参数必须按照该规则。 14 | // go语言大写就是公开,所以首字母必须大写 15 | func Handler(w http.ResponseWriter, r *http.Request) { 16 | path := r.URL.Path 17 | switch path { 18 | case "/yqk/huyayqk.m3u": 19 | yaobj := &list.HuyaYqk{} 20 | res, _ := yaobj.HuYaYqk("https://live.cdn.huya.com/liveHttpUI/getLiveList?iGid=2135") 21 | var result list.YaResponse 22 | json.Unmarshal(res, &result) 23 | pageCount := result.ITotalPage 24 | pageSize := result.IPageSize 25 | w.Header().Set("Content-Type", "application/octet-stream") 26 | w.Header().Set("Content-Disposition", "attachment; filename=huyayqk.m3u") 27 | utils.GetTestVideoUrl(w) 28 | 29 | for i := 1; i <= pageCount; i++ { 30 | apiRes, _ := yaobj.HuYaYqk(fmt.Sprintf("https://live.cdn.huya.com/liveHttpUI/getLiveList?iGid=2135&iPageNo=%d&iPageSize=%d", i, pageSize)) 31 | var res list.YaResponse 32 | json.Unmarshal(apiRes, &res) 33 | data := res.VList 34 | for _, value := range data { 35 | fmt.Fprintf(w, "#EXTINF:-1 tvg-logo=\"%s\" group-title=\"%s\", %s\n", value.SAvatar180, value.SGameFullName, value.SNick) 36 | fmt.Fprintf(w, "%s/huya/%v\n", utils.GetLivePrefix(r), value.LProfileRoom) 37 | } 38 | } 39 | case "/yqk/douyuyqk.m3u": 40 | yuobj := &list.DouYuYqk{} 41 | resAPI, _ := yuobj.Douyuyqk("https://www.douyu.com/gapi/rkc/directory/mixList/2_208/list") 42 | 43 | var result list.DouYuResponse 44 | json.Unmarshal(resAPI, &result) 45 | pageCount := result.Data.Pgcnt 46 | 47 | w.Header().Set("Content-Type", "application/octet-stream") 48 | w.Header().Set("Content-Disposition", "attachment; filename=douyuyqk.m3u") 49 | utils.GetTestVideoUrl(w) 50 | 51 | for i := 1; i <= pageCount; i++ { 52 | apiRes, _ := yuobj.Douyuyqk("https://www.douyu.com/gapi/rkc/directory/mixList/2_208/" + strconv.Itoa(i)) 53 | 54 | var res list.DouYuResponse 55 | json.Unmarshal(apiRes, &res) 56 | data := res.Data.Rl 57 | 58 | for _, value := range data { 59 | fmt.Fprintf(w, "#EXTINF:-1 tvg-logo=\"https://apic.douyucdn.cn/upload/%s_big.jpg\" group-title=\"%s\", %s\n", value.Av, value.C2name, value.Nn) 60 | fmt.Fprintf(w, "%s/douyu/%v\n", utils.GetLivePrefix(r), value.Rid) 61 | } 62 | } 63 | case "/yqk/yylunbo.m3u": 64 | yylistobj := &list.Yylist{} 65 | w.Header().Set("Content-Type", "application/octet-stream") 66 | w.Header().Set("Content-Disposition", "attachment; filename=yylunbo.m3u") 67 | utils.GetTestVideoUrl(w) 68 | 69 | i := 1 70 | for { 71 | apiRes := yylistobj.Yylb(fmt.Sprintf("http://rubiks-ipad.yy.com/nav/other/idx/213?channel=appstore&ispType=0&model=iPad8,6&netType=2&os=iOS&osVersion=17.2&page=%d&uid=0&yyVersion=6.17.0", i)) 72 | var res list.ApiResponse 73 | json.Unmarshal([]byte(apiRes), &res) 74 | for _, value := range res.Data.Data { 75 | fmt.Fprintf(w, "#EXTINF:-1 tvg-logo=\"%s\" group-title=\"%s\", %s\n", value.Avatar, value.Biz, value.Desc) 76 | fmt.Fprintf(w, "%s/yy/%v\n", utils.GetLivePrefix(r), value.Sid) 77 | } 78 | if res.Data.IsLastPage == 1 { 79 | break 80 | } 81 | i++ 82 | } 83 | default: 84 | log.Println("Invalid path:", path) 85 | fmt.Fprintf(w, "

链接错误!

") 86 | } 87 | } -------------------------------------------------------------------------------- /liveurls/youtube.go: -------------------------------------------------------------------------------- 1 | // Package liveurls 2 | // @Time:2023/02/17 16:32 3 | // @File:youtube.go 4 | // @SoftWare:Goland 5 | // @Author:Popeye 6 | // @Contact:TG@popeyelau 7 | 8 | package liveurls 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "github.com/tidwall/gjson" 14 | "io" 15 | "net/http" 16 | "strconv" 17 | "sync" 18 | "time" 19 | 20 | "github.com/etherlabsio/go-m3u8/m3u8" 21 | ) 22 | 23 | var streamCachedMap sync.Map 24 | 25 | type Youtube struct { 26 | //https://www.youtube.com/watch?v=cK4LemjoFd0 27 | //Rid: cK4LemjoFd0 28 | Rid string 29 | Quality string 30 | } 31 | 32 | func (y *Youtube) GetLiveUrl() any { 33 | if cached, ok := getKey(y.Rid); ok { 34 | return cached 35 | } 36 | //proxyUrl, err := url.Parse("http://127.0.0.1:8888") 37 | client := &http.Client{ 38 | Timeout: time.Second * 5, 39 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 40 | return http.ErrUseLastResponse 41 | }, 42 | //Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)}, 43 | } 44 | 45 | json := []byte(fmt.Sprintf(`{"context": {"client": {"hl": "zh","clientVersion": "2.20201021.03.00","clientName": "WEB"}},"videoId": "%s"}`, y.Rid)) 46 | reqBody := bytes.NewBuffer(json) 47 | r, _ := http.NewRequest("POST", "https://www.youtube.com/youtubei/v1/player", reqBody) 48 | r.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36") 49 | resp, err := client.Do(r) 50 | if err != nil { 51 | return err 52 | } 53 | defer resp.Body.Close() 54 | body, _ := io.ReadAll(resp.Body) 55 | str := string(body) 56 | 57 | stream := gjson.Get(str, "streamingData.hlsManifestUrl") 58 | if stream.Exists() && len(stream.String()) > 0 { 59 | quality := y.getResolution(stream.String()) 60 | if quality != nil { 61 | return *quality 62 | } 63 | return stream 64 | } 65 | 66 | formats := gjson.Get(str, "streamingData.formats") 67 | if formats.Exists() && formats.IsArray() { 68 | arr := formats.Array() 69 | playback := arr[len(arr)-1].Get("url").String() 70 | return playback 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func (y *Youtube) getResolution(liveurl string) *string { 77 | client := &http.Client{Timeout: time.Second * 5} 78 | r, _ := http.NewRequest("GET", liveurl, nil) 79 | r.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36") 80 | resp, err := client.Do(r) 81 | if err != nil { 82 | return nil 83 | } 84 | playlist, err := m3u8.Read(resp.Body) 85 | defer resp.Body.Close() 86 | if err != nil { 87 | return nil 88 | } 89 | 90 | playlists := playlist.Playlists() 91 | 92 | if len(playlists) < 1 { 93 | return nil 94 | } 95 | 96 | mapping := map[string]string{} 97 | for _, item := range playlists { 98 | mapping[strconv.Itoa(item.Resolution.Height)] = item.URI 99 | } 100 | 101 | if stream, ok := mapping[y.Quality]; ok { 102 | setKey(y.Rid, stream, 600) 103 | return &stream 104 | } 105 | 106 | stream := playlists[len(playlists)-1].URI 107 | setKey(y.Rid, stream, 600) 108 | return &stream 109 | } 110 | 111 | func setKey(key string, data interface{}, timeout int) { 112 | streamCachedMap.Store(key, data) 113 | time.AfterFunc(time.Second*time.Duration(timeout), func() { 114 | streamCachedMap.Delete(key) 115 | }) 116 | } 117 | 118 | func getKey(key string) (interface{}, bool) { 119 | return streamCachedMap.Load(key) 120 | } -------------------------------------------------------------------------------- /liveurls/bilibili.go: -------------------------------------------------------------------------------- 1 | // Package liveurls 2 | // @Time:2023/02/10 01:03 3 | // @File:bilibili.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package liveurls 9 | 10 | import ( 11 | "encoding/json" 12 | "fmt" 13 | "github.com/tidwall/gjson" 14 | "io" 15 | "net/http" 16 | ) 17 | 18 | type BiliBili struct { 19 | Rid string 20 | Line string 21 | Quality string 22 | Platform string 23 | } 24 | 25 | func (b *BiliBili) GetRealRoomID() any { 26 | var firstmap = make(map[string]any) 27 | var realroomid string 28 | apiurl := "https://api.live.bilibili.com/room/v1/Room/room_init?id=" + b.Rid 29 | client := &http.Client{} 30 | r, _ := http.NewRequest("GET", apiurl, nil) 31 | r.Header.Add("user-agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Mobile/15E148 Safari/604.1") 32 | resp, _ := client.Do(r) 33 | defer resp.Body.Close() 34 | body, _ := io.ReadAll(resp.Body) 35 | json.Unmarshal(body, &firstmap) 36 | if firstmap["msg"] == "直播间不存在" { 37 | return nil 38 | } 39 | if newmap, ok := firstmap["data"].(map[string]any); ok { 40 | if newmap["live_status"] != float64(1) { 41 | return nil 42 | } else { 43 | if flt, ok := newmap["room_id"].(float64); ok { 44 | realroomid = fmt.Sprintf("%v", int(flt)) 45 | } 46 | } 47 | 48 | } 49 | return realroomid 50 | } 51 | 52 | func (b *BiliBili) GetPlayUrl() any { 53 | var roomid string 54 | var realurl string 55 | if str, ok := b.GetRealRoomID().(string); ok { 56 | roomid = str 57 | } else { 58 | return nil 59 | } 60 | client := &http.Client{} 61 | params := map[string]string{ 62 | "room_id": roomid, 63 | "protocol": "0,1", 64 | "format": "0,1,2", 65 | "codec": "0,1", 66 | "qn": b.Quality, 67 | "platform": b.Platform, 68 | "ptype": "8", 69 | } 70 | r, _ := http.NewRequest("GET", "https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo", nil) 71 | q := r.URL.Query() 72 | for k, v := range params { 73 | q.Add(k, v) 74 | } 75 | r.URL.RawQuery = q.Encode() 76 | resp, _ := client.Do(r) 77 | defer resp.Body.Close() 78 | body, _ := io.ReadAll(resp.Body) 79 | var json = string(body) 80 | value := gjson.Get(json, "data.playurl_info.playurl.stream") 81 | value.ForEach(func(key, value gjson.Result) bool { 82 | newvalue := gjson.Get(value.String(), "format.0.format_name") 83 | if newvalue.String() == "ts" { 84 | nnvalue := gjson.Get(value.String(), "format.#") 85 | valuelast := fmt.Sprintf("%v", nnvalue.Int()-1) 86 | codeclen := gjson.Get(value.String(), "format."+valuelast+".codec.#") 87 | codeclast := fmt.Sprintf("%v", codeclen.Int()-1) 88 | base_url := gjson.Get(value.String(), "format."+valuelast+".codec."+codeclast+".base_url") 89 | url_info := gjson.Get(value.String(), "format."+valuelast+".codec."+codeclast+".url_info") 90 | url_info.ForEach(func(key, value gjson.Result) bool { 91 | keyval := fmt.Sprintf("%v", key) 92 | switch b.Line { 93 | case "first": 94 | if keyval == "0" { 95 | host := gjson.Get(value.String(), "host") 96 | extra := gjson.Get(value.String(), "extra") 97 | realurl = fmt.Sprintf("%v%v%v", host, base_url, extra) 98 | } 99 | 100 | case "second": 101 | if keyval == "1" { 102 | host := gjson.Get(value.String(), "host") 103 | extra := gjson.Get(value.String(), "extra") 104 | realurl = fmt.Sprintf("%v%v%v", host, base_url, extra) 105 | } 106 | case "third": 107 | if keyval == "2" { 108 | host := gjson.Get(value.String(), "host") 109 | extra := gjson.Get(value.String(), "extra") 110 | realurl = fmt.Sprintf("%v%v%v", host, base_url, extra) 111 | } 112 | } 113 | 114 | return true 115 | }) 116 | } 117 | return true 118 | }) 119 | return realurl 120 | } 121 | -------------------------------------------------------------------------------- /api/live.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "Golang/liveurls" 5 | "Golang/utils" 6 | "fmt" 7 | "net/http" 8 | "strings" 9 | "log" 10 | "os" 11 | ) 12 | 13 | // vercel 平台会将请求传递给该函数,这个函数名随意,但函数参数必须按照该规则。 14 | func Handler(w http.ResponseWriter, r *http.Request) { 15 | adurl := "https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/sdr1080pvideo/index.m3u8" 16 | path := r.URL.Path 17 | params := strings.Split(path, "/") 18 | 19 | // 是否禁用TV 20 | enableTV := os.Getenv("TV") != "false" 21 | 22 | // fmt.Fprintf(w, "request url: %s", path) 23 | 24 | if len(params) >= 4 { 25 | // 解析成功 26 | // 平台 27 | platform := params[2] 28 | // 房间号 29 | rid := params[3] 30 | ts := utils.DefaultQuery(r, "ts", "") 31 | // fmt.Fprintf(w, "parsed platform=%s, room=%s", platform, rid) 32 | switch platform { 33 | case "itv": 34 | if enableTV { 35 | itvobj := &liveurls.Itv{} 36 | cdn := utils.DefaultQuery(r, "cdn", "") 37 | if ts == "" { 38 | itvobj.HandleMainRequest(w, r, cdn, rid) 39 | } else { 40 | itvobj.HandleTsRequest(w, ts) 41 | } 42 | } else { 43 | http.Error(w, "公共服务不提供TV直播", http.StatusForbidden) 44 | } 45 | case "ysptp": 46 | if enableTV { 47 | ysptpobj := &liveurls.Ysptp{} 48 | if ts == "" { 49 | ysptpobj.HandleMainRequest(w, r, rid) 50 | } else { 51 | ysptpobj.HandleTsRequest(w, ts, utils.DefaultQuery(r, "wsTime", "")) 52 | } 53 | } else { 54 | http.Error(w, "公共服务不提供TV直播", http.StatusForbidden) 55 | } 56 | case "douyin": 57 | // 抖音 58 | douyinobj := &liveurls.Douyin{} 59 | douyinobj.Rid = rid 60 | douyinobj.Stream = utils.DefaultQuery(r, "stream", "flv") 61 | http.Redirect(w, r, utils.Duanyan(adurl, douyinobj.GetDouYinUrl()), http.StatusMovedPermanently) 62 | case "douyu": 63 | // 斗鱼 64 | douyuobj := &liveurls.Douyu{} 65 | douyuobj.Rid = rid 66 | douyuobj.Stream_type = utils.DefaultQuery(r, "stream", "flv") 67 | http.Redirect(w, r, utils.Duanyan(adurl, douyuobj.GetRealUrl()), http.StatusMovedPermanently) 68 | case "huya": 69 | // 虎牙 70 | huyaobj := &liveurls.Huya{} 71 | huyaobj.Rid = rid 72 | huyaobj.Cdn = utils.DefaultQuery(r, "cdn", "hwcdn") 73 | huyaobj.Media = utils.DefaultQuery(r, "media", "flv") 74 | huyaobj.Type = utils.DefaultQuery(r, "cdntype", "nodisplay") 75 | if huyaobj.Type == "display" { 76 | fmt.Fprintf(w, huyaobj.GetLiveUrl().(string)) 77 | } else { 78 | http.Redirect(w, r, utils.Duanyan(adurl, huyaobj.GetLiveUrl()), http.StatusMovedPermanently) 79 | } 80 | case "bilibili": 81 | // B站 82 | biliobj := &liveurls.BiliBili{} 83 | biliobj.Rid = rid 84 | biliobj.Platform = utils.DefaultQuery(r, "platform", "web") 85 | biliobj.Quality = utils.DefaultQuery(r, "quality", "10000") 86 | biliobj.Line = utils.DefaultQuery(r, "line", "first") 87 | http.Redirect(w, r, utils.Duanyan(adurl, biliobj.GetPlayUrl()), http.StatusMovedPermanently) 88 | case "youtube": 89 | // 油管 90 | ytbObj := &liveurls.Youtube{} 91 | ytbObj.Rid = rid 92 | ytbObj.Quality = utils.DefaultQuery(r, "quality", "1080") 93 | http.Redirect(w, r, utils.Duanyan(adurl, ytbObj.GetLiveUrl()), http.StatusMovedPermanently) 94 | case "yy": 95 | // YY直播 96 | yyObj := &liveurls.Yy{} 97 | yyObj.Rid = rid 98 | yyObj.Quality = utils.DefaultQuery(r, "quality", "4") 99 | http.Redirect(w, r, utils.Duanyan(adurl, yyObj.GetLiveUrl()), http.StatusMovedPermanently) 100 | default: 101 | fmt.Fprintf(w, "Unknown platform=%s, room=%s", platform, rid) 102 | } 103 | } else { 104 | log.Println("Invalid path:", path) 105 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /liveurls/douyu.go: -------------------------------------------------------------------------------- 1 | // Package liveurls 2 | // @Time:2023/02/05 06:36 3 | // @File:douyu.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package liveurls 9 | 10 | import ( 11 | "Golang/utils" 12 | "crypto/md5" 13 | "encoding/json" 14 | "fmt" 15 | "io" 16 | "net/http" 17 | "regexp" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | type Douyu struct { 23 | Rid string 24 | Stream_type string 25 | } 26 | 27 | func md5V3(str string) string { 28 | w := md5.New() 29 | io.WriteString(w, str) 30 | md5str := fmt.Sprintf("%x", w.Sum(nil)) 31 | return md5str 32 | } 33 | 34 | func (d *Douyu) GetRoomId() any { 35 | liveurl := "https://m.douyu.com/" + d.Rid 36 | client := &http.Client{} 37 | r, _ := http.NewRequest("GET", liveurl, nil) 38 | r.Header.Add("user-agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Mobile/15E148 Safari/604.1") 39 | r.Header.Add("upgrade-insecure-requests", "1") 40 | resp, _ := client.Do(r) 41 | defer resp.Body.Close() 42 | body, _ := io.ReadAll(resp.Body) 43 | roomidreg := regexp.MustCompile(`(?i)rid":(\d{1,8}),"vipId`) 44 | roomidres := roomidreg.FindStringSubmatch(string(body)) 45 | if roomidres == nil { 46 | return nil 47 | } 48 | realroomid := roomidres[1] 49 | return realroomid 50 | } 51 | 52 | func (d *Douyu) GetRealUrl() any { 53 | var jsUtil = &utils.JsUtil{} 54 | did := "10000000000000000000000000001501" 55 | var timestamp = time.Now().Unix() 56 | var realroomid string 57 | rid := d.GetRoomId() 58 | if str, ok := rid.(string); ok { 59 | realroomid = str 60 | } else { 61 | return nil 62 | } 63 | liveurl := "https://www.douyu.com/" + realroomid 64 | client := &http.Client{} 65 | r, _ := http.NewRequest("GET", liveurl, nil) 66 | r.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36") 67 | r.Header.Add("upgrade-insecure-requests", "1") 68 | resp, _ := client.Do(r) 69 | defer resp.Body.Close() 70 | body, _ := io.ReadAll(resp.Body) 71 | reg := regexp.MustCompile(`(?i)(vdwdae325w_64we[\s\S]*function ub98484234[\s\S]*?)function`) 72 | res := reg.FindStringSubmatch(string(body)) 73 | nreg := regexp.MustCompile(`(?i)eval.*?;}`) 74 | strfn := nreg.ReplaceAllString(res[1], "strc;}") 75 | var funcContent1 []string 76 | funcContent1 = append(append(funcContent1, strfn), "ub98484234") 77 | result := jsUtil.JsRun(funcContent1, "ub98484234") 78 | nres := fmt.Sprintf("%s", result) 79 | nnreg := regexp.MustCompile(`(?i)v=(\d+)`) 80 | nnres := nnreg.FindStringSubmatch(nres) 81 | unrb := fmt.Sprintf("%v%v%v%v", realroomid, did, timestamp, nnres[1]) 82 | rb := md5V3(unrb) 83 | nnnreg := regexp.MustCompile(`(?i)return rt;}\);?`) 84 | strfn2 := nnnreg.ReplaceAllString(nres, "return rt;}") 85 | strfn3 := strings.Replace(strfn2, `(function (`, `function sign(`, -1) 86 | strfn4 := strings.Replace(strfn3, `CryptoJS.MD5(cb).toString()`, `"`+rb+`"`, -1) 87 | var funcContent2 []string 88 | funcContent2 = append(append(funcContent2, strfn4), "sign") 89 | result2 := jsUtil.JsRun(funcContent2, realroomid, did, timestamp) 90 | param := fmt.Sprintf("%s", result2) 91 | realparam := param + "&rate=0" 92 | r1, n4err := http.Post("https://www.douyu.com/lapi/live/getH5Play/"+realroomid, "application/x-www-form-urlencoded", strings.NewReader(realparam)) 93 | if n4err != nil { 94 | return nil 95 | } 96 | defer r1.Body.Close() 97 | body1, _ := io.ReadAll(r1.Body) 98 | var s1 map[string]any 99 | json.Unmarshal(body1, &s1) 100 | var flv_url string 101 | var rtmp_url string 102 | var rtmp_live string 103 | for k, v := range s1 { 104 | if k == "error" { 105 | if s1[k] != float64(0) { 106 | return nil 107 | } 108 | } 109 | if v, ok := v.(map[string]any); ok { 110 | for k, v := range v { 111 | if k == "rtmp_url" { 112 | if urlstr, ok := v.(string); ok { 113 | rtmp_url = urlstr 114 | } 115 | } else if k == "rtmp_live" { 116 | if urlstr, ok := v.(string); ok { 117 | rtmp_live = urlstr 118 | } 119 | } 120 | } 121 | } 122 | } 123 | flv_url = rtmp_url + "/" + rtmp_live 124 | n4reg := regexp.MustCompile(`(?i)(\d{1,8}[0-9a-zA-Z]+)_?\d{0,4}(.flv|/playlist)`) 125 | houzhui := n4reg.FindStringSubmatch(flv_url) 126 | var real_url string 127 | switch d.Stream_type { 128 | case "hls": 129 | real_url = strings.Replace(flv_url, houzhui[1]+".flv", houzhui[1]+".m3u8", -1) 130 | case "flv": 131 | real_url = flv_url 132 | case "xs": 133 | real_url = strings.Replace(flv_url, houzhui[1]+".flv", houzhui[1]+".xs", -1) 134 | } 135 | return real_url 136 | } -------------------------------------------------------------------------------- /liveurls/ysptp.go: -------------------------------------------------------------------------------- 1 | package liveurls 2 | 3 | import ( 4 | "Golang/utils" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "regexp" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type Ysptp struct{} 15 | 16 | var cache sync.Map 17 | 18 | type CacheItem struct { 19 | Value string 20 | Expiration int64 21 | } 22 | 23 | var cctvList = map[string]string{ 24 | "cctv1.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv1.m3u8,http://liveali-tpgq.cctv.cn/live/", 25 | "cctv2.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv2.m3u8,http://liveali-tpgq.cctv.cn/live/", 26 | "cctv3.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv3.m3u8,http://liveali-tpgq.cctv.cn/live/", 27 | "cctv4.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv4.m3u8,http://liveali-tpgq.cctv.cn/live/", 28 | "cctv5.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv5.m3u8,http://liveali-tpgq.cctv.cn/live/", 29 | "cctv5p.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv5p.m3u8,http://liveali-tpgq.cctv.cn/live/", 30 | "cctv6.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv6.m3u8,http://liveali-tpgq.cctv.cn/live/", 31 | "cctv7.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv7.m3u8,http://liveali-tpgq.cctv.cn/live/", 32 | "cctv8.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv8.m3u8,http://liveali-tpgq.cctv.cn/live/", 33 | "cctv9.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv9.m3u8,http://liveali-tpgq.cctv.cn/live/", 34 | "cctv10.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv10.m3u8,http://liveali-tpgq.cctv.cn/live/", 35 | "cctv11.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv11.m3u8,http://liveali-tpgq.cctv.cn/live/", 36 | "cctv12.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv12.m3u8,http://liveali-tpgq.cctv.cn/live/", 37 | "cctv13.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv13.m3u8,http://liveali-tpgq.cctv.cn/live/", 38 | "cctv14.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv14.m3u8,http://liveali-tpgq.cctv.cn/live/", 39 | "cctv15.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv15.m3u8,http://liveali-tpgq.cctv.cn/live/", 40 | "cctv16.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv16.m3u8,http://liveali-tpgq.cctv.cn/live/", 41 | "cctv17.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv17.m3u8,http://liveali-tpgq.cctv.cn/live/", 42 | "cgtnar.m3u8": "http://liveali-tpgq.cctv.cn/live/cgtnar.m3u8,http://liveali-tpgq.cctv.cn/live/", 43 | "cgtndoc.m3u8": "http://liveali-tpgq.cctv.cn/live/cgtndoc.m3u8,http://liveali-tpgq.cctv.cn/live/", 44 | "cgtnen.m3u8": "http://liveali-tpgq.cctv.cn/live/cgtnen.m3u8,http://liveali-tpgq.cctv.cn/live/", 45 | "cgtnfr.m3u8": "http://liveali-tpgq.cctv.cn/live/cgtnfr.m3u8,http://liveali-tpgq.cctv.cn/live/", 46 | "cgtnru.m3u8": "http://liveali-tpgq.cctv.cn/live/cgtnru.m3u8,http://liveali-tpgq.cctv.cn/live/", 47 | "cgtnsp.m3u8": "http://liveali-tpgq.cctv.cn/live/cgtnsp.m3u8,http://liveali-tpgq.cctv.cn/live/", 48 | "cctv4k.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv4k.m3u8,http://liveali-tpgq.cctv.cn/live/", 49 | "cctv4k_10m.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv4k10m.m3u8,http://liveali-tpgq.cctv.cn/live/", 50 | "cctv4k16.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv4k16.m3u8,http://liveali-tpgq.cctv.cn/live/", 51 | "cctv4k16_10m.m3u8": "http://liveali-tpgq.cctv.cn/live/cctv4k1610m.m3u8,http://liveali-tpgq.cctv.cn/live/", 52 | "cctv8k_36m.m3u8": "http://liveali-tp4k.cctv.cn/live/4K36M/playlist.m3u8,http://liveali-tp4k.cctv.cn/live/4K36M/", 53 | "cctv8k_120m.m3u8": "http://liveali-tp4k.cctv.cn/live/8K120M/playlist.m3u8,http://liveali-tp4k.cctv.cn/live/8K120M/", 54 | } 55 | 56 | func (y *Ysptp) HandleMainRequest(w http.ResponseWriter, r *http.Request, id string) { 57 | uid := utils.DefaultQuery(r, "uid", "1234123122") 58 | 59 | if _, ok := cctvList[id]; !ok { 60 | http.Error(w, "id not found!", http.StatusNotFound) 61 | return 62 | } 63 | 64 | urls := strings.Split(cctvList[id], ",") 65 | data := getURL(id, urls[0], uid, urls[1]) 66 | golang := "http://" + r.Host + r.URL.Path 67 | re := regexp.MustCompile(`((?i).*?\.ts)`) 68 | data = re.ReplaceAllString(data, golang+"?ts="+urls[1]+"$1") 69 | 70 | w.Header().Set("Content-Disposition", "attachment;filename="+id) 71 | w.WriteHeader(http.StatusOK) // Set the status code to 200 72 | w.Write([]byte(data)) // Write the response body 73 | } 74 | 75 | func (y *Ysptp) HandleTsRequest(w http.ResponseWriter, ts, wsTime string) { 76 | data := ts + "&wsTime=" + wsTime 77 | w.Header().Set("Content-Type", "video/MP2T") 78 | w.WriteHeader(http.StatusOK) // Set the status code to 200 79 | w.Write([]byte(getTs(data))) // Write the response body 80 | } 81 | 82 | func getURL(id, url, uid, path string) string { 83 | cacheKey := id + uid 84 | if playURL, found := getCache(cacheKey); found { 85 | return fetchData(playURL, path, uid) 86 | } 87 | 88 | bstrURL := "https://ytpvdn.cctv.cn/cctvmobileinf/rest/cctv/videoliveUrl/getstream" 89 | postData := `appcommon={"ap":"cctv_app_tv","an":"央视投屏助手","adid":" ` + uid + `","av":"1.1.7"}&url=` + url 90 | 91 | req, _ := http.NewRequest("POST", bstrURL, strings.NewReader(postData)) 92 | req.Header.Set("User-Agent", "cctv_app_tv") 93 | req.Header.Set("Referer", "api.cctv.cn") 94 | req.Header.Set("UID", uid) 95 | 96 | client := &http.Client{} 97 | resp, _ := client.Do(req) 98 | defer resp.Body.Close() 99 | var body strings.Builder 100 | _, _ = io.Copy(&body, resp.Body) 101 | 102 | var result map[string]interface{} 103 | json.Unmarshal([]byte(body.String()), &result) 104 | playURL := result["url"].(string) 105 | 106 | setCache(cacheKey, playURL) 107 | 108 | return fetchData(playURL, path, uid) 109 | } 110 | 111 | func fetchData(playURL, path, uid string) string { 112 | client := &http.Client{} 113 | for { 114 | req, _ := http.NewRequest("GET", playURL, nil) 115 | req.Header.Set("User-Agent", "cctv_app_tv") 116 | req.Header.Set("Referer", "api.cctv.cn") 117 | req.Header.Set("UID", uid) 118 | 119 | resp, _ := client.Do(req) 120 | defer resp.Body.Close() 121 | var body strings.Builder 122 | _, _ = io.Copy(&body, resp.Body) 123 | 124 | data := body.String() 125 | re := regexp.MustCompile(`(.*\.m3u8\?.*)`) 126 | matches := re.FindStringSubmatch(data) 127 | if len(matches) > 0 { 128 | playURL = path + matches[0] 129 | } else { 130 | return data 131 | } 132 | } 133 | } 134 | 135 | func getTs(url string) string { 136 | req, _ := http.NewRequest("GET", url, nil) 137 | req.Header.Set("User-Agent", "cctv_app_tv") 138 | req.Header.Set("Referer", "https://api.cctv.cn/") 139 | req.Header.Set("UID", "1234123122") 140 | req.Header.Set("accept", "*/*") 141 | req.Header.Set("accept-encoding", "gzip, deflate") 142 | req.Header.Set("accept-language", "zh-CN,zh;q=0.9") 143 | req.Header.Set("Connection", "keep-alive") 144 | 145 | client := &http.Client{} 146 | resp, _ := client.Do(req) 147 | defer resp.Body.Close() 148 | var body strings.Builder 149 | _, _ = io.Copy(&body, resp.Body) 150 | 151 | return body.String() 152 | } 153 | 154 | func getCache(key string) (string, bool) { 155 | if item, found := cache.Load(key); found { 156 | cacheItem := item.(CacheItem) 157 | if time.Now().Unix() < cacheItem.Expiration { 158 | return cacheItem.Value, true 159 | } 160 | } 161 | return "", false 162 | } 163 | 164 | func setCache(key, value string) { 165 | cache.Store(key, CacheItem{ 166 | Value: value, 167 | Expiration: time.Now().Unix() + 3600, 168 | }) 169 | } -------------------------------------------------------------------------------- /api/index.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "Golang/liveurls" 5 | "Golang/list" 6 | "Golang/utils" 7 | "fmt" 8 | "net/http" 9 | "strings" 10 | "encoding/json" 11 | "strconv" 12 | "log" 13 | "os" 14 | ) 15 | 16 | // vercel 平台会将请求传递给该函数,这个函数名随意,但函数参数必须按照该规则。 17 | func Handler(w http.ResponseWriter, r *http.Request) { 18 | // 是否禁用TV 19 | enableTV := os.Getenv("TV") != "false" 20 | path := r.URL.Path 21 | ts := utils.DefaultQuery(r, "ts", "") 22 | switch path { 23 | // 电视直播 24 | case "/tv.m3u": 25 | if enableTV { 26 | itvm3uobj := &list.Tvm3u{} 27 | w.Header().Set("Content-Type", "application/octet-stream") 28 | w.Header().Set("Content-Disposition", "attachment; filename=tv.m3u") 29 | itvm3uobj.GetTvM3u(w, r.Host) 30 | } else { 31 | http.Error(w, "公共服务不提供TV直播", http.StatusForbidden) 32 | } 33 | // 虎牙一起看 34 | case "/huyayqk.m3u": 35 | yaobj := &list.HuyaYqk{} 36 | res, _ := yaobj.HuYaYqk("https://live.cdn.huya.com/liveHttpUI/getLiveList?iGid=2135") 37 | var result list.YaResponse 38 | json.Unmarshal(res, &result) 39 | pageCount := result.ITotalPage 40 | pageSize := result.IPageSize 41 | w.Header().Set("Content-Type", "application/octet-stream") 42 | w.Header().Set("Content-Disposition", "attachment; filename=huyayqk.m3u") 43 | utils.GetTestVideoUrl(w) 44 | 45 | for i := 1; i <= pageCount; i++ { 46 | apiRes, _ := yaobj.HuYaYqk(fmt.Sprintf("https://live.cdn.huya.com/liveHttpUI/getLiveList?iGid=2135&iPageNo=%d&iPageSize=%d", i, pageSize)) 47 | var res list.YaResponse 48 | json.Unmarshal(apiRes, &res) 49 | data := res.VList 50 | for _, value := range data { 51 | fmt.Fprintf(w, "#EXTINF:-1 tvg-logo=\"%s\" group-title=\"%s\", %s\n", value.SAvatar180, value.SGameFullName, value.SNick) 52 | fmt.Fprintf(w, "%s/huya/%v\n", utils.GetLivePrefix(r), value.LProfileRoom) 53 | } 54 | } 55 | // 斗鱼一起看 56 | case "/douyuyqk.m3u": 57 | yuobj := &list.DouYuYqk{} 58 | resAPI, _ := yuobj.Douyuyqk("https://www.douyu.com/gapi/rkc/directory/mixList/2_208/list") 59 | 60 | var result list.DouYuResponse 61 | json.Unmarshal(resAPI, &result) 62 | pageCount := result.Data.Pgcnt 63 | 64 | w.Header().Set("Content-Type", "application/octet-stream") 65 | w.Header().Set("Content-Disposition", "attachment; filename=douyuyqk.m3u") 66 | utils.GetTestVideoUrl(w) 67 | 68 | for i := 1; i <= pageCount; i++ { 69 | apiRes, _ := yuobj.Douyuyqk("https://www.douyu.com/gapi/rkc/directory/mixList/2_208/" + strconv.Itoa(i)) 70 | 71 | var res list.DouYuResponse 72 | json.Unmarshal(apiRes, &res) 73 | data := res.Data.Rl 74 | 75 | for _, value := range data { 76 | fmt.Fprintf(w, "#EXTINF:-1 tvg-logo=\"https://apic.douyucdn.cn/upload/%s_big.jpg\" group-title=\"%s\", %s\n", value.Av, value.C2name, value.Nn) 77 | fmt.Fprintf(w, "%s/douyu/%v\n", utils.GetLivePrefix(r), value.Rid) 78 | } 79 | } 80 | // YY轮播 81 | case "/yylunbo.m3u": 82 | yylistobj := &list.Yylist{} 83 | w.Header().Set("Content-Type", "application/octet-stream") 84 | w.Header().Set("Content-Disposition", "attachment; filename=yylunbo.m3u") 85 | utils.GetTestVideoUrl(w) 86 | 87 | i := 1 88 | for { 89 | apiRes := yylistobj.Yylb(fmt.Sprintf("http://rubiks-ipad.yy.com/nav/other/idx/213?channel=appstore&ispType=0&model=iPad8,6&netType=2&os=iOS&osVersion=17.2&page=%d&uid=0&yyVersion=6.17.0", i)) 90 | var res list.ApiResponse 91 | json.Unmarshal([]byte(apiRes), &res) 92 | for _, value := range res.Data.Data { 93 | fmt.Fprintf(w, "#EXTINF:-1 tvg-logo=\"%s\" group-title=\"%s\", %s\n", value.Avatar, value.Biz, value.Desc) 94 | fmt.Fprintf(w, "%s/yy/%v\n", utils.GetLivePrefix(r), value.Sid) 95 | } 96 | if res.Data.IsLastPage == 1 { 97 | break 98 | } 99 | i++ 100 | } 101 | // 其他链接 102 | default: 103 | adurl := "https://cdn.jsdelivr.net/gh/feiyangdigital/testvideo/sdr1080pvideo/index.m3u8" 104 | params := strings.Split(path, "/") 105 | 106 | // log.Println("request url: ", path) 107 | 108 | if len(params) >= 3 { 109 | // 解析成功 110 | // 平台 111 | platform := params[1] 112 | // 房间号 113 | rid := params[2] 114 | // fmt.Fprintf(w, "parsed platform=%s, room=%s", platform, rid) 115 | switch platform { 116 | case "itv": 117 | if enableTV { 118 | itvobj := &liveurls.Itv{} 119 | cdn := utils.DefaultQuery(r, "cdn", "") 120 | if ts == "" { 121 | itvobj.HandleMainRequest(w, r, cdn, rid) 122 | } else { 123 | itvobj.HandleTsRequest(w, ts) 124 | } 125 | } else { 126 | http.Error(w, "公共服务不提供TV直播", http.StatusForbidden) 127 | } 128 | case "ysptp": 129 | if enableTV { 130 | ysptpobj := &liveurls.Ysptp{} 131 | if ts == "" { 132 | ysptpobj.HandleMainRequest(w, r, rid) 133 | } else { 134 | ysptpobj.HandleTsRequest(w, ts, utils.DefaultQuery(r, "wsTime", "")) 135 | } 136 | } else { 137 | http.Error(w, "公共服务不提供TV直播", http.StatusForbidden) 138 | } 139 | case "douyin": 140 | // 抖音 141 | douyinobj := &liveurls.Douyin{} 142 | douyinobj.Rid = rid 143 | douyinobj.Stream = utils.DefaultQuery(r, "stream", "flv") 144 | http.Redirect(w, r, utils.Duanyan(adurl, douyinobj.GetDouYinUrl()), http.StatusMovedPermanently) 145 | case "douyu": 146 | // 斗鱼 147 | douyuobj := &liveurls.Douyu{} 148 | douyuobj.Rid = rid 149 | douyuobj.Stream_type = utils.DefaultQuery(r, "stream", "flv") 150 | http.Redirect(w, r, utils.Duanyan(adurl, douyuobj.GetRealUrl()), http.StatusMovedPermanently) 151 | case "huya": 152 | // 虎牙 153 | huyaobj := &liveurls.Huya{} 154 | huyaobj.Rid = rid 155 | huyaobj.Cdn = utils.DefaultQuery(r, "cdn", "hwcdn") 156 | huyaobj.Media = utils.DefaultQuery(r, "media", "flv") 157 | huyaobj.Type = utils.DefaultQuery(r, "cdntype", "nodisplay") 158 | if huyaobj.Type == "display" { 159 | fmt.Fprintf(w, huyaobj.GetLiveUrl().(string)) 160 | } else { 161 | http.Redirect(w, r, utils.Duanyan(adurl, huyaobj.GetLiveUrl()), http.StatusMovedPermanently) 162 | } 163 | case "bilibili": 164 | // B站 165 | biliobj := &liveurls.BiliBili{} 166 | biliobj.Rid = rid 167 | biliobj.Platform = utils.DefaultQuery(r, "platform", "web") 168 | biliobj.Quality = utils.DefaultQuery(r, "quality", "10000") 169 | biliobj.Line = utils.DefaultQuery(r, "line", "first") 170 | http.Redirect(w, r, utils.Duanyan(adurl, biliobj.GetPlayUrl()), http.StatusMovedPermanently) 171 | case "youtube": 172 | // 油管 173 | ytbObj := &liveurls.Youtube{} 174 | ytbObj.Rid = rid 175 | ytbObj.Quality = utils.DefaultQuery(r, "quality", "1080") 176 | http.Redirect(w, r, utils.Duanyan(adurl, ytbObj.GetLiveUrl()), http.StatusMovedPermanently) 177 | case "yy": 178 | // YY直播 179 | yyObj := &liveurls.Yy{} 180 | yyObj.Rid = rid 181 | yyObj.Quality = utils.DefaultQuery(r, "quality", "4") 182 | http.Redirect(w, r, utils.Duanyan(adurl, yyObj.GetLiveUrl()), http.StatusMovedPermanently) 183 | default: 184 | fmt.Fprintf(w, "Unknown platform=%s, room=%s", platform, rid) 185 | } 186 | } else { 187 | log.Println("Invalid path:", path) 188 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 189 | // http.Error(w, "链接错误!", http.StatusInternalServerError) 190 | fmt.Fprintf(w, "

参数错误!


使用教程

") 191 | } 192 | // log.Println("Invalid path:", path) 193 | // fmt.Fprintf(w, "

链接错误!

") 194 | } 195 | return 196 | } -------------------------------------------------------------------------------- /liveurls/huya.go: -------------------------------------------------------------------------------- 1 | // Package liveurls 2 | // @Time:2023/02/05 23:34 3 | // @File:huya.go 4 | // @SoftWare:Goland 5 | // @Author:feiyang 6 | // @Contact:TG@feiyangdigital 7 | 8 | package liveurls 9 | 10 | import ( 11 | "bytes" 12 | "crypto/md5" 13 | "encoding/base64" 14 | "encoding/hex" 15 | "encoding/json" 16 | "fmt" 17 | "io" 18 | "math/rand" 19 | "net/http" 20 | "net/url" 21 | "regexp" 22 | "strconv" 23 | "strings" 24 | "time" 25 | 26 | "github.com/tidwall/gjson" 27 | ) 28 | 29 | type Huya struct { 30 | Rid string 31 | Media string 32 | Type string 33 | Cdn string 34 | } 35 | 36 | type Data struct { 37 | } 38 | 39 | type Payload struct { 40 | AppId int `json:"appId"` 41 | ByPass int `json:"byPass"` 42 | Context string `json:"context"` 43 | Version string `json:"version"` 44 | Data Data `json:"data"` 45 | } 46 | 47 | type ResponseData struct { 48 | Data struct { 49 | Uid string `json:"uid"` 50 | } `json:"data"` 51 | } 52 | 53 | func getContent(apiUrl string) ([]byte, error) { 54 | payload := Payload{ 55 | AppId: 5002, 56 | ByPass: 3, 57 | Context: "", 58 | Version: "2.4", 59 | Data: Data{}, 60 | } 61 | jsonPayload, err := json.Marshal(payload) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | req, err := http.NewRequest("POST", apiUrl, bytes.NewBuffer(jsonPayload)) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | req.Header.Set("Content-Type", "application/json") 72 | req.Header.Set("Content-Length", fmt.Sprintf("%d", len(jsonPayload))) 73 | req.Header.Set("upgrade-insecure-requests", "1") 74 | req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36") 75 | 76 | client := &http.Client{} 77 | resp, err := client.Do(req) 78 | if err != nil { 79 | return nil, err 80 | } 81 | defer resp.Body.Close() 82 | 83 | return io.ReadAll(resp.Body) 84 | } 85 | 86 | var streamInfo = map[string]any{"flv": make(map[string]string), "hls": make(map[string]string)} 87 | 88 | func getUid() string { 89 | content, _ := getContent("https://udblgn.huya.com/web/anonymousLogin") 90 | var responseData ResponseData 91 | json.Unmarshal(content, &responseData) 92 | uid := responseData.Data.Uid 93 | return uid 94 | } 95 | 96 | var uid, _ = strconv.Atoi(getUid()) 97 | 98 | func getUUID() int64 { 99 | now := time.Now().UnixNano() / int64(time.Millisecond) 100 | randNum := rand.Intn(1000) 101 | return ((now % 10000000000 * 1000) + int64(randNum)) % 4294967295 102 | } 103 | 104 | func processAntiCode(antiCode string, uid int, streamName string) string { 105 | TimeLocation, err := time.LoadLocation("Asia/Shanghai") 106 | if err != nil { 107 | TimeLocation = time.FixedZone("CST", 8*60*60) 108 | } 109 | now := time.Now().In(TimeLocation) 110 | q, _ := url.ParseQuery(antiCode) 111 | q.Set("t", "102") 112 | q.Set("ctype", "tars_mp") 113 | q.Set("wsTime", strconv.FormatInt(time.Now().Unix()+21600, 16)) 114 | q.Set("ver", "1") 115 | q.Set("sv", now.Format("2006010215")) 116 | seqId := strconv.Itoa(uid + int(time.Now().UnixNano()/int64(time.Millisecond))) 117 | q.Set("seqid", seqId) 118 | q.Set("uid", strconv.Itoa(uid)) 119 | q.Set("uuid", strconv.FormatInt(getUUID(), 10)) 120 | h := md5.New() 121 | h.Write([]byte(seqId + "|" + q.Get("ctype") + "|" + q.Get("t"))) 122 | ss := hex.EncodeToString(h.Sum(nil)) 123 | fm, _ := base64.StdEncoding.DecodeString(q.Get("fm")) 124 | q.Set("fm", strings.Replace(strings.Replace(strings.Replace(strings.Replace(string(fm), "$0", q.Get("uid"), -1), "$1", streamName, -1), "$2", ss, -1), "$3", q.Get("wsTime"), -1)) 125 | h.Reset() 126 | h.Write([]byte(q.Get("fm"))) 127 | q.Set("wsSecret", hex.EncodeToString(h.Sum(nil))) 128 | q.Del("fm") 129 | if _, ok := q["txyp"]; ok { 130 | q.Del("txyp") 131 | } 132 | return q.Encode() 133 | } 134 | 135 | func format(jsonStr string, uid int) map[string]any { 136 | cdnType := map[string]string{"HY": "hycdn", "AL": "alicdn", "TX": "txcdn", "HW": "hwcdn", "HS": "hscdn", "WS": "wscdn"} 137 | ojsonStr := gjson.Get(jsonStr, "roomInfo.tLiveInfo.tLiveStreamInfo.vStreamInfo").String() 138 | fmt.Println(gjson.Get(ojsonStr, "value")) 139 | qreg := regexp.MustCompile(`(?i){"_proto"[\s\S]*?"value":([\s\S]*),"_classname"`) 140 | qres := qreg.FindStringSubmatch(ojsonStr) 141 | gjson.Parse(qres[1]).ForEach(func(_, value gjson.Result) bool { 142 | sFlvUrl := value.Get("sFlvUrl").String() 143 | sFlvUrlSuffix := value.Get("sFlvUrlSuffix").String() 144 | sHlsUrl := value.Get("sHlsUrl").String() 145 | sHlsUrlSuffix := value.Get("sHlsUrlSuffix").String() 146 | sStreamName := value.Get("sStreamName").String() 147 | sCdnType := value.Get("sCdnType").String() 148 | sFlvAntiCode := value.Get("sFlvAntiCode").String() 149 | sHlsAntiCode := value.Get("sHlsAntiCode").String() 150 | if sFlvUrl != "" { 151 | streamInfo["flv"].(map[string]string)[cdnType[sCdnType]] = sFlvUrl + "/" + sStreamName + "." + sFlvUrlSuffix + "?" + processAntiCode(sFlvAntiCode, uid, sStreamName) 152 | } 153 | if sHlsUrl != "" { 154 | streamInfo["hls"].(map[string]string)[cdnType[sCdnType]] = sHlsUrl + "/" + sStreamName + "." + sHlsUrlSuffix + "?" + processAntiCode(sHlsAntiCode, uid, sStreamName) 155 | } 156 | return true 157 | }) 158 | return streamInfo 159 | } 160 | 161 | func (h *Huya) GetLiveUrl() any { 162 | liveurl := "https://m.huya.com/" + h.Rid 163 | client := &http.Client{} 164 | r, _ := http.NewRequest("GET", liveurl, nil) 165 | r.Header.Add("user-agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 16_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Mobile/15E148 Safari/604.1") 166 | r.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") 167 | resp, _ := client.Do(r) 168 | defer resp.Body.Close() 169 | body, _ := io.ReadAll(resp.Body) 170 | str := string(body) 171 | freg := regexp.MustCompile(`(?i)`) 172 | res := freg.FindStringSubmatch(str) 173 | if len(res) > 1 && !strings.Contains(res[1], "\"exceptionType\":0") { 174 | jsonStr := res[1] 175 | liveStatus := gjson.Get(jsonStr, "roomInfo.eLiveStatus").Int() 176 | var mediaurl any 177 | 178 | if liveStatus == 2 { 179 | realurl := format(jsonStr, uid) 180 | if h.Type == "display" { 181 | return realurl 182 | } 183 | for k, v := range realurl { 184 | if k == h.Media { 185 | if urlarr, ok := v.(map[string]string); ok { 186 | for k, v := range urlarr { 187 | if k == h.Cdn { 188 | mediaurl = strings.Replace(v, "http://", "https://", 1) 189 | } 190 | } 191 | } 192 | } 193 | } 194 | } else if liveStatus == 3 { 195 | liveLineUrl := gjson.Get(jsonStr, "roomProfile.liveLineUrl").String() 196 | if liveLineUrl != "" { 197 | decodedLiveLineUrl, _ := base64.StdEncoding.DecodeString(liveLineUrl) 198 | mediaurl = "https:" + string(decodedLiveLineUrl) 199 | } 200 | } else { 201 | mediaurl = nil 202 | } 203 | return mediaurl 204 | } else if strings.Contains(res[1], "\"exceptionType\":0") { 205 | var h5info any 206 | ostr, _ := getContent("https://www.huya.com/" + h.Rid) 207 | nstr := string(ostr) 208 | lreg := regexp.MustCompile(`(?i)