// Base code taken from: https://gist.github.com/marians/3b55318106df0e4e648158f1ffb43d38 package main import ( "context" "crypto/tls" // "fmt" "log" "net/http" "time" "encoding/json" "github.com/fatih/color" "github.com/webview/webview" "golang.org/x/oauth2" "io/ioutil" "strconv" "fmt" ) var ( conf *oauth2.Config ctx context.Context w webview.WebView client *http.Client ) func saveToken(tok *oauth2.Token, filename string) { j, err := json.MarshalIndent(tok, "", "\t") if err != nil { log.Fatalf("Failed to save token: %v", err) } ioutil.WriteFile(filename, j, 0777) } func loadToken(filename string) *oauth2.Token { data, err := ioutil.ReadFile(filename) if err != nil { return nil } var v = new(oauth2.Token) err = json.Unmarshal(data, &v) if err != nil { log.Fatalf("Failed to unmarshal data: %v", err) } return v } func giveCode(code string) { log.Printf("Code: %s", code) // Exchange will do the handshake to retrieve the initial access token. tok, err := conf.Exchange(ctx, code) if err != nil { log.Fatal(err) } log.Printf("Token: %s", tok) saveToken(tok, "./.token") // The HTTP Client returned by conf.Client will refresh the token as necessary. client = conf.Client(ctx, tok) _, err = client.Get("https://embed.gog.com/user/data/games") if err != nil { log.Fatal(err) } else { log.Println(color.CyanString("Authentication successful")) } w.Navigate(`data:text/html,

Success!

You are authenticated and can now return to the CLI.

`) time.Sleep(1 * time.Second) w.Terminate() } func openBrowser(url string) { w = webview.New(true) defer w.Destroy() w.SetTitle("Login") w.SetSize(600,600, webview.HintNone) w.Navigate(url) w.Bind("giveCode", giveCode) w.Init(` var params = new URLSearchParams(window.location.search); if (params.has('code')) { giveCode(params.get('code')); } `) w.Run() } type GameList struct { Owned []int `json:"owned"` } func getGameList() *GameList { res, err := client.Get("https://embed.gog.com/user/data/games") if err != nil { log.Fatalf("Failed to get game list: %v", err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatalf("Failed to parse game body: %v", err) } // log.Println(string(body)) var gl = new(GameList) err = json.Unmarshal(body, gl) if err != nil { log.Fatalf("Failed to parse game list response: %v", err) } return gl } type Product struct { Id int `json:"id"` Title string `json:"title"` Downloads struct { Installers [] struct { Name string `json:"name"` Os string `json:"os"` Files [] struct { Id string `json:"id"` Size int64 `json:"size"` Downlink string `json:"downlink"` } `json:"files"` } `json:"installers"` } `json:"downloads` } func getProduct(id int) *Product { url := "https://api.gog.com/products/" + strconv.Itoa(id) + "?expand=downloads" res, err := client.Get(url) if err != nil { log.Fatalf("Failed to get product info: %v", err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatalf("Failed to parse product body: %v", err) } //log.Println(string(body)) var p = new(Product) err = json.Unmarshal(body, p) if err != nil { log.Fatalf("Failed to parse product response: %v", err) } return p } type DownloadLinks struct { Downlink string `json:"downlink"` Checksum string `json:"checksum"` } func (dl *DownloadLinks) String() string { return fmt.Sprintf("{downlink: %s, checksum: %s}", dl.Downlink, dl.Checksum) } func getDownloadLinks(p *Product) []*DownloadLinks { var links []*DownloadLinks for _, i := range p.Downloads.Installers { for _, f := range i.Files { res, err := client.Get(f.Downlink) if err != nil { log.Fatalf("Failed to get file download info: %v", err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatalf("Failed to parse file download body: %v", err) } //log.Println(string(body)) var dl = new(DownloadLinks) err = json.Unmarshal(body, dl) if err != nil { log.Fatalf("Failed to parse file download response: %v", err) } links = append(links, dl) } } return links } func login(url string) { log.Println(color.CyanString("You will now be taken to your browser for authentication")) time.Sleep(1 * time.Second) openBrowser(url) } func main() { ctx = context.Background() conf = &oauth2.Config{ ClientID: "46899977096215655", ClientSecret: "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9", Scopes: []string{}, Endpoint: oauth2.Endpoint{ AuthURL: "https://auth.gog.com/auth", TokenURL: "https://auth.gog.com/token", }, // my own callback URL RedirectURL: "https://embed.gog.com/on_login_success?origin=client", //"http://127.0.0.1:9999/oauth/callback", } // add transport for self-signed certificate to context tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } sslcli := &http.Client{Transport: tr} ctx = context.WithValue(ctx, oauth2.HTTPClient, sslcli) // Redirect user to consent page to ask for permission // for the scopes specified above. url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline) tok := loadToken("./.token") if tok != nil { client = conf.Client(ctx, tok) _, err := client.Get("https://embed.gog.com/user/data/games") if err != nil { login(url) } else { log.Println(color.CyanString("Authentication successful")) } } else { login(url) } gl := getGameList() log.Println(gl.Owned) p := getProduct(gl.Owned[0]) log.Println(p) dl := getDownloadLinks(p) log.Printf("%+v", dl) }