Skip to content

Commit 7020fc5

Browse files
authored
Merge pull request #142 from Julian-Chu/add_decipherOperationsCache
Add decipher operations cache
2 parents e2c3fae + f710189 commit 7020fc5

5 files changed

Lines changed: 146 additions & 6 deletions

File tree

client.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ type Client struct {
1616
// HTTPClient can be used to set a custom HTTP client.
1717
// If not set, http.DefaultClient will be used
1818
HTTPClient *http.Client
19+
20+
// decipherOpsCache cache decipher operations
21+
decipherOpsCache DecipherOperationsCache
1922
}
2023

2124
// GetVideo fetches video metadata

decipher.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (c *Client) decipherURL(ctx context.Context, videoID string, cipher string)
3737
return a.join("")
3838
*/
3939

40-
operations, err := c.parseDecipherOps(ctx, videoID)
40+
operations, err := c.parseDecipherOpsWithCache(ctx, videoID)
4141
if err != nil {
4242
return "", err
4343
}
@@ -83,7 +83,7 @@ var (
8383
swapRegexp = regexp.MustCompile(fmt.Sprintf("(?m)(?:^|,)(%s)%s", jsvarStr, swapStr))
8484
)
8585

86-
func (c *Client) parseDecipherOps(ctx context.Context, videoID string) (operations []operation, err error) {
86+
func (c *Client) parseDecipherOps(ctx context.Context, videoID string) (operations []DecipherOperation, err error) {
8787
embedURL := fmt.Sprintf("https://youtube.com/embed/%s?hl=en", videoID)
8888
embedBody, err := c.httpGetBodyBytes(ctx, embedURL)
8989
if err != nil {
@@ -129,7 +129,7 @@ func (c *Client) parseDecipherOps(ctx context.Context, videoID string) (operatio
129129
return nil, err
130130
}
131131

132-
var ops []operation
132+
var ops []DecipherOperation
133133
for _, s := range regex.FindAllSubmatch(funcBody, -1) {
134134
switch string(s[1]) {
135135
case reverseKey:
@@ -144,3 +144,21 @@ func (c *Client) parseDecipherOps(ctx context.Context, videoID string) (operatio
144144
}
145145
return ops, nil
146146
}
147+
148+
func (c *Client) parseDecipherOpsWithCache(ctx context.Context, videoID string) (operations []DecipherOperation, err error) {
149+
if c.decipherOpsCache == nil {
150+
c.decipherOpsCache = NewSimpleCache()
151+
}
152+
153+
if ops := c.decipherOpsCache.Get(videoID); ops != nil {
154+
return ops, nil
155+
}
156+
157+
ops, err := c.parseDecipherOps(ctx, videoID)
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
c.decipherOpsCache.Set(videoID, ops)
163+
return ops, err
164+
}

decipher_operations.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package youtube
22

3-
type operation func([]byte) []byte
3+
type DecipherOperation func([]byte) []byte
44

5-
func newSpliceFunc(pos int) operation {
5+
func newSpliceFunc(pos int) DecipherOperation {
66
return func(bs []byte) []byte {
77
return bs[pos:]
88
}
99
}
1010

11-
func newSwapFunc(arg int) operation {
11+
func newSwapFunc(arg int) DecipherOperation {
1212
return func(bs []byte) []byte {
1313
pos := arg % len(bs)
1414
bs[0], bs[pos] = bs[pos], bs[0]

decipher_operations_cache.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package youtube
2+
3+
import "time"
4+
5+
var (
6+
_ DecipherOperationsCache = NewSimpleCache()
7+
)
8+
9+
const defaultCacheExpiration = time.Minute * time.Duration(5)
10+
11+
type DecipherOperationsCache interface {
12+
Get(videoID string) []DecipherOperation
13+
Set(video string, operations []DecipherOperation)
14+
}
15+
16+
type SimpleCache struct {
17+
videoID string
18+
expiredAt time.Time
19+
operations []DecipherOperation
20+
}
21+
22+
func NewSimpleCache() *SimpleCache {
23+
return &SimpleCache{}
24+
}
25+
26+
// Get : get cache when it has same video id and not expired
27+
func (s SimpleCache) Get(videoID string) []DecipherOperation {
28+
return s.GetCacheBefore(videoID, time.Now())
29+
}
30+
31+
// GetCacheBefore : can pass time for testing
32+
func (s SimpleCache) GetCacheBefore(videoID string, time time.Time) []DecipherOperation {
33+
if videoID == s.videoID && s.expiredAt.After(time) {
34+
operations := make([]DecipherOperation, len(s.operations))
35+
copy(operations, s.operations)
36+
return operations
37+
}
38+
return nil
39+
}
40+
41+
// Set : set cache with default expiration
42+
func (s *SimpleCache) Set(videoID string, operations []DecipherOperation) {
43+
s.setWithExpiredTime(videoID, operations, time.Now().Add(defaultCacheExpiration))
44+
}
45+
46+
func (s *SimpleCache) setWithExpiredTime(videoID string, operations []DecipherOperation, time time.Time) {
47+
s.videoID = videoID
48+
s.operations = make([]DecipherOperation, len(operations))
49+
copy(s.operations, operations)
50+
s.expiredAt = time
51+
}

decipher_operations_cache_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package youtube
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestSimpleCache(t *testing.T) {
9+
type args struct {
10+
setVideoID string
11+
getVideoID string
12+
operations []DecipherOperation
13+
expiredAt string
14+
getCacheAt string
15+
}
16+
tests := []struct {
17+
name string
18+
args args
19+
want []DecipherOperation
20+
}{
21+
{
22+
name: "Get cache data with video id",
23+
args: args{
24+
setVideoID: "test",
25+
getVideoID: "test",
26+
operations: []DecipherOperation{func(bytes []byte) []byte { return nil }},
27+
expiredAt: "2021-01-01 00:01:00",
28+
getCacheAt: "2021-01-01 00:00:00",
29+
},
30+
want: []DecipherOperation{func(bytes []byte) []byte { return nil }},
31+
},
32+
{
33+
name: "Get nil when cache is expired",
34+
args: args{
35+
setVideoID: "test",
36+
getVideoID: "test",
37+
operations: []DecipherOperation{func(bytes []byte) []byte { return nil }},
38+
expiredAt: "2021-01-01 00:00:00",
39+
getCacheAt: "2021-01-01 00:00:00",
40+
},
41+
want: nil,
42+
},
43+
{
44+
name: "Get nil when video id is not cached",
45+
args: args{
46+
setVideoID: "test",
47+
getVideoID: "not test",
48+
operations: []DecipherOperation{func(bytes []byte) []byte { return nil }},
49+
expiredAt: "2021-01-01 00:00:01",
50+
getCacheAt: "2021-01-01 00:00:00",
51+
},
52+
want: nil,
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
s := NewSimpleCache()
59+
timeFormat := "2006-01-02 15:04:05"
60+
expiredAt, _ := time.Parse(timeFormat, tt.args.expiredAt)
61+
s.setWithExpiredTime(tt.args.setVideoID, tt.args.operations, expiredAt)
62+
getCacheAt, _ := time.Parse(timeFormat, tt.args.getCacheAt)
63+
if got := s.GetCacheBefore(tt.args.getVideoID, getCacheAt); len(got) != len(tt.want) {
64+
t.Errorf("GetCacheBefore() = %v, want %v", got, tt.want)
65+
}
66+
})
67+
}
68+
}

0 commit comments

Comments
 (0)