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
2 changes: 1 addition & 1 deletion src/boost/boost_menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func HandleMenuReactions(s *discordgo.Session, i *discordgo.InteractionCreate) {
},
})
contract.EstimateUpdateTime = time.Now()
go updateEstimatedTime(s, i.ChannelID, contract, false)
go updateEstimatedTime(s, i.ChannelID, contract, false, i.Member.User.ID)
case "want":
message := "**%s** wants at least 1 more token."
contract.Boosters[i.Member.User.ID].TokenRequestFlag = !contract.Boosters[i.Member.User.ID].TokenRequestFlag
Expand Down
8 changes: 4 additions & 4 deletions src/boost/boost_reactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func ReactionAdd(s *discordgo.Session, r *discordgo.MessageReaction) string {
} else {
log.Print("Updating estimated time")
contract.EstimateUpdateTime = time.Now()
go updateEstimatedTime(s, r.ChannelID, contract, true)
go updateEstimatedTime(s, r.ChannelID, contract, true, r.UserID)
}
}
case "🐓":
Expand Down Expand Up @@ -245,9 +245,9 @@ func ReactionAdd(s *discordgo.Session, r *discordgo.MessageReaction) string {
return returnVal
}

func updateEstimatedTime(s *discordgo.Session, channelID string, contract *Contract, displayMsg bool) {
func updateEstimatedTime(s *discordgo.Session, channelID string, contract *Contract, displayMsg bool, userID string) {
if !displayMsg {
startTime, contractDurationSeconds, err := track.DownloadCoopStatusTracker(contract.ContractID, contract.CoopID)
startTime, contractDurationSeconds, err := track.DownloadCoopStatusTracker(contract.ContractID, contract.CoopID, userID)
if err == nil {
contract.StartTime = startTime
contract.EstimatedDuration = time.Duration(contractDurationSeconds) * time.Second
Expand All @@ -260,7 +260,7 @@ func updateEstimatedTime(s *discordgo.Session, channelID string, contract *Contr
data.Content = "⏱️ reaction received, updating contract duration."
data.Flags = discordgo.MessageFlagsEphemeral
msg, msgErr := s.ChannelMessageSendComplex(channelID, &data)
startTime, contractDurationSeconds, err := track.DownloadCoopStatusTracker(contract.ContractID, contract.CoopID)
startTime, contractDurationSeconds, err := track.DownloadCoopStatusTracker(contract.ContractID, contract.CoopID, userID)
if err == nil {
Comment on lines 248 to 264
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.

updateEstimatedTime is launched in a goroutine and now calls track.DownloadCoopStatusTracker, which (after this PR) performs a farmerstate lookup to get "encrypted_ei_id". farmerstate.GetMiscSettingString can mutate shared maps (newFarmer / map init), so doing this from background goroutines increases the chance of concurrent map access panics. Safer pattern: resolve the encrypted EI ID before starting the goroutine and pass the eeidOverride string through (and/or update DownloadCoopStatusTracker to take eeidOverride directly).

Copilot uses AI. Check for mistakes.
if msgErr == nil {
_ = s.ChannelMessageDelete(msg.ChannelID, msg.ID)
Expand Down
2 changes: 1 addition & 1 deletion src/boost/boost_speedrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ func speedrunReactions(s *discordgo.Session, r *discordgo.MessageReaction, contr
} else {
log.Print("Updating estimated time")
contract.EstimateUpdateTime = time.Now()
go updateEstimatedTime(s, r.ChannelID, contract, true)
go updateEstimatedTime(s, r.ChannelID, contract, true, r.UserID)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/boost/contract_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,8 @@ func ContractReport(

// Get coop status; validate response
// Using GetCoopStatusForCompletedContracts to ensure we cache completed contract data locally
coopStatus, nowTime, _, err := ei.GetCoopStatusForCompletedContracts(contractID, coopID, "")
callerEIID := farmerstate.GetMiscSettingString(callerUserID, "encrypted_ei_id")
coopStatus, nowTime, _, err := ei.GetCoopStatusForCompletedContracts(contractID, coopID, callerEIID)
if err != nil {
return fmt.Errorf("%w: %v", ErrCoopStatusFetch, err)
}
Expand Down
4 changes: 3 additions & 1 deletion src/boost/estimate_scores.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ func runCsEstimate(s *discordgo.Session, i *discordgo.InteractionCreate, p csEst
}

// Get coopStatus with the given contractID and coopID
str, fields, contractScore := DownloadCoopStatusTeamwork(contractID, coopID, true)
userID := bottools.GetInteractionUserID(i)
eiID := farmerstate.GetMiscSettingString(userID, "encrypted_ei_id")
str, fields, contractScore := DownloadCoopStatusTeamwork(contractID, coopID, true, eiID)
if fields == nil || strings.HasSuffix(str, "no such file or directory") || strings.HasPrefix(str, "No grade found") {
_, sendErr := s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
Flags: p.flags,
Expand Down
8 changes: 5 additions & 3 deletions src/boost/stones.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ func HandleStonesCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
coopID = strings.ToLower(contract.CoopID)
}

s1, urls, tiles := DownloadCoopStatusStones(contractID, coopID, details, soloName, useBuffHistory)
eiID := farmerstate.GetMiscSettingString(userID, "encrypted_ei_id")
s1, urls, tiles := DownloadCoopStatusStones(contractID, coopID, details, soloName, useBuffHistory, eiID)

contract := FindContractByIDs(contractID, coopID)
if contract != nil {
Expand All @@ -166,6 +167,7 @@ func HandleStonesCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Fill in our calling parameters
cache.contractID = contractID
cache.coopID = coopID
cache.eiID = eiID
cache.details = details
cache.soloName = soloName
cache.private = privateReply
Expand Down Expand Up @@ -247,11 +249,11 @@ type artifactSet struct {
}

// DownloadCoopStatusStones will download the coop status for a given contract and coop ID
func DownloadCoopStatusStones(contractID string, coopID string, details bool, soloName string, useBuffHistory bool) (string, string, []*discordgo.MessageEmbedField) {
func DownloadCoopStatusStones(contractID string, coopID string, details bool, soloName string, useBuffHistory bool, eeidOverride string) (string, string, []*discordgo.MessageEmbedField) {
var builderURL strings.Builder
var field []*discordgo.MessageEmbedField

coopStatus, _, dataTimestampStr, err := ei.GetCoopStatus(contractID, coopID, "")
coopStatus, _, dataTimestampStr, err := ei.GetCoopStatus(contractID, coopID, eeidOverride)
if err != nil {
return err.Error(), "", field
}
Expand Down
4 changes: 3 additions & 1 deletion src/boost/stones_pages.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,14 @@ func sendStonesPage(s *discordgo.Session, i *discordgo.InteractionCreate, newMes

if exists && (refresh || cache.expirationTimestamp.Before(time.Now())) {

s1, urls, tiles := DownloadCoopStatusStones(cache.contractID, cache.coopID, cache.details, cache.soloName, cache.useBuffHistory)
s1, urls, tiles := DownloadCoopStatusStones(cache.contractID, cache.coopID, cache.details, cache.soloName, cache.useBuffHistory, cache.eiID)
newCache := buildStonesCache(s1, urls, tiles)

newCache.private = cache.private
newCache.xid = cache.xid
newCache.contractID = cache.contractID
newCache.coopID = cache.coopID
newCache.eiID = cache.eiID
newCache.details = cache.details
newCache.soloName = cache.soloName
newCache.useBuffHistory = cache.useBuffHistory
Expand Down Expand Up @@ -359,6 +360,7 @@ type stonesCache struct {
expirationTimestamp time.Time
contractID string
coopID string
eiID string
details bool
soloName string
useBuffHistory bool
Expand Down
8 changes: 5 additions & 3 deletions src/boost/teamwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,9 @@ func HandleTeamworkEvalCommand(s *discordgo.Session, i *discordgo.InteractionCre
coopID = strings.ToLower(contract.CoopID)
}

eiID := farmerstate.GetMiscSettingString(userID, "encrypted_ei_id")
var str string
str, fields, _ := DownloadCoopStatusTeamwork(contractID, coopID, true)
str, fields, _ := DownloadCoopStatusTeamwork(contractID, coopID, true, eiID)
if fields == nil || strings.HasSuffix(str, "no such file or directory") || strings.HasPrefix(str, "No grade found") {
// Trim output to 3500 characters if needed
trimmedStr := str
Expand All @@ -198,6 +199,7 @@ func HandleTeamworkEvalCommand(s *discordgo.Session, i *discordgo.InteractionCre
// Fill in our calling parameters
cache.contractID = contractID
cache.coopID = coopID
cache.eiID = eiID
cache.public = publicReply
cache.showScores = scoresFirst
if eggign != "" {
Expand All @@ -224,7 +226,7 @@ func HandleTeamworkEvalCommand(s *discordgo.Session, i *discordgo.InteractionCre
}

// DownloadCoopStatusTeamwork will download the coop status for a given contract and coop ID
func DownloadCoopStatusTeamwork(contractID string, coopID string, setContractEstimate bool) (string, map[string][]TeamworkOutputData, ContractScore) {
func DownloadCoopStatusTeamwork(contractID string, coopID string, setContractEstimate bool, eeidOverride string) (string, map[string][]TeamworkOutputData, ContractScore) {
var dataTimestampStr string
var nowTime time.Time

Expand Down Expand Up @@ -275,7 +277,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, eeidOverride)
if err != nil {
return err.Error(), nil, ContractScore{}
}
Expand Down
4 changes: 3 additions & 1 deletion src/boost/teamwork_pages.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type teamworkCache struct {
expirationTimestamp time.Time
contractID string
coopID string
eiID string
public bool
names []string
fields map[string][]TeamworkOutputData
Expand Down Expand Up @@ -91,14 +92,15 @@ func sendTeamworkPage(s *discordgo.Session, i *discordgo.InteractionCreate, newM

if exists && (refresh || cache.expirationTimestamp.Before(time.Now())) {

s1, fields, _ := DownloadCoopStatusTeamwork(cache.contractID, cache.coopID, true)
s1, fields, _ := DownloadCoopStatusTeamwork(cache.contractID, cache.coopID, true, cache.eiID)
newCache := buildTeamworkCache(s1, fields)

newCache.public = cache.public
newCache.previousPage = cache.previousPage
newCache.xid = cache.xid
newCache.contractID = cache.contractID
newCache.coopID = cache.coopID
newCache.eiID = cache.eiID
newCache.page = cache.page
newCache.showScores = cache.showScores
if refresh {
Expand Down
8 changes: 5 additions & 3 deletions src/track/track_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/bwmarrin/discordgo"
"github.com/mkmccarty/TokenTimeBoostBot/src/ei"
"github.com/mkmccarty/TokenTimeBoostBot/src/farmerstate"
)

// HandleTrackerRefresh will call the API and update the start time and duration
Expand Down Expand Up @@ -37,7 +38,7 @@ func HandleTrackerRefresh(s *discordgo.Session, i *discordgo.InteractionCreate)
// Update the tracker with new values
t, err := getTrack(userID, name)
if err == nil {
startTime, contractDurationSeconds, err := DownloadCoopStatusTracker(t.ContractID, t.CoopID)
startTime, contractDurationSeconds, err := DownloadCoopStatusTracker(t.ContractID, t.CoopID, userID)
if err != nil {
errorStr := fmt.Sprintf("Error: %s, check your coop-id", err)
_, _ = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{
Expand All @@ -60,15 +61,16 @@ func HandleTrackerRefresh(s *discordgo.Session, i *discordgo.InteractionCreate)
}

// DownloadCoopStatusTracker will download the coop status for a given contract and coop ID
func DownloadCoopStatusTracker(contractID string, coopID string) (time.Time, float64, error) {
func DownloadCoopStatusTracker(contractID string, coopID string, userID string) (time.Time, float64, error) {
nowTime := time.Now()

eiContract := ei.EggIncContractsAll[contractID]
if eiContract.ID == "" {
return time.Time{}, 0, fmt.Errorf("invalid contract ID")
}

coopStatus, _, _, err := ei.GetCoopStatus(contractID, coopID, "")
eiID := farmerstate.GetMiscSettingString(userID, "encrypted_ei_id")
coopStatus, _, _, err := ei.GetCoopStatus(contractID, coopID, eiID)
Comment on lines +64 to +73
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.

DownloadCoopStatusTracker now accepts a Discord userID and reaches into farmerstate to fetch "encrypted_ei_id" internally. This couples the track package to farmerstate and makes the function harder to reuse/test; it also hides a potentially mutating map access (newFarmer / map init) behind what looks like a pure API call. Consider changing this to accept an eeidOverride (encrypted EI ID) like the other DownloadCoopStatus* helpers, and do the farmerstate lookup at the call site (where you already have user context).

Copilot uses AI. Check for mistakes.
if err != nil {
return time.Time{}, 0, err
}
Expand Down
Loading