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
16 changes: 14 additions & 2 deletions src/boost/boost.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,8 +893,13 @@ func AddFarmerToContract(client dc.Client, contract *Contract, guildID string, c
}

for _, el := range contract.Location {
if el.GuildID == guildID && b.UserID != b.Name && el.GuildContractRole.ID != "" {
_ = client.AddGuildMemberRole(guildID, b.UserID, el.GuildContractRole.ID)
if (guildID == "" || el.GuildID == guildID) && dc.IsSnowflake(b.UserID) {
if el.GuildContractRole.ID != "" {
_ = client.AddGuildMemberRole(el.GuildID, b.UserID, el.GuildContractRole.ID)
}
if el.ChannelID != "" {
_ = client.AddThreadMember(el.ChannelID, b.UserID)
}
}
}

Expand Down Expand Up @@ -1424,6 +1429,13 @@ func JoinContract(client dc.Client, guildID string, channelID string, userID str
// test if userID in Boosters
if contract.Boosters[userID] != nil {
contract.Boosters[userID].Ping = bell
for _, el := range contract.Location {
if (guildID == "" || el.GuildID == guildID) && dc.IsSnowflake(userID) {
if el.ChannelID != "" {
_ = client.AddThreadMember(el.ChannelID, userID)
}
}
}
}

if bell {
Expand Down
97 changes: 97 additions & 0 deletions src/boost/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,3 +807,100 @@ func TestBoostMenuNextBoosterTokens(t *testing.T) {
t.Errorf("Expected next2:user2 emoji to match ultra_gg name %q, got %q", ultraGGEmoji.Name, emoji)
}
}

func TestAddFarmerToContract_AddsThreadMember(t *testing.T) {
client := dctest.New().
WithGuild("guild1", "Guild 1").
WithChannel("thread1", "guild1", "contract-thread").
WithUser("123456789012345678", "farmer1", "Farmer One")

contract := &Contract{
ContractHash: "test-hash-thread",
ContractID: "test-contract",
CoopID: "test-coop",
CoopSize: 10,
State: ContractStateFastrun,
CreatorID: []string{"creator1"},
Order: make([]string, 0),
Boosters: make(map[string]*Booster),
Location: []*LocationData{{GuildID: "guild1", ChannelID: "thread1"}},
}
Contracts[contract.ContractHash] = contract
defer delete(Contracts, contract.ContractHash)

b, err := AddFarmerToContract(client, contract, "guild1", "thread1", "123456789012345678", ContractOrderSignup, false, false)
if err != nil {
t.Fatalf("unexpected error adding farmer: %v", err)
}
if b == nil {
t.Fatalf("expected booster to be created, got nil")
}

// Verify AddThreadMember call was recorded for snowflake user
found := false
for _, call := range client.Calls {
if call.Method == "AddThreadMember" {
if len(call.Args) >= 2 && call.Args[0] == "thread1" && call.Args[1] == "123456789012345678" {
found = true
break
}
}
}
if !found {
t.Errorf("expected AddThreadMember(thread1, 123456789012345678) to be called, recorded calls: %v", client.Calls)
}

// Adding a non-snowflake guest should NOT call AddThreadMember
client.Calls = nil
_, err = AddFarmerToContract(client, contract, "guild1", "thread1", "guest-farmer", ContractOrderSignup, false, false)
if err != nil {
t.Fatalf("unexpected error adding guest farmer: %v", err)
}
for _, call := range client.Calls {
if call.Method == "AddThreadMember" {
t.Errorf("unexpected AddThreadMember call for guest: %v", call)
}
}
}

func TestJoinRunningContract_AddsThreadMember(t *testing.T) {
client := dctest.New().
WithGuild("guild1", "Guild 1").
WithChannel("thread2", "guild1", "running-contract-thread").
WithUser("234567890123456789", "farmer2", "Farmer Two")

contract := &Contract{
ContractHash: "test-hash-running-join",
ContractID: "test-contract-running",
CoopID: "test-coop-running",
CoopSize: 5,
State: ContractStateFastrun,
CreatorID: []string{"creator1"},
Order: []string{"creator1"},
Boosters: map[string]*Booster{
"creator1": {UserID: "creator1", Name: "Creator", Nick: "Creator"},
},
Location: []*LocationData{{GuildID: "guild1", ChannelID: "thread2"}},
}
Contracts[contract.ContractHash] = contract
defer delete(Contracts, contract.ContractHash)

err := JoinContract(client, "guild1", "thread2", "234567890123456789", false)
if err != nil {
t.Fatalf("unexpected error joining contract: %v", err)
}

// Verify AddThreadMember call was recorded
found := false
for _, call := range client.Calls {
if call.Method == "AddThreadMember" {
if len(call.Args) >= 2 && call.Args[0] == "thread2" && call.Args[1] == "234567890123456789" {
found = true
break
}
}
}
if !found {
t.Errorf("expected AddThreadMember(thread2, 234567890123456789) to be called on join, recorded calls: %v", client.Calls)
}
}
2 changes: 2 additions & 0 deletions src/dc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ type Client interface {
StartThread(channelID, name string, archiveDurationMinutes int) (*Channel, error)
// JoinThread adds the bot to a thread.
JoinThread(channelID string) error
// AddThreadMember adds a member to a thread.
AddThreadMember(threadID, userID string) error
// ActiveThreads lists the active threads in a guild. Discord's
// active-threads endpoint is guild-scoped, so callers that want the
// threads under one channel filter the result on ParentID.
Expand Down
15 changes: 15 additions & 0 deletions src/dc/client_disgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ func newDisgoClient(b *bot.Client) *disgoClient {
return &disgoClient{bot: b}
}

// IsSnowflake reports whether s is a valid Discord snowflake ID.
func IsSnowflake(s string) bool {
id, err := snowflake.Parse(s)
return err == nil && id != 0
}

// parseIDs parses a list of facade IDs, returning the first failure.
func parseIDs(ids ...string) ([]snowflake.ID, error) {
out := make([]snowflake.ID, 0, len(ids))
Expand Down Expand Up @@ -405,6 +411,15 @@ func (c *disgoClient) JoinThread(channelID string) error {
return wrapAPIError(c.bot.Rest.JoinThread(ids[0]))
}

// AddThreadMember adds a member to a thread.
func (c *disgoClient) AddThreadMember(threadID, userID string) error {
ids, err := parseIDs(threadID, userID)
if err != nil {
return err
}
return wrapAPIError(c.bot.Rest.AddThreadMember(ids[0], ids[1]))
}

// ActiveThreads lists the active threads in a guild. The argument is a guild
// ID: Discord's active-threads endpoint is guild-scoped.
func (c *disgoClient) ActiveThreads(guildID string) ([]Channel, error) {
Expand Down
6 changes: 6 additions & 0 deletions src/dc/dctest/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,9 @@ func (f *FakeClient) ActiveThreads(guildID string) ([]dc.Channel, error) {
}
return threads, nil
}

// AddThreadMember records adding a member to a thread.
func (f *FakeClient) AddThreadMember(threadID, userID string) error {
f.record("AddThreadMember", threadID, userID)
return nil
}
Loading