package main import ( "context" "errors" "fmt" "os" "time" "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct type App struct { ctx context.Context ssi *ESISSO } // NewApp creates a new App application struct func NewApp() *App { return &App{} } // startup is called when the app starts. The context is saved // so we can call the runtime methods func (a *App) startup(ctx context.Context) { a.ctx = ctx clientID := os.Getenv("EVE_SSO_CLIENT_ID") if clientID == "" { clientID = "77c5adb91e46459b874204ceeedb459f" } redirectURI := os.Getenv("EVE_SSO_REDIRECT_URI") if redirectURI == "" { redirectURI = "http://localhost:8080/callback" } // Add location read scope so we can fetch character locations a.ssi = NewESISSO(clientID, redirectURI, []string{ "esi-location.read_location.v1", "esi-location.read_ship_type.v1", "esi-mail.organize_mail.v1", "esi-mail.read_mail.v1", "esi-mail.send_mail.v1", "esi-skills.read_skills.v1", "esi-skills.read_skillqueue.v1", "esi-wallet.read_character_wallet.v1", "esi-wallet.read_corporation_wallet.v1", "esi-characters.read_contacts.v1", "esi-killmails.read_killmails.v1", "esi-assets.read_assets.v1", "esi-planets.manage_planets.v1", "esi-ui.write_waypoint.v1", "esi-characters.write_contacts.v1", "esi-markets.structure_markets.v1", "esi-characters.read_loyalty.v1", "esi-characters.read_chat_channels.v1", "esi-characters.read_medals.v1", "esi-characters.read_standings.v1", "esi-characters.read_agents_research.v1", "esi-industry.read_character_jobs.v1", "esi-markets.read_character_orders.v1", "esi-characters.read_blueprints.v1", "esi-characters.read_corporation_roles.v1", "esi-location.read_online.v1", "esi-characters.read_fatigue.v1", "esi-killmails.read_corporation_killmails.v1", "esi-wallet.read_corporation_wallets.v1", "esi-characters.read_notifications.v1", "esi-assets.read_corporation_assets.v1", "esi-industry.read_corporation_jobs.v1", "esi-markets.read_corporation_orders.v1", "esi-industry.read_character_mining.v1", "esi-industry.read_corporation_mining.v1", "esi-planets.read_customs_offices.v1", "esi-characters.read_titles.v1", "esi-characters.read_fw_stats.v1", }) } // Greet returns a greeting for the given name func (a *App) Greet(name string) string { return fmt.Sprintf("Hello %s, It's show time!", name) } // StartESILogin begins the PKCE SSO flow and opens a browser to the EVE login page func (a *App) StartESILogin() (string, error) { if a.ssi == nil { return "", errors.New("ESI not initialised") } url, err := a.ssi.BuildAuthorizeURL() if err != nil { return "", err } if err := a.ssi.StartCallbackServerAsync(); err != nil { return "", err } runtime.BrowserOpenURL(a.ctx, url) return url, nil } func (a *App) ESILoginStatus() string { if a.ssi == nil { return "not initialised" } st := a.ssi.Status() if st.LoggedIn { return fmt.Sprintf("logged in as %s (%d)", st.CharacterName, st.CharacterID) } return "not logged in" } func (a *App) ESILoggedIn() bool { if a.ssi == nil { return false } return a.ssi.Status().LoggedIn } func (a *App) SetDestinationForAll(systemName string, clearOthers bool, addToBeginning bool) error { if a.ssi == nil { return errors.New("ESI not initialised") } id, err := a.ssi.ResolveSystemIDByName(a.ctx, systemName) if err != nil { return err } return a.ssi.PostWaypointForAll(id, clearOthers, addToBeginning) } func (a *App) AddWaypointForAllByName(systemName string, addToBeginning bool) error { if a.ssi == nil { return errors.New("ESI not initialised") } id, err := a.ssi.ResolveSystemIDByName(a.ctx, systemName) if err != nil { return err } return a.ssi.PostWaypointForAll(id, false, addToBeginning) } // PostRouteForAllByNames posts a full route: via names (in order) then destination at the end (clearing first) func (a *App) PostRouteForAllByNames(destination string, vias []string) error { if a.ssi == nil { return errors.New("ESI not initialised") } ids, err := a.ssi.ResolveSystemIDsByNames(a.ctx, append(vias, destination)) if err != nil { return err } viaIDs := ids[:len(vias)] destID := ids[len(vias)] return a.ssi.PostRouteForAll(destID, viaIDs) } func (a *App) ListCharacters() ([]CharacterInfo, error) { if a.ssi == nil || a.ssi.db == nil { return nil, errors.New("ESI not initialised") } var tokens []ESIToken if err := a.ssi.db.Find(&tokens).Error; err != nil { return nil, err } list := make([]CharacterInfo, 0, len(tokens)) for _, t := range tokens { list = append(list, CharacterInfo{CharacterID: t.CharacterID, CharacterName: t.CharacterName, WaypointEnabled: t.WaypointEnabled}) } return list, nil } // GetCharacterLocations exposes current locations for all characters func (a *App) GetCharacterLocations() ([]CharacterLocation, error) { if a.ssi == nil { return nil, errors.New("ESI not initialised") } ctx, cancel := context.WithTimeout(a.ctx, 6*time.Second) defer cancel() return a.ssi.GetCharacterLocations(ctx) } // ToggleCharacterWaypointEnabled toggles waypoint enabled status for a character func (a *App) ToggleCharacterWaypointEnabled(characterID int64) error { if a.ssi == nil { return errors.New("ESI not initialised") } return a.ssi.ToggleCharacterWaypointEnabled(characterID) } // GetSystemJumps fetches system jump statistics from ESI func (a *App) GetSystemJumps() ([]SystemJumps, error) { fmt.Printf("🔍 App.GetSystemJumps() called - this should ONLY happen when toggle is ON!\n") if a.ssi == nil { return nil, errors.New("ESI not initialised") } ctx, cancel := context.WithTimeout(a.ctx, 15*time.Second) defer cancel() return a.ssi.GetSystemJumps(ctx) } // GetSystemKills fetches system kill statistics from ESI func (a *App) GetSystemKills() ([]SystemKills, error) { fmt.Printf("🔍 App.GetSystemKills() called - this should ONLY happen when toggle is ON!\n") if a.ssi == nil { return nil, errors.New("ESI not initialised") } ctx, cancel := context.WithTimeout(a.ctx, 15*time.Second) defer cancel() return a.ssi.GetSystemKills(ctx) } // ResolveSystemIDByName resolves a system name to its ID func (a *App) ResolveSystemIDByName(systemName string) (int64, error) { fmt.Printf("🔍 App.ResolveSystemIDByName() called for system: %s\n", systemName) if a.ssi == nil { return 0, errors.New("ESI not initialised") } ctx, cancel := context.WithTimeout(a.ctx, 5*time.Second) defer cancel() return a.ssi.ResolveSystemIDByName(ctx, systemName) } // SystemRegion holds system + region names from local DB type SystemRegion struct { System string `json:"system"` Region string `json:"region"` } // ListSystemsWithRegions returns all solar system names and their regions from the local SQLite DB func (a *App) ListSystemsWithRegions() ([]SystemRegion, error) { if a.ssi == nil || a.ssi.db == nil { return nil, errors.New("db not initialised") } var rows []SystemRegion // mapSolarSystems has regionID; mapRegions has regionName q := `SELECT s.solarSystemName AS system, r.regionName AS region FROM mapSolarSystems s JOIN mapRegions r ON r.regionID = s.regionID` if err := a.ssi.db.Raw(q).Scan(&rows).Error; err != nil { return nil, err } return rows, nil }