Skip to content

Commit d548c9a

Browse files
feat: moderation apis (#163)
* feat: moderation apis * refactor: lint fixes * Revert obsolete changes * Revert obsolete change * Fix failing tests. The `api/v2/moderation/check` endpoint returns 201 (Created) HTTP status code on success. --------- Co-authored-by: Daniel Sierpiński <33436839+sierpinskid@users.noreply.github.com>
1 parent f1c6140 commit d548c9a

13 files changed

+235
-4
lines changed

samples/DocsExamples/ChannelUpdate.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public async Task ArchivingAChannel()
3333
},
3434
UserId = "user-id",
3535
});
36-
36+
3737
// Unarchive
3838
var unarchiveResponse = await _channelClient.UnarchiveAsync("messaging", "channel-id", "user-id");
3939
}
@@ -55,7 +55,7 @@ public async Task PinningAChannel()
5555
},
5656
UserId = "user-id",
5757
});
58-
58+
5959
// Unpin
6060
var unpinResponse = await _channelClient.UnpinAsync("messaging", "channel-id", "user-id");
6161
}

samples/DocsExamples/ExportingChannels.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public async Task ExportUsersAsync()
2727
public async Task RetrievingTaskStatusAsync()
2828
{
2929
var taskId = string.Empty;
30-
30+
3131
// ITaskClient can provide the status of the export operation
3232
var taskStatus = await _taskClient.GetTaskStatusAsync(taskId);
3333
if (taskStatus.Status == AsyncTaskStatus.Completed)

samples/DocsExamples/UserPermissions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal class UserPermissions
1313
private readonly IPermissionClient _permissionClient;
1414
private readonly IChannelTypeClient _channelTypeClient;
1515
private readonly IAppClient _appClient;
16-
16+
1717
public UserPermissions()
1818
{
1919
var factory = new StreamClientFactory("{{ api_key }}", "{{ api_secret }}");

src/Clients/AppClient.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ namespace StreamChat.Clients
1212
public class AppClient : ClientBase, IAppClient
1313
{
1414
private readonly string _apiSecret;
15+
private readonly Lazy<IModerationClient> _moderationClient;
1516

1617
internal AppClient(IRestClient client, string apiSecret) : base(client)
1718
{
1819
_apiSecret = apiSecret;
20+
_moderationClient = new Lazy<IModerationClient>(() => new ModerationClient(client));
1921
}
2022

23+
public IModerationClient Moderation => _moderationClient.Value;
24+
2125
public async Task<GetAppResponse> GetAppSettingsAsync()
2226
=> await ExecuteRequestAsync<GetAppResponse>("app",
2327
HttpMethod.GET,

src/Clients/IAppClient.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ namespace StreamChat.Clients
1010
/// <remarks>https://getstream.io/chat/docs/dotnet-csharp/app_setting_overview/?language=csharp</remarks>
1111
public interface IAppClient
1212
{
13+
/// <summary>
14+
/// Gets the moderation client that can be used to access moderation endpoints.
15+
/// </summary>
16+
IModerationClient Moderation { get; }
17+
1318
/// <summary>
1419
/// <para>Returns the application settings.</para>
1520
/// Application level settings allow you to configure settings that impact all the channel types in your app.

src/Clients/IModerationClient.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Threading.Tasks;
2+
using StreamChat.Models;
3+
4+
namespace StreamChat.Clients
5+
{
6+
/// <summary>
7+
/// A client that can be used to access moderation endpoints of a Stream Chat application.
8+
/// </summary>
9+
public interface IModerationClient
10+
{
11+
/// <summary>
12+
/// Check content for moderation.
13+
/// </summary>
14+
/// <param name="entityType">Type of entity to be checked E.g., stream:user, stream:chat:v1:message, or any custom string. Predefined values are listed in <see cref="ModerationEntityTypes"/></param>
15+
/// <param name="entityId">ID of the entity to be checked. This is mainly for tracking purposes</param>
16+
/// <param name="entityCreatorId">ID of the entity creator</param>
17+
/// <param name="moderationPayload">Content to be checked for moderation. E.g., texts, images, videos</param>
18+
/// <param name="configKey">Configuration key for moderation</param>
19+
/// <param name="options">Additional options for moderation check</param>
20+
Task<ModerationCheckResponse> CheckAsync(
21+
string entityType,
22+
string entityId,
23+
string entityCreatorId,
24+
ModerationPayload moderationPayload,
25+
string configKey,
26+
ModerationCheckOptions options = null);
27+
28+
/// <summary>
29+
/// Experimental: Check user profile for moderation.
30+
/// This will not create any review queue items for the user profile.
31+
/// You can use this to check whether to allow certain user profile to be created or not.
32+
/// </summary>
33+
/// <param name="userId">ID of the user</param>
34+
/// <param name="profile">User profile data containing username and/or profile image</param>
35+
Task<ModerationCheckResponse> CheckUserProfileAsync(string userId, UserProfileCheckRequest profile);
36+
}
37+
}

src/Clients/IStreamClientFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,11 @@ public interface IStreamClientFactory
8989
/// </summary>
9090
/// <remarks>https://getstream.io/chat/docs/dotnet-csharp/tokens_and_authentication/?language=csharp</remarks>
9191
IUserClient GetUserClient();
92+
93+
/// <summary>
94+
/// Gets a client that can be used to access moderation endpoints.
95+
/// </summary>
96+
/// <remarks>https://getstream.io/chat/docs/dotnet-csharp/moderation/?language=csharp</remarks>
97+
IModerationClient GetModerationClient();
9298
}
9399
}

src/Clients/ModerationClient.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net;
4+
using System.Threading.Tasks;
5+
using StreamChat.Models;
6+
using StreamChat.Rest;
7+
8+
namespace StreamChat.Clients
9+
{
10+
public class ModerationClient : ClientBase, IModerationClient
11+
{
12+
internal ModerationClient(IRestClient client) : base(client)
13+
{
14+
}
15+
16+
public async Task<ModerationCheckResponse> CheckAsync(
17+
string entityType,
18+
string entityId,
19+
string entityCreatorId,
20+
ModerationPayload moderationPayload,
21+
string configKey,
22+
ModerationCheckOptions options = null)
23+
{
24+
var request = new
25+
{
26+
entity_type = entityType,
27+
entity_id = entityId,
28+
entity_creator_id = entityCreatorId,
29+
moderation_payload = moderationPayload,
30+
config_key = configKey,
31+
options,
32+
};
33+
34+
return await ExecuteRequestAsync<ModerationCheckResponse>(
35+
"api/v2/moderation/check",
36+
HttpMethod.POST,
37+
HttpStatusCode.Created,
38+
request);
39+
}
40+
41+
public Task<ModerationCheckResponse> CheckUserProfileAsync(string userId, UserProfileCheckRequest profile)
42+
{
43+
if (string.IsNullOrEmpty(profile?.Username) && string.IsNullOrEmpty(profile?.Image))
44+
{
45+
throw new ArgumentException($"Either `{nameof(profile.Username)}` or `{nameof(profile.Image)}` must be provided", nameof(profile));
46+
}
47+
48+
var payload = new ModerationPayload
49+
{
50+
Texts = !string.IsNullOrEmpty(profile.Username) ? new List<string> { profile.Username } : null,
51+
Images = !string.IsNullOrEmpty(profile.Image) ? new List<string> { profile.Image } : null,
52+
};
53+
54+
return CheckAsync(
55+
ModerationEntityTypes.UserProfile,
56+
userId,
57+
userId,
58+
payload,
59+
"user_profile:default",
60+
new ModerationCheckOptions
61+
{
62+
ForceSync = true,
63+
TestMode = true,
64+
});
65+
}
66+
}
67+
}

src/Clients/StreamClientFactory.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class StreamClientFactory : IStreamClientFactory
2525
private readonly IPermissionClient _permissionClient;
2626
private readonly IReactionClient _reactionClient;
2727
private readonly ITaskClient _taskClient;
28+
private readonly IModerationClient _moderationClient;
2829

2930
/// <summary>
3031
/// Initializes a new instance of the <see cref="StreamClientFactory"/> class.
@@ -90,6 +91,7 @@ public StreamClientFactory(string apiKey, string apiSecret, Action<ClientOptions
9091
_reactionClient = new ReactionClient(restClient);
9192
_taskClient = new TaskClient(restClient);
9293
_userClient = new UserClient(restClient, jwtGeneratorClient, apiSecret);
94+
_moderationClient = new ModerationClient(restClient);
9395
}
9496

9597
public IAppClient GetAppClient() => _appClient;
@@ -106,5 +108,6 @@ public StreamClientFactory(string apiKey, string apiSecret, Action<ClientOptions
106108
public IReactionClient GetReactionClient() => _reactionClient;
107109
public ITaskClient GetTaskClient() => _taskClient;
108110
public IUserClient GetUserClient() => _userClient;
111+
public IModerationClient GetModerationClient() => _moderationClient;
109112
}
110113
}

src/Models/ModerationModels.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Collections.Generic;
2+
3+
namespace StreamChat.Models
4+
{
5+
public class ModerationPayload
6+
{
7+
public List<string> Texts { get; set; }
8+
public List<string> Images { get; set; }
9+
public List<string> Videos { get; set; }
10+
public Dictionary<string, object> Custom { get; set; }
11+
}
12+
13+
public class ModerationCheckOptions
14+
{
15+
public bool? ForceSync { get; set; }
16+
public bool? TestMode { get; set; }
17+
}
18+
19+
public class UserProfileCheckRequest
20+
{
21+
public string Username { get; set; }
22+
public string Image { get; set; }
23+
}
24+
25+
public class ModerationCheckResponse : ApiResponse
26+
{
27+
public string RecommendedAction { get; set; }
28+
public string Status { get; set; }
29+
}
30+
31+
public static class ModerationEntityTypes
32+
{
33+
public const string User = "stream:user";
34+
public const string Message = "stream:chat:v1:message";
35+
public const string UserProfile = "stream:v1:user_profile";
36+
}
37+
}

0 commit comments

Comments
 (0)