From 08ffd72dc66838d7d74dad923a77aa5e0e62e9b0 Mon Sep 17 00:00:00 2001 From: VnPower Date: Sat, 28 Sep 2024 19:23:49 +0700 Subject: [PATCH] App API base for credentials --- core/appapi.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 core/appapi.go diff --git a/core/appapi.go b/core/appapi.go new file mode 100644 index 0000000..1c5e8dd --- /dev/null +++ b/core/appapi.go @@ -0,0 +1,145 @@ +package core + +// Based on https://gist.github.com/ZipFile/c9ebedb224406f4f11845ab700124362 +// Don't panic on any errors in production btw. + +import ( + "io" + "bytes" + "strings" + "fmt" + "net/http" + "crypto/sha256" + "encoding/base64" + "crypto/rand" + "encoding/json" +) + +const USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)" +const REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback" +const LOGIN_URL = "https://app-api.pixiv.net/web/v1/login" +const AUTH_TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token" +const CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT" +const CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj" + +type AppAPICredentials struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` +} + +// VnPower: This function must be run before any of the generators could be used. +func assertAvailablePRNG() { + // Assert that a cryptographically secure PRNG is available. + // Panic otherwise. + buf := make([]byte, 1) + + _, err := io.ReadFull(rand.Reader, buf) + if err != nil { + panic(fmt.Sprintf("crypto/rand is unavailable: Read() failed with %#v", err)) + } +} + +// GenerateRandomBytes returns securely generated random bytes. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func generateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + // Note that err == nil only if we read len(b) bytes. + if err != nil { + return nil, err + } + + return b, nil +} + +// GenerateRandomStringURLSafe returns a URL-safe, base64 encoded +// securely generated random string. +// It will return an error if the system's secure random +// number generator fails to function correctly, in which +// case the caller should not continue. +func generateRandomStringURLSafe(n int) (string, error) { + b, err := generateRandomBytes(n) + return base64.URLEncoding.EncodeToString(b), err +} + +func s256(data []byte) string { + // SHA-256 + hasher := sha256.New() + hasher.Write(data) + return base64.URLEncoding.EncodeToString(hasher.Sum(nil)) +} + +func oauth_pkce() (string, string) { + code_verifier, err := generateRandomStringURLSafe(32) + if err != nil { + panic(err) + } + code_verifier = strings.Trim(code_verifier, "=") + code_challenge := strings.Trim(s256([]byte(code_verifier)), "=") + + return code_verifier, code_challenge +} + +func AppAPIRefresh(refresh_token string) { + var body = []byte(fmt.Sprintf(`client_id=%s&client_secret=%s&grant_type=refresh_token&include_policy=true&refresh_token=%s`, CLIENT_ID, CLIENT_SECRET, refresh_token)) + req, err := http.NewRequest("POST", AUTH_TOKEN_URL, bytes.NewBuffer(body)) + if err != nil { + panic(err) + } + + req.Header.Set("User-Agent", USER_AGENT) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + data, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + err = json.Unmarshal(data, &credentials) + if err != nil { + panic(err) + } + + return credentials +} + +func AppAPILogin() AppAPICredentials { + var credentials AppAPICredentials + code_verifier, code_challenge := oauth_pkce() + + // Let users open this URL and log in. Then enter the code. + fmt.Printf(`%s?code_challenge=%s&code_challenge_method=S256&client=pixiv-android`, LOGIN_URL, code_challenge) + var s string + // Change this. + fmt.Scanln(&s) + + var body = []byte(fmt.Sprintf(`client_id=%s&client_secret=%s&code=%s&code_verifier=%s&grant_type=authorization_code&include_policy=true&redirect_uri=%s`, CLIENT_ID, CLIENT_SECRET, s, code_verifier, REDIRECT_URI)) + req, err := http.NewRequest("POST", AUTH_TOKEN_URL, bytes.NewBuffer(body)) + if err != nil { + panic(err) + } + + req.Header.Set("User-Agent", USER_AGENT) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + panic(err) + } + data, err := io.ReadAll(resp.Body) + if err != nil { + panic(err) + } + err = json.Unmarshal(data, &credentials) + if err != nil { + panic(err) + } + + return credentials +}