184 lines
4.9 KiB
Go
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
|
||
|
}
|