diff --git a/.gitignore b/.gitignore
index bff54b29..451fd09a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -225,6 +225,9 @@ packages
*.lock.json
# Cake
-tools/
+tools/*
+!tools/packages.config
+
+
.vs/
artifacts/
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 5a366409..71125298 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,9 +1,5 @@
- 1.1.0
-
- dev.0+sha.0
-
A Slack wrapper for direct interaction with their APIs.
Inumedia - Copyright © 2018
SlackAPI
@@ -15,5 +11,9 @@
true
git
https://github.com/Inumedia/SlackAPI
+
+ false
+ false
+ false
\ No newline at end of file
diff --git a/GlobalAssemblyInfo.cs b/GlobalAssemblyInfo.cs
new file mode 100644
index 00000000..35472f28
--- /dev/null
+++ b/GlobalAssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Reflection;
+
+[assembly: AssemblyVersion("1.1.0.0")]
+[assembly: AssemblyFileVersion("1.1.0.0")]
+[assembly: AssemblyInformationalVersion("1.1.0.0")]
diff --git a/SlackAPI.Tests/Conversations.cs b/SlackAPI.Tests/Conversations.cs
new file mode 100644
index 00000000..3c8b74dc
--- /dev/null
+++ b/SlackAPI.Tests/Conversations.cs
@@ -0,0 +1,41 @@
+using System.Linq;
+using SlackAPI.RPCMessages;
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class Conversations
+ {
+ private readonly IntegrationFixture fixture;
+
+ public Conversations(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+ [Fact]
+ public void ConversationList()
+ {
+ var client = this.fixture.UserClient;
+ ConversationsListResponse actual = null;
+ using (var sync = new InSync(nameof(SlackClient.ChannelLookup)))
+ {
+ client.GetConversationsList(response =>
+ {
+ actual = response;
+ sync.Proceed();
+ });
+ }
+
+ Assert.True(actual.ok, "Error while fetching conversation list.");
+ Assert.True(actual.channels.Any());
+
+ // check to null
+ var someChannel = actual.channels.First();
+ Assert.NotNull(someChannel.id);
+ Assert.NotNull(someChannel.name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/SlackAPI.Tests.csproj b/SlackAPI.Tests/SlackAPI.Tests.csproj
index 5ad68896..bc4dc3b4 100644
--- a/SlackAPI.Tests/SlackAPI.Tests.csproj
+++ b/SlackAPI.Tests/SlackAPI.Tests.csproj
@@ -30,6 +30,12 @@
+
+
+ Properties\GlobalAssemblyInfo.cs
+
+
+
$(DefineConstants);RELEASE
diff --git a/SlackAPI.Tests/UserUIInteraction.cs b/SlackAPI.Tests/UserUIInteraction.cs
index 60b7faf8..0ece63cf 100644
--- a/SlackAPI.Tests/UserUIInteraction.cs
+++ b/SlackAPI.Tests/UserUIInteraction.cs
@@ -52,7 +52,7 @@ public void TestGetAccessToken()
var accessTokenResponse = GetAccessToken(slackClientHelpers, clientId, clientSecret, redirectUrl, code);
Assert.True(accessTokenResponse.ok);
- Assert.Equal("identify", accessTokenResponse.scope);
+ Assert.Contains("identify", accessTokenResponse.scope);
}
}
diff --git a/SlackAPI.sln b/SlackAPI.sln
index 77a33f65..f3f74ac0 100644
--- a/SlackAPI.sln
+++ b/SlackAPI.sln
@@ -1,12 +1,21 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26228.4
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30320.27
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlackAPI", "SlackAPI\SlackAPI.csproj", "{7EED3D9B-9B7A-49A4-AFBF-599153A47DDA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlackAPI.Tests", "SlackAPI.Tests\SlackAPI.Tests.csproj", "{DEFA9559-0F8F-4C38-9644-67A080EDC46D}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{532C9828-A2CC-4281-950A-248B06D42E9C}"
+ ProjectSection(SolutionItems) = preProject
+ appveyor.yml = appveyor.yml
+ build.cake = build.cake
+ Directory.Build.props = Directory.Build.props
+ GlobalAssemblyInfo.cs = GlobalAssemblyInfo.cs
+ README.md = README.md
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/SlackAPI/Attachment.cs b/SlackAPI/Attachment.cs
index b9e9582f..64b28e1d 100644
--- a/SlackAPI/Attachment.cs
+++ b/SlackAPI/Attachment.cs
@@ -45,6 +45,7 @@ public AttachmentAction(string name, string text)
public string type = "button";
public string value;
public ActionConfirm confirm;
+ public string url;
}
//see: https://api.slack.com/docs/message-buttons#confirmation_fields
diff --git a/SlackAPI/Block.cs b/SlackAPI/Block.cs
index d2aa0403..5b05099f 100644
--- a/SlackAPI/Block.cs
+++ b/SlackAPI/Block.cs
@@ -46,6 +46,12 @@ public class ContextBlock : IBlock
public string block_id { get; set; }
public IElement[] elements { get; set; }
}
+ public class HeaderBlock : IBlock
+ {
+ public string type { get; } = BlockTypes.Header;
+ public Text text { get; set; }
+ public string block_id { get; set; }
+ }
public class Text : IElement
{
public string type { get; set; } = TextTypes.PlainText;
@@ -176,6 +182,12 @@ public class DatePickerElement : IElement
public Confirm confirm { get; set; }
}
+ public class View
+ {
+ public string type { get; set; }
+ public IBlock[] blocks { get; set; }
+ }
+
public static class ButtonStyles
{
public const string Primary = "primary";
@@ -189,6 +201,13 @@ public static class BlockTypes
public const string Actions = "actions";
public const string Context = "context";
public const string Image = "image";
+ public const string Header = "header";
+ }
+
+ public static class ViewTypes
+ {
+ public const string Home = "home";
+ public const string Modal = "modal";
}
public static class TextTypes
diff --git a/SlackAPI/Channel.cs b/SlackAPI/Channel.cs
index 7335e8bd..c45d1c23 100644
--- a/SlackAPI/Channel.cs
+++ b/SlackAPI/Channel.cs
@@ -10,12 +10,14 @@ public class Channel : Conversation
{
public string name;
public string creator;
+ public string user;
public bool is_archived;
public bool is_member;
public bool is_general;
public bool is_channel;
public bool is_group;
+ public bool is_im;
//Is this deprecated by is_open?
public bool IsPrivateGroup { get { return id != null && id[0] == 'G'; } }
diff --git a/SlackAPI/Message.cs b/SlackAPI/Message.cs
index dfb25656..6077ec6c 100644
--- a/SlackAPI/Message.cs
+++ b/SlackAPI/Message.cs
@@ -16,6 +16,7 @@ public class Message : SlackSocketMessage
///
public string username;
public string text;
+ public Attachment[] attachments;
public bool is_starred;
public string permalink;
public Reaction[] reactions;
diff --git a/SlackAPI/Preferences.cs b/SlackAPI/Preferences.cs
index 528f8c1d..f0931eec 100644
--- a/SlackAPI/Preferences.cs
+++ b/SlackAPI/Preferences.cs
@@ -73,5 +73,6 @@ public class Preferences
public string mac_ssb_bounce;
public string last_snippet_type;
public int display_real_names_override;
+ public string muted_channels;
}
}
\ No newline at end of file
diff --git a/SlackAPI/RPCMessages/AppHomeTabResponse.cs b/SlackAPI/RPCMessages/AppHomeTabResponse.cs
new file mode 100644
index 00000000..e0b8db02
--- /dev/null
+++ b/SlackAPI/RPCMessages/AppHomeTabResponse.cs
@@ -0,0 +1,38 @@
+namespace SlackAPI
+{
+ [RequestPath("views.publish")]
+ public class AppHomeTabResponse : Response
+ {
+ public AppHomeTabView view;
+
+ public class AppHomeTabView
+ {
+ public string id;
+ public string team_id;
+ public string type;
+ public object close;
+ public object submit;
+ public Block[] blocks;
+ public string private_metadata;
+ public string callback_id;
+ public State state;
+ public string hash;
+ public bool clear_on_close;
+ public bool notify_on_close;
+ public string root_view_id;
+ public object previous_view_id;
+ public string app_id;
+ public string external_id;
+ public string bot_id;
+ }
+
+ public class State
+ {
+ public Values values;
+ }
+
+ public class Values
+ {
+ }
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsArchiveResponse.cs b/SlackAPI/RPCMessages/ConversationsArchiveResponse.cs
new file mode 100644
index 00000000..d3203251
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsArchiveResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.archive")]
+ public class ConversationsArchiveResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsCloseResponse.cs b/SlackAPI/RPCMessages/ConversationsCloseResponse.cs
new file mode 100644
index 00000000..d8a7fca8
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsCloseResponse.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.close")]
+ public class ConversationsCloseResponse : Response
+ {
+ public string no_op;
+ public string already_closed;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsCreateResponse.cs b/SlackAPI/RPCMessages/ConversationsCreateResponse.cs
new file mode 100644
index 00000000..ec71d2e3
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsCreateResponse.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.create")]
+ public class ConversationsCreateResponse : Response
+ {
+ public Channel channel;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsInviteResponse.cs b/SlackAPI/RPCMessages/ConversationsInviteResponse.cs
new file mode 100644
index 00000000..95f04de4
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsInviteResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.invite")]
+ public class ConversationsInviteResponse : Response
+ {
+ public Channel channel;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsJoinResponse.cs b/SlackAPI/RPCMessages/ConversationsJoinResponse.cs
new file mode 100644
index 00000000..873818de
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsJoinResponse.cs
@@ -0,0 +1,9 @@
+namespace SlackAPI.RPCMessages
+{
+
+ [RequestPath("conversations.join")]
+ public class ConversationsJoinResponse : Response
+ {
+ public Channel channel;
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI/RPCMessages/ConversationsKickResponse.cs b/SlackAPI/RPCMessages/ConversationsKickResponse.cs
new file mode 100644
index 00000000..daa081e3
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsKickResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.kick")]
+ public class ConversationsKickResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsLeaveResponse.cs b/SlackAPI/RPCMessages/ConversationsLeaveResponse.cs
new file mode 100644
index 00000000..0f66be8f
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsLeaveResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.leave")]
+ public class ConversationsLeaveResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsListResponse.cs b/SlackAPI/RPCMessages/ConversationsListResponse.cs
new file mode 100644
index 00000000..005938f6
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsListResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("conversations.list")]
+ public class ConversationsListResponse : Response
+ {
+ public Channel[] channels;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsMarkResponse.cs b/SlackAPI/RPCMessages/ConversationsMarkResponse.cs
new file mode 100644
index 00000000..fe7d6665
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsMarkResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.mark")]
+ public class ConversationsMarkResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsMembersResponse.cs b/SlackAPI/RPCMessages/ConversationsMembersResponse.cs
new file mode 100644
index 00000000..bd53a648
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsMembersResponse.cs
@@ -0,0 +1,8 @@
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("conversations.members")]
+ public class ConversationsMembersResponse : Response
+ {
+ public string[] members;
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI/RPCMessages/ConversationsMessageHistory.cs b/SlackAPI/RPCMessages/ConversationsMessageHistory.cs
new file mode 100644
index 00000000..ffff5abc
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsMessageHistory.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("conversations.history")]
+ public class ConversationsMessageHistory : MessageHistory
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsOpenResponse.cs b/SlackAPI/RPCMessages/ConversationsOpenResponse.cs
new file mode 100644
index 00000000..527f8f16
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsOpenResponse.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.open")]
+ public class ConversationsOpenResponse : Response
+ {
+ public string no_op;
+ public string already_open;
+ public Channel channel;
+ public string error;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsRenameResponse.cs b/SlackAPI/RPCMessages/ConversationsRenameResponse.cs
new file mode 100644
index 00000000..e633eddd
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsRenameResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.rename")]
+ public class ConversationsRenameResponse : Response
+ {
+ public Channel channel;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsSetPurposeResponse.cs b/SlackAPI/RPCMessages/ConversationsSetPurposeResponse.cs
new file mode 100644
index 00000000..20d4fafe
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsSetPurposeResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.setPurpose")]
+ public class ConversationsSetPurposeResponse : Response
+ {
+ public string purpose;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsSetTopicResponse.cs b/SlackAPI/RPCMessages/ConversationsSetTopicResponse.cs
new file mode 100644
index 00000000..61ead8db
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsSetTopicResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.setTopic")]
+ public class ConversationsSetTopicResponse : Response
+ {
+ public string topic;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ConversationsUnarchiveResponse.cs b/SlackAPI/RPCMessages/ConversationsUnarchiveResponse.cs
new file mode 100644
index 00000000..1d894e01
--- /dev/null
+++ b/SlackAPI/RPCMessages/ConversationsUnarchiveResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("conversations.unarchive")]
+ public class ConversationsUnarchiveResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/JoinDirectMessageChannelResponse.cs b/SlackAPI/RPCMessages/JoinDirectMessageChannelResponse.cs
index 2e9d54bb..a7095398 100644
--- a/SlackAPI/RPCMessages/JoinDirectMessageChannelResponse.cs
+++ b/SlackAPI/RPCMessages/JoinDirectMessageChannelResponse.cs
@@ -1,6 +1,6 @@
namespace SlackAPI
{
- [RequestPath("im.open")]
+ [RequestPath("conversations.open")]
public class JoinDirectMessageChannelResponse : Response
{
public Channel channel;
diff --git a/SlackAPI/RPCMessages/ScheduleMessageResponse.cs b/SlackAPI/RPCMessages/ScheduleMessageResponse.cs
new file mode 100644
index 00000000..70a4b5c9
--- /dev/null
+++ b/SlackAPI/RPCMessages/ScheduleMessageResponse.cs
@@ -0,0 +1,21 @@
+namespace SlackAPI
+{
+ [RequestPath("chat.scheduleMessage")]
+ public class ScheduleMessageResponse : Response
+ {
+ public string ts;
+ public string channel;
+ public string scheduled_message_id;
+ public int post_at;
+ public Message message;
+
+ public class Message
+ {
+ public string text;
+ public string user;
+ public string username;
+ public string type;
+ public string subtype;
+ }
+ }
+}
diff --git a/SlackAPI/RPCMessages/UserEmailLookupResponse.cs b/SlackAPI/RPCMessages/UserEmailLookupResponse.cs
new file mode 100644
index 00000000..487adcda
--- /dev/null
+++ b/SlackAPI/RPCMessages/UserEmailLookupResponse.cs
@@ -0,0 +1,8 @@
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("users.lookupByEmail")]
+ public class UserEmailLookupResponse : Response
+ {
+ public User user;
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI/Request.cs b/SlackAPI/Request.cs
index ddd42d45..e7ed01a5 100644
--- a/SlackAPI/Request.cs
+++ b/SlackAPI/Request.cs
@@ -169,11 +169,7 @@ public static RequestPath GetRequestPath()
// the same function will try to add the path again.
// This may be removed in the future if TryAdd is verified as safe.
// See #190.
-#if NET45 || NETSTANDARD2_0
Trace.TraceError(e.ToString());
-#else
- Debug.Write(e);
-#endif
}
return path;
diff --git a/SlackAPI/RequestStateForTask.cs b/SlackAPI/RequestStateForTask.cs
index 63075b73..f535f5a4 100644
--- a/SlackAPI/RequestStateForTask.cs
+++ b/SlackAPI/RequestStateForTask.cs
@@ -40,7 +40,7 @@ private async Task ExecuteResult()
HttpWebResponse response = null;
try
{
- response = (HttpWebResponse)await this.request.GetResponseAsync();
+ response = (HttpWebResponse)await this.request.GetResponseAsync().ConfigureAwait(false);
Success = true;
}
catch (WebException we)
@@ -68,7 +68,7 @@ private async Task ExecutePost()
{
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
- using (Stream requestStream = await request.GetRequestStreamAsync())
+ using (Stream requestStream = await request.GetRequestStreamAsync().ConfigureAwait(false))
{
if (Post.Length > 0)
{
@@ -80,7 +80,7 @@ private async Task ExecutePost()
if (!first)
writer.Write('&');
- await writer.WriteAsync(string.Format("{0}={1}", Uri.EscapeDataString(postEntry.Item1), Uri.EscapeDataString(postEntry.Item2)));
+ await writer.WriteAsync(string.Format("{0}={1}", Uri.EscapeDataString(postEntry.Item1), Uri.EscapeDataString(postEntry.Item2))).ConfigureAwait(false);
first = false;
}
@@ -88,7 +88,7 @@ private async Task ExecutePost()
}
}
- return await this.ExecuteResult();
+ return await this.ExecuteResult().ConfigureAwait(false);
}
}
}
diff --git a/SlackAPI/Response.cs b/SlackAPI/Response.cs
index 6c60a0dc..8cd5e2c7 100644
--- a/SlackAPI/Response.cs
+++ b/SlackAPI/Response.cs
@@ -17,11 +17,22 @@ public abstract class Response
/// if ok is false, then this is the reason-code
///
public string error;
+ public string needed;
+ public string provided;
+ public string warning;
public void AssertOk()
{
if (!(ok))
throw new InvalidOperationException(string.Format("An error occurred: {0}", this.error));
}
+
+ public ResponseMetaData response_metadata;
+ }
+
+ public class ResponseMetaData
+ {
+ public string next_cursor;
+ public string[] messages;
}
}
diff --git a/SlackAPI/SlackAPI.csproj b/SlackAPI/SlackAPI.csproj
index f6d875d0..934c5356 100644
--- a/SlackAPI/SlackAPI.csproj
+++ b/SlackAPI/SlackAPI.csproj
@@ -1,14 +1,14 @@
- net45;netstandard1.3;netstandard1.6;netstandard2.0
+ net45;netstandard2.0
Full
SlackAPI
SlackAPI
-
+
@@ -27,6 +27,12 @@
+
+
+ Properties\GlobalAssemblyInfo.cs
+
+
+
$(DefineConstants);RELEASE
diff --git a/SlackAPI/SlackClient.cs b/SlackAPI/SlackClient.cs
index 4666a7a8..e81a55c3 100644
--- a/SlackAPI/SlackClient.cs
+++ b/SlackAPI/SlackClient.cs
@@ -108,14 +108,7 @@ protected virtual void Connected(LoginResponse loginDetails)
public void APIRequestWithToken(Action callback, params Tuple[] getParameters)
where K : Response
{
- Tuple[] tokenArray = new Tuple[]{
- new Tuple("token", APIToken)
- };
-
- if (getParameters != null && getParameters.Length > 0)
- tokenArray = tokenArray.Concat(getParameters).ToArray();
-
- APIRequest(callback, tokenArray, new Tuple[0]);
+ APIRequest(callback, getParameters, new Tuple[0], APIToken);
}
public void TestAuth(Action callback)
@@ -128,7 +121,12 @@ public void GetUserList(Action callback)
APIRequestWithToken(callback);
}
- public void ChannelsCreate(Action callback, string name) {
+ public void GetUserByEmail(Action callback, string email)
+ {
+ APIRequestWithToken(callback, new Tuple("email", email));
+ }
+
+ public void ChannelsCreate(Action callback, string name) {
APIRequestWithToken(callback, new Tuple("name", name));
}
@@ -142,6 +140,36 @@ public void ChannelsInvite(Action callback, string userId
APIRequestWithToken(callback, parameters.ToArray());
}
+ public void GetConversationsList(Action callback, string cursor = "", bool ExcludeArchived = true, int limit = 100, string[] types = null)
+ {
+ List> parameters = new List>()
+ {
+ Tuple.Create("exclude_archived", ExcludeArchived ? "1" : "0")
+ };
+ if (limit > 0)
+ parameters.Add(Tuple.Create("limit", limit.ToString()));
+ if ((types != null) && types.Any())
+ parameters.Add(Tuple.Create("types", string.Join(",", types)));
+ if (!string.IsNullOrEmpty(cursor))
+ parameters.Add(Tuple.Create("cursor", cursor));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void GetConversationsMembers(Action callback, string channelId, string cursor = "", int limit = 100)
+ {
+ List> parameters = new List>
+ {
+ new Tuple("channel", channelId)
+ };
+ if (limit > 0)
+ parameters.Add(Tuple.Create("limit", limit.ToString()));
+ if (!string.IsNullOrEmpty(cursor))
+ parameters.Add(Tuple.Create("cursor", cursor));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
public void GetChannelList(Action callback, bool ExcludeArchived = true)
{
APIRequestWithToken(callback, new Tuple("exclude_archived", ExcludeArchived ? "1" : "0"));
@@ -237,6 +265,11 @@ public void GetGroupHistory(Action callback, Channel groupI
GetHistory(callback, groupInfo.id, latest, oldest, count, unreads);
}
+ public void GetConversationsHistory(Action callback, Channel conversationInfo, DateTime? latest = null, DateTime? oldest = null, int? count = null, bool? unreads = false)
+ {
+ GetHistory(callback, conversationInfo.id, latest, oldest, count, unreads);
+ }
+
public void MarkChannel(Action callback, string channelId, DateTime ts)
{
APIRequestWithToken(callback,
@@ -352,6 +385,105 @@ public void GroupsUnarchive(Action callback, string chan
#endregion
+ #region Conversations
+ public void ConversationsArchive(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void ConversationsClose(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void ConversationsCreate(Action callback, string name)
+ {
+ APIRequestWithToken(callback, new Tuple("name", name));
+ }
+
+ public void ConversationsInvite(Action callback, string channelId, string[] userIds)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("users", string.Join(",", userIds)));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void ConversationsJoin(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void ConversationsKick(Action callback, string channelId, string userId)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("user", userId));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void ConversationsLeave(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void ConversationsMark(Action callback, string channelId, DateTime ts)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("ts", ts.ToProperTimeStamp()));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void ConversationsOpen(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void ConversationsRename(Action callback, string channelId, string name)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("name", name));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void ConversationsSetPurpose(Action callback, string channelId, string purpose)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("purpose", purpose));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void ConversationsSetTopic(Action callback, string channelId, string topic)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("topic", topic));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void ConversationsUnarchive(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ #endregion
+
+
public void SearchAll(Action callback, string query, string sorting = null, SearchSortDirection? direction = null, bool enableHighlights = false, int? count = null, int? page = null)
{
List> parameters = new List>();
@@ -491,7 +623,7 @@ public void Update(
bool linkNames = false,
IBlock[] blocks = null,
Attachment[] attachments = null,
- bool as_user = false)
+ bool? as_user = null)
{
List> parameters = new List>();
@@ -521,16 +653,16 @@ public void Update(
{
NullValueHandling = NullValueHandling.Ignore
})));
-
-
- parameters.Add(new Tuple("as_user", as_user.ToString()));
+
+ if (as_user.HasValue)
+ parameters.Add(new Tuple("as_user", as_user.ToString()));
APIRequestWithToken(callback, parameters.ToArray());
}
public void JoinDirectMessageChannel(Action callback, string user)
{
- var param = new Tuple("user", user);
+ var param = new Tuple("users", user);
APIRequestWithToken(callback, param);
}
@@ -543,7 +675,7 @@ public void PostMessage(
bool linkNames = false,
IBlock[] blocks = null,
Attachment[] attachments = null,
- bool unfurl_links = false,
+ bool? unfurl_links = null,
string icon_url = null,
string icon_emoji = null,
bool? as_user = null,
@@ -579,8 +711,8 @@ public void PostMessage(
NullValueHandling = NullValueHandling.Ignore
})));
- if (unfurl_links)
- parameters.Add(new Tuple("unfurl_links", "1"));
+ if (unfurl_links.HasValue)
+ parameters.Add(new Tuple("unfurl_links", unfurl_links.Value ? "true" : "false"));
if (!string.IsNullOrEmpty(icon_url))
parameters.Add(new Tuple("icon_url", icon_url));
@@ -641,6 +773,73 @@ public void PostEphemeralMessage(
APIRequestWithToken(callback, parameters.ToArray());
}
+
+
+ public void ScheduleMessage(
+ Action callback,
+ string channelId,
+ string text,
+ DateTime post_at,
+ string botName = null,
+ string parse = null,
+ bool linkNames = false,
+ IBlock[] blocks = null,
+ Attachment[] attachments = null,
+ bool? unfurl_links = null,
+ string icon_url = null,
+ string icon_emoji = null,
+ bool? as_user = null,
+ string thread_ts = null)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("text", text));
+ parameters.Add(new Tuple("post_at", Convert.ToUInt64((post_at - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds).ToString()));
+
+ if (!string.IsNullOrEmpty(botName))
+ parameters.Add(new Tuple("username", botName));
+
+ if (!string.IsNullOrEmpty(parse))
+ parameters.Add(new Tuple("parse", parse));
+
+ if (linkNames)
+ parameters.Add(new Tuple("link_names", "1"));
+
+ if (blocks != null && blocks.Length > 0)
+ parameters.Add(new Tuple("blocks",
+ JsonConvert.SerializeObject(blocks, Formatting.None,
+ new JsonSerializerSettings // Shouldn't include a not set property
+ {
+ NullValueHandling = NullValueHandling.Ignore
+ })));
+
+ if (attachments != null && attachments.Length > 0)
+ parameters.Add(new Tuple("attachments",
+ JsonConvert.SerializeObject(attachments, Formatting.None,
+ new JsonSerializerSettings // Shouldn't include a not set property
+ {
+ NullValueHandling = NullValueHandling.Ignore
+ })));
+
+ if (unfurl_links.HasValue)
+ parameters.Add(new Tuple("unfurl_links", unfurl_links.Value ? "true" : "false"));
+
+ if (!string.IsNullOrEmpty(icon_url))
+ parameters.Add(new Tuple("icon_url", icon_url));
+
+ if (!string.IsNullOrEmpty(icon_emoji))
+ parameters.Add(new Tuple("icon_emoji", icon_emoji));
+
+ if (as_user.HasValue)
+ parameters.Add(new Tuple("as_user", as_user.ToString()));
+
+ if (!string.IsNullOrEmpty(thread_ts))
+ parameters.Add(new Tuple("thread_ts", thread_ts));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
public void DialogOpen(
Action callback,
string triggerId,
@@ -685,7 +884,6 @@ public void UploadFile(Action callback, byte[] fileData, str
Uri target = new Uri(Path.Combine(APIBaseLocation, useAsync ? "files.uploadAsync" : "files.upload"));
List parameters = new List();
- parameters.Add(string.Format("token={0}", APIToken));
//File/Content
if (!string.IsNullOrEmpty(fileType))
@@ -705,7 +903,7 @@ public void UploadFile(Action callback, byte[] fileData, str
using (MultipartFormDataContent form = new MultipartFormDataContent())
{
form.Add(new ByteArrayContent(fileData), "file", fileName);
- HttpResponseMessage response = PostRequest(string.Format("{0}?{1}", target, string.Join("&", parameters.ToArray())), form);
+ HttpResponseMessage response = PostRequestAsync(string.Format("{0}?{1}", target, string.Join("&", parameters.ToArray())), form, APIToken).Result;
string result = response.Content.ReadAsStringAsync().Result;
callback(result.Deserialize());
}
@@ -718,5 +916,24 @@ public void DeleteFile(Action callback, string file = null)
APIRequestWithToken(callback, new Tuple("file", file));
}
+
+ public void PublishAppHomeTab(
+ Action callback,
+ string userId,
+ View view)
+ {
+ view.type = ViewTypes.Home;
+ var parameters = new List>
+ {
+ new Tuple("user_id", userId),
+ new Tuple("view", JsonConvert.SerializeObject(view, Formatting.None,
+ new JsonSerializerSettings // Shouldn't include a not set property
+ {
+ NullValueHandling = NullValueHandling.Ignore
+ }))
+ };
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
}
}
diff --git a/SlackAPI/SlackClientBase.cs b/SlackAPI/SlackClientBase.cs
index c2418a9d..1e6d357b 100644
--- a/SlackAPI/SlackClientBase.cs
+++ b/SlackAPI/SlackClientBase.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
@@ -16,10 +17,8 @@ public abstract class SlackClientBase
static SlackClientBase()
{
-#if !NETSTANDARD1_3 && !NETSTANDARD1_6
// Force Tls 1.2 for Slack
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
-#endif
}
protected SlackClientBase()
@@ -35,7 +34,11 @@ protected SlackClientBase(IWebProxy proxySettings)
protected Uri GetSlackUri(string path, Tuple[] getParameters)
{
- string parameters = getParameters
+ string parameters = default;
+
+ if (getParameters != null && getParameters.Length > 0)
+ {
+ parameters = getParameters
.Where(x => x.Item2 != null)
.Select(new Func, string>(a =>
{
@@ -56,11 +59,19 @@ protected Uri GetSlackUri(string path, Tuple[] getParameters)
return string.Format("{0}&{1}", a, b);
});
- Uri requestUri = new Uri(string.Format("{0}?{1}", path, parameters));
+ }
+
+ Uri requestUri = default;
+
+ if (!string.IsNullOrEmpty(parameters))
+ requestUri = new Uri(string.Format("{0}?{1}", path, parameters));
+ else
+ requestUri = new Uri(path);
+
return requestUri;
}
- protected void APIRequest(Action callback, Tuple[] getParameters, Tuple[] postParameters)
+ protected void APIRequest(Action callback, Tuple[] getParameters, Tuple[] postParameters, string token = "")
where K : Response
{
RequestPath path = RequestPath.GetRequestPath();
@@ -70,12 +81,15 @@ protected void APIRequest(Action callback, Tuple[] getPara
Uri requestUri = GetSlackUri(Path.Combine(APIBaseLocation, path.Path), getParameters);
HttpWebRequest request = CreateWebRequest(requestUri);
+ if (!string.IsNullOrEmpty(token))
+ request.Headers.Add("Authorization", "Bearer " + token);
+
//This will handle all of the processing.
RequestState state = new RequestState(request, postParameters, callback);
state.Begin();
}
- public Task APIRequestAsync(Tuple[] getParameters, Tuple[] postParameters)
+ public Task APIRequestAsync(Tuple[] getParameters, Tuple[] postParameters, string token = "")
where K : Response
{
RequestPath path = RequestPath.GetRequestPath();
@@ -85,6 +99,9 @@ public Task APIRequestAsync(Tuple[] getParameters, Tuple(request, postParameters);
return state.Execute();
@@ -113,9 +130,18 @@ protected HttpWebRequest CreateWebRequest(Uri requestUri)
return httpWebRequest;
}
- protected HttpResponseMessage PostRequest(string requestUri, MultipartFormDataContent form)
+ protected Task PostRequestAsync(string requestUri, MultipartFormDataContent form, string token)
{
- return httpClient.PostAsync(requestUri, form).Result;
+ var requestMessage = new HttpRequestMessage
+ {
+ Method = HttpMethod.Post,
+ Content = form,
+ RequestUri = new Uri(requestUri),
+ };
+
+ requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
+
+ return httpClient.SendAsync(requestMessage);
}
public void RegisterConverter(JsonConverter converter)
diff --git a/SlackAPI/SlackSocket.cs b/SlackAPI/SlackSocket.cs
index c4915bee..75903825 100644
--- a/SlackAPI/SlackSocket.cs
+++ b/SlackAPI/SlackSocket.cs
@@ -11,10 +11,6 @@
using System.Linq;
using System.Net;
-#if NETSTANDARD1_6
-using Microsoft.Extensions.DependencyModel;
-#endif
-
namespace SlackAPI
{
public class SlackSocket
@@ -42,17 +38,7 @@ public class SlackSocket
static SlackSocket()
{
routing = new Dictionary>();
-
-#if NET45 || NETSTANDARD2_0
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.GlobalAssemblyCache == false);
-#elif NETSTANDARD1_6
- var assemblies = DependencyContext.Default.GetDefaultAssemblyNames().Select(Assembly.Load);
-#elif NETSTANDARD1_3
- var assemblies = new[] { typeof(SlackSocket).GetType().GetTypeInfo().Assembly };
-#warning Routing messages in custom assemblies are not supported with .Net Standard 1.3
-#else
-#error Platform not supported
-#endif
foreach (Assembly assembly in assemblies)
{
Type[] assemblyTypes;
@@ -228,7 +214,7 @@ void SetupReceiving()
WebSocketReceiveResult result = null;
try
{
- result = await socket.ReceiveAsync(buffer, cts.Token);
+ result = await socket.ReceiveAsync(buffer, cts.Token).ConfigureAwait(false);
}
catch (WebSocketException wex)
{
diff --git a/SlackAPI/SlackSocketClient.cs b/SlackAPI/SlackSocketClient.cs
index e067acc0..687ae4a7 100644
--- a/SlackAPI/SlackSocketClient.cs
+++ b/SlackAPI/SlackSocketClient.cs
@@ -15,6 +15,7 @@ public class SlackSocketClient : SlackClient
public event Action OnReactionAdded;
public event Action OnPongReceived;
public event Action OnPresenceChanged;
+ public event Action OnConnectionLost;
bool HelloReceived;
public const int PingInterval = 3000;
@@ -50,19 +51,25 @@ protected override void Connected(LoginResponse loginDetails)
public void ConnectSocket(Action onSocketConnected){
underlyingSocket = new SlackSocket(loginDetails, this, onSocketConnected, this.proxySettings);
+ underlyingSocket.ConnectionClosed += UnderlyingSocket_ConnectionClosed;
+ }
+
+ private void UnderlyingSocket_ConnectionClosed()
+ {
+ OnConnectionLost?.Invoke();
}
- public void ErrorReceiving(Action callback)
+ public void ErrorReceiving(Action callback)
{
if (callback != null) underlyingSocket.ErrorReceiving += callback;
}
- public void ErrorReceivingDesiralization(Action callback)
+ public void ErrorReceivingDesiralization(Action callback)
{
if (callback != null) underlyingSocket.ErrorReceivingDesiralization += callback;
}
- public void ErrorHandlingMessage(Action callback)
+ public void ErrorHandlingMessage(Action callback)
{
if (callback != null) underlyingSocket.ErrorHandlingMessage += callback;
}
diff --git a/SlackAPI/SlackTaskClient.cs b/SlackAPI/SlackTaskClient.cs
index 6b0d21df..fd65669e 100644
--- a/SlackAPI/SlackTaskClient.cs
+++ b/SlackAPI/SlackTaskClient.cs
@@ -44,7 +44,7 @@ public SlackTaskClient(string token, IWebProxy proxySettings)
public virtual async Task ConnectAsync()
{
- var loginDetails = await EmitLoginAsync();
+ var loginDetails = await EmitLoginAsync().ConfigureAwait(false);
if(loginDetails.ok)
Connected(loginDetails);
@@ -91,11 +91,7 @@ public Task APIRequestWithTokenAsync()
public Task APIRequestWithTokenAsync(params Tuple[] postParameters)
where K : Response
{
- Tuple[] tokenArray = new Tuple[]{
- new Tuple("token", APIToken)
- };
-
- return APIRequestAsync(tokenArray, postParameters);
+ return APIRequestAsync(new Tuple[] { }, postParameters, APIToken);
}
public Task TestAuthAsync()
@@ -103,9 +99,29 @@ public Task TestAuthAsync()
return APIRequestWithTokenAsync();
}
- public Task GetUserListAsync()
+ public Task GetUserListAsync(int limit = 0, bool include_locale = false, string cursor = null, string team_id = null)
+ {
+ if (limit < 0)
+ {
+ throw new ArgumentException(nameof(limit));
+ }
+ var args = new List>();
+ args.Add(new Tuple("limit", limit.ToString()));
+ args.Add(new Tuple("include_locale", include_locale.ToString()));
+ if (cursor != null)
+ {
+ args.Add(new Tuple("cursor", cursor));
+ }
+ if (team_id != null)
+ {
+ args.Add(new Tuple("team_id", team_id));
+ }
+ return APIRequestWithTokenAsync(args.ToArray());
+ }
+
+ public Task GetUserByEmailAsync(string email)
{
- return APIRequestWithTokenAsync();
+ return APIRequestWithTokenAsync(new Tuple("email", email));
}
public Task ChannelsCreateAsync(string name) {
@@ -122,6 +138,36 @@ public Task ChannelsInviteAsync(string userId, string cha
return APIRequestWithTokenAsync(parameters.ToArray());
}
+ public Task GetConversationsListAsync(string cursor = "", bool ExcludeArchived = true, int limit = 100, string[] types = null)
+ {
+ List> parameters = new List>()
+ {
+ Tuple.Create("exclude_archived", ExcludeArchived ? "1" : "0")
+ };
+ if (limit > 0)
+ parameters.Add(Tuple.Create("limit", limit.ToString()));
+ if (types != null && types.Any())
+ parameters.Add(Tuple.Create("types", string.Join(",", types)));
+ if (!string.IsNullOrEmpty(cursor))
+ parameters.Add(new Tuple("cursor", cursor));
+
+ return APIRequestWithTokenAsync(parameters.ToArray());
+ }
+
+ public Task GetConversationsMembersAsync(string channelId, string cursor = "", int limit = 100)
+ {
+ List> parameters = new List>
+ {
+ new Tuple("channel", channelId)
+ };
+ if (limit > 0)
+ parameters.Add(Tuple.Create("limit", limit.ToString()));
+ if (!string.IsNullOrEmpty(cursor))
+ parameters.Add(new Tuple("cursor", cursor));
+
+ return APIRequestWithTokenAsync(parameters.ToArray());
+ }
+
public Task GetChannelListAsync(bool ExcludeArchived = true)
{
return APIRequestWithTokenAsync(new Tuple("exclude_archived", ExcludeArchived ? "1" : "0"));
@@ -214,6 +260,11 @@ public Task GetGroupHistoryAsync(Channel groupInfo, DateTim
return GetHistoryAsync(groupInfo.id, latest, oldest, count, unreads);
}
+ public Task GetConversationsHistoryAsync(Channel conversationInfo, DateTime? latest = null, DateTime? oldest = null, int? count = null, bool? unreads = false)
+ {
+ return GetHistoryAsync(conversationInfo.id, latest, oldest, count, unreads);
+ }
+
public Task MarkChannelAsync(string channelId, DateTime ts)
{
return APIRequestWithTokenAsync(new Tuple("channel", channelId),
@@ -328,6 +379,118 @@ public Task GroupsUnarchiveAsync(string channelId)
#endregion
+ #region Conversations
+ public Task ConversationsArchiveAsync(string channelId)
+ {
+ return APIRequestWithTokenAsync(new Tuple("channel", channelId));
+ }
+
+ public Task ConversationsCloseAsync(string channelId)
+ {
+ return APIRequestWithTokenAsync(new Tuple("channel", channelId));
+ }
+
+ public Task ConversationsCreateAsync(string name, bool? isPrivate = null, string teamId = null)
+ {
+ List> parameters = new List>();
+ parameters.Add(new Tuple("name", name));
+
+ if (isPrivate.HasValue)
+ parameters.Add(new Tuple("is_private", isPrivate.Value ? "true" : "false"));
+
+ if (!string.IsNullOrEmpty(teamId))
+ parameters.Add(new Tuple("team_id", teamId));
+
+ return APIRequestWithTokenAsync(parameters.ToArray());
+ }
+
+ public Task ConversationsInviteAsync(string channelId, string[] userIds)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("users", string.Join(",", userIds)));
+
+ return APIRequestWithTokenAsync(parameters.ToArray());
+ }
+
+ public Task ConversationsJoinAsync(string channelId)
+ {
+ return APIRequestWithTokenAsync(new Tuple("channel", channelId));
+ }
+
+ public Task ConversationsKickAsync(string channelId, string userId)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("user", userId));
+
+ return APIRequestWithTokenAsync(parameters.ToArray());
+ }
+
+ public Task ConversationsLeaveAsync(string channelId)
+ {
+ return APIRequestWithTokenAsync(new Tuple("channel", channelId));
+ }
+
+ public Task ConversationsMarkAsync(string channelId, DateTime ts)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("ts", ts.ToProperTimeStamp()));
+
+ return APIRequestWithTokenAsync(parameters.ToArray());
+ }
+
+ public Task ConversationsOpenAsync(string channelId)
+ {
+ return APIRequestWithTokenAsync