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
24 changes: 21 additions & 3 deletions src/boost/boost.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,14 +502,18 @@ func isOldBoostListMessage(contract *Contract, channelID string, messageID strin
if contract == nil || channelID == "" || messageID == "" {
return false
}
if contract.CRMessageIDs != nil {
for _, crMsgID := range contract.CRMessageIDs {
if crMsgID == messageID {
return false
}
}
}
for _, loc := range contract.Location {
if loc.ChannelID == channelID {
if messageID == loc.ListMsgID || messageID == loc.ReactionID {
return false
}
if contract.CRMessageIDs != nil && contract.CRMessageIDs[channelID] == messageID {
return false
}
return slices.Contains(loc.MessageIDs, messageID)
}
}
Expand All @@ -521,6 +525,20 @@ func cleanupOldBoostListMessage(client dc.Client, contract *Contract, channelID
if client == nil || contract == nil || channelID == "" || messageID == "" {
return
}
if contract.CRMessageIDs != nil {
for _, crMsgID := range contract.CRMessageIDs {
if crMsgID == messageID {
log.Printf("cleanupOldBoostListMessage: refused to delete chicken run message %s in channel %s for contract %s",
messageID, channelID, contract.ContractHash)
return
}
}
}
if loc := contract.findLocation(channelID); loc != nil {
if loc.ListMsgID == messageID || loc.ReactionID == messageID {
return
}
}
log.Printf("cleanupOldBoostListMessage: deleting stale boost list message %s in channel %s for contract %s",
messageID, channelID, contract.ContractHash)
_ = client.DeleteMessage(channelID, messageID)
Expand Down
31 changes: 19 additions & 12 deletions src/boost/boost_button_reactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,18 @@ func HandleContractReactions(client dc.Client, e *dc.ComponentEvent) {

redraw := false

// Check if this interaction came from an old/stale boost list message
isStale := isOldBoostListMessage(contract, e.ChannelID(), e.MessageID())
if !isStale {
if loc := contract.findLocation(e.ChannelID()); loc != nil {
if loc.ListMsgID != "" && e.MessageID() != loc.ListMsgID && e.MessageID() != loc.ReactionID {
isStale = true
}
// Check if this interaction came from an old/stale boost list message.
// Only boost list buttons can come from a boost list message; non-list interactions
// (such as Chicken Run messages or availability menus) must never be treated as stale boost lists.
isStale := false
if isBoostListButtonCommand(cmd) {
isStale = isOldBoostListMessage(contract, e.ChannelID(), e.MessageID())
if isStale {
cleanupOldBoostListMessage(client, contract, e.ChannelID(), e.MessageID())
redraw = true
}
}

if isStale {
cleanupOldBoostListMessage(client, contract, e.ChannelID(), e.MessageID())
redraw = true
}

switch cmd {
case "boost":
if isStale {
Expand Down Expand Up @@ -209,6 +206,16 @@ func HandleContractReactions(client dc.Client, e *dc.ComponentEvent) {
}
}

// isBoostListButtonCommand returns true if the button command originates from the boost list message.
func isBoostListButtonCommand(cmd string) bool {
switch cmd {
case "boost", "token", "2token", "bag", "truck", "tango", "leg", "swap", "last", "cr", "check", "complain", "notoken":
return true
default:
return false
}
}

func buttonReactionBoost(client dc.Client, GuildID string, ChannelID string, contract *Contract, cUserID string) bool {
// If Rocket reaction on Boost List, only that boosting user can apply a reaction
redraw := false
Expand Down
56 changes: 56 additions & 0 deletions src/boost/stale_boost_list_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package boost

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -301,3 +302,58 @@ func TestStaleBoostList_RocketEmojiReactionCleansUpWithoutAdvancing(t *testing.T
t.Errorf("expected userB to remain unboosted, got %v", contract.Boosters[userBID].BoostState)
}
}

func TestChickenRunMessage_NotDeletedOnReactionOrButton(t *testing.T) {
contractHash := "test-cr-msg-safety"
chanID := "200000000000000001"
activeListID := "300000000000000100"
oldListID := "300000000000000090"
signupID := "300000000000000001"
crMsgID := "300000000000000999"
userAID := "400000000000000001"
userBID := "400000000000000002"

contract := setupStaleTestContract(contractHash, chanID, activeListID, oldListID, signupID)
contract.CRMessageIDs = map[string]string{
chanID: crMsgID,
}
defer func() {
ContractsMutex.Lock()
delete(Contracts, contractHash)
ContractsMutex.Unlock()
}()

client := dctest.New()

// 1. User B clicks RanChicken button on crMsgID
crButtonEvent := dctest.ComponentButtonEventFull("100000000000000001", chanID, crMsgID, userBID, fmt.Sprintf("rc_#RanChicken#%s#%s", userAID, contractHash))
HandleContractReactions(client, crButtonEvent)

// Verify DeleteMessage was NEVER called for the CR message
for _, call := range client.CallsTo("DeleteMessage") {
if len(call.Args) >= 2 && call.Args[1] == crMsgID {
t.Fatalf("DeleteMessage was unexpectedly called for CR message %s", crMsgID)
}
}

// 2. User B clicks CRPing on crMsgID
crPingEvent := dctest.ComponentButtonEventFull("100000000000000001", chanID, crMsgID, userBID, fmt.Sprintf("rc_#CRPing#%s", contractHash))
HandleContractReactions(client, crPingEvent)

for _, call := range client.CallsTo("DeleteMessage") {
if len(call.Args) >= 2 && call.Args[1] == crMsgID {
t.Fatalf("DeleteMessage was unexpectedly called for CR message %s after CRPing", crMsgID)
}
}

// 3. User B reacts with an emoji on crMsgID
rxEvent := dctest.ReactionEvent("100000000000000001", chanID, crMsgID, userBID, "👍")
ReactionAdd(client, rxEvent)

for _, call := range client.CallsTo("DeleteMessage") {
if len(call.Args) >= 2 && call.Args[1] == crMsgID {
t.Fatalf("DeleteMessage was unexpectedly called for CR message %s after reaction", crMsgID)
}
}
}

Loading