-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to pin an automated comment #360
Comments
Which language are you consuming the API? |
python |
Well, I don't find any reference for pin a comment on the API guide but if you perform a pinned comment on youtube manually and inspect the network tab on developers tools when you pin a comment it makes a POST request on as long I tested the "key" parameter doesn't change, it could be some reference for the user who commented or the parameter for the action to pin a comment. Automate this probably is easy, if it's key to "action for pin comments " some testing could bring you an idea of how to do this |
Hi, I am a bit immature with Υoutube automations but I really need to find a way to pin comments in YouTube. Is there a new idea? Please help |
Can confirm that I'm getting the same URL or After reverse engineering for a bit I finally found where the comment ID is passed: It's part of an action string that is base64 encoded and then query escaped. This action string then has to be part of a request body. The request body the browser sends has a lot of information but luckily, almost everything can be stripped. However, besides the action string, we need at least a client context with the client name and version. {
"error": {
"code": 400,
"message": "Precondition check failed.",
"errors": [
{
"message": "Precondition check failed.",
"domain": "global",
"reason": "failedPrecondition"
}
],
"status": "FAILED_PRECONDITION"
}
} But this is a valid request body if you replace
(Yeah, we're lying by saying we use the web client but I'm not sure which other input values would work.) Last but not least, you'll need to be authenticated in some way. It's all a bit complicated so here's my go code: package main
import (
"bytes"
"context"
_ "embed"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/youtube/v3"
)
type PinCommentRequestBody struct {
Context *Context `json:"context,omitempty"`
Actions []string `json:"actions,omitempty"`
}
type Context struct {
Client *ClientContext `json:"client,omitempty"`
}
type ClientContext struct {
ClientName string `json:"clientName,omitempty"`
ClientVersion string `json:"clientVersion,omitempty"`
}
// this is the key to perform the pin action, same for all users
const pinCommentActionKey = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"
// getOauthConfig returns an oauth2 config that can be used to authenticate with Google
// You'll need to set the GOOGLE_CLIENT_SECRET_JSON environment variable to a JSON file
// containing the client secret for your Google app.
func getOauthConfig() (*oauth2.Config, error) {
googleJSON := os.Getenv("GOOGLE_CLIENT_SECRET_JSON")
if googleJSON == "" {
return nil, errors.New("GOOGLE_CLIENT_SECRET_JSON is empty")
}
cfg, err := google.ConfigFromJSON([]byte(googleJSON),
youtube.YoutubeForceSslScope, // write and pin comments
)
if err != nil {
return nil, err
}
return cfg, nil
}
// getClient returns an http client that can be used to perform authenticated requests.
// The token can be obtained by calling `cfg.AuthCodeURL` and authenticating with Google,
// then using a local server (http://localhost:8080/callback) for the callback to get the code.
func getClient(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) *http.Client {
return cfg.Client(ctx, token)
}
// pinComment pins a YouTube comment
// NOTE: The client must authenticate with a Google account that has a YouTube channel
// and have at least the `https://www.googleapis.com/auth/youtube.force-ssl` scope
func pinComment(ctx context.Context, cli *http.Client, commentID string) error {
// the URL to perform the pin action
pinURL := fmt.Sprintf("https://www.youtube.com/youtubei/v1/comment/perform_comment_action?key=%s&prettyPrint=false", pinCommentActionKey)
// it's not clear why this strange string is needed, but it works
// note the `%s` argument after the first control characters, it's the comment ID
actionRaw := fmt.Sprintf("\b\v\x10\x02\x1a\x1a%s*\vuKed7esvBEI0\x00J\x15102700233928915755015\xa8\x01\f\xba\x01\x18UCcNBwmeFr0IeLVlP_49glrA\xf0\x01\x00\x8a\x02\x10comments-section", commentID)
action := url.QueryEscape(base64.StdEncoding.EncodeToString([]byte(actionRaw)))
// create the request body
// besides the action, it also needs a client context with at least the client name and version
body := &PinCommentRequestBody{
Context: &Context{
Client: &ClientContext{
// obviously a lie but it works
ClientName: "WEB",
ClientVersion: "2.20230622.01.00",
},
},
Actions: []string{action},
}
// encode the body as JSON
bodyBytes, err := json.Marshal(body)
if err != nil {
return err
}
// create the request with the pin URL and the body
req, err := http.NewRequestWithContext(ctx, http.MethodPost, pinURL, bytes.NewReader(bodyBytes))
if err != nil {
return err
}
// make the request
rsp, err := cli.Do(req)
if err != nil {
return err
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(rsp.Body)
return fmt.Errorf("unexpected status %s: %s", rsp.Status, string(b))
}
// b, _ := io.ReadAll(rsp.Body)
// fmt.Printf("body: %v\n", string(b))
return nil // success!
} |
Hi! Hope your code is still working... Maybe I'll try to replicate it in Go... |
No description provided.
The text was updated successfully, but these errors were encountered: