YTSFlix_Go/vendor/github.com/rylio/ytdl/signature.go
2018-11-04 15:58:15 +01:00

184 lines
4.9 KiB
Go

package ytdl
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
)
func getDownloadURL(format Format, htmlPlayerFile string) (*url.URL, error) {
var sig string
if s, ok := format.meta["s"]; ok && len(s.(string)) > 0 {
tokens, err := getSigTokens(htmlPlayerFile)
if err != nil {
return nil, err
}
sig = decipherTokens(tokens, s.(string))
} else {
if s, ok := format.meta["sig"]; ok {
sig = s.(string)
}
}
var urlString string
if s, ok := format.meta["url"]; ok {
urlString = s.(string)
} else if s, ok := format.meta["stream"]; ok {
if c, ok := format.meta["conn"]; ok {
urlString = c.(string)
if urlString[len(urlString)-1] != '/' {
urlString += "/"
}
}
urlString += s.(string)
} else {
return nil, fmt.Errorf("Couldn't extract url from format")
}
urlString, err := url.QueryUnescape(urlString)
if err != nil {
return nil, err
}
u, err := url.Parse(urlString)
if err != nil {
return nil, err
}
query := u.Query()
query.Set("ratebypass", "yes")
if len(sig) > 0 {
query.Set("signature", sig)
}
u.RawQuery = query.Encode()
return u, nil
}
func decipherTokens(tokens []string, sig string) string {
var pos int
sigSplit := strings.Split(sig, "")
for i, l := 0, len(tokens); i < l; i++ {
tok := tokens[i]
if len(tok) > 1 {
pos, _ = strconv.Atoi(string(tok[1:]))
pos = ^^pos
}
switch string(tok[0]) {
case "r":
reverseStringSlice(sigSplit)
case "w":
s := sigSplit[0]
sigSplit[0] = sigSplit[pos]
sigSplit[pos] = s
case "s":
sigSplit = sigSplit[pos:]
case "p":
sigSplit = sigSplit[pos:]
}
}
return strings.Join(sigSplit, "")
}
const (
jsvarStr = "[a-zA-Z_\\$][a-zA-Z_0-9]*"
reverseStr = ":function\\(a\\)\\{" +
"(?:return )?a\\.reverse\\(\\)" +
"\\}"
sliceStr = ":function\\(a,b\\)\\{" +
"return a\\.slice\\(b\\)" +
"\\}"
spliceStr = ":function\\(a,b\\)\\{" +
"a\\.splice\\(0,b\\)" +
"\\}"
swapStr = ":function\\(a,b\\)\\{" +
"var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?" +
"\\}"
)
var actionsObjRegexp = regexp.MustCompile(fmt.Sprintf(
"var (%s)=\\{((?:(?:%s%s|%s%s|%s%s|%s%s),?\\n?)+)\\};", jsvarStr, jsvarStr, reverseStr, jsvarStr, sliceStr, jsvarStr, spliceStr, jsvarStr, swapStr))
var actionsFuncRegexp = regexp.MustCompile(fmt.Sprintf(
"function(?: %s)?\\(a\\)\\{"+
"a=a\\.split\\(\"\"\\);\\s*"+
"((?:(?:a=)?%s\\.%s\\(a,\\d+\\);)+)"+
"return a\\.join\\(\"\"\\)"+
"\\}", jsvarStr, jsvarStr, jsvarStr))
var reverseRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, reverseStr))
var sliceRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, sliceStr))
var spliceRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, spliceStr))
var swapRegexp = regexp.MustCompile(fmt.Sprintf(
"(?m)(?:^|,)(%s)%s", jsvarStr, swapStr))
func getSigTokens(htmlPlayerFile string) ([]string, error) {
u, _ := url.Parse(youtubeBaseURL)
p, err := url.Parse(htmlPlayerFile)
if err != nil {
return nil, err
}
resp, err := http.Get(u.ResolveReference(p).String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Error fetching signature tokens, status code %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
bodyString := string(body)
if err != nil {
return nil, err
}
objResult := actionsObjRegexp.FindStringSubmatch(bodyString)
funcResult := actionsFuncRegexp.FindStringSubmatch(bodyString)
if len(objResult) < 3 || len(funcResult) < 2 {
return nil, fmt.Errorf("Error parsing signature tokens")
}
obj := strings.Replace(objResult[1], "$", "\\$", -1)
objBody := strings.Replace(objResult[2], "$", "\\$", -1)
funcBody := strings.Replace(funcResult[1], "$", "\\$", -1)
var reverseKey, sliceKey, spliceKey, swapKey string
var result []string
if result = reverseRegexp.FindStringSubmatch(objBody); len(result) > 1 {
reverseKey = strings.Replace(result[1], "$", "\\$", -1)
}
if result = sliceRegexp.FindStringSubmatch(objBody); len(result) > 1 {
sliceKey = strings.Replace(result[1], "$", "\\$", -1)
}
if result = spliceRegexp.FindStringSubmatch(objBody); len(result) > 1 {
spliceKey = strings.Replace(result[1], "$", "\\$", -1)
}
if result = swapRegexp.FindStringSubmatch(objBody); len(result) > 1 {
swapKey = strings.Replace(result[1], "$", "\\$", -1)
}
keys := []string{reverseKey, sliceKey, spliceKey, swapKey}
regex, err := regexp.Compile(fmt.Sprintf("(?:a=)?%s\\.(%s)\\(a,(\\d+)\\)", obj, strings.Join(keys, "|")))
if err != nil {
return nil, err
}
results := regex.FindAllStringSubmatch(funcBody, -1)
var tokens []string
for _, s := range results {
switch s[1] {
case swapKey:
tokens = append(tokens, "w"+s[2])
case reverseKey:
tokens = append(tokens, "r")
case sliceKey:
tokens = append(tokens, "s"+s[2])
case spliceKey:
tokens = append(tokens, "p"+s[2])
}
}
return tokens, nil
}