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 }