diff --git a/src/Clients/IUserClient.cs b/src/Clients/IUserClient.cs
index f58cba57..eb2aa37b 100644
--- a/src/Clients/IUserClient.cs
+++ b/src/Clients/IUserClient.cs
@@ -201,7 +201,7 @@ public interface IUserClient
/// To ban a user, use method.
///
/// https://getstream.io/chat/docs/dotnet-csharp/moderation/?language=csharp#ban
- Task UnbanAsync(BanRequest banRequest);
+ Task UnbanAsync(BanRequest banRequest, bool removeFutureChannelsBan = false);
///
/// Queries banned users.
@@ -214,6 +214,13 @@ public interface IUserClient
/// https://getstream.io/chat/docs/dotnet-csharp/moderation/?language=csharp#query-banned-users
Task QueryBannedUsersAsync(QueryBannedUsersRequest request);
+ ///
+ /// 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.
+ ///
+ Task QueryFutureChannelBansAsync(QueryFutureChannelBansRequest request);
+
///
/// Mutes a user.
/// Any user is allowed to mute another user. Mutes are stored at user level and returned with the
diff --git a/src/Clients/UserClient.cs b/src/Clients/UserClient.cs
index cae8b088..51065625 100644
--- a/src/Clients/UserClient.cs
+++ b/src/Clients/UserClient.cs
@@ -130,22 +130,36 @@ public async Task BanAsync(BanRequest banRequest)
HttpStatusCode.Created,
banRequest);
- public async Task UnbanAsync(BanRequest banRequest)
- => await ExecuteRequestAsync("moderation/ban",
+ public async Task UnbanAsync(BanRequest banRequest, bool removeFutureChannelsBan = false)
+ {
+ var queryParams = new List>
+ {
+ new KeyValuePair("target_user_id", banRequest.TargetUserId),
+ new KeyValuePair("type", banRequest.Type),
+ new KeyValuePair("id", banRequest.Id),
+ };
+ if (removeFutureChannelsBan)
+ {
+ queryParams.Add(new KeyValuePair("remove_future_channels_ban", "true"));
+ }
+ return await ExecuteRequestAsync("moderation/ban",
HttpMethod.DELETE,
HttpStatusCode.OK,
- queryParams: new List>
- {
- new KeyValuePair("target_user_id", banRequest.TargetUserId),
- new KeyValuePair("type", banRequest.Type),
- new KeyValuePair("id", banRequest.Id),
- });
+ queryParams: queryParams);
+ }
+
public async Task QueryBannedUsersAsync(QueryBannedUsersRequest request)
=> await ExecuteRequestAsync("query_banned_users",
HttpMethod.GET,
HttpStatusCode.OK,
queryParams: request.ToQueryParameters());
+ public async Task QueryFutureChannelBansAsync(QueryFutureChannelBansRequest request)
+ => await ExecuteRequestAsync("query_future_channel_bans",
+ HttpMethod.GET,
+ HttpStatusCode.OK,
+ queryParams: request.ToQueryParameters());
+
public async Task MuteAsync(string targetId, string id)
=> await ExecuteRequestAsync("moderation/mute",
HttpMethod.POST,
diff --git a/src/Models/Moderation.cs b/src/Models/Moderation.cs
index 23912a90..89738d5d 100644
--- a/src/Models/Moderation.cs
+++ b/src/Models/Moderation.cs
@@ -32,6 +32,9 @@ public class BanRequest
/// Channel ID to ban user in
public string Id { get; set; }
+
+ /// When true, the user will be automatically banned from all future channels created by the user who issued the ban
+ public bool? BanFromFutureChannels { get; set; }
}
public class ShadowBanRequest : BanRequest
@@ -53,6 +56,69 @@ public class Ban
public User BannedBy { get; set; }
}
+ public class FutureChannelBan
+ {
+ /// Gets or sets the banned user (checks multiple possible API response fields).
+ [Newtonsoft.Json.JsonProperty("user")]
+ public User User
+ {
+ get => _user ?? Target;
+ set => _user = value;
+ }
+
+ private User _user;
+
+ /// Gets or sets the banned user (target of the ban).
+ [Newtonsoft.Json.JsonProperty("target")]
+ public User Target { get; set; }
+
+ /// Gets or sets the ID of the banned user.
+ [Newtonsoft.Json.JsonProperty("target_id")]
+ public string TargetId { get; set; }
+
+ /// Gets or sets the ID of the user who created the ban.
+ [Newtonsoft.Json.JsonProperty("created_by_id")]
+ public string CreatedById { get; set; }
+
+ /// Gets or sets the user who created the ban.
+ [Newtonsoft.Json.JsonProperty("created_by")]
+ public User CreatedBy { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("created_at")]
+ public DateTimeOffset CreatedAt { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("expires")]
+ public DateTimeOffset? Expires { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("reason")]
+ public string Reason { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("shadow")]
+ public bool Shadow { get; set; }
+ }
+
+ public class QueryFutureChannelBansRequest : IQueryParameterConvertible
+ {
+ public string UserId { get; set; }
+ public string TargetUserId { get; set; }
+ public bool? ExcludeExpiredBans { get; set; }
+ public int? Limit { get; set; }
+ public int? Offset { get; set; }
+
+ public List> ToQueryParameters()
+ {
+ return new List>
+ {
+ new KeyValuePair("payload", StreamJsonConverter.SerializeObject(this)),
+ };
+ }
+ }
+
+ public class QueryFutureChannelBansResponse : ApiResponse
+ {
+ public List Bans { get; set; }
+ }
+
public class QueryBannedUsersRequest : IQueryParameterConvertible
{
public Dictionary FilterConditions { get; set; }
diff --git a/tests/MessageClientTests.cs b/tests/MessageClientTests.cs
index b9357183..97b94d06 100644
--- a/tests/MessageClientTests.cs
+++ b/tests/MessageClientTests.cs
@@ -65,7 +65,7 @@ private async Task EnableUserMessageRemindersAsync()
///
private async Task DisableUserMessageRemindersAsync()
{
- var request = new PartialUpdateChannelRequest
+ var request = new PartialUpdateChannelRequest
{
Set = new Dictionary
{
diff --git a/tests/UserClientTests.cs b/tests/UserClientTests.cs
index ba5b1ade..4f23f700 100644
--- a/tests/UserClientTests.cs
+++ b/tests/UserClientTests.cs
@@ -559,7 +559,7 @@ public async Task TestMarkDeliveredAsync()
{
ChannelCID = "channel2",
MessageID = "message2",
- }
+ },
},
UserID = _user1.Id,
};
@@ -580,7 +580,7 @@ public async Task TestMarkDelivered_WithUserIdAsync()
{
ChannelCID = "channel1",
MessageID = "message1",
- }
+ },
},
UserID = _user1.Id,
};
@@ -623,13 +623,96 @@ public Task TestMarkDelivered_NoUserOrUserId_ThrowsArgumentExceptionAsync()
{
ChannelCID = "channel1",
MessageID = "message1",
- }
- }
+ },
+ },
};
Func markDeliveredCall = async () => await _userClient.MarkDeliveredAsync(markDeliveredOptions);
return markDeliveredCall.Should().ThrowAsync();
}
+
+ [Test]
+ public async Task TestQueryFutureChannelBansWithTargetUserIdAsync()
+ {
+ var creator = await UpsertNewUserAsync();
+ var target1 = await UpsertNewUserAsync();
+ var target2 = await UpsertNewUserAsync();
+ var channel = await CreateChannelAsync(createdByUserId: creator.Id);
+
+ try
+ {
+ // Ban both targets from future channels created by creator
+ // Note: ban_from_future_channels requires a channel_cid to be set
+ await _userClient.BanAsync(new BanRequest
+ {
+ TargetUserId = target1.Id,
+ UserId = creator.Id,
+ Type = channel.Type,
+ Id = channel.Id,
+ BanFromFutureChannels = true,
+ Reason = "test ban 1",
+ });
+
+ await _userClient.BanAsync(new BanRequest
+ {
+ TargetUserId = target2.Id,
+ UserId = creator.Id,
+ Type = channel.Type,
+ Id = channel.Id,
+ BanFromFutureChannels = true,
+ Reason = "test ban 2",
+ });
+
+ // Query all future channel bans by creator
+ var resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
+ {
+ UserId = creator.Id,
+ });
+
+ // Should have at least the 2 bans we just created
+ resp.Bans.Should().HaveCountGreaterOrEqualTo(2);
+
+ // Verify we can find our bans by checking the reasons we set
+ var reasons = resp.Bans.Select(b => b.Reason).ToList();
+ reasons.Should().Contain("test ban 1");
+ reasons.Should().Contain("test ban 2");
+
+ // Query with TargetUserId filter - should only return the specific target
+ resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
+ {
+ UserId = creator.Id,
+ TargetUserId = target1.Id,
+ });
+
+ resp.Bans.Should().HaveCount(1);
+ resp.Bans[0].Reason.Should().Be("test ban 1");
+
+ // Query for the other target
+ resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
+ {
+ UserId = creator.Id,
+ TargetUserId = target2.Id,
+ });
+
+ resp.Bans.Should().HaveCount(1);
+ resp.Bans[0].Reason.Should().Be("test ban 2");
+ }
+ finally
+ {
+ // Cleanup - unban both users
+ await _userClient.UnbanAsync(new BanRequest
+ {
+ TargetUserId = target1.Id,
+ UserId = creator.Id,
+ });
+ await _userClient.UnbanAsync(new BanRequest
+ {
+ TargetUserId = target2.Id,
+ UserId = creator.Id,
+ });
+ await TryDeleteUsersAsync(creator.Id, target1.Id, target2.Id);
+ }
+ }
}
}
\ No newline at end of file