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
13 changes: 13 additions & 0 deletions src/boost/boost_button_reactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ func buttonReactionRunChickens(client dc.Client, contract *Contract, cUserID str

go func() {
client := client
sendSuccess := false
for _, location := range contract.Location {
contract.mutex.Lock()
components, _ := buildCRMessageComponents(contract, location.RoleMention)
Expand All @@ -418,6 +419,7 @@ func buttonReactionRunChickens(client dc.Client, contract *Contract, cUserID str
continue
}

sendSuccess = true
contract.mutex.Lock()
setChickenRunMessageID(contract, location.ChannelID, newMsg.ID)
contract.CRNoticeCount++
Expand All @@ -438,6 +440,17 @@ func buttonReactionRunChickens(client dc.Client, contract *Contract, cUserID str
}
}
}

if !sendSuccess {
// Rollback RunChickensTime so the user isn't permanently locked out of retrying
contract.mutex.Lock()
if booster := contract.Boosters[userID]; booster != nil {
booster.RunChickensTime = time.Time{}
}
contract.mutex.Unlock()
} else {
saveData(contract.ContractHash)
}
}()
str = "You've asked for Chicken Runs, now what...\n...\nMaybe.. check on your habs and gusset?\nI'm sure you've already forced a game sync so no need to remind about that."
return true, str
Expand Down
48 changes: 48 additions & 0 deletions src/boost/chicken_run_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package boost

import (
"errors"
"slices"
"strings"
"testing"
"time"

"github.com/mkmccarty/TokenTimeBoostBot/src/dc"
"github.com/mkmccarty/TokenTimeBoostBot/src/dc/dctest"
)

func TestRanCoopAndBuildChickenRunLists(t *testing.T) {
Expand Down Expand Up @@ -199,3 +201,49 @@ func TestBuildCRMessageComponentsCompleted(t *testing.T) {
t.Errorf("expected completion message with Player1, got %q", textDisplay.Content)
}
}

func TestButtonReactionRunChickensSendFailureRollback(t *testing.T) {
c := &Contract{
ContractHash: "test-hash-rollback",
Order: []string{"user1", "user2"},
Location: []*LocationData{
{
GuildID: "guild1",
ChannelID: "channel1",
},
},
CRMessageIDs: make(map[string]string),
Boosters: map[string]*Booster{
"user1": {
UserID: "user1",
Nick: "Player1",
BoostState: BoostStateBoosted,
},
"user2": {
UserID: "user2",
Nick: "Player2",
BoostState: BoostStateBoosted,
},
},
}

client := dctest.New()
client.SendErr = errors.New("failed to send CR message")

// Trigger buttonReactionRunChickens
ok, _ := buttonReactionRunChickens(client, c, "user1")
if !ok {
t.Fatalf("expected buttonReactionRunChickens to return true")
}

// Wait for goroutine to finish
time.Sleep(50 * time.Millisecond)

c.mutex.Lock()
runTime := c.Boosters["user1"].RunChickensTime
c.mutex.Unlock()

if !runTime.IsZero() {
t.Errorf("expected RunChickensTime to be rolled back to zero on send error, got %v", runTime)
}
}
13 changes: 12 additions & 1 deletion src/dc/client_disgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package dc

import (
"context"
"errors"

"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/gateway"
"github.com/disgoorg/disgo/rest"
"github.com/disgoorg/snowflake/v2"
)

Expand Down Expand Up @@ -52,7 +54,16 @@ func (c *disgoClient) SendMessage(channelID string, m Message) (*MessageRef, err
}
msg, err := c.bot.Rest.CreateMessage(ids[0], m.toMessageCreate())
if err != nil {
return nil, wrapAPIError(err)
var restErr *rest.Error
if errors.As(err, &restErr) && restErr.Code == ErrCodeThreadArchived {
unarchived := false
if _, updateErr := c.bot.Rest.UpdateChannel(ids[0], discord.GuildThreadUpdate{Archived: &unarchived}); updateErr == nil {
msg, err = c.bot.Rest.CreateMessage(ids[0], m.toMessageCreate())
}
}
if err != nil {
return nil, wrapAPIError(err)
}
}
return messageRefFrom(msg), nil
}
Expand Down
10 changes: 10 additions & 0 deletions src/dc/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const (
// ErrCodeMissingPermissions means the bot can see the channel but is not
// allowed the action it attempted.
ErrCodeMissingPermissions = 50013
// ErrCodeThreadArchived means the thread is archived and must be unarchived
// before messages can be sent to it.
ErrCodeThreadArchived = 50083
)

// APIError is a rejected Discord REST call. Code is Discord's own error code
Expand Down Expand Up @@ -96,3 +99,10 @@ func IsUnknownChannel(err error) bool {
}
return apiErr.Code == ErrCodeUnknownChannel || apiErr.StatusCode == 404
}

// IsThreadArchived reports whether err is Discord refusing a message send
// because the target thread is archived.
func IsThreadArchived(err error) bool {
apiErr, ok := AsAPIError(err)
return ok && apiErr.Code == ErrCodeThreadArchived
}
12 changes: 12 additions & 0 deletions src/dc/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,15 @@ func TestIsUnknownChannel(t *testing.T) {
t.Fatal("missing permissions is not unknown channel")
}
}

func TestIsThreadArchived(t *testing.T) {
if !IsThreadArchived(restError(400, ErrCodeThreadArchived, "Thread is archived")) {
t.Fatal("expected thread is archived")
}
if IsThreadArchived(restError(404, ErrCodeUnknownChannel, "Unknown Channel")) {
t.Fatal("unknown channel is not thread archived")
}
if IsThreadArchived(errors.New("nope")) {
t.Fatal("a plain error is not thread archived")
}
}
Loading