diff --git a/source/src/Slackbot.Net.SlackClients.Http/ISlackClient.cs b/source/src/Slackbot.Net.SlackClients.Http/ISlackClient.cs
index 5f20a44..452c62d 100644
--- a/source/src/Slackbot.Net.SlackClients.Http/ISlackClient.cs
+++ b/source/src/Slackbot.Net.SlackClients.Http/ISlackClient.cs
@@ -10,7 +10,9 @@
using Slackbot.Net.SlackClients.Http.Models.Responses.ChatGetPermalink;
using Slackbot.Net.SlackClients.Http.Models.Responses.ChatPostMessage;
using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsHistoryResponse;
+using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsInfo;
using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsList;
+using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsMembers;
using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsRepliesResponse;
using Slackbot.Net.SlackClients.Http.Models.Responses.FileUpload;
using Slackbot.Net.SlackClients.Http.Models.Responses.UserProfile;
@@ -75,12 +77,18 @@ public interface ISlackClient
///
/// Scopes required: channels:read | groups:read | im:read
- /// Only requests `public_channel` types of conversations
+ /// Returns the member user ids of a conversation.
///
/// https://api.slack.com/methods/conversations.members
- Task ConversationsMembers(string channel);
-
-
+ Task ConversationsMembers(string channel);
+
+ ///
+ /// Scopes required: channels:read | groups:read | im:read | mpim:read
+ ///
+ /// https://api.slack.com/methods/conversations.info
+ Task ConversationsInfo(string channel, bool includeNumMembers = false);
+
+
///
/// Scopes required: channels:history/groups:history/im:history or mpim:history
///
diff --git a/source/src/Slackbot.Net.SlackClients.Http/Models/Responses/ConversationsInfo/ConversationsInfoResponse.cs b/source/src/Slackbot.Net.SlackClients.Http/Models/Responses/ConversationsInfo/ConversationsInfoResponse.cs
new file mode 100644
index 0000000..20024ec
--- /dev/null
+++ b/source/src/Slackbot.Net.SlackClients.Http/Models/Responses/ConversationsInfo/ConversationsInfoResponse.cs
@@ -0,0 +1,17 @@
+namespace Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsInfo;
+
+public class ConversationsInfoResponse : Response
+{
+ public ConversationInfo Channel { get; set; }
+}
+
+public class ConversationInfo
+{
+ public string Id { get; set; }
+ public string Name { get; set; }
+ public bool Is_Channel { get; set; }
+ public bool Is_Archived { get; set; }
+
+ // Only populated when include_num_members=true is passed.
+ public int? Num_Members { get; set; }
+}
diff --git a/source/src/Slackbot.Net.SlackClients.Http/Models/Responses/ConversationsMembers/ConversationsMembersResponse.cs b/source/src/Slackbot.Net.SlackClients.Http/Models/Responses/ConversationsMembers/ConversationsMembersResponse.cs
new file mode 100644
index 0000000..b23a88f
--- /dev/null
+++ b/source/src/Slackbot.Net.SlackClients.Http/Models/Responses/ConversationsMembers/ConversationsMembersResponse.cs
@@ -0,0 +1,10 @@
+using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsList;
+
+namespace Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsMembers;
+
+public class ConversationsMembersResponse : Response
+{
+ public IEnumerable Members { get; set; }
+
+ public ResponseMetadata Response_Metadata { get; set; }
+}
diff --git a/source/src/Slackbot.Net.SlackClients.Http/SlackClient.cs b/source/src/Slackbot.Net.SlackClients.Http/SlackClient.cs
index 2f9771e..0ef41a5 100644
--- a/source/src/Slackbot.Net.SlackClients.Http/SlackClient.cs
+++ b/source/src/Slackbot.Net.SlackClients.Http/SlackClient.cs
@@ -12,7 +12,9 @@
using Slackbot.Net.SlackClients.Http.Models.Responses.ChatGetPermalink;
using Slackbot.Net.SlackClients.Http.Models.Responses.ChatPostMessage;
using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsHistoryResponse;
+using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsInfo;
using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsList;
+using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsMembers;
using Slackbot.Net.SlackClients.Http.Models.Responses.ConversationsRepliesResponse;
using Slackbot.Net.SlackClients.Http.Models.Responses.FileUpload;
using Slackbot.Net.SlackClients.Http.Models.Responses.UserProfile;
@@ -113,13 +115,24 @@ public async Task ConversationsListPublicChannels(int
}
///
- public async Task ConversationsMembers(string channel)
+ public async Task ConversationsMembers(string channel)
{
var parameters = new List>
{
new KeyValuePair("channel", channel)
};
- return await _client.PostParametersAsForm(parameters, "conversations.members", s => _logger.LogTrace(s));
+ return await _client.PostParametersAsForm(parameters, "conversations.members", s => _logger.LogTrace(s));
+ }
+
+ ///
+ public async Task ConversationsInfo(string channel, bool includeNumMembers = false)
+ {
+ var parameters = new List>
+ {
+ new KeyValuePair("channel", channel),
+ new KeyValuePair("include_num_members", includeNumMembers ? "true" : "false")
+ };
+ return await _client.PostParametersAsForm(parameters, "conversations.info", s => _logger.LogTrace(s));
}
///
diff --git a/source/test/Slackbot.Net.SlackClients.Http.Tests/ConversationsInfoAndMembersDeserializationTests.cs b/source/test/Slackbot.Net.SlackClients.Http.Tests/ConversationsInfoAndMembersDeserializationTests.cs
new file mode 100644
index 0000000..ae78e9a
--- /dev/null
+++ b/source/test/Slackbot.Net.SlackClients.Http.Tests/ConversationsInfoAndMembersDeserializationTests.cs
@@ -0,0 +1,73 @@
+using Microsoft.Extensions.Logging;
+using Slackbot.Net.SlackClients.Http;
+using Slackbot.Net.Tests.Helpers;
+
+namespace Slackbot.Net.Tests;
+
+// Self-contained: deserializes realistic Slack payloads through the real SlackClient
+// path (no network, no credentials).
+public class ConversationsInfoAndMembersDeserializationTests(ITestOutputHelper helper)
+{
+ private ISlackClient ClientReturning(string json) =>
+ new SlackClient(
+ new HttpClient(new StubHttpMessageHandler(json)) { BaseAddress = new Uri("https://slack.com/api/") },
+ new XUnitLogger(helper));
+
+ // Based on https://docs.slack.dev/reference/methods/conversations.info
+ [Fact]
+ public async Task ConversationsInfo_PopulatesNumMembers_WhenRequested()
+ {
+ var json = """
+ {
+ "ok": true,
+ "channel": {
+ "id": "C012AB3CD",
+ "name": "general",
+ "is_channel": true,
+ "is_archived": false,
+ "num_members": 6
+ }
+ }
+ """;
+ var response = await ClientReturning(json).ConversationsInfo("C012AB3CD", includeNumMembers: true);
+
+ Assert.True(response.Ok);
+ Assert.Equal("C012AB3CD", response.Channel.Id);
+ Assert.Equal("general", response.Channel.Name);
+ Assert.True(response.Channel.Is_Channel);
+ Assert.False(response.Channel.Is_Archived);
+ Assert.Equal(6, response.Channel.Num_Members);
+ }
+
+ // num_members is only present when include_num_members=true was passed on the request.
+ [Fact]
+ public async Task ConversationsInfo_NumMembersIsNull_WhenNotRequested()
+ {
+ var json = """
+ { "ok": true, "channel": { "id": "C012AB3CD", "name": "general", "is_channel": true, "is_archived": false } }
+ """;
+ var response = await ClientReturning(json).ConversationsInfo("C012AB3CD");
+
+ Assert.True(response.Ok);
+ Assert.Null(response.Channel.Num_Members);
+ }
+
+ // Based on https://docs.slack.dev/reference/methods/conversations.members - a flat list of
+ // member user ids, not a list of Conversation objects.
+ [Fact]
+ public async Task ConversationsMembers_PopulatesMemberIds_AndPagination()
+ {
+ var json = """
+ {
+ "ok": true,
+ "members": ["U023BECGF", "U061F7AUR", "W012A3CDE"],
+ "response_metadata": { "next_cursor": "dXNlcjpVMDYxTkZUVDI=" }
+ }
+ """;
+ var response = await ClientReturning(json).ConversationsMembers("C012AB3CD");
+
+ Assert.True(response.Ok);
+ Assert.Equal(["U023BECGF", "U061F7AUR", "W012A3CDE"], response.Members);
+ Assert.Equal("dXNlcjpVMDYxTkZUVDI=", response.Response_Metadata.Next_Cursor);
+ }
+}