From fb310d5fd0c66a8b78af46058d18d6aafd8785a1 Mon Sep 17 00:00:00 2001 From: Julien Desgats Date: Tue, 21 Jan 2025 19:51:12 +0000 Subject: [PATCH] Pass visitorData to requests This is a nearly verbatim copy of [1], for now the code is pretty rough and I'm not even sure that this data should be passed for all requests but it does seem to unblock the iOS client for now. [1] https://github.com/TeamNewPipe/NewPipeExtractor/pull/1262 --- client.go | 33 ++++++++++++++++++++++ protobuilder.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 protobuilder.go diff --git a/client.go b/client.go index 587bbb1..f80676d 100644 --- a/client.go +++ b/client.go @@ -12,7 +12,9 @@ import ( "net/http" "net/url" "strconv" + "strings" "sync/atomic" + "time" ) const ( @@ -23,6 +25,8 @@ const ( playerParams = "CgIQBg==" ) +const CONTENT_PLAYBACK_NONCE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + var ErrNoFormat = errors.New("no video format provided") // DefaultClient type to use. No reason to change but you could if you wanted to. @@ -157,6 +161,7 @@ type innertubeClient struct { TimeZone string `json:"timeZone"` UTCOffset int `json:"utcOffsetMinutes"` DeviceModel string `json:"deviceModel,omitempty"` + VisitorData string `json:"visitorData,omitempty"` } // client info for the innertube API @@ -232,6 +237,33 @@ func (c *Client) transcriptDataByInnertube(ctx context.Context, id string, lang return c.httpPostBodyBytes(ctx, "https://www.youtube.com/youtubei/v1/get_transcript?key="+c.client.key, data) } +func randString(alphabet string, sz int) string { + var buf strings.Builder + buf.Grow(sz) + for i := 0; i < sz; i++ { + buf.WriteByte(alphabet[rand.Intn(len(alphabet))]) + } + return buf.String() +} + +func randomVisitorData(countryCode string) string { + var pbE2 ProtoBuilder + + pbE2.String(2, "") + pbE2.Varint(4, int64(rand.Intn(255)+1)) + + var pbE ProtoBuilder + pbE.String(1, countryCode) + pbE.Bytes(2, pbE2.ToBytes()) + + var pb ProtoBuilder + pb.String(1, randString(CONTENT_PLAYBACK_NONCE_ALPHABET, 11)) + pb.Varint(5, time.Now().Unix()-int64(rand.Intn(600000))) + pb.Bytes(6, pbE.ToBytes()) + + return pb.ToUrlEncodedBase64() +} + func prepareInnertubeContext(clientInfo clientInfo) inntertubeContext { return inntertubeContext{ Client: innertubeClient{ @@ -243,6 +275,7 @@ func prepareInnertubeContext(clientInfo clientInfo) inntertubeContext { ClientVersion: clientInfo.version, AndroidSDKVersion: clientInfo.androidVersion, UserAgent: clientInfo.userAgent, + VisitorData: randomVisitorData("US"), }, } } diff --git a/protobuilder.go b/protobuilder.go new file mode 100644 index 0000000..155a051 --- /dev/null +++ b/protobuilder.go @@ -0,0 +1,73 @@ +package youtube + +import ( + "bytes" + "encoding/base64" + "net/url" +) + +type ProtoBuilder struct { + byteBuffer bytes.Buffer +} + +func (pb *ProtoBuilder) ToBytes() []byte { + return pb.byteBuffer.Bytes() +} + +func (pb *ProtoBuilder) ToUrlEncodedBase64() string { + b64 := base64.URLEncoding.EncodeToString(pb.ToBytes()) + return url.QueryEscape(b64) +} + +func (pb *ProtoBuilder) writeVarint(val int64) error { + if val == 0 { + _, err := pb.byteBuffer.Write([]byte{0}) + return err + } + for { + b := byte(val & 0x7F) + val >>= 7 + if val != 0 { + b |= 0x80 + } + _, err := pb.byteBuffer.Write([]byte{b}) + if err != nil { + return err + } + if val == 0 { + break + } + } + return nil +} + +func (pb *ProtoBuilder) field(field int, wireType byte) error { + val := int64(field<<3) | int64(wireType&0x07) + return pb.writeVarint(val) +} + +func (pb *ProtoBuilder) Varint(field int, val int64) error { + err := pb.field(field, 0) + if err != nil { + return err + } + return pb.writeVarint(val) +} + +func (pb *ProtoBuilder) String(field int, stringVal string) error { + strBts := []byte(stringVal) + return pb.Bytes(field, strBts) +} + +func (pb *ProtoBuilder) Bytes(field int, bytesVal []byte) error { + if err := pb.field(field, 2); err != nil { + return err + } + + if err := pb.writeVarint(int64(len(bytesVal))); err != nil { + return err + } + + _, err := pb.byteBuffer.Write(bytesVal) + return err +}