add new youtubedl dependency
This commit is contained in:
285
downloader/vendor/github.com/kkdai/youtube/v2/decipher.go
generated
vendored
Normal file
285
downloader/vendor/github.com/kkdai/youtube/v2/decipher.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
package youtube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
func (c *Client) decipherURL(ctx context.Context, videoID string, cipher string) (string, error) {
|
||||
params, err := url.ParseQuery(cipher)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uri, err := url.Parse(params.Get("url"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query := uri.Query()
|
||||
|
||||
config, err := c.getPlayerConfig(ctx, videoID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// decrypt s-parameter
|
||||
bs, err := config.decrypt([]byte(params.Get("s")))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query.Add(params.Get("sp"), string(bs))
|
||||
|
||||
query, err = c.decryptNParam(config, query)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
|
||||
return uri.String(), nil
|
||||
}
|
||||
|
||||
// see https://github.com/kkdai/youtube/pull/244
|
||||
func (c *Client) unThrottle(ctx context.Context, videoID string, urlString string) (string, error) {
|
||||
config, err := c.getPlayerConfig(ctx, videoID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uri, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// for debugging
|
||||
if artifactsFolder != "" {
|
||||
writeArtifact("video-"+videoID+".url", []byte(uri.String()))
|
||||
}
|
||||
|
||||
query, err := c.decryptNParam(config, uri.Query())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uri.RawQuery = query.Encode()
|
||||
return uri.String(), nil
|
||||
}
|
||||
|
||||
func (c *Client) decryptNParam(config playerConfig, query url.Values) (url.Values, error) {
|
||||
// decrypt n-parameter
|
||||
nSig := query.Get("v")
|
||||
log := Logger.With("n", nSig)
|
||||
|
||||
if nSig != "" {
|
||||
nDecoded, err := config.decodeNsig(nSig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode nSig: %w", err)
|
||||
}
|
||||
query.Set("v", nDecoded)
|
||||
log = log.With("decoded", nDecoded)
|
||||
}
|
||||
|
||||
log.Debug("nParam")
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
const (
|
||||
jsvarStr = "[a-zA-Z_\\$][a-zA-Z_0-9]*"
|
||||
reverseStr = ":function\\(a\\)\\{" +
|
||||
"(?:return )?a\\.reverse\\(\\)" +
|
||||
"\\}"
|
||||
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 (
|
||||
nFunctionNameRegexp = regexp.MustCompile("\\.get\\(\"n\"\\)\\)&&\\(b=([a-zA-Z0-9$]{0,3})\\[(\\d+)\\](.+)\\|\\|([a-zA-Z0-9]{0,3})")
|
||||
actionsObjRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
"var (%s)=\\{((?:(?:%s%s|%s%s|%s%s),?\\n?)+)\\};", jsvarStr, jsvarStr, swapStr, jsvarStr, spliceStr, jsvarStr, reverseStr))
|
||||
|
||||
actionsFuncRegexp = regexp.MustCompile(fmt.Sprintf(
|
||||
"function(?: %s)?\\(a\\)\\{"+
|
||||
"a=a\\.split\\(\"\"\\);\\s*"+
|
||||
"((?:(?:a=)?%s\\.%s\\(a,\\d+\\);)+)"+
|
||||
"return a\\.join\\(\"\"\\)"+
|
||||
"\\}", jsvarStr, jsvarStr, jsvarStr))
|
||||
|
||||
reverseRegexp = regexp.MustCompile(fmt.Sprintf("(?m)(?:^|,)(%s)%s", jsvarStr, reverseStr))
|
||||
spliceRegexp = regexp.MustCompile(fmt.Sprintf("(?m)(?:^|,)(%s)%s", jsvarStr, spliceStr))
|
||||
swapRegexp = regexp.MustCompile(fmt.Sprintf("(?m)(?:^|,)(%s)%s", jsvarStr, swapStr))
|
||||
)
|
||||
|
||||
func (config playerConfig) decodeNsig(encoded string) (string, error) {
|
||||
fBody, err := config.getNFunction()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return evalJavascript(fBody, encoded)
|
||||
}
|
||||
|
||||
func evalJavascript(jsFunction, arg string) (string, error) {
|
||||
const myName = "myFunction"
|
||||
|
||||
vm := goja.New()
|
||||
_, err := vm.RunString(myName + "=" + jsFunction)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var output func(string) string
|
||||
err = vm.ExportTo(vm.Get(myName), &output)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return output(arg), nil
|
||||
}
|
||||
|
||||
func (config playerConfig) getNFunction() (string, error) {
|
||||
nameResult := nFunctionNameRegexp.FindSubmatch(config)
|
||||
if len(nameResult) == 0 {
|
||||
return "", errors.New("unable to extract n-function name")
|
||||
}
|
||||
|
||||
var name string
|
||||
if idx, _ := strconv.Atoi(string(nameResult[2])); idx == 0 {
|
||||
name = string(nameResult[4])
|
||||
} else {
|
||||
name = string(nameResult[1])
|
||||
}
|
||||
|
||||
return config.extraFunction(name)
|
||||
|
||||
}
|
||||
|
||||
func (config playerConfig) extraFunction(name string) (string, error) {
|
||||
// find the beginning of the function
|
||||
def := []byte(name + "=function(")
|
||||
start := bytes.Index(config, def)
|
||||
if start < 1 {
|
||||
return "", fmt.Errorf("unable to extract n-function body: looking for '%s'", def)
|
||||
}
|
||||
|
||||
// start after the first curly bracket
|
||||
pos := start + bytes.IndexByte(config[start:], '{') + 1
|
||||
|
||||
var strChar byte
|
||||
|
||||
// find the bracket closing the function
|
||||
for brackets := 1; brackets > 0; pos++ {
|
||||
b := config[pos]
|
||||
switch b {
|
||||
case '{':
|
||||
if strChar == 0 {
|
||||
brackets++
|
||||
}
|
||||
case '}':
|
||||
if strChar == 0 {
|
||||
brackets--
|
||||
}
|
||||
case '`', '"', '\'':
|
||||
if config[pos-1] == '\\' && config[pos-2] != '\\' {
|
||||
continue
|
||||
}
|
||||
if strChar == 0 {
|
||||
strChar = b
|
||||
} else if strChar == b {
|
||||
strChar = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string(config[start:pos]), nil
|
||||
}
|
||||
|
||||
func (config playerConfig) decrypt(cyphertext []byte) ([]byte, error) {
|
||||
operations, err := config.parseDecipherOps()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// apply operations
|
||||
bs := []byte(cyphertext)
|
||||
for _, op := range operations {
|
||||
bs = op(bs)
|
||||
}
|
||||
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
/*
|
||||
parses decipher operations from https://youtube.com/s/player/4fbb4d5b/player_ias.vflset/en_US/base.js
|
||||
|
||||
var Mt={
|
||||
splice:function(a,b){a.splice(0,b)},
|
||||
reverse:function(a){a.reverse()},
|
||||
EQ:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}};
|
||||
|
||||
a=a.split("");
|
||||
Mt.splice(a,3);
|
||||
Mt.EQ(a,39);
|
||||
Mt.splice(a,2);
|
||||
Mt.EQ(a,1);
|
||||
Mt.splice(a,1);
|
||||
Mt.EQ(a,35);
|
||||
Mt.EQ(a,51);
|
||||
Mt.splice(a,2);
|
||||
Mt.reverse(a,52);
|
||||
return a.join("")
|
||||
*/
|
||||
func (config playerConfig) parseDecipherOps() (operations []DecipherOperation, err error) {
|
||||
objResult := actionsObjRegexp.FindSubmatch(config)
|
||||
funcResult := actionsFuncRegexp.FindSubmatch(config)
|
||||
if len(objResult) < 3 || len(funcResult) < 2 {
|
||||
return nil, fmt.Errorf("error parsing signature tokens (#obj=%d, #func=%d)", len(objResult), len(funcResult))
|
||||
}
|
||||
|
||||
obj := objResult[1]
|
||||
objBody := objResult[2]
|
||||
funcBody := funcResult[1]
|
||||
|
||||
var reverseKey, spliceKey, swapKey string
|
||||
|
||||
if result := reverseRegexp.FindSubmatch(objBody); len(result) > 1 {
|
||||
reverseKey = string(result[1])
|
||||
}
|
||||
if result := spliceRegexp.FindSubmatch(objBody); len(result) > 1 {
|
||||
spliceKey = string(result[1])
|
||||
}
|
||||
if result := swapRegexp.FindSubmatch(objBody); len(result) > 1 {
|
||||
swapKey = string(result[1])
|
||||
}
|
||||
|
||||
regex, err := regexp.Compile(fmt.Sprintf("(?:a=)?%s\\.(%s|%s|%s)\\(a,(\\d+)\\)", regexp.QuoteMeta(string(obj)), regexp.QuoteMeta(reverseKey), regexp.QuoteMeta(spliceKey), regexp.QuoteMeta(swapKey)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ops []DecipherOperation
|
||||
for _, s := range regex.FindAllSubmatch(funcBody, -1) {
|
||||
switch string(s[1]) {
|
||||
case reverseKey:
|
||||
ops = append(ops, reverseFunc)
|
||||
case swapKey:
|
||||
arg, _ := strconv.Atoi(string(s[2]))
|
||||
ops = append(ops, newSwapFunc(arg))
|
||||
case spliceKey:
|
||||
arg, _ := strconv.Atoi(string(s[2]))
|
||||
ops = append(ops, newSpliceFunc(arg))
|
||||
}
|
||||
}
|
||||
return ops, nil
|
||||
}
|
Reference in New Issue
Block a user