437 lines
12 KiB
Go
437 lines
12 KiB
Go
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 := getCharacterInfo(client)
|
|
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 {
|
|
|
|
req, _ := http.NewRequest("GET", "https://login.eveonline.com/oauth/verify", nil)
|
|
response, errDo := client.Do(req)
|
|
if errDo != nil {
|
|
fmt.Printf("Request error '%s'\n", errDo)
|
|
return nil
|
|
}
|
|
|
|
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)
|
|
return nil
|
|
}
|
|
|
|
return &m
|
|
}
|
|
|
|
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(" %s - 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
|
|
}
|