package bilibili import ( "encoding/json" "errors" "fmt" "strconv" "strings" "github.com/iawia002/annie/config" "github.com/iawia002/annie/downloader" "github.com/iawia002/annie/extractors" "github.com/iawia002/annie/parser" "github.com/iawia002/annie/request" "github.com/iawia002/annie/utils" ) const ( bilibiliAPI = "https://interface.bilibili.com/v2/playurl?" bilibiliBangumiAPI = "https://bangumi.bilibili.com/player/web_api/v2/playurl?" bilibiliTokenAPI = "https://api.bilibili.com/x/player/playurl/token?" ) const ( // BiliBili blocks keys from time to time. // You can extract from the Android client or bilibiliPlayer.min.js appKey = "iVGUTjsxvpLeuDCf" secKey = "aHRmhWMLkdeMuILqORnYZocwMBpMEOdt" ) const referer = "https://www.bilibili.com" var utoken string func genAPI(aid, cid int, bangumi bool, quality string, seasonType string) (string, error) { var ( err error baseAPIURL string params string ) if config.Cookie != "" && utoken == "" { utoken, err = request.Get( fmt.Sprintf("%said=%d&cid=%d", bilibiliTokenAPI, aid, cid), referer, nil, ) if err != nil { return "", err } var t token err = json.Unmarshal([]byte(utoken), &t) if err != nil { return "", err } if t.Code != 0 { return "", fmt.Errorf("cookie error: %s", t.Message) } utoken = t.Data.Token } if bangumi { // The parameters need to be sorted by name // qn=0 flag makes the CDN address different every time // quality=116(1080P 60) is the highest quality so far params = fmt.Sprintf( "appkey=%s&cid=%d&module=bangumi&otype=json&qn=%s&quality=%s&season_type=%s&type=", appKey, cid, quality, quality, seasonType, ) baseAPIURL = bilibiliBangumiAPI } else { params = fmt.Sprintf( "appkey=%s&cid=%d&otype=json&qn=%s&quality=%s&type=", appKey, cid, quality, quality, ) baseAPIURL = bilibiliAPI } // bangumi utoken also need to put in params to sign, but the ordinary video doesn't need api := fmt.Sprintf( "%s%s&sign=%s", baseAPIURL, params, utils.Md5(params+secKey), ) if !bangumi && utoken != "" { api = fmt.Sprintf("%s&utoken=%s", api, utoken) } return api, nil } func genURL(durl []dURLData) ([]downloader.URL, int64) { var size int64 urls := make([]downloader.URL, len(durl)) for index, data := range durl { size += data.Size urls[index] = downloader.URL{ URL: data.URL, Size: data.Size, Ext: "flv", } } return urls, size } type bilibiliOptions struct { url string html string bangumi bool aid int cid int page int subtitle string } func extractBangumi(url, html string) ([]downloader.Data, error) { dataString := utils.MatchOneOf(html, `window.__INITIAL_STATE__=(.+?);\(function`)[1] var data bangumiData err := json.Unmarshal([]byte(dataString), &data) if err != nil { return nil, err } if !config.Playlist { options := bilibiliOptions{ url: url, html: html, bangumi: true, aid: data.EpInfo.Aid, cid: data.EpInfo.Cid, } return []downloader.Data{bilibiliDownload(options)}, nil } // handle bangumi playlist needDownloadItems := utils.NeedDownloadList(len(data.EpList)) extractedData := make([]downloader.Data, len(needDownloadItems)) wgp := utils.NewWaitGroupPool(config.ThreadNumber) dataIndex := 0 for index, u := range data.EpList { if !utils.ItemInSlice(index+1, needDownloadItems) { continue } wgp.Add() id := u.EpID if id == 0 { id = u.ID } // html content can't be reused here options := bilibiliOptions{ url: fmt.Sprintf("https://www.bilibili.com/bangumi/play/ep%d", id), bangumi: true, aid: u.Aid, cid: u.Cid, } go func(index int, options bilibiliOptions, extractedData []downloader.Data) { defer wgp.Done() extractedData[index] = bilibiliDownload(options) }(dataIndex, options, extractedData) dataIndex++ } wgp.Wait() return extractedData, nil } func getMultiPageData(html string) (*multiPage, error) { var data multiPage multiPageDataString := utils.MatchOneOf( html, `window.__INITIAL_STATE__=(.+?);\(function`, ) if multiPageDataString == nil { return &data, errors.New("this page has no playlist") } err := json.Unmarshal([]byte(multiPageDataString[1]), &data) if err != nil { return nil, err } return &data, nil } func extractNormalVideo(url, html string) ([]downloader.Data, error) { pageData, err := getMultiPageData(html) if err != nil { return nil, err } if !config.Playlist { // handle URL that has a playlist, mainly for unified titles //