// 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" "regexp" "strings" "os" ) 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 marshal token: %v", err) } err = ioutil.WriteFile(filename, j, 0600) if err != nil { log.Fatalf("Failed to save token: %v", err) } } 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"` Language string `json:"language"` 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"` InstallerName string Filename string Md5 string } 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) } dl.InstallerName = sanitizeName(i.Name) links = append(links, dl) } } return links } func sanitizeName(name string) string { // s := strings.Replace(name, " ", "_", -1) // s = strings.Replace(s, "'", "", -1) // s = strings.Replace(s, "™", "", -1) re := regexp.MustCompile("[^0-1a-zA-Z.-_]+") s := name s = re.ReplaceAllString(s, "_") return s } // func writeChecksumIfNeeded(dl *DownloadLinks, folder string) { // // TODO: reuse an existing checksum file somehow // res, err := client.Get(dl.Checksum) // if err != nil { // log.Fatalf("Failed to get file checksum info (%s): %v", dl.Checksum, err) // } // defer res.Body.Close() // body, err := ioutil.ReadAll(res.Body) // if err != nil { // log.Fatalf("Failed to parse file checksum body (%s): %v", dl.Checksum, err) // } // re := regexp.MustCompile(`