If you visit this page:
and hover over one of the videos, you should capture a request like this:
POST https://www.youtube.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false HTTP/2.0
cookie: VISITOR_INFO1_LIVE=Ba9qACMRbm8
x-goog-visitor-id: CgtCYTlxQUNNUmJtOCiE8fahBg%3D%3D
{
"context": {
"client": {
"visitorData": "CgtCYTlxQUNNUmJtOCiE8fahBg%3D%3D"
}
},
"params": "YAHIAQE%3D"
}
lets start with the visitorData
/x-goog-visitor-id
value. we can unmarshal
the data:
package main
import (
"2a.pages.dev/rosso/protobuf"
"encoding/base64"
"fmt"
"net/url"
)
func main() {
s, err := url.QueryUnescape("CgtCYTlxQUNNUmJtOCiE8fahBg%3D%3D")
if err != nil {
panic(err)
}
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
panic(err)
}
m, err := protobuf.Unmarshal(b)
if err != nil {
panic(err)
}
fmt.Println(m)
}
result:
protobuf.Message{
1: protobuf.String("Ba9qACMRbm8"),
5: protobuf.Varint(1681766532),
}
value 5
is just the current timestamp:
package main
import "time"
func main() {
s := time.Unix(1681766532, 0).String()
println(s) // 2023-04-17 16:22:12 -0500 CDT
}
I have found that it can be omitted, which leaves:
protobuf.Message{
1: protobuf.String("Ba9qACMRbm8"),
}
whats interesting if you look above, this string also matches the
VISITOR_INFO1_LIVE
cookie. If we marshal the message, we get:
package main
import (
"2a.pages.dev/rosso/protobuf"
"encoding/base64"
)
func main() {
b := protobuf.Message{
1: protobuf.String("Ba9qACMRbm8"),
}.Marshal()
println(base64.StdEncoding.EncodeToString(b)) // CgtCYTlxQUNNUmJtOA==
}
if we plug that into the request body, everything works as expected:
package main
import (
"io"
"net/http"
"net/url"
"strings"
"time"
)
func main() {
var req http.Request
req.Header = make(http.Header)
req.Header["X-Goog-Api-Key"] = []string{"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"}
req.Method = "POST"
req.URL = new(url.URL)
req.URL.Host = "www.youtube.com"
req.URL.Path = "/youtubei/v1/player"
req.URL.Scheme = "https"
req_body := `
{
"context": {
"client": {
"clientName": "ANDROID",
"clientVersion": "18.14.40",
"visitorData": "CgtCYTlxQUNNUmJtOA=="
}
},
"videoId": "sG8DXQhNEGU"
}
`
for range [16]struct{}{} {
req.Body = io.NopCloser(strings.NewReader(req_body))
res, err := new(http.Transport).RoundTrip(&req)
if err != nil {
panic(err)
}
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
if err := res.Body.Close(); err != nil {
panic(err)
}
text := string(body)
println(
"adaptiveFormats", strings.Contains(text, `"adaptiveFormats"`),
"viewCount", strings.Contains(text, `"viewCount"`),
)
time.Sleep(time.Second)
}
}
If we remove visitorData
, we get random failures:
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats false viewCount false
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats false viewCount false
adaptiveFormats false viewCount false
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats true viewCount true
adaptiveFormats false viewCount false
adaptiveFormats false viewCount false
note setting the x-goog-visitor-id
header instead is also an option:
req.Header = http.Header{
"x-goog-api-key": {"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"},
"x-goog-visitor-id": {"CgtCYTlxQUNNUmJtOA=="},
}
but the cookie
header also fails randomly for some reason:
req.Header = http.Header{
"cookie": {"VISITOR_INFO1_LIVE=Ba9qACMRbm8"},
"x-goog-api-key": {"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"},
}
note trying params
instead of visitorData
/x-goog-visitor-id
as in the
original request:
"params": "YAHIAQE%3D"
fails randomly as well. Note I also found this interesting comment:
the required parameter is field number 12 in the protobuf (very long base64 string), the JSON name is "params" -- it seems to be some kind of ad token?
strangely even though using the JSON API without the parameter gets you a 403, when using the protobuf interface you get the famous 'content is not available on this app' message instead.
edit: since params contains protobuf-encoded data, removing all the fields except the ones the server needs gets you the following working (for now) value:
CgIQBg%3D%3D
TeamNewPipe/NewPipe#9038 (comment)
Sadly this person didnt explain what "field number 12 in the protobuf" is. they could mean part of a web client HTTP request or response body, which was then base64 decoded and then ProtoBuf unmarshaled. or they could mean part of an Android client HTTP request or response body. At any rate, adding that value to the request body:
"params": "CgIQBg%3D%3D"
does fix the issue. in addition, if you unmarshal the value, you get:
protobuf.Message{
1: protobuf.Message{
2: protobuf.Varint(6)
}
}
I found you can replace the Varint with any value and the request still works, so here are the encoded versions:
0 CgIQAA==
1 CgIQAQ==
2 CgIQAg==
3 CgIQAw==
4 CgIQBA==
5 CgIQBQ==
6 CgIQBg==
7 CgIQBw==
8 CgIQCA==
9 CgIQCQ==