Skip to content

Commit 6db3237

Browse files
authored
Merge pull request kkdai#81 from Julian-Chu/add-error-check-to-decipher
Add error check to decipher
2 parents b6d6346 + fb7c9cc commit 6db3237

File tree

6 files changed

+132
-86
lines changed

6 files changed

+132
-86
lines changed

Makefile

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,28 @@ LDFLAGS += -X $(PKG)/version.version=$(VERSION)
1616
LDFLAGS += -X $(PKG)/version.commit=$(GITSHA)
1717
LDFLAGS += -X $(PKG)/version.buildTime=$(BUILDTIME)
1818

19+
## help: Show makefile commands
20+
.PHONY: help
21+
help: Makefile
22+
@echo "---- Project: kkdai/youtube ----"
23+
@echo " Usage: make COMMAND"
24+
@echo
25+
@echo " Management Commands:"
26+
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
27+
@echo
28+
29+
## build: Build project
1930
.PHONY: build
2031
build:
2132
@go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o ./bin ./...
2233

34+
## deps: Ensures fresh go.mod and go.sum
2335
.PHONY: deps
24-
deps: ## Ensures fresh go.mod and go.sum.
36+
deps:
2537
@go mod tidy
2638
@go mod verify
2739

40+
## lint: Run golangci-lint check
2841
.PHONY: lint
2942
lint:
3043
@if [ ! -f ./bin/golangci-lint ]; then \
@@ -34,17 +47,19 @@ lint:
3447
@./bin/golangci-lint run --deadline=30m --enable=misspell --enable=gosec --enable=gofmt --enable=goimports ./cmd/... ./...
3548
@go vet ./...
3649

50+
## format: Formats Go code
3751
.PHONY: format
38-
format: ## Formats Go code
52+
format:
3953
@echo ">> formatting code"
4054
@gofmt -s -w $(FILES_TO_FMT)
4155

56+
## test-unit: Run all Youtube Go unit tests
4257
.PHONY: test-unit
43-
test-unit: ## Runs all Youtube Go unit tests
4458
test-unit:
45-
@go test -v -cover
59+
@go test -v -cover . ./pkg/...
60+
4661

62+
## test-integration: Run all Youtube Go integration tests
4763
.PHONY: test-integration
48-
test-integration: ## Runs all Youtube Go integration tests
4964
test-integration:
50-
@go test -v -tags="integration" -coverprofile=coverage.out
65+
@go test -v -tags="integration" -coverprofile=coverage.out . ./pkg/...

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.13
44

55
require (
66
github.com/google/uuid v1.1.1
7+
github.com/pkg/errors v0.9.1
78
github.com/vbauerster/mpb/v5 v5.2.3
89
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553
910
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
66
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
77
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
88
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
9+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
10+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
911
github.com/vbauerster/mpb/v5 v5.2.3 h1:OfqncMAhUojApki4/AxW4Z14cpiYBw7+MVLOyGklBmM=
1012
github.com/vbauerster/mpb/v5 v5.2.3/go.mod h1:K4iCHQp5sWnmAgEn+uW1sAxSilctb4JPAGXx49jV+Aw=
1113
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

decipher.go renamed to pkg/decipher/decipher.go

Lines changed: 99 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,104 @@
1-
package youtube
1+
package decipher
22

33
import (
44
"fmt"
55
"io/ioutil"
6+
"net/http"
67
"net/url"
78
"regexp"
89
"strconv"
910
"strings"
11+
12+
"github.com/pkg/errors"
1013
)
1114

12-
func (y *Youtube) parseDecipherOpsAndArgs() (operations []string, args []int, err error) {
13-
// try to get whole page
14-
client, err := y.getHTTPClient()
15+
type Decipher struct {
16+
client *http.Client
17+
}
18+
19+
func NewDecipher(client *http.Client) *Decipher {
20+
return &Decipher{client: client}
21+
}
22+
23+
func (d Decipher) Url(videoId string, cipher string) (string, error) {
24+
queryParams, err := url.ParseQuery(cipher)
25+
if err != nil {
26+
return "", err
27+
}
28+
cipherMap := make(map[string]string)
29+
for key, value := range queryParams {
30+
cipherMap[key] = strings.Join(value, "")
31+
}
32+
/* eg:
33+
extract decipher from https://youtube.com/s/player/4fbb4d5b/player_ias.vflset/en_US/base.js
34+
35+
var Mt={
36+
splice:function(a,b){a.splice(0,b)},
37+
reverse:function(a){a.reverse()},
38+
EQ:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}};
39+
40+
a=a.split("");
41+
Mt.splice(a,3);
42+
Mt.EQ(a,39);
43+
Mt.splice(a,2);
44+
Mt.EQ(a,1);
45+
Mt.splice(a,1);
46+
Mt.EQ(a,35);
47+
Mt.EQ(a,51);
48+
Mt.splice(a,2);
49+
Mt.reverse(a,52);
50+
return a.join("")
51+
*/
52+
53+
s := cipherMap["s"]
54+
bs := []byte(s)
55+
splice := func(b int) {
56+
bs = bs[b:]
57+
}
58+
swap := func(b int) {
59+
pos := b % len(bs)
60+
bs[0], bs[pos] = bs[pos], bs[0]
61+
}
62+
reverse := func(options ...interface{}) {
63+
l, r := 0, len(bs)-1
64+
for l < r {
65+
bs[l], bs[r] = bs[r], bs[l]
66+
l++
67+
r--
68+
}
69+
}
70+
operations, args, err := d.parseDecipherOpsAndArgs(videoId)
1571
if err != nil {
16-
return nil, nil, fmt.Errorf("get http client error=%s", err)
72+
return "", err
1773
}
74+
for i, op := range operations {
75+
switch op {
76+
case "splice":
77+
splice(args[i])
78+
case "swap":
79+
swap(args[i])
80+
case "reverse":
81+
reverse(args[i])
82+
}
83+
}
84+
cipherMap["s"] = string(bs)
85+
86+
decipheredUrl := fmt.Sprintf("%s&%s=%s", cipherMap["url"], cipherMap["sp"], cipherMap["s"])
87+
return decipheredUrl, nil
88+
}
89+
func (d Decipher) parseDecipherOpsAndArgs(videoId string) (operations []string, args []int, err error) {
90+
// try to get whole page
91+
//client, err := y.getHTTPClient()
92+
//if err != nil {
93+
// return nil, nil, fmt.Errorf("get http client error=%s", err)
94+
//}
1895

19-
if y.VideoID == "" {
96+
if videoId == "" {
2097
return nil, nil, fmt.Errorf("video id is empty , err=%s", err)
2198
}
22-
embedUrl := fmt.Sprintf("https://youtube.com/embed/%s?hl=en", y.VideoID)
99+
embedUrl := fmt.Sprintf("https://youtube.com/embed/%s?hl=en", videoId)
23100

24-
embeddedPageResp, err := client.Get(embedUrl)
101+
embeddedPageResp, err := d.client.Get(embedUrl)
25102
if err != nil {
26103
return nil, nil, err
27104
}
@@ -46,7 +123,7 @@ func (y *Youtube) parseDecipherOpsAndArgs() (operations []string, args []int, er
46123
// eg: ["js", "\/s\/player\/f676c671\/player_ias.vflset\/en_US\/base.js]
47124
arr := strings.Split(escapedBasejsUrl, ":\"")
48125
basejsUrl := "https://youtube.com" + strings.ReplaceAll(arr[len(arr)-1], "\\", "")
49-
basejsUrlResp, err := client.Get(basejsUrl)
126+
basejsUrlResp, err := d.client.Get(basejsUrl)
50127
if err != nil {
51128
return nil, nil, err
52129
}
@@ -67,22 +144,33 @@ func (y *Youtube) parseDecipherOpsAndArgs() (operations []string, args []int, er
67144

68145
// Ft=function(a){a=a.split("");Et.vw(a,2);Et.Zm(a,4);Et.Zm(a,46);Et.vw(a,2);Et.Zm(a,34);Et.Zm(a,59);Et.cn(a,42);return a.join("")} => get Ft
69146
arr = decipherFuncNamePattern.FindStringSubmatch(basejs)
147+
if len(arr) < 2 {
148+
return nil, nil, errors.New("decipher: function names not found")
149+
}
70150
funcName := arr[1]
71151
decipherFuncBodyPattern := regexp.MustCompile(fmt.Sprintf(`[^h\.]%s=function\(\w+\)\{(.*?)\}`, funcName))
72152

73153
// eg: get a=a.split("");Et.vw(a,2);Et.Zm(a,4);Et.Zm(a,46);Et.vw(a,2);Et.Zm(a,34);Et.Zm(a,59);Et.cn(a,42);return a.join("")
74154
arr = decipherFuncBodyPattern.FindStringSubmatch(basejs)
155+
if len(arr) < 2 {
156+
return nil, nil, errors.New("decipher: function bodies not found")
157+
}
75158
decipherFuncBody := arr[1]
76159

77160
// FuncName in Body => get Et
78161
funcNameInBodyRegex := regexp.MustCompile(`(\w+).\w+\(\w+,\d+\);`)
79162
arr = funcNameInBodyRegex.FindStringSubmatch(decipherFuncBody)
163+
if len(arr) < 2 {
164+
return nil, nil, errors.New("decipher: function name from body not found")
165+
}
80166
funcNameInBody := arr[1]
81-
decipherDefBodyRegex := regexp.MustCompile(fmt.Sprintf(`var\s+%s=\{(\w+:function\(\w+(,\w+)?\)\{(.*?)\}),?\};`, funcNameInBody))
167+
decipherDefBodyRegex := regexp.MustCompile(fmt.Sprintf(`%s=\{(\w+:function\(\w+(,\w+)?\)\{(.*?)\}),?\};`, funcNameInBody))
82168
re := regexp.MustCompile(`\r?\n`)
83169
basejs = re.ReplaceAllString(basejs, "")
84170
arr1 := decipherDefBodyRegex.FindStringSubmatch(basejs)
85-
171+
if len(arr) < 2 {
172+
return nil, nil, errors.New("decipher: function def body not found")
173+
}
86174
// eg: vw:function(a,b){a.splice(0,b)},cn:function(a){a.reverse()},Zm:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}
87175
decipherDefBody := arr1[1]
88176
// eq: [ a=a.split("") , Et.vw(a,2) , Et.Zm(a,4) , Et.Zm(a,46) , Et.vw(a,2) , Et.Zm(a,34), Et.Zm(a,59) , Et.cn(a,42) , return a.join("") ]
@@ -148,70 +236,3 @@ func (y *Youtube) parseDecipherOpsAndArgs() (operations []string, args []int, er
148236
}
149237
return funcSeq, funcArgs, nil
150238
}
151-
152-
func (y *Youtube) decipher(cipher string) (string, error) {
153-
queryParams, err := url.ParseQuery(cipher)
154-
if err != nil {
155-
return "", err
156-
}
157-
cipherMap := make(map[string]string)
158-
for key, value := range queryParams {
159-
cipherMap[key] = strings.Join(value, "")
160-
}
161-
/* eg:
162-
extract decipher from https://youtube.com/s/player/4fbb4d5b/player_ias.vflset/en_US/base.js
163-
164-
var Mt={
165-
splice:function(a,b){a.splice(0,b)},
166-
reverse:function(a){a.reverse()},
167-
EQ:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}};
168-
169-
a=a.split("");
170-
Mt.splice(a,3);
171-
Mt.EQ(a,39);
172-
Mt.splice(a,2);
173-
Mt.EQ(a,1);
174-
Mt.splice(a,1);
175-
Mt.EQ(a,35);
176-
Mt.EQ(a,51);
177-
Mt.splice(a,2);
178-
Mt.reverse(a,52);
179-
return a.join("")
180-
*/
181-
182-
s := cipherMap["s"]
183-
bs := []byte(s)
184-
splice := func(b int) {
185-
bs = bs[b:]
186-
}
187-
swap := func(b int) {
188-
pos := b % len(bs)
189-
bs[0], bs[pos] = bs[pos], bs[0]
190-
}
191-
reverse := func(options ...interface{}) {
192-
l, r := 0, len(bs)-1
193-
for l < r {
194-
bs[l], bs[r] = bs[r], bs[l]
195-
l++
196-
r--
197-
}
198-
}
199-
operations, args, err := y.parseDecipherOpsAndArgs()
200-
if err != nil {
201-
return "", err
202-
}
203-
for i, op := range operations {
204-
switch op {
205-
case "splice":
206-
splice(args[i])
207-
case "swap":
208-
swap(args[i])
209-
case "reverse":
210-
reverse(args[i])
211-
}
212-
}
213-
cipherMap["s"] = string(bs)
214-
215-
decipheredUrl := fmt.Sprintf("%s&%s=%s", cipherMap["url"], cipherMap["sp"], cipherMap["s"])
216-
return decipheredUrl, nil
217-
}

youtube.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"strings"
2222
"time"
2323

24+
"github.com/kkdai/youtube/pkg/decipher"
25+
2426
"github.com/google/uuid"
2527
"github.com/vbauerster/mpb/v5"
2628
"github.com/vbauerster/mpb/v5/decor"
@@ -138,7 +140,12 @@ func (y *Youtube) getStreamUrl(stream Stream) (string, error) {
138140
if cipher == "" {
139141
return "", ErrCipherNotFound
140142
}
141-
decipherUrl, err := y.decipher(cipher)
143+
client, err := y.getHTTPClient()
144+
if err != nil {
145+
return "", fmt.Errorf("get http client error=%s", err)
146+
}
147+
decipher := decipher.NewDecipher(client)
148+
decipherUrl, err := decipher.Url(y.VideoID, cipher)
142149
if err != nil {
143150
return "", err
144151
}

youtubedr/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ var (
2929

3030
func main() {
3131
if err := run(); err != nil {
32-
fmt.Fprintf(os.Stderr, "%v", err)
32+
fmt.Fprintf(os.Stderr, "%+v", err)
3333
os.Exit(1)
3434
}
3535
}

0 commit comments

Comments
 (0)