package main import ( "encoding/json" "time" "golang.org/x/net/context" "golang.org/x/oauth2" "io/ioutil" "net/http" "fmt" "log" "strconv" "github.com/go-openapi/strfmt" ESI "client" ESIPlanetaryInteraction "client/planetary_interaction" ESISkills "client/skills" ESIUniverse "client/universe" "database/sql" _ "github.com/mattn/go-sqlite3" httptransport "github.com/go-openapi/runtime/client" ) // Character - Structure to save the verification data. type Character struct { CharacterID int32 CharacterName string ExpiresOn 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", }, 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" ) var ctx = context.Background() var messages = make(chan *oauth2.Token) func main() { cToken := getDatabaseToken() if cToken == nil { cToken = getNewAuthorizationToken() } client := googleOauthConfig.Client(oauth2.NoContext, cToken) m, err := getCharacterInfo(client) if err != nil { cToken = getNewAuthorizationToken() client = googleOauthConfig.Client(oauth2.NoContext, cToken) m, err = getCharacterInfo(client) if err != nil { log.Fatal(err) } } fmt.Printf("Name: %s\n", m.CharacterName) fmt.Printf("Id: %d\n", m.CharacterID) //getCharacterPlanets(client, m) getCharacterSkillQueue(client, m) } 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 { return nil, errJSON } return &m, nil } func getCharacterSkillQueue(client *http.Client, m *Character) { // create the transport transport := httptransport.NewWithClient("esi.tech.ccp.is", "/latest", []string{"https"}, client) // create the API client, with the transport swaggerclient := ESI.New(transport, strfmt.Default) charIDSkilqueue := ESISkills.NewGetCharactersCharacterIDSkillqueueParams().WithCharacterID(m.CharacterID) skillqueueresp, skillerr := swaggerclient.Skills.GetCharactersCharacterIDSkillqueue(charIDSkilqueue, nil) if skillerr != nil { log.Fatalf("Error on GetCharactersCharacterIDSkillqueue\n%s\n", skillerr) } skillqueue := skillqueueresp.Payload skillNames := make(map[int32]string) skillMissingNames := make(map[int32]string) for _, skill := range skillqueue { skillName := getCachedData(fmt.Sprintf("%d.name", *skill.SkillID)) if skillName != "" { skillNames[*skill.SkillID] = skillName } else { skillMissingNames[*skill.SkillID] = "" } } if len(skillMissingNames) > 0 { snIds := make([]int32, 0, len(skillMissingNames)) for skillID := range skillMissingNames { snIds = append(snIds, skillID) } var sncallParamBody ESIUniverse.PostUniverseNamesBody sncallParamBody.Ids = snIds sncallParam := ESIUniverse.NewPostUniverseNamesParams() sncallParam.SetIds(sncallParamBody) skillNameResp, skillNameErr := swaggerclient.Universe.PostUniverseNames(sncallParam) if skillNameErr != nil { log.Fatalf("Error on PostUniverseNames\n%s\n", skillNameErr) } for _, searchResult := range skillNameResp.Payload { itemName := searchResult.Name itemID := searchResult.ID putCacheData(fmt.Sprintf("%d.name", *itemID), *itemName) skillNames[*itemID] = *itemName } } for _, skill := range skillqueue { // element is the element from someSlice for where we are name := skillNames[*skill.SkillID] finishDate := time.Time(skill.FinishDate) // 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()) { continue } fmt.Printf("% 35s - level %d - %s to %s\n", name, *skill.FinishedLevel, time.Time(skill.StartDate).Format("_2 Jan 2006, 15:04"), time.Time(skill.FinishDate).Format("_2 Jan 2006, 15:04")) } } func getCharacterPlanets(client *http.Client, m *Character) { // create the transport transport := httptransport.NewWithClient("esi.tech.ccp.is", "/latest", []string{"https"}, client) // create the API client, with the transport swaggerclient := ESI.New(transport, strfmt.Default) callParam := ESIPlanetaryInteraction.NewGetCharactersCharacterIDPlanetsParams().WithCharacterID(m.CharacterID) esiresponse, _ := swaggerclient.PlanetaryInteraction.GetCharactersCharacterIDPlanets(callParam, nil) planets := esiresponse.Payload for _, planet := range planets { // element is the element from someSlice for where we are name := "UNK-PLANET" pcallParam := ESIPlanetaryInteraction.NewGetCharactersCharacterIDPlanetsPlanetIDParams() pcallParam.WithCharacterID(m.CharacterID).WithPlanetID(*planet.PlanetID) solarSystemInfo := getSolarSystemInformation(swaggerclient, *planet.SolarSystemID) fmt.Printf(" %s (%d) %s - %s, level %d with %d structures - Updated %s\n", name, *planet.PlanetID, solarSystemInfo.SolarSystemName, *planet.PlanetType, *planet.UpgradeLevel, *planet.NumPins, planet.LastUpdate, ) pesiresponse, _ := swaggerclient.PlanetaryInteraction.GetCharactersCharacterIDPlanetsPlanetID(pcallParam, nil) for _, pin := range pesiresponse.Payload.Pins { if pin.ExtractorDetails != nil { fmt.Printf(" Extractor - cycle started %s, %d per cycle, cycles of %ds\n", pin.LastCycleStart, *pin.ExtractorDetails.QtyPerCycle, *pin.ExtractorDetails.CycleTime, ) } else if pin.SchematicID != 0 { // Get the schematic from ESI and cache it schematicInfo := getSchematicsInformation(swaggerclient, pin.SchematicID) fmt.Printf(" Factory - cycle started %s, producing %s, cycles of %ds\n", pin.LastCycleStart, schematicInfo.SchematicName, schematicInfo.CycleTime, ) } else { /* fmt.Printf(" %d %d (%s)\n%v\n", *pin.TypeID, *pin.PinID, pin.LastCycleStart, pin) */ } } } } // SolarSystemInfo - Structure to store and cache the Solar System information from ESI type SolarSystemInfo struct { SolarSystemName string } func getSolarSystemInformation(swaggerclient *ESI.App, solarSystemID int32) SolarSystemInfo { systemName := getCachedData(fmt.Sprintf("%d.name", solarSystemID)) if systemName == "" { scallParams := ESIUniverse.NewGetUniverseSystemsSystemIDParams() scallParams.WithSystemID(solarSystemID) sesiresponse, _ := swaggerclient.Universe.GetUniverseSystemsSystemID(scallParams) solarSystemESIInfo := sesiresponse.Payload var solarSystemInfo SolarSystemInfo solarSystemInfo.SolarSystemName = *solarSystemESIInfo.SolarSystemName putCacheData(fmt.Sprintf("%d.name", solarSystemID), solarSystemInfo.SolarSystemName) return solarSystemInfo } var solarSystemInfo SolarSystemInfo solarSystemInfo.SolarSystemName = systemName return solarSystemInfo } // 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 { schematicName := getCachedData(fmt.Sprintf("%d.name", schematicID)) schematicCycle := getCachedData(fmt.Sprintf("%d.cycle", schematicID)) if schematicName == "" { scallParams := ESIPlanetaryInteraction.NewGetUniverseSchematicsSchematicIDParams() scallParams.WithSchematicID(schematicID) sesiresponse, _ := swaggerclient.PlanetaryInteraction.GetUniverseSchematicsSchematicID(scallParams) schematicsESIInfo := sesiresponse.Payload var schematicInfo SchematicInfo schematicInfo.CycleTime = *schematicsESIInfo.CycleTime schematicInfo.SchematicName = *schematicsESIInfo.SchematicName putCacheData(fmt.Sprintf("%d.name", schematicID), schematicInfo.SchematicName) putCacheData(fmt.Sprintf("%d.cycle", schematicID), fmt.Sprintf("%d", schematicInfo.CycleTime)) return schematicInfo } var schematicInfo SchematicInfo pCycleTime, _ := strconv.ParseInt(schematicCycle, 10, 32) schematicInfo.CycleTime = int32(pCycleTime) schematicInfo.SchematicName = schematicName return schematicInfo } func getNewAuthorizationToken() *oauth2.Token { http.HandleFunc("/", handleLogin) http.HandleFunc("/callback", handleAuthenticationCallback) go func() { log.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("refreshToken", token.RefreshToken) messages <- token } func getCachedData(key string) string { db, err := sql.Open("sqlite3", "./foo.db") if err != nil { log.Fatal(err) } defer db.Close() _, err = db.Exec("CREATE TABLE IF NOT EXISTS properties (id text NOT NULL PRIMARY KEY, value TEXT);") if err != nil { log.Fatal(err) } stmt, err := db.Prepare("SELECT value FROM properties WHERE id = ?") if err != nil { log.Fatal(err) } defer stmt.Close() var response string err = stmt.QueryRow(key).Scan(&response) if err != nil { return "" } return response } func putCacheData(key string, value string) { db, err := sql.Open("sqlite3", "./foo.db") if err != nil { log.Fatal(err) } defer db.Close() _, err = db.Exec("CREATE TABLE IF NOT EXISTS properties (id text NOT NULL PRIMARY KEY, value TEXT);") if err != nil { log.Fatal(err) } tx, err := db.Begin() if err != nil { log.Fatal(err) } stmt, err := tx.Prepare("INSERT OR REPLACE INTO properties(id, value) values(?, ?)") if err != nil { log.Fatal(err) } defer stmt.Close() _, err = stmt.Exec(key, value) if err != nil { log.Fatal(err) } tx.Commit() } func getDatabaseToken() *oauth2.Token { refreshToken := getCachedData("refreshToken") token := new(oauth2.Token) token.RefreshToken = refreshToken token.TokenType = "Bearer" return token }