package main import ( "crypto/tls" "encoding/json" "sort" "time" "golang.org/x/net/context" "golang.org/x/oauth2" "io/ioutil" "net/http" "errors" "flag" "fmt" "log" "os" "strconv" "github.com/BurntSushi/toml" "github.com/go-openapi/strfmt" "github.com/gregjones/httpcache" "github.com/gregjones/httpcache/leveldbcache" "github.com/leekchan/accounting" "github.com/logrusorgru/aurora" "github.com/syndtr/goleveldb/leveldb" ESI "./client" ESIClones "./client/clones" ESILocation "./client/location" ESIPlanetaryInteraction "./client/planetary_interaction" ESISkills "./client/skills" ESIUniverse "./client/universe" ESIWallet "./client/wallet" httptransport "github.com/go-openapi/runtime/client" loghttp "github.com/motemen/go-loghttp" ) // Character - Structure to save the verification data. type Character struct { CharacterID int32 CharacterName string ExpiresOn string } type configurationFile struct { ClientID string ClientSecret string } var ( googleOauthConfig = &oauth2.Config{ RedirectURL: "http://localhost:3000/callback", ClientID: "CLIENTKEY", ClientSecret: "SECRETKEY", Scopes: []string{ "esi-skills.read_skillqueue.v1", "esi-skills.read_skills.v1", "esi-planets.manage_planets.v1", "esi-wallet.read_character_wallet.v1", "esi-location.read_location.v1", "esi-clones.read_clones.v1", "esi-universe.read_structures.v1", }, Endpoint: oauth2.Endpoint{ AuthURL: "https://login.eveonline.com/oauth/authorize/", TokenURL: "https://login.eveonline.com/oauth/token/", }, } // Some random string, random for each request oauthStateString = "random" defaultDateFormat = "_2 Jan 2006, 15:04" cfgFilePath = flag.String("config", "configuration.toml", "Path to the configuration file.") cacheDBPath = flag.String("cache", "cache.ldb", "Path to the cache leveldb database.") insecureFlag = flag.Bool("insecure", false, "Do not check the HTTPS certificate") logCalls = flag.Bool("httplog", false, "Log HTTP calls") ) var ctx = context.Background() var messages = make(chan *oauth2.Token) var cacheDB *leveldb.DB func main() { flag.Parse() logfilename := fmt.Sprintf("log-%s.log", time.Now().Format("2006-01-02")) f, err := os.OpenFile(logfilename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening file: %v", err) } defer f.Close() log.SetOutput(f) var cacheDBerr error cacheDB, cacheDBerr = leveldb.OpenFile(*cacheDBPath, nil) if cacheDBerr != nil { fmt.Println("Unable to initialize the LevelDB cache.") log.Fatal(cacheDBerr) } cErr := readConfigurationFile() if cErr != nil { fmt.Println("Missing configuration file.") log.Fatal(cErr) } cToken := getDatabaseToken() if cToken == nil { cToken = getNewAuthorizationToken() } ldb := leveldbcache.NewWithDB(cacheDB) cachingTransport := httpcache.NewTransport(ldb) if *insecureFlag { insecureTransport := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, Proxy: http.ProxyFromEnvironment, } cachingTransport.Transport = insecureTransport } if *logCalls { var CustomLogResponse = func(resp *http.Response) { log.Printf("<--- HTTP %d.%d %d %s (expires on %s) %s", resp.ProtoMajor, resp.ProtoMinor, resp.StatusCode, resp.Request.URL, resp.Header.Get("expires"), resp.Header.Get("content-type"), ) } loggingTransport := &loghttp.Transport{ LogResponse: CustomLogResponse, } loggingTransport.Transport = cachingTransport.Transport cachingTransport.Transport = loggingTransport } cachingClient := &http.Client{Transport: cachingTransport} ctx := context.WithValue(context.TODO(), oauth2.HTTPClient, cachingClient) client := googleOauthConfig.Client(ctx, cToken) m, err := getCharacterInfo(client) if err != nil { log.Println(err) cToken = getNewAuthorizationToken() client = googleOauthConfig.Client(ctx, cToken) m, err = getCharacterInfo(client) if err != nil { log.Fatal(err) } } transport := httptransport.NewWithClient("esi.tech.ccp.is", "/latest", []string{"https"}, client) //transport.Debug = true swaggerclient := ESI.New(transport, strfmt.Default) charInfoChan := make(chan string) cloneInfoChan := make(chan string) planetInfoChan := make(chan string) skillsInfoChan := make(chan string) go printCharacterInformation(charInfoChan, swaggerclient, m) go printClonesInformation(cloneInfoChan, swaggerclient, m) go printCharacterPlanets(planetInfoChan, swaggerclient, m) go printCharacterSkillQueue(skillsInfoChan, swaggerclient, m) for msg := range charInfoChan { fmt.Print(msg) } fmt.Printf("\n\nClones\n") for msg := range cloneInfoChan { fmt.Print(msg) } fmt.Printf("\n\nPlanetary interaction\n") for msg := range planetInfoChan { fmt.Print(msg) } fmt.Printf("\n\nSkill queue\n") for msg := range skillsInfoChan { fmt.Print(msg) } } func readConfigurationFile() error { var config configurationFile if _, err := toml.DecodeFile(*cfgFilePath, &config); err != nil { log.Println(err) return err } if config.ClientID == "" || config.ClientSecret == "" { log.Println("Missing ClientID or ClientSecret configuration option in configuration file" + *cfgFilePath + ".") return errors.New("Missing ClientID or ClientSecret in configuration file " + *cfgFilePath + ".") } googleOauthConfig.ClientID = config.ClientID googleOauthConfig.ClientSecret = config.ClientSecret return nil } func getCharacterInfo(client *http.Client) (*Character, error) { req, _ := http.NewRequest("GET", "https://login.eveonline.com/oauth/verify", nil) response, errDo := client.Do(req) if errDo != nil { return nil, errDo } defer response.Body.Close() contents, _ := ioutil.ReadAll(response.Body) var m Character errJSON := json.Unmarshal(contents, &m) if errJSON != nil { log.Printf("Unmarshalling error: %s\n", errJSON) return nil, errJSON } if m.CharacterID == 0 || m.CharacterName == "" { log.Printf("Invalid return value from verify call: %s", contents) return nil, errors.New("Invalid token") } return &m, nil } func printCharacterSkillQueue(outChan chan string, swaggerclient *ESI.App, m *Character) { callParam := ESISkills.NewGetCharactersCharacterIDSkillqueueParams() callParam.WithCharacterID(m.CharacterID) skillqueueresp, skillerr := swaggerclient.Skills.GetCharactersCharacterIDSkillqueue(callParam, nil) if skillerr != nil { log.Fatalf("Got error on GetCharactersCharacterIDSkillqueue: %T %s", skillerr, skillerr) } skillqueue := skillqueueresp.Payload now := time.Now() snIds := make([]int32, 0, len(skillqueue)) for _, skill := range skillqueue { snIds = append(snIds, *skill.SkillID) } skillNames := getUniverseNames(swaggerclient, &snIds) maxSkillLength := 0 for _, skill := range skillqueue { name := skillNames[*skill.SkillID] if maxSkillLength < len(name) { maxSkillLength = len(name) } } for _, skill := range skillqueue { // element is the element from someSlice for where we are name := skillNames[*skill.SkillID] finishDate := time.Time(skill.FinishDate) startDate := time.Time(skill.StartDate) skillDuration := finishDate.Sub(startDate) finishDuration := finishDate.Sub(now) maxSkillFormat := fmt.Sprintf("%d", maxSkillLength) // see https://github.com/ccpgames/esi-issues/issues/113 // The queue is only updated when the user logs in with the client // we thus need to do the computations and filtering ourselves if finishDate.Before(time.Now()) { outChan <- fmt.Sprintf("✔ %"+maxSkillFormat+"s %d\n", name, *skill.FinishedLevel, ) continue } if startDate.Before(time.Now()) { outChan <- fmt.Sprintf("%s %"+maxSkillFormat+"s %d, %s - ends on %s (%s)\n", aurora.Green("➠").Bold(), name, *skill.FinishedLevel, formatDuration(skillDuration, 2), time.Time(skill.FinishDate).Format(defaultDateFormat), formatDuration(finishDuration, 3), ) continue } outChan <- fmt.Sprintf(" %"+maxSkillFormat+"s %d, %s - ends on %s (%s)\n", name, *skill.FinishedLevel, formatDuration(skillDuration, 2), time.Time(skill.FinishDate).Format(defaultDateFormat), formatDuration(finishDuration, 3), ) } close(outChan) } func formatDuration(duration time.Duration, daysLength int32) string { days := int32(duration.Hours()) / 24 hours := int32(duration.Minutes()) / 60 % 24 minutes := int32(duration.Minutes()) % 60 return fmt.Sprintf("%"+fmt.Sprintf("%d", daysLength)+"dd %02d:%02d", days, hours, minutes) } func printCharacterInformation(outChan chan string, swaggerclient *ESI.App, m *Character) { callParam := ESIWallet.NewGetCharactersCharacterIDWalletsParams() callParam.WithCharacterID(m.CharacterID) esiresponse, esierr := swaggerclient.Wallet.GetCharactersCharacterIDWallets(callParam, nil) if esierr != nil { fmt.Println("Error while getting the wallet information") log.Fatalf("Got error on GetCharactersCharacterIDWallets: %T %s", esierr, esierr) } wallets := esiresponse.Payload ac := accounting.Accounting{Symbol: "ISK ", Precision: 0, Thousand: "'"} outChan <- fmt.Sprintf("Name: %s\n", m.CharacterName) for _, wallet := range wallets { if wallet.Balance > 0 { outChan <- fmt.Sprintf("Wallet: %s\n", ac.FormatMoney(wallet.Balance/100)) } } posCallParam := ESILocation.NewGetCharactersCharacterIDLocationParams() posCallParam.WithCharacterID(m.CharacterID) posresponse, poserr := swaggerclient.Location.GetCharactersCharacterIDLocation(posCallParam, nil) if poserr != nil { fmt.Println("Error while getting the current character location.") log.Fatalf("Got error on GetCharactersCharacterIDLocation: %s", poserr) } position := posresponse.Payload itemIds := make([]int32, 0) itemIds = append(itemIds, *position.SolarSystemID) if position.StationID != nil { itemIds = append(itemIds, *position.StationID) } universeNames := getUniverseNames(swaggerclient, &itemIds) stationName := "" if position.StationID != nil { stationName = universeNames[*position.StationID] } if position.StructureID != nil { retName, retErr := getStructureStationInfo(swaggerclient, "structure", *position.StructureID) if retErr == nil { stationName = retName.Name } } outChan <- fmt.Sprintf("Currently in %s - %s\n", universeNames[*position.SolarSystemID], stationName, ) close(outChan) } func printClonesInformation(outChan chan string, swaggerclient *ESI.App, m *Character) { cloCallParams := ESIClones.NewGetCharactersCharacterIDClonesParams() cloCallParams.WithCharacterID(m.CharacterID) cloresponse, cloerr := swaggerclient.Clones.GetCharactersCharacterIDClones(cloCallParams, nil) if cloerr != nil { fmt.Println("Error while getting the current character clones.") log.Fatalf("Got error on GetCharactersCharacterIDClones: %s", cloerr) } clones := cloresponse.Payload.JumpClones for _, clone := range clones { sInfo, _ := getStructureStationInfo(swaggerclient, clone.LocationType, clone.LocationID) outChan <- fmt.Sprintf(" %s, %s, %s\n ", sInfo.Name, sInfo.System.ConstellationName, sInfo.System.RegionName, ) implantNames := getUniverseNames(swaggerclient, &clone.Implants) for _, implant := range clone.Implants { implantName := implantNames[implant] outChan <- fmt.Sprintf(" %s, ", implantName) } outChan <- "\n" } close(outChan) } type structureInfo struct { ID int64 Name string SystemID int32 System solarSystemInfo } func getStructureStationInfo(swaggerclient *ESI.App, typeID string, ID int64) (*structureInfo, error) { locationName := getCachedDataSub64(ID, "name") locationSystem := getCachedDataIntSub64(ID, "system") if locationName != "" && locationSystem > 0 { var sInfo structureInfo sInfo.ID = ID sInfo.Name = locationName sInfo.System = getSolarSystemInformation(swaggerclient, locationSystem) return &sInfo, nil } if typeID == "station" { stacallParams := ESIUniverse.NewGetUniverseStationsStationIDParams() stacallParams.WithStationID(int32(ID)) staresponse, staerr := swaggerclient.Universe.GetUniverseStationsStationID(stacallParams) if staerr != nil { log.Printf("Got error on GetUniverseStationsStationID: %s", staerr) return nil, staerr } staInfo := staresponse.Payload var sInfo structureInfo sInfo.ID = int64(*staInfo.StationID) sInfo.Name = *staInfo.Name sInfo.SystemID = *staInfo.SystemID sInfo.System = getSolarSystemInformation(swaggerclient, sInfo.SystemID) putCacheDataSub64(sInfo.ID, "name", sInfo.Name) putCacheDataIntSub64(sInfo.ID, "system", sInfo.SystemID) return &sInfo, nil } if typeID == "structure" { strcallParams := ESIUniverse.NewGetUniverseStructuresStructureIDParams() strcallParams.WithStructureID(ID) strresponse, strerr := swaggerclient.Universe.GetUniverseStructuresStructureID(strcallParams, nil) if strerr != nil { log.Printf("Got error on GetUniverseStructuresStructureID: %s", strerr) return nil, strerr } strInfo := strresponse.Payload var sInfo structureInfo sInfo.ID = ID sInfo.Name = *strInfo.Name sInfo.SystemID = *strInfo.SolarSystemID sInfo.System = getSolarSystemInformation(swaggerclient, sInfo.SystemID) putCacheDataSub64(sInfo.ID, "name", sInfo.Name) putCacheDataIntSub64(sInfo.ID, "system", sInfo.SystemID) return &sInfo, nil } return nil, nil } type byPIPinType []*ESIPlanetaryInteraction.PinsItems0 func (s byPIPinType) Len() int { return len(s) } func (s byPIPinType) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byPIPinType) Less(i, j int) bool { a := s[i] b := s[j] if *a.TypeID != *b.TypeID { return *a.TypeID < *b.TypeID } if a.ExtractorDetails != nil { return a.ExtractorDetails.ProductTypeID < a.ExtractorDetails.ProductTypeID } if a.SchematicID > 0 { return a.SchematicID < b.SchematicID } return *a.PinID < *b.PinID } func printCharacterPlanets(outChan chan string, swaggerclient *ESI.App, m *Character) { callParam := ESIPlanetaryInteraction.NewGetCharactersCharacterIDPlanetsParams() callParam.WithCharacterID(m.CharacterID) esiresponse, esierr := swaggerclient.PlanetaryInteraction.GetCharactersCharacterIDPlanets(callParam, nil) if esierr != nil { fmt.Println("Error while getting the planetary interaction information.") log.Fatalf("Got error on GetCharactersCharacterIDPlanets: %T %s", esierr, esierr) } planets := esiresponse.Payload now := time.Now() for _, planet := range planets { pcallParam := ESIPlanetaryInteraction.NewGetCharactersCharacterIDPlanetsPlanetIDParams() pcallParam.WithCharacterID(m.CharacterID).WithPlanetID(*planet.PlanetID) planetSystemInfo := getSolarSystemInformation(swaggerclient, *planet.SolarSystemID) planetName := getPlanetInformation(swaggerclient, *planet.PlanetID) outChan <- fmt.Sprintf("Planet %s, %s, %s - Type %s, Lvl %d - Updated %s\n", planetName.PlanetName, planetSystemInfo.ConstellationName, planetSystemInfo.RegionName, *planet.PlanetType, *planet.UpgradeLevel, time.Time(*planet.LastUpdate).Format(defaultDateFormat), ) pesiresponse, pesierr := swaggerclient.PlanetaryInteraction.GetCharactersCharacterIDPlanetsPlanetID(pcallParam, nil) if pesierr != nil { fmt.Println(" Error while getting the planetary interaction information") log.Printf("Error on CharactersCharacterIDPlanetsPlanetID: %s\n", pesierr) continue } pins := pesiresponse.Payload.Pins sort.Sort(byPIPinType(pins)) ptIds := make([]int32, 0, len(pins)) for _, pin := range pins { if pin.ExtractorDetails != nil { ptIds = append(ptIds, pin.ExtractorDetails.ProductTypeID) } } pinNames := getUniverseNames(swaggerclient, &ptIds) for _, pin := range pins { if pin.ExtractorDetails != nil { status := fmt.Sprint(aurora.Red("✘").Bold()) statuscomment := fmt.Sprint(aurora.Red("expired").Bold()) duration := now.Sub(time.Time(pin.ExpiryTime)) if time.Time(pin.ExpiryTime).After(now) { status = fmt.Sprint(aurora.Green("✔").Bold()) statuscomment = "expires" duration = time.Time(pin.ExpiryTime).Sub(now) } outChan <- fmt.Sprintf(" %s Extractor %4ds cycle, %s, %d per cycle, %s %s (%s)\n", status, pin.ExtractorDetails.CycleTime, pinNames[pin.ExtractorDetails.ProductTypeID], pin.ExtractorDetails.QtyPerCycle, statuscomment, time.Time(pin.ExpiryTime).Format(defaultDateFormat), formatDuration(duration, 2), ) } else if pin.SchematicID != 0 { // Get the schematic from ESI and cache it schematicInfo, serr := getSchematicsInformation(swaggerclient, pin.SchematicID) if serr != nil { log.Printf("Error on getSchematicsInformation: %T, %s\n", serr, serr) outChan <- fmt.Sprintf(" ✔ Factory ????? cycle, ?????\n") } else { outChan <- fmt.Sprintf(" ✔ Factory %4ds cycle, %s\n", schematicInfo.CycleTime, schematicInfo.SchematicName, ) } } } } close(outChan) } func getUniverseNames(swaggerclient *ESI.App, itemIds *[]int32) map[int32]string { itemNames := make(map[int32]string) itemMissingNames := make(map[int32]string) for _, itemID := range *itemIds { itemName := getCachedDataSub(itemID, "name") if itemName != "" { itemNames[itemID] = itemName } else { itemMissingNames[itemID] = "" } } if len(itemMissingNames) > 0 { snIds := make([]int32, 0, len(itemMissingNames)) for itemID := range itemMissingNames { snIds = append(snIds, itemID) } sncallParam := ESIUniverse.NewPostUniverseNamesParams() sncallParam.WithIds(snIds) skillNameResp, skillNameErr := swaggerclient.Universe.PostUniverseNames(sncallParam) if skillNameErr != nil { log.Printf("Error on PostUniverseNames on items: %v\n", snIds) log.Printf("Error on PostUniverseNames: %s\n", skillNameErr) } else { for _, searchResult := range skillNameResp.Payload { itemName := searchResult.Name itemID := searchResult.ID putCacheDataSub(*itemID, "name", *itemName) itemNames[*itemID] = *itemName } } } return itemNames } type planetInformation struct { PlanetName string PlanetTypeID int32 } func getPlanetInformation(swaggerclient *ESI.App, planetID int32) planetInformation { planetName := getCachedDataSub(planetID, "name") planetType := getCachedDataIntSub(planetID, "type") if planetName == "" { pcallParams := ESIUniverse.NewGetUniversePlanetsPlanetIDParams() pcallParams.WithPlanetID(planetID) pesiresponse, _ := swaggerclient.Universe.GetUniversePlanetsPlanetID(pcallParams) planetESIInfo := pesiresponse.Payload var planetInfo planetInformation planetInfo.PlanetName = *planetESIInfo.Name planetInfo.PlanetTypeID = *planetESIInfo.TypeID putCacheDataSub(planetID, "name", planetInfo.PlanetName) putCacheDataIntSub(planetID, "type", planetInfo.PlanetTypeID) return planetInfo } var planetInfo planetInformation planetInfo.PlanetName = planetName planetInfo.PlanetTypeID = planetType return planetInfo } type solarSystemInfo struct { SolarSystemName string ConstellationID int32 ConstellationName string RegionID int32 RegionName string } func getSolarSystemInformation(swaggerclient *ESI.App, solarSystemID int32) solarSystemInfo { systemName := getCachedDataSub(solarSystemID, "name") systemConstellationID := getCachedDataIntSub(solarSystemID, "constellation") constellationName := getCachedDataSub(systemConstellationID, "name") constellationRegionID := getCachedDataIntSub(systemConstellationID, "region") regionName := getCachedDataSub(constellationRegionID, "name") if systemName == "" || constellationName == "" || regionName == "" { scallParams := ESIUniverse.NewGetUniverseSystemsSystemIDParams() scallParams.WithSystemID(solarSystemID) sesiresponse, _ := swaggerclient.Universe.GetUniverseSystemsSystemID(scallParams) solarSystemESIInfo := sesiresponse.Payload var ssInfo solarSystemInfo ssInfo.SolarSystemName = *solarSystemESIInfo.Name ssInfo.ConstellationID = *solarSystemESIInfo.ConstellationID ccallParams := ESIUniverse.NewGetUniverseConstellationsConstellationIDParams() ccallParams.WithConstellationID(ssInfo.ConstellationID) cesiresponse, _ := swaggerclient.Universe.GetUniverseConstellationsConstellationID(ccallParams) constellationESIInfo := cesiresponse.Payload ssInfo.ConstellationName = *constellationESIInfo.Name ssInfo.RegionID = *constellationESIInfo.RegionID rcallParams := ESIUniverse.NewGetUniverseRegionsRegionIDParams() rcallParams.WithRegionID(ssInfo.RegionID) resiresponse, _ := swaggerclient.Universe.GetUniverseRegionsRegionID(rcallParams) regionESIInfo := resiresponse.Payload ssInfo.RegionName = *regionESIInfo.Name putCacheDataSub(solarSystemID, "name", ssInfo.SolarSystemName) putCacheDataIntSub(solarSystemID, "constellation", ssInfo.ConstellationID) putCacheDataSub(ssInfo.ConstellationID, "name", ssInfo.ConstellationName) putCacheDataIntSub(ssInfo.ConstellationID, "region", ssInfo.RegionID) putCacheDataSub(ssInfo.RegionID, "name", ssInfo.RegionName) return ssInfo } var ssInfo solarSystemInfo ssInfo.SolarSystemName = systemName ssInfo.ConstellationID = systemConstellationID ssInfo.ConstellationName = constellationName ssInfo.RegionID = constellationRegionID ssInfo.RegionName = regionName return ssInfo } // SchematicInfo - Structure to store and cache the schematics information from ESI type SchematicInfo struct { CycleTime int32 SchematicName string } func getSchematicsInformation(swaggerclient *ESI.App, schematicID int32) (*SchematicInfo, error) { schematicName := getCachedDataSub(schematicID, "name") schematicCycle := getCachedDataIntSub(schematicID, "cycle") if schematicName == "" { scallParams := ESIPlanetaryInteraction.NewGetUniverseSchematicsSchematicIDParams() scallParams.WithSchematicID(schematicID) sesiresponse, sesierr := swaggerclient.PlanetaryInteraction.GetUniverseSchematicsSchematicID(scallParams) if sesierr != nil { return nil, sesierr } schematicsESIInfo := sesiresponse.Payload var schematicInfo SchematicInfo schematicInfo.CycleTime = *schematicsESIInfo.CycleTime schematicInfo.SchematicName = *schematicsESIInfo.SchematicName putCacheDataSub(schematicID, "name", schematicInfo.SchematicName) putCacheDataIntSub(schematicID, "cycle", schematicInfo.CycleTime) return &schematicInfo, nil } var schematicInfo SchematicInfo schematicInfo.CycleTime = schematicCycle schematicInfo.SchematicName = schematicName return &schematicInfo, nil } func getNewAuthorizationToken() *oauth2.Token { http.HandleFunc("/", handleLogin) http.HandleFunc("/callback", handleAuthenticationCallback) go func() { fmt.Println("No available token. Please visit http://localhost:3000 to renew.") http.ListenAndServe(":3000", nil) }() return <-messages } func handleLogin(w http.ResponseWriter, r *http.Request) { url := googleOauthConfig.AuthCodeURL(oauthStateString, oauth2.AccessTypeOffline) // https://eveonline-third-party-documentation.readthedocs.io/en/latest/sso/authentication.html // response_type: Must be set to “code”. url = url + "&response_type=code" http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func handleAuthenticationCallback(w http.ResponseWriter, r *http.Request) { state := r.FormValue("state") if state != oauthStateString { log.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) // No token to pass, we will get one on the second pass return } code := r.FormValue("code") token, err := googleOauthConfig.Exchange(oauth2.NoContext, code) if err != nil { log.Printf("Code exchange failed with '%s'\n", err) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) // No token to pass, we will get one on the second pass return } client := googleOauthConfig.Client(oauth2.NoContext, token) req, _ := http.NewRequest("GET", "https://login.eveonline.com/oauth/verify", nil) response, _ := client.Do(req) defer response.Body.Close() contents, _ := ioutil.ReadAll(response.Body) var m Character errJSON := json.Unmarshal(contents, &m) if errJSON != nil { fmt.Printf("JSON read error with '%s'\n", errJSON) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } fmt.Fprintf(w, "Got token for character %s.\n", m.CharacterName) fmt.Fprintf(w, "You can now close this navigator tab.\n") log.Printf("Refresh token is %s\n", token.RefreshToken) putCacheData("accessToken", token.AccessToken) putCacheData("refreshToken", token.RefreshToken) messages <- token } func getCachedDataIntSub(key int32, subkey string) int32 { return getCachedDataInt(fmt.Sprintf("%d.%s", key, subkey)) } func getCachedDataSub(key int32, subkey string) string { return getCachedData(fmt.Sprintf("%d.%s", key, subkey)) } func getCachedDataIntSub64(key int64, subkey string) int32 { return getCachedDataInt(fmt.Sprintf("%d.%s", key, subkey)) } func getCachedDataSub64(key int64, subkey string) string { return getCachedData(fmt.Sprintf("%d.%s", key, subkey)) } func getCachedDataInt(key string) int32 { strVal := getCachedData(key) intVal, convErr := strconv.ParseInt(strVal, 10, 32) if convErr != nil { return -1 } return int32(intVal) } func getCachedData(key string) string { response, err := cacheDB.Get([]byte(key), nil) if err != nil { return "" } return string(response[:]) } func putCacheDataIntSub(key int32, subkey string, value int32) { putCacheDataSub(key, subkey, fmt.Sprintf("%d", value)) } func putCacheDataSub(key int32, subkey string, value string) { putCacheData(fmt.Sprintf("%d.%s", key, subkey), value) } func putCacheDataIntSub64(key int64, subkey string, value int32) { putCacheDataSub64(key, subkey, fmt.Sprintf("%d", value)) } func putCacheDataSub64(key int64, subkey string, value string) { putCacheData(fmt.Sprintf("%d.%s", key, subkey), value) } func putCacheData(key string, value string) { err := cacheDB.Put([]byte(key), []byte(value), nil) if err != nil { log.Fatal(err) } } func getDatabaseToken() *oauth2.Token { refreshToken := getCachedData("refreshToken") //accessToken := getCachedData("accessToken") token := new(oauth2.Token) token.RefreshToken = refreshToken //token.AccessToken = accessToken token.TokenType = "Bearer" return token }