This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
eve-goclient/main.go

1116 lines
32 KiB
Go

package main
import (
"encoding/json"
"sort"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"runtime/debug"
"io/ioutil"
"net/http"
"errors"
"flag"
"fmt"
"log"
"os"
"github.com/BurntSushi/toml"
"github.com/go-openapi/strfmt"
"github.com/leekchan/accounting"
"github.com/logrusorgru/aurora"
"github.com/syndtr/goleveldb/leveldb"
terminal "github.com/wayneashleyberry/terminal-dimensions"
InternalUtils "./internals"
ESI "git.inf3.xyz/tschwery/eve-goclient.git/client"
ESIModels "git.inf3.xyz/tschwery/eve-goclient.git/models"
ESIClones "git.inf3.xyz/tschwery/eve-goclient.git/client/clones"
ESIIndustry "git.inf3.xyz/tschwery/eve-goclient.git/client/industry"
ESILocation "git.inf3.xyz/tschwery/eve-goclient.git/client/location"
ESIMarket "git.inf3.xyz/tschwery/eve-goclient.git/client/market"
ESIPlanetaryInteraction "git.inf3.xyz/tschwery/eve-goclient.git/client/planetary_interaction"
ESISkills "git.inf3.xyz/tschwery/eve-goclient.git/client/skills"
ESIUniverse "git.inf3.xyz/tschwery/eve-goclient.git/client/universe"
ESIWallet "git.inf3.xyz/tschwery/eve-goclient.git/client/wallet"
httptransport "github.com/go-openapi/runtime/client"
)
// Character - Structure to save the verification data.
type Character struct {
CharacterID int32
CharacterName string
ExpiresOn string
}
type configurationFile struct {
ClientID string
ClientSecret string
}
var (
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")
renewToken = flag.Bool("renewToken", false, "Renew the token even if it is still valid")
)
var ctx = context.Background()
var messages = make(chan *oauth2.Token)
var cache InternalUtils.CacheConn
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)
}
cache.SetCache(cacheDB)
httpConfiguration, cErr := readConfigurationFile()
if cErr != nil {
fmt.Println("Missing configuration file.")
log.Fatal(cErr)
}
cToken := getDatabaseToken()
if cToken == nil || *renewToken {
cToken = getNewAuthorizationToken(httpConfiguration)
}
httpConfiguration.ConnectionToken = cToken
client := InternalUtils.GetDefaultClient(cacheDB, httpConfiguration)
m, err := getCharacterInfo(client)
if err != nil {
log.Println(err)
cToken = getNewAuthorizationToken(httpConfiguration)
httpConfiguration.ConnectionToken = cToken
client = InternalUtils.GetDefaultClient(cacheDB, httpConfiguration)
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)
marketInfoChan := make(chan string)
industryInfoChan := make(chan string)
transactionsInfoChan := make(chan string)
go func() {
charInfoChan <- printCharacterInformation(swaggerclient, m)
close(charInfoChan)
}()
go func() {
cloneInfoChan <- printClonesInformation(swaggerclient, m)
close(cloneInfoChan)
}()
go func() {
planetInfoChan <- printCharacterPlanets(swaggerclient, m)
close(planetInfoChan)
}()
go func() {
marketInfoChan <- printCharacterMarketOrders(swaggerclient, m)
close(marketInfoChan)
}()
go func() {
industryInfoChan <- printCharacterIndustryJobs(swaggerclient, m)
close(industryInfoChan)
}()
go func() {
skillsInfoChan <- printCharacterSkillQueue(swaggerclient, m)
close(skillsInfoChan)
}()
go func() {
transactionsInfoChan <- printCharacterTransactions(swaggerclient, m)
close(transactionsInfoChan)
}()
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)
}
fmt.Printf("\n\nMarket orders\n")
for msg := range marketInfoChan {
fmt.Print(msg)
}
fmt.Printf("\n\nIndustry Jobs\n")
for msg := range industryInfoChan {
fmt.Print(msg)
}
fmt.Printf("\n\nTransactions\n")
for msg := range transactionsInfoChan {
fmt.Print(msg)
}
}
func readConfigurationFile() (*InternalUtils.HTTPConfiguration, error) {
var config configurationFile
if _, err := toml.DecodeFile(*cfgFilePath, &config); err != nil {
log.Println(err)
return nil, err
}
if config.ClientID == "" || config.ClientSecret == "" {
log.Println("Missing ClientID or ClientSecret configuration option in configuration file" + *cfgFilePath + ".")
err := errors.New("Missing ClientID or ClientSecret in configuration file " + *cfgFilePath + ".")
return nil, err
}
var httpConfig InternalUtils.HTTPConfiguration
httpConfig.ClientID = config.ClientID
httpConfig.ClientSecret = config.ClientSecret
return &httpConfig, 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, errRead := ioutil.ReadAll(response.Body)
if errRead != nil {
log.Printf("Read error: %s\n", errRead)
return nil, errRead
}
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(swaggerclient *ESI.App, m *Character) string {
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)
var content string
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()) {
content = content + fmt.Sprintf("✔ %"+maxSkillFormat+"s %d\n",
name,
*skill.FinishedLevel,
)
continue
}
if startDate.Before(time.Now()) {
content = content + 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
}
content = content + 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),
)
}
return content
}
func printCharacterTransactions(swaggerclient *ESI.App, m *Character) string {
callParam := ESIWallet.NewGetCharactersCharacterIDWalletTransactionsParams()
callParam.WithCharacterID(m.CharacterID)
esiresponse, esierr := swaggerclient.Wallet.GetCharactersCharacterIDWalletTransactions(callParam, nil)
if esierr != nil {
fmt.Println("Error while getting the wallet information")
log.Fatalf("Got error on GetCharactersCharacterIDWalletTransactions: %T %s", esierr, esierr)
}
transactions := esiresponse.Payload
ac := accounting.Accounting{Symbol: "", Precision: 2, Thousand: "'"}
var content string
typeIds := make([]int32, 0)
clientIds := make([]int32, 0)
for _, transaction := range transactions {
typeIds = append(typeIds, *transaction.TypeID)
clientIds = append(clientIds, *transaction.ClientID)
}
typeNames := getUniverseNames(swaggerclient, &typeIds)
clientNames := getUniverseNames(swaggerclient, &clientIds)
for _, transaction := range transactions[:20] {
locationName, locationErr := getStructureStationInfo(swaggerclient, *transaction.LocationID)
if locationErr != nil {
fmt.Println("Error while getting the location information")
log.Fatalf("Got error on getStructureStationInfo: %T %s", locationErr, locationErr)
}
status := fmt.Sprint(aurora.Red("✘").Bold())
if *transaction.IsBuy {
status = fmt.Sprint(aurora.Green("✔").Bold())
}
content = content + fmt.Sprintf(" %s %s ISK% 15s,% 7d %s, %s, %s",
status,
time.Time(*transaction.Date).Format(defaultDateFormat),
ac.FormatMoney(*transaction.UnitPrice*float32(*transaction.Quantity)),
*transaction.Quantity,
typeNames[*transaction.TypeID],
locationName.Name,
clientNames[*transaction.ClientID],
)
content = content + "\n"
}
return content
}
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(swaggerclient *ESI.App, m *Character) string {
callParam := ESIWallet.NewGetCharactersCharacterIDWalletParams()
callParam.WithCharacterID(m.CharacterID)
esiresponse, esierr := swaggerclient.Wallet.GetCharactersCharacterIDWallet(callParam, nil)
if esierr != nil {
fmt.Println("Error while getting the wallet information")
log.Fatalf("Got error on GetCharactersCharacterIDWallet: %T %s", esierr, esierr)
}
wallet := esiresponse.Payload
ac := accounting.Accounting{Symbol: "ISK ", Precision: 0, Thousand: "'"}
var content string
content = content + fmt.Sprintf("Name: %s (%d)\n", m.CharacterName, m.CharacterID)
content = content + fmt.Sprintf("Wallet: %s\n", ac.FormatMoney(wallet))
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 != 0 {
itemIds = append(itemIds, position.StationID)
}
universeNames := getUniverseNames(swaggerclient, &itemIds)
stationName := ""
if position.StationID != 0 {
stationName = universeNames[position.StationID]
}
if position.StructureID != 0 {
retName, retErr := getStructureStationInfo(swaggerclient, position.StructureID)
if retErr == nil {
stationName = retName.Name
}
}
content = content + fmt.Sprintf("Currently in %s - %s\n",
universeNames[*position.SolarSystemID],
stationName,
)
return content
}
func printClonesInformation(swaggerclient *ESI.App, m *Character) string {
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)
}
var content string
clones := cloresponse.Payload.JumpClones
for _, clone := range clones {
sInfo, sErr := getStructureStationInfo(swaggerclient, clone.LocationID)
if sErr != nil {
fmt.Printf("Error on structure information read on structure %d\n", clone.LocationID)
log.Fatalf("Got error on getStructureStationInfo: %T %s", sErr, sErr)
}
content = content + 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]
content = content + fmt.Sprintf(" %s, ", implantName)
}
content = content + "\n"
}
return content
}
type structureInfo struct {
ID int64
Name string
SystemID int32
System solarSystemInfo
}
func getStructureStationInfo(swaggerclient *ESI.App, ID int64) (*structureInfo, error) {
locationName := cache.GetCachedDataSub64(ID, "name")
locationSystem := cache.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 ID <= 1000000000000 {
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)
cache.PutCacheDataSub64(sInfo.ID, "name", sInfo.Name)
cache.PutCacheDataIntSub64(sInfo.ID, "system", sInfo.SystemID)
return &sInfo, nil
}
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)
cache.PutCacheDataSub64(sInfo.ID, "name", sInfo.Name)
cache.PutCacheDataIntSub64(sInfo.ID, "system", sInfo.SystemID)
return &sInfo, nil
}
type byPIPinType ESIModels.GetCharactersCharacterIDPlanetsPlanetIDOKBodyPins
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(swaggerclient *ESI.App, m *Character) string {
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
var content string
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)
content = content + 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)
}
content = content + 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)
content = content + fmt.Sprintf(" ✔ Factory ????? cycle, ?????\n")
} else {
content = content + fmt.Sprintf(" ✔ Factory %4ds cycle, %s\n",
schematicInfo.CycleTime,
schematicInfo.SchematicName,
)
}
}
}
}
return content
}
func printCharacterMarketOrders(swaggerclient *ESI.App, m *Character) string {
cloCallParams := ESIMarket.NewGetCharactersCharacterIDOrdersParams()
cloCallParams.WithCharacterID(m.CharacterID)
cloresponse, cloerr := swaggerclient.Market.GetCharactersCharacterIDOrders(cloCallParams, nil)
if cloerr != nil {
fmt.Println("Error while getting the current character market orders.")
log.Fatalf("Got error on GetCharactersCharacterIDOrders: %s", cloerr)
}
ac := accounting.Accounting{Symbol: "", Precision: 2, Thousand: "'"}
var content string
orders := cloresponse.Payload
ptIds := make([]int32, 0, len(orders))
for _, order := range orders {
ptIds = append(ptIds, *order.TypeID)
}
pinNames := getUniverseNames(swaggerclient, &ptIds)
maxWidth, _ := terminal.Width()
// We need to add the length of the colors
maxWidth = maxWidth + uint(len(fmt.Sprint(aurora.Red("")))*3)
lineFormat := fmt.Sprintf("%%.%ds\n", maxWidth)
for _, order := range orders {
expirationDate := time.Time(*order.Issued).Add(time.Duration(*order.Duration) * time.Hour * 24)
remainingAmount := *order.Price * float32(*order.VolumeRemain)
remainingDuration := expirationDate.Sub(time.Now())
buySellStatus := aurora.Green("↟").Bold()
if *order.IsBuyOrder {
buySellStatus = aurora.Magenta("↡").Bold()
}
duration := fmt.Sprintf("%d/%d", remainingDuration/time.Hour/24, *order.Duration)
pctDaysRemaining := float32(remainingDuration/time.Hour/24) / float32(*order.Duration)
if pctDaysRemaining <= 0.25 {
duration = fmt.Sprint(aurora.Red(duration))
} else if pctDaysRemaining <= 0.5 {
duration = fmt.Sprint(aurora.Brown(duration))
} else {
duration = fmt.Sprint(aurora.Green(duration))
}
quantity := fmt.Sprintf("%d/%d", *order.VolumeRemain, *order.VolumeTotal)
pctRemaining := float32(*order.VolumeRemain) / float32(*order.VolumeTotal)
if pctRemaining <= 0.25 {
quantity = fmt.Sprint(aurora.Red(quantity))
} else if pctRemaining <= 0.5 {
quantity = fmt.Sprint(aurora.Brown(quantity))
} else {
quantity = fmt.Sprint(aurora.Green(quantity))
}
sInfo, sErr := getStructureStationInfo(swaggerclient, int64(*order.LocationID))
if sErr != nil {
fmt.Printf("Error on structure information read on structure %d\n", order.LocationID)
log.Fatalf("Got error on getStructureStationInfo: %T %s", sErr, sErr)
}
line := fmt.Sprintf(" %s % 25.25s ISK % 13s ISK % 13s (%s) (%s) %s",
buySellStatus,
pinNames[*order.TypeID],
ac.FormatMoney(*order.Price),
ac.FormatMoney(remainingAmount),
quantity,
duration,
sInfo.Name,
)
content = content + fmt.Sprintf(lineFormat, line)
}
return content
}
func printCharacterIndustryJobs(swaggerclient *ESI.App, m *Character) string {
cloCallParams := ESIIndustry.NewGetCharactersCharacterIDIndustryJobsParams()
cloCallParams.WithCharacterID(m.CharacterID)
cloresponse, cloerr := swaggerclient.Industry.GetCharactersCharacterIDIndustryJobs(cloCallParams, nil)
if cloerr != nil {
fmt.Println("Error while getting the current character industry jobs.")
log.Fatalf("Got error on GetCharactersCharacterIDIndustryJobs: %s", cloerr)
}
ac := accounting.Accounting{Symbol: "", Precision: 0, Thousand: "'"}
jobs := cloresponse.Payload
ptIds := make([]int32, 0, len(jobs))
for _, job := range jobs {
if job.ProductTypeID > 0 {
ptIds = append(ptIds, job.ProductTypeID)
}
}
productNames := getUniverseNames(swaggerclient, &ptIds)
maxWidth, _ := terminal.Width()
// We need to add the length of the colors
maxWidth = maxWidth + uint(len(fmt.Sprint(aurora.Red("")))*1)
lineFormat := fmt.Sprintf("%%.%ds\n", maxWidth)
priresponse, prierr := swaggerclient.Market.GetMarketsPrices(nil)
if prierr != nil {
fmt.Println("Error while getting the current market prices.")
log.Fatalf("Got error on GetMarketsPrices: %s", cloerr)
}
prices := priresponse.Payload
priIds := make(map[int32]float32)
for _, price := range prices {
priIds[*price.TypeID] = price.AdjustedPrice
}
var content string
for _, job := range jobs {
var status string
if *job.Status == "delivered" || *job.Status == "cancelled" {
continue
} else if *job.Status == "active" {
status = "➠"
} else if *job.Status == "paused" {
status = "↟"
} else if *job.Status == "ready" {
status = "✔"
} else {
status = "✘"
}
remainingDuration := time.Time(*job.EndDate).Sub(time.Now())
if remainingDuration.Hours() <= 0 {
status = fmt.Sprint(aurora.Red(status).Bold())
} else if remainingDuration.Hours() <= 72 {
status = fmt.Sprint(aurora.Magenta(status).Bold())
} else {
status = fmt.Sprint(aurora.Green(status).Bold())
}
var durationInfo string
if remainingDuration < 0 {
durationInfo = " "
} else {
durationInfo = formatDuration(remainingDuration, 2)
}
sInfo, sErr := getStructureStationInfo(swaggerclient, int64(*job.FacilityID))
if sErr != nil {
fmt.Printf("Error on structure information read on structure %d\n", *job.FacilityID)
log.Fatalf("Got error on getStructureStationInfo: %T %s", sErr, sErr)
}
totalEstimatedPrice := float32(*job.Runs) * priIds[job.ProductTypeID]
line := fmt.Sprintf(" %s % 5d %- 20.20s ~ISK % 12s %s (%s) %s",
status,
*job.Runs,
productNames[job.ProductTypeID],
ac.FormatMoney(totalEstimatedPrice),
time.Time(*job.EndDate).Format(defaultDateFormat),
durationInfo,
sInfo.Name,
)
content = content + fmt.Sprintf(lineFormat, line)
}
return content
}
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 := cache.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 {
if itemID < 100000000 || itemID > 2099999999 { // cf https://developers.eveonline.com/blog/article/universe-names-update
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)
log.Printf("Error on PostUniverseNames: %s\n", debug.Stack())
} else {
for _, searchResult := range skillNameResp.Payload {
itemName := searchResult.Name
itemID := searchResult.ID
cache.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 := cache.GetCachedDataSub(planetID, "name")
planetType := cache.GetCachedDataIntSub(planetID, "type")
if planetName == "" {
pcallParams := ESIUniverse.NewGetUniversePlanetsPlanetIDParams()
pcallParams.WithPlanetID(planetID)
pesiresponse, pesierror := swaggerclient.Universe.GetUniversePlanetsPlanetID(pcallParams)
if pesierror != nil {
log.Printf("Error on GetUniversePlanetsPlanetID on planet: %v\n", planetID)
log.Printf("Error on GetUniversePlanetsPlanetID: %s\n", pesierror)
}
planetESIInfo := pesiresponse.Payload
var planetInfo planetInformation
planetInfo.PlanetName = *planetESIInfo.Name
planetInfo.PlanetTypeID = *planetESIInfo.TypeID
cache.PutCacheDataSub(planetID, "name", planetInfo.PlanetName)
cache.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 := cache.GetCachedDataSub(solarSystemID, "name")
systemConstellationID := cache.GetCachedDataIntSub(solarSystemID, "constellation")
constellationName := cache.GetCachedDataSub(systemConstellationID, "name")
constellationRegionID := cache.GetCachedDataIntSub(systemConstellationID, "region")
regionName := cache.GetCachedDataSub(constellationRegionID, "name")
if systemName == "" || constellationName == "" || regionName == "" {
scallParams := ESIUniverse.NewGetUniverseSystemsSystemIDParams()
scallParams.WithSystemID(solarSystemID)
sesiresponse, sesierror := swaggerclient.Universe.GetUniverseSystemsSystemID(scallParams)
if sesierror != nil {
log.Printf("Error on GetUniverseSystemsSystemID on system: %v\n", solarSystemID)
log.Printf("Error on GetUniverseSystemsSystemID: %s\n", sesierror)
}
solarSystemESIInfo := sesiresponse.Payload
var ssInfo solarSystemInfo
ssInfo.SolarSystemName = *solarSystemESIInfo.Name
ssInfo.ConstellationID = *solarSystemESIInfo.ConstellationID
ccallParams := ESIUniverse.NewGetUniverseConstellationsConstellationIDParams()
ccallParams.WithConstellationID(ssInfo.ConstellationID)
cesiresponse, cesierror := swaggerclient.Universe.GetUniverseConstellationsConstellationID(ccallParams)
if cesierror != nil {
log.Printf("Error on GetUniverseConstellationsConstellationID on constellation: %v\n", ssInfo.ConstellationID)
log.Printf("Error on GetUniverseConstellationsConstellationID: %s\n", cesierror)
}
constellationESIInfo := cesiresponse.Payload
ssInfo.ConstellationName = *constellationESIInfo.Name
ssInfo.RegionID = *constellationESIInfo.RegionID
rcallParams := ESIUniverse.NewGetUniverseRegionsRegionIDParams()
rcallParams.WithRegionID(ssInfo.RegionID)
resiresponse, resierror := swaggerclient.Universe.GetUniverseRegionsRegionID(rcallParams)
if resierror != nil {
log.Printf("Error on GetUniverseRegionsRegionID on region: %v\n", ssInfo.RegionID)
log.Printf("Error on GetUniverseRegionsRegionID: %s\n", cesierror)
}
regionESIInfo := resiresponse.Payload
ssInfo.RegionName = *regionESIInfo.Name
cache.PutCacheDataSub(solarSystemID, "name", ssInfo.SolarSystemName)
cache.PutCacheDataIntSub(solarSystemID, "constellation", ssInfo.ConstellationID)
cache.PutCacheDataSub(ssInfo.ConstellationID, "name", ssInfo.ConstellationName)
cache.PutCacheDataIntSub(ssInfo.ConstellationID, "region", ssInfo.RegionID)
cache.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 := cache.GetCachedDataSub(schematicID, "name")
schematicCycle := cache.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
cache.PutCacheDataSub(schematicID, "name", schematicInfo.SchematicName)
cache.PutCacheDataIntSub(schematicID, "cycle", schematicInfo.CycleTime)
return &schematicInfo, nil
}
var schematicInfo SchematicInfo
schematicInfo.CycleTime = schematicCycle
schematicInfo.SchematicName = schematicName
return &schematicInfo, nil
}
func getNewAuthorizationToken(httpConfiguration *InternalUtils.HTTPConfiguration) *oauth2.Token {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { handleLogin(w, r, httpConfiguration) })
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, httpConfiguration *InternalUtils.HTTPConfiguration) {
url := InternalUtils.GetTokenURL(httpConfiguration)
log.Printf("URL will be %s\n", url)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleAuthenticationCallback(w http.ResponseWriter, r *http.Request) {
client, token, clientErr := InternalUtils.GetTemporaryClient(r)
if clientErr != nil {
log.Printf("Code exchange failed with '%s'\n", clientErr)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
// No token to pass, we will get one on the second pass
return
}
charInfo, charErr := getCharacterInfo(client)
if charErr != nil {
fmt.Printf("Character read error with '%s'\n", charErr)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
}
fmt.Fprintf(w, "Got token for character %s.\n", charInfo.CharacterName)
fmt.Fprintf(w, "You can now close this navigator tab.\n")
cache.PutCacheData("accessToken", token.AccessToken)
cache.PutCacheData("refreshToken", token.RefreshToken)
messages <- token
}
func getDatabaseToken() *oauth2.Token {
refreshToken := cache.GetCachedData("refreshToken")
//accessToken := cache.GetCachedData("accessToken")
token := new(oauth2.Token)
token.RefreshToken = refreshToken
//token.AccessToken = accessToken
token.TokenType = "Bearer"
return token
}