diff --git a/src/Clients/IUserClient.cs b/src/Clients/IUserClient.cs
index f58cba5..eb2aa37 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 cae8b08..5106562 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 23912a9..3db3404 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,37 @@ public class Ban
public User BannedBy { get; set; }
}
+ public class FutureChannelBan
+ {
+ public User User { get; set; }
+ public DateTimeOffset CreatedAt { get; set; }
+ public DateTimeOffset? Expires { get; set; }
+ public string Reason { get; set; }
+ 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/UserClientTests.cs b/tests/UserClientTests.cs
index ba5b1ad..9cb4b6b 100644
--- a/tests/UserClientTests.cs
+++ b/tests/UserClientTests.cs
@@ -631,5 +631,76 @@ public Task TestMarkDelivered_NoUserOrUserId_ThrowsArgumentExceptionAsync()
return markDeliveredCall.Should().ThrowAsync();
}
+
+ [Test]
+ public async Task TestQueryFutureChannelBansWithTargetUserIdAsync()
+ {
+ var creator = await UpsertNewUserAsync();
+ var target1 = await UpsertNewUserAsync();
+ var target2 = await UpsertNewUserAsync();
+
+ try
+ {
+ // Ban both targets from future channels created by creator
+ await _userClient.BanAsync(new BanRequest
+ {
+ TargetUserId = target1.Id,
+ UserId = creator.Id,
+ BanFromFutureChannels = true,
+ Reason = "test ban 1",
+ });
+
+ await _userClient.BanAsync(new BanRequest
+ {
+ TargetUserId = target2.Id,
+ UserId = creator.Id,
+ BanFromFutureChannels = true,
+ Reason = "test ban 2",
+ });
+
+ // Query with TargetUserId filter - should only return the specific target
+ var resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
+ {
+ UserId = creator.Id,
+ TargetUserId = target1.Id,
+ });
+
+ resp.Bans.Should().HaveCount(1);
+ resp.Bans[0].User.Id.Should().Be(target1.Id);
+
+ // 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].User.Id.Should().Be(target2.Id);
+
+ // Query all future channel bans by creator (without target filter)
+ resp = await _userClient.QueryFutureChannelBansAsync(new QueryFutureChannelBansRequest
+ {
+ UserId = creator.Id,
+ });
+
+ resp.Bans.Should().HaveCountGreaterOrEqualTo(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