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
113 changes: 108 additions & 5 deletions ban.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,24 @@ func (c *Client) BanUser(ctx context.Context, targetID, bannedBy string, options
}

// UnBanUser removes the ban for targetID.
func (c *Client) UnBanUser(ctx context.Context, targetID string) (*Response, error) {
func (c *Client) UnBanUser(ctx context.Context, targetID string, options ...UnbanOption) (*Response, error) {
if targetID == "" {
return nil, errors.New("targetID should not be empty")
}

opts := &unbanOptions{}
for _, fn := range options {
fn(opts)
}

params := url.Values{}
params.Set("target_user_id", targetID)
if opts.RemoveFutureChannelsBan {
params.Set("remove_future_channels_ban", "true")
}
if opts.CreatedBy != "" {
params.Set("created_by", opts.CreatedBy)
}

var resp Response
err := c.makeRequest(ctx, http.MethodDelete, "moderation/ban", params, nil, &resp)
Expand All @@ -59,15 +70,23 @@ func (ch *Channel) BanUser(ctx context.Context, targetID, bannedBy string, optio
}

// UnBanUser removes the ban for targetID from the channel ch.
func (ch *Channel) UnBanUser(ctx context.Context, targetID string) (*Response, error) {
func (ch *Channel) UnBanUser(ctx context.Context, targetID string, options ...UnbanOption) (*Response, error) {
if targetID == "" {
return nil, errors.New("targetID should not be empty")
}

opts := &unbanOptions{}
for _, fn := range options {
fn(opts)
}

params := url.Values{}
params.Set("target_user_id", targetID)
params.Set("id", ch.ID)
params.Set("type", ch.Type)
if opts.RemoveFutureChannelsBan {
params.Set("remove_future_channels_ban", "true")
}

var resp Response
err := ch.client.makeRequest(ctx, http.MethodDelete, "moderation/ban", params, nil, &resp)
Expand Down Expand Up @@ -130,15 +149,21 @@ type banOptions struct {
Reason string `json:"reason,omitempty"`
Expiration int `json:"timeout,omitempty"`

TargetUserID string `json:"target_user_id"`
BannedBy string `json:"user_id"`
Shadow bool `json:"shadow"`
TargetUserID string `json:"target_user_id"`
BannedBy string `json:"user_id"`
Shadow bool `json:"shadow"`
BanFromFutureChannels bool `json:"ban_from_future_channels,omitempty"`

// ID and Type of the channel when acting on a channel member.
ID string `json:"id"`
Type string `json:"type"`
}

type unbanOptions struct {
RemoveFutureChannelsBan bool `json:"remove_future_channels_ban,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
}

type BanOption func(*banOptions)

func BanWithReason(reason string) func(*banOptions) {
Expand Down Expand Up @@ -167,3 +192,81 @@ func banFromChannel(_type, id string) func(*banOptions) {
opt.ID = id
}
}

// BanWithBanFromFutureChannels when set to true, the user will be automatically
// banned from all future channels created by the user who issued the ban.
// Note: This option requires BanWithChannel to also be set.
func BanWithBanFromFutureChannels() func(*banOptions) {
return func(opt *banOptions) {
opt.BanFromFutureChannels = true
}
}

// BanWithChannel sets the channel context for the ban operation.
// This is required when using BanWithBanFromFutureChannels at the client level.
func BanWithChannel(channelType, channelID string) func(*banOptions) {
return func(opt *banOptions) {
opt.Type = channelType
opt.ID = channelID
}
}

type UnbanOption func(*unbanOptions)

// UnbanWithRemoveFutureChannelsBan when set to true, also removes the future
// channel ban, so the user will no longer be auto-banned in new channels.
// Note: This option requires UnbanWithCreatedBy to also be set.
func UnbanWithRemoveFutureChannelsBan() func(*unbanOptions) {
return func(opt *unbanOptions) {
opt.RemoveFutureChannelsBan = true
}
}

// UnbanWithCreatedBy sets the user ID who originally created the ban.
// This is required when using UnbanWithRemoveFutureChannelsBan.
func UnbanWithCreatedBy(userID string) func(*unbanOptions) {
return func(opt *unbanOptions) {
opt.CreatedBy = userID
}
}

// FutureChannelBan represents a future channel ban entry.
type FutureChannelBan struct {
User *User `json:"user,omitempty"`
Expires *time.Time `json:"expires,omitempty"`
Reason string `json:"reason,omitempty"`
Shadow bool `json:"shadow,omitempty"`
CreatedAt time.Time `json:"created_at"`
}

// QueryFutureChannelBansOptions contains options for querying future channel bans.
type QueryFutureChannelBansOptions struct {
UserID string `json:"user_id,omitempty"`
TargetUserID string `json:"target_user_id,omitempty"`
ExcludeExpiredBans bool `json:"exclude_expired_bans,omitempty"`
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
}

// QueryFutureChannelBansResponse is the response from QueryFutureChannelBans.
type QueryFutureChannelBansResponse struct {
Bans []*FutureChannelBan `json:"bans"`
Response
}

// QueryFutureChannelBans queries future channel bans.
// Future channel bans are automatically applied when a user creates a new channel
// or adds a member to an existing channel.
func (c *Client) QueryFutureChannelBans(ctx context.Context, opts *QueryFutureChannelBansOptions) (*QueryFutureChannelBansResponse, error) {
data, err := json.Marshal(opts)
if err != nil {
return nil, err
}

values := url.Values{}
values.Set("payload", string(data))

var resp QueryFutureChannelBansResponse
err = c.makeRequest(ctx, http.MethodGet, "query_future_channel_bans", values, nil, &resp)
return &resp, err
}
51 changes: 51 additions & 0 deletions ban_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,57 @@ func TestChannelBanUnban(t *testing.T) {
require.Empty(t, resp.Bans)
}

func TestQueryFutureChannelBans(t *testing.T) {
c := initClient(t)
creator := randomUser(t, c)
target1 := randomUser(t, c)
target2 := randomUser(t, c)
ctx := context.Background()

// Create a channel to use for future channel bans
ch := initChannel(t, c, creator.ID)

// Ban both targets from future channels created by creator
_, err := c.BanUser(ctx, target1.ID, creator.ID, BanWithBanFromFutureChannels(), BanWithChannel(ch.Type, ch.ID), BanWithReason("test ban 1"))
require.NoError(t, err)

_, err = c.BanUser(ctx, target2.ID, creator.ID, BanWithBanFromFutureChannels(), BanWithChannel(ch.Type, ch.ID), BanWithReason("test ban 2"))
require.NoError(t, err)

// Query all future channel bans by creator
resp, err := c.QueryFutureChannelBans(ctx, &QueryFutureChannelBansOptions{
UserID: creator.ID,
})
require.NoError(t, err)
require.GreaterOrEqual(t, len(resp.Bans), 2)

// Query with target_user_id filter - should only return the specific target
// Note: When filtering by target_user_id, the API doesn't return the user object
// since it's already known from the filter
resp, err = c.QueryFutureChannelBans(ctx, &QueryFutureChannelBansOptions{
UserID: creator.ID,
TargetUserID: target1.ID,
})
require.NoError(t, err)
require.Len(t, resp.Bans, 1)
require.Equal(t, "test ban 1", resp.Bans[0].Reason)

// Query for the other target
resp, err = c.QueryFutureChannelBans(ctx, &QueryFutureChannelBansOptions{
UserID: creator.ID,
TargetUserID: target2.ID,
})
require.NoError(t, err)
require.Len(t, resp.Bans, 1)
require.Equal(t, "test ban 2", resp.Bans[0].Reason)

// Cleanup - unban both users with RemoveFutureChannelsBan
_, err = c.UnBanUser(ctx, target1.ID, UnbanWithRemoveFutureChannelsBan(), UnbanWithCreatedBy(creator.ID))
require.NoError(t, err)
_, err = c.UnBanUser(ctx, target2.ID, UnbanWithRemoveFutureChannelsBan(), UnbanWithCreatedBy(creator.ID))
require.NoError(t, err)
}

func ExampleClient_BanUser() {
client, _ := NewClient("XXXX", "XXXX")
ctx := context.Background()
Expand Down
Loading