From 42b1571dcb793623b1033667ab14e784faf9a25e Mon Sep 17 00:00:00 2001 From: Michael McCarty Date: Sat, 24 Oct 2026 07:43:54 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feat:=20avoid=20deleting=20Chicken=20R?= =?UTF-8?q?un=20messages=20on=20boost=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/boost/boost.go | 24 +++++++++++-- src/boost/boost_button_reactions.go | 31 +++++++++------- src/boost/stale_boost_list_test.go | 56 +++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 15 deletions(-) diff --git a/src/boost/boost.go b/src/boost/boost.go index 1f3cd325..d2c22a66 100644 --- a/src/boost/boost.go +++ b/src/boost/boost.go @@ -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) } } @@ -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) diff --git a/src/boost/boost_button_reactions.go b/src/boost/boost_button_reactions.go index 45c3c5de..86c4ec8d 100644 --- a/src/boost/boost_button_reactions.go +++ b/src/boost/boost_button_reactions.go @@ -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 { @@ -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 diff --git a/src/boost/stale_boost_list_test.go b/src/boost/stale_boost_list_test.go index 1c196f43..beb2eb59 100644 --- a/src/boost/stale_boost_list_test.go +++ b/src/boost/stale_boost_list_test.go @@ -1,6 +1,7 @@ package boost import ( + "fmt" "testing" "time" @@ -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) + } + } +} +