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
4 changes: 2 additions & 2 deletions src/boost/boost_reactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ func ReactionAdd(client dc.Client, e *dc.ReactionEvent) string {
// Anyone can use these reactions
switch e.EmojiName() {
case "🌊":
if time.Since(contract.ThreadRenameTime) < 30*time.Second {
msg, err := client.SendMessage(e.ChannelID(), dc.Message{Content: fmt.Sprintf("🌊 thread renaming is on cooldown, try again <t:%d:R>", contract.ThreadRenameTime.Add(30*time.Second).Unix())})
if time.Since(contract.ThreadRenameTime) < ThreadRenameCooldown {
msg, err := client.SendMessage(e.ChannelID(), dc.Message{Content: fmt.Sprintf("🌊 thread renaming is on cooldown, try again <t:%d:R>", contract.ThreadRenameTime.Add(ThreadRenameCooldown).Unix())})
if err == nil {
time.AfterFunc(10*time.Second, func() {
err := client.DeleteMessage(msg.ChannelID, msg.ID)
Expand Down
30 changes: 17 additions & 13 deletions src/boost/boost_slashcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,31 @@ func tokenSerialToInt(serial string) int32 {
return 0
}

// ThreadRenameCooldown is the cooldown between Discord thread/channel renames (Discord limits PATCH /channels to 2 per 10m).
const ThreadRenameCooldown = 3 * time.Minute

// UpdateThreadName will update a threads name to the current contract state
func UpdateThreadName(client dc.Client, contract *Contract) {
if contract == nil {
if contract == nil || client == nil {
return
}

contract.ThreadRenameTime = time.Now()

var builder strings.Builder
builder.WriteString(generateThreadName(contract))
contract.ThreadRenameTime = time.Now()

desiredName := generateThreadName(contract)
for _, loc := range contract.Location {
if loc == nil || loc.ChannelID == "" {
continue
}
ch, err := client.Channel(loc.ChannelID)
if err == nil {
if err == nil && ch != nil && ch.IsThread {
// Skip editing if the thread already has the desired name
if ch.Name == desiredName {
continue
}

if ch.IsThread {
_, err := client.EditChannel(loc.ChannelID, builder.String())
if err != nil {
log.Println("Error updating thread name", err)
}
contract.ThreadRenameTime = time.Now()
_, err := client.EditChannel(loc.ChannelID, desiredName)
if err != nil {
log.Println("Error updating thread name", err)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/boost/boost_speedrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ func speedrunReactions(client dc.Client, e *dc.ReactionEvent, contract *Contract
}

if e.EmojiName() == "🌊" {
if time.Since(contract.ThreadRenameTime) < 3*time.Minute {
msg, err := client.SendMessage(e.ChannelID(), dc.Message{Content: fmt.Sprintf("🌊 thread renaming is on cooldown, try again <t:%d:R>", contract.ThreadRenameTime.Add(3*time.Minute).Unix())})
if time.Since(contract.ThreadRenameTime) < ThreadRenameCooldown {
msg, err := client.SendMessage(e.ChannelID(), dc.Message{Content: fmt.Sprintf("🌊 thread renaming is on cooldown, try again <t:%d:R>", contract.ThreadRenameTime.Add(ThreadRenameCooldown).Unix())})
if err == nil {
time.AfterFunc(10*time.Second, func() {
err := client.DeleteMessage(msg.ChannelID, msg.ID)
Expand Down
39 changes: 39 additions & 0 deletions src/boost/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -935,3 +935,42 @@ func TestRefreshBoostListMessage_EmptyMsgIDs(t *testing.T) {
t.Errorf("expected 0 client calls when ListMsgID and ReactionID are empty, got %d calls: %v", len(client.Calls), client.Calls)
}
}

func TestUpdateThreadName_SkipIfUnchanged(t *testing.T) {
contract := &Contract{
ContractHash: "test-contract-skip-rename",
ContractID: "test-contract",
CoopID: "test-coop",
CoopSize: 5,
State: ContractStateSignup,
Location: []*LocationData{{
GuildID: "guild1",
ChannelID: "thread1",
}},
}
expectedName := generateThreadName(contract)

// Case 1: Thread already has desired name -> should not call EditChannel
client := dctest.New().
WithGuild("guild1", "Guild 1").
WithThread("thread1", "guild1", "parent1", expectedName)

UpdateThreadName(client, contract)
if client.Called("EditChannel") {
t.Errorf("expected EditChannel NOT to be called when name is already matching, calls: %v", client.Calls)
}

// Case 2: Thread has a different name -> should call EditChannel
client2 := dctest.New().
WithGuild("guild1", "Guild 1").
WithThread("thread1", "guild1", "parent1", "Old Thread Name")

UpdateThreadName(client2, contract)
if !client2.Called("EditChannel") {
t.Errorf("expected EditChannel to be called when thread name changed")
}
edits := client2.CallsTo("EditChannel")
if len(edits) != 1 || edits[0].Args[1] != expectedName {
t.Errorf("expected EditChannel with name %q, got: %v", expectedName, edits)
}
}
4 changes: 4 additions & 0 deletions src/boost/thread.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func HandleRenameThreadCommand(client dc.Client, e *dc.CommandEvent) {
if c.ThreadName != "" {
fmt.Fprintf(&builder, "\nThe thread name is currently set to:\n> %s", c.ThreadName)
}

if time.Since(c.ThreadRenameTime) < ThreadRenameCooldown {
fmt.Fprintf(&builder, "\n\n⚠️ Thread renaming is on cooldown until <t:%d:R>.", c.ThreadRenameTime.Add(ThreadRenameCooldown).Unix())
}
}

_ = e.Respond(dc.Message{
Expand Down
31 changes: 24 additions & 7 deletions src/dc/dctest/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ type FakeClient struct {
// ErrNotFound.
Threads map[string][]dc.Channel

// SendErr, EditErr and DeleteErr are returned by the message methods.
SendErr error
EditErr error
DeleteErr error
// SendErr, EditErr, DeleteErr and ChannelEditErr are returned by the respective client methods.
SendErr error
EditErr error
DeleteErr error
ChannelEditErr error

// NextMessageID is the ID handed back by SendMessage. It is suffixed with
// the call count so repeated sends do not collide.
Expand Down Expand Up @@ -88,15 +89,17 @@ func (f *FakeClient) WithChannel(id, guildID, name string) *FakeClient {
}

// WithThread registers an active thread the fake will return from
// ActiveThreads for the thread's guild.
// ActiveThreads for the thread's guild and Channel.
func (f *FakeClient) WithThread(id, guildID, parentID, name string) *FakeClient {
f.Threads[guildID] = append(f.Threads[guildID], dc.Channel{
th := dc.Channel{
ID: id,
GuildID: guildID,
ParentID: parentID,
Name: name,
IsThread: true,
})
}
f.Channels[id] = &th
f.Threads[guildID] = append(f.Threads[guildID], th)
return f
}

Expand Down Expand Up @@ -248,6 +251,20 @@ func (f *FakeClient) EditMessage(channelID, messageID string, m dc.Message) (*dc
return &dc.MessageRef{ID: messageID, ChannelID: channelID, Content: m.Content}, nil
}

// EditChannel records the channel edit and updates the channel's name in the fake cache.
func (f *FakeClient) EditChannel(channelID, name string) (*dc.Channel, error) {
f.record("EditChannel", channelID, name)
if f.ChannelEditErr != nil {
return nil, f.ChannelEditErr
}
ch, ok := f.Channels[channelID]
if !ok {
return nil, ErrNotFound
}
ch.Name = name
return ch, nil
}

// DeleteMessage records the delete.
func (f *FakeClient) DeleteMessage(channelID, messageID string) error {
f.record("DeleteMessage", channelID, messageID)
Expand Down
Loading