Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ func init() {
// Load status messages
ei.LoadStatusMessages(statusMessagesFileName)

// Wire the coop status fix check to avoid import cycle in ei package
ei.CoopStatusFixEnabled = func() bool {
return guildstate.GetGuildSettingString("DEFAULT", "coop_status_fix") == "1"
}

// Read application parameters
flag.Parse()

Expand Down
2 changes: 1 addition & 1 deletion src/boost/contract_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ func ContractReport(

// Get coop status; validate response
// Using GetCoopStatusForCompletedContracts to ensure we cache completed contract data locally
coopStatus, nowTime, _, err := ei.GetCoopStatusForCompletedContracts(contractID, coopID)
coopStatus, nowTime, _, err := ei.GetCoopStatusForCompletedContracts(contractID, coopID, "")
if err != nil {
return fmt.Errorf("%w: %v", ErrCoopStatusFetch, err)
}
Expand Down
3 changes: 2 additions & 1 deletion src/boost/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ func printArchivedContracts(userID string, archive []*ei.LocalContract, percent
var components []discordgo.MessageComponent
tvalFooterMessage := false
eiUserName := farmerstate.GetMiscSettingString(userID, "ei_ign")
eiID := farmerstate.GetMiscSettingString(userID, "encrypted_ei_id")
divider := true
spacing := discordgo.SeparatorSpacingSizeSmall
builder := strings.Builder{}
Expand Down Expand Up @@ -425,7 +426,7 @@ func printArchivedContracts(userID string, archive []*ei.LocalContract, percent

log.Printf("Evaluating contract %s coop %s for user %s\n", contractID, coopID, eiUserName)
if coopID != "[solo]" {
coopStatus, _, _, err := ei.GetCoopStatusForCompletedContracts(contractID, a.GetCoopIdentifier())
coopStatus, _, _, err := ei.GetCoopStatusForCompletedContracts(contractID, a.GetCoopIdentifier(), eiID)
if err == nil {
builder.Reset()
for _, c := range coopStatus.GetContributors() {
Expand Down
2 changes: 1 addition & 1 deletion src/boost/stones.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ func DownloadCoopStatusStones(contractID string, coopID string, details bool, so
var builderURL strings.Builder
var field []*discordgo.MessageEmbedField

coopStatus, _, dataTimestampStr, err := ei.GetCoopStatus(contractID, coopID)
coopStatus, _, dataTimestampStr, err := ei.GetCoopStatus(contractID, coopID, "")
if err != nil {
return err.Error(), "", field
}
Expand Down
2 changes: 1 addition & 1 deletion src/boost/teamwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ func DownloadCoopStatusTeamwork(contractID string, coopID string, setContractEst
return fmt.Sprintf("Filenames:\n%s", strings.Join(fileNames, "\n")), nil, ContractScore{}
}

coopStatus, nowTime, dataTimestampStr, err := ei.GetCoopStatus(contractID, coopID)
coopStatus, nowTime, dataTimestampStr, err := ei.GetCoopStatus(contractID, coopID, "")
if err != nil {
return err.Error(), nil, ContractScore{}
}
Expand Down
17 changes: 15 additions & 2 deletions src/ei/coop_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,25 @@ type eiData struct {
var (
// Contracts is a map of contracts and is saved to disk
eiDatas map[string]*eiData

// CoopStatusFixEnabled is a callback set from outside the ei package (to avoid import
// cycles) that returns true when the alternate coop_status endpoint and eeid override
// should be used. It is wired up in main.
CoopStatusFixEnabled func() bool
)

func init() {
eiDatas = make(map[string]*eiData)
}

// GetCoopStatus retrieves the coop status for a given contract and coop
func GetCoopStatus(contractID string, coopID string) (*ContractCoopStatusResponse, time.Time, string, error) {
func GetCoopStatus(contractID string, coopID string, eeidOverride string) (*ContractCoopStatusResponse, time.Time, string, error) {
eggIncID := config.EIUserIDBasic
reqURL := "https://www.auxbrain.com/ei/coop_status_bot"
if eeidOverride != "" && CoopStatusFixEnabled != nil && CoopStatusFixEnabled() {
eggIncID = DecryptEID(eeidOverride)
reqURL = "https://www.auxbrain.com/ei/coop_status"
}
Comment on lines +45 to +51
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When eeidOverride is provided and the fix is enabled, this switches to the user-authenticated coop_status endpoint but still uses the same in-memory cache key / file cache scheme (keyed only by contractID+coopID later in the function). That can let a response fetched with one user’s Egg Inc ID be served from cache to other callers (or to callers using the bot endpoint), effectively bypassing the endpoint’s authorization. Consider either including the effective Egg Inc ID + endpoint in the cache key / filename, or disabling cache reads/writes when using eeidOverride.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +51
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DecryptEID returns an empty string on decode/decrypt failure, but the code will still proceed and send a request with UserId="" when eeidOverride is set. That likely yields confusing Unauthorized errors and can also poison caches with a response keyed only by contract+coop. Consider validating the decrypted ID (non-empty, looks like an EI* ID) and either falling back to the bot ID/endpoint or returning a clear error.

Copilot uses AI. Check for mistakes.
enc := base64.StdEncoding
timestamp := time.Now()

Expand Down Expand Up @@ -205,9 +214,13 @@ func ClearCoopStatusCachedData() {

// GetCoopStatusForCompletedContracts retrieves the coop status for a given contract and coop, but is intended for completed contracts
// This saves the data in compressed form without a timestamp in the filename
func GetCoopStatusForCompletedContracts(contractID string, coopID string) (*ContractCoopStatusResponse, time.Time, string, error) {
func GetCoopStatusForCompletedContracts(contractID string, coopID string, eeidOverride string) (*ContractCoopStatusResponse, time.Time, string, error) {
eggIncID := config.EIUserIDBasic
reqURL := "https://www.auxbrain.com/ei/coop_status_bot"
if eeidOverride != "" && CoopStatusFixEnabled != nil && CoopStatusFixEnabled() {
eggIncID = DecryptEID(eeidOverride)
reqURL = "https://www.auxbrain.com/ei/coop_status"
}
Comment on lines +217 to +223
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pb-completed on-disk cache is keyed only by contractID+coopID, but with eeidOverride enabled this function may fetch via the user-authenticated coop_status endpoint. Persisting that response to a shared filename allows later callers without the same Egg Inc ID (or without any ID) to read the cached data and bypass authorization. Consider namespacing the completed-cache filename by effective Egg Inc ID / endpoint, or skipping disk cache when eeidOverride is used.

Copilot uses AI. Check for mistakes.
enc := base64.StdEncoding
timestamp := time.Now()

Expand Down
20 changes: 9 additions & 11 deletions src/guildstate/slashcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,21 +312,19 @@ func GetGuildSettingsForGuild(s *discordgo.Session, i *discordgo.InteractionCrea
for _, key := range keys {
value := guild.MiscSettingsString[key]
items := splitCSV(value)
fmt.Fprintf(&builder, "- %s = %s\n", key, value)
if len(items) > 1 {
fmt.Fprintf(&builder, "- %s (%d items)\n", key, len(items))
fmt.Fprintf(&builder, " - parsed items (%d):\n", len(items))
for _, item := range items {
details := getSnowflakeDetails(s, guildID, item)
if len(details) == 1 {
fmt.Fprintf(&builder, " - %s\n", details[0])
} else {
fmt.Fprintf(&builder, " - %s\n", item)
fmt.Fprintf(&builder, " - %s\n", item)
for _, detail := range getSnowflakeDetails(s, guildID, item) {
fmt.Fprintf(&builder, " - resolved: %s\n", detail)
}
}
} else {
fmt.Fprintf(&builder, "- %s = %s\n", key, value)
for _, detail := range getSnowflakeDetails(s, guildID, value) {
fmt.Fprintf(&builder, " - resolved: %s\n", detail)
}
continue
}
for _, detail := range getSnowflakeDetails(s, guildID, value) {
fmt.Fprintf(&builder, " - resolved: %s\n", detail)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/track/track_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func DownloadCoopStatusTracker(contractID string, coopID string) (time.Time, flo
return time.Time{}, 0, fmt.Errorf("invalid contract ID")
}

coopStatus, _, _, err := ei.GetCoopStatus(contractID, coopID)
coopStatus, _, _, err := ei.GetCoopStatus(contractID, coopID, "")
if err != nil {
return time.Time{}, 0, err
}
Expand Down
Loading