diff --git a/.gitignore b/.gitignore
index b9d6bd92..451fd09a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
## Eclipse
#################
+config.json
+
*.pydevproject
.project
.metadata
@@ -213,3 +215,19 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
+*.chk
+*.ide
+packages
+*.private
+.vs/config/applicationhost.config
+
+#.Net core
+*.lock.json
+
+# Cake
+tools/*
+!tools/packages.config
+
+
+.vs/
+artifacts/
\ No newline at end of file
diff --git a/Attachment.cs b/Attachment.cs
deleted file mode 100644
index 25a5626c..00000000
--- a/Attachment.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SlackAPI
-{
- public class Attachment
- {
- public string Pretext;
- public string text;
- public string fallback;
- public string color;
- public Field[] fields;
- ///I have absolutely no idea what goes on in here.
- }
-
- public class Field{
- public string title;
- public string value;
- public string @short;
- }
-}
diff --git a/Bot.cs b/Bot.cs
deleted file mode 100644
index 87e00662..00000000
--- a/Bot.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SlackAPI
-{
- public class Bot
- {
- public string emoji;
- public string image_24;
- public string image_32;
- public string image_48;
- public string image_72;
- public string image_192;
-
- public bool deleted;
- public UserProfile icons;
- public string id;
- public string name;
- }
-}
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 00000000..71125298
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,19 @@
+
+
+ A Slack wrapper for direct interaction with their APIs.
+ Inumedia - Copyright © 2018
+ SlackAPI
+ en-US
+ Inumedia
+ Inumedia
+ https://github.com/Inumedia/SlackAPI
+ http://choosealicense.com/licenses/mit/
+ true
+ git
+ https://github.com/Inumedia/SlackAPI
+
+ false
+ false
+ false
+
+
\ No newline at end of file
diff --git a/Extensions.cs b/Extensions.cs
deleted file mode 100644
index b0b25a3e..00000000
--- a/Extensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SlackAPI
-{
- public static class Extensions
- {
- ///
- /// Converts to a propert JavaScript timestamp interpretted by Slack. Also handles converting to UTC.
- ///
- ///
- ///
- public static string ToProperTimeStamp(this DateTime that, bool toUTC = true)
- {
- if (toUTC)
- return that.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString();
- else
- return that.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString();
- }
- }
-}
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/JavascriptBotsToArray.cs b/JavascriptBotsToArray.cs
deleted file mode 100644
index c8d0a38a..00000000
--- a/JavascriptBotsToArray.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SlackAPI
-{
- public class JavascriptBotsToArray : Newtonsoft.Json.JsonConverter
- {
- public override bool CanConvert(Type objectType)
- {
- return true;
- }
-
- public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
- {
- List bots = new List();
- int d = reader.Depth;
-
- while (reader.Read() && reader.Depth > d)
- {
- Bot current = new Bot();
- int depth = reader.Depth;
-
- current.name = reader.Value.ToString();
-
- reader.Read();
- while (reader.Read() && reader.Depth > depth)
- {
- if (reader.Value == null) break;
- switch (reader.Value.ToString())
- {
- case "image_48":
- reader.Read();
- current.image_48 = reader.Value.ToString();
- break;
-
- case "image_64":
- reader.Read();
- current.image_48 = reader.Value.ToString();
- break;
-
- case "emoji":
- reader.Read();
- current.emoji = reader.Value.ToString();
- break;
- }
- }
-
- bots.Add(current);
- }
-
- return bots.ToArray();
- }
-
- public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
- {
- //Not sure if this is correct :D
- throw new NotSupportedException("Too hackish for this shi.");
- }
- }
-}
diff --git a/JavascriptDateTimeConverter.cs b/JavascriptDateTimeConverter.cs
deleted file mode 100644
index f68b108b..00000000
--- a/JavascriptDateTimeConverter.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SlackAPI
-{
- class JavascriptDateTimeConverter : Newtonsoft.Json.JsonConverter
- {
- public override bool CanConvert(Type objectType)
- {
- return objectType == typeof(DateTime);
- }
-
- public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
- {
- double value = double.Parse(reader.Value.ToString());
- return new DateTime(1970, 1, 1).Add(TimeSpan.FromSeconds(value)).ToLocalTime();
- }
-
- public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
- {
- //Not sure if this is correct :D
- writer.WriteValue(((DateTime)value).Subtract(new DateTime(1970, 1, 1)).TotalSeconds);
- }
- }
-}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..ee687600
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Andrew Turner
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/LICENSE-2.0 b/LICENSE-2.0
deleted file mode 100644
index 0b42f8f2..00000000
--- a/LICENSE-2.0
+++ /dev/null
@@ -1,202 +0,0 @@
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
diff --git a/LoginResponse.cs b/LoginResponse.cs
deleted file mode 100644
index 97acd515..00000000
--- a/LoginResponse.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SlackAPI
-{
- [RequestPath("users.login")]
- public class LoginResponse : Response
- {
- public Bot[] bots;
- public Channel[] channels;
- public Channel[] groups;
- public DirectMessageConversation[] ims;
- public Self self;
- public int svn_rev;
- public int min_svn_rev;
- public Team team;
- public string url;
- public User[] users;
- }
-
- public class Self
- {
- public DateTime created;
- public string id;
- public string manual_presence;
- public string name;
- public Preferences prefs;
- }
-}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
deleted file mode 100644
index fd34e2f2..00000000
--- a/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("SlackAPI")]
-[assembly: AssemblyDescription("A Slack wrapper for direct interaction with their APIs.")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Inumedia")]
-[assembly: AssemblyProduct("SlackAPI")]
-[assembly: AssemblyCopyright("Copyright © 2014")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("80fe3ab4-f0d5-4fee-a6ae-524b523cebcc")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..ebae38ab
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+[](https://ci.appveyor.com/project/Inumedia/slackapi/branch/master)
+[](https://www.nuget.org/packages/SlackAPI/)
+[](https://www.myget.org/feed/slackapi/package/nuget/SlackAPI)
+
+# SlackAPI
+
+This is a third party implementation of Slack's API written in C#. This supports their WebAPI as well as their Real Time Messaging API.
+
+# Examples
+
+Some examples can be found on the Wiki: https://github.com/Inumedia/SlackAPI/wiki/Examples
+
+# Issues and Bugs
+
+Please log an issue if you find any bugs or think something isn't correct.
+
+# Getting in touch
+
+I have a Slack setup for personal projects with a few friends, this includes this Github and a few others as public channels. If you want access, shoot me a quick email inumedia@inumedia.net.
+
+# Committer access
+
+Want committer access? Feel like I'm too lazy to keep up with Slack's ever changing API? Want a bug fixed but don't want to log an issue for it?
+
+Create some pull requests, give me a reason to give you access.
+
+# How to build the solution
+###### (aka where is the config.json file?)
+The project **SlackAPI.Tests** requires a valid `config.json` file for tests. You have two options to build the solution:
+- Unload SlackAPI.Tests project and you're able to build SlackAPI solution.
+- Create your own config.json file to be able to run tests and validate your changes.
+ - Copy/paste `config.default.json` to `config.json`
+ - Update `config.json` file with your settings
+ - *userAuthToken* : Visit https://api.slack.com/docs/oauth-test-tokens to generate a token for your user
+ - *botAuthToken* : Visit https://my.slack.com/services/new/bot to create a bot for your Slack team and retrieve associated token
+ - *testChannel* : A channel ID (user associated to *userAuthToken* must be member of the channel)
+ - *directMessageUser* : A Slack member username
+ - *clientId*/*clientSecret*/*authCode* : Not used
+
+# NuGet package
+SlackAPI NuGet package is build with following platforms support:
+- .NET Framework 4.5
+- .NET Standard 1.3 (UWP support).
+ - The version cannot detect SlackSocketRouting attributes in loaded assemblies (used to extend SlackAPI to handle custom messages).
+- .NET Standard 1.6
+- .NET Standard 2.0
+
+[(.NET implementation compatibility table)](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support)
diff --git a/Response.cs b/Response.cs
deleted file mode 100644
index 3627fa70..00000000
--- a/Response.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace SlackAPI
-{
- public abstract class Response
- {
- ///
- /// Should always be checked before trying to process a response.
- ///
- public bool ok;
-
- ///
- /// Purely speculative. Might not be bools, and might not always be included when a request fails.
- ///
- public bool invalid_auth;
- ///
- /// Purely speculative. Might not be bools, and might not always be included when a request fails.
- ///
- public bool account_inactive;
- }
-}
diff --git a/SlackAPI.Tests/BlockMessage.cs b/SlackAPI.Tests/BlockMessage.cs
new file mode 100644
index 00000000..0f1d451d
--- /dev/null
+++ b/SlackAPI.Tests/BlockMessage.cs
@@ -0,0 +1,98 @@
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class BlockMessage
+ {
+ private readonly IntegrationFixture fixture;
+
+ public BlockMessage(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+
+ [Fact]
+ public void Blocks()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ PostMessageResponse actual = null;
+
+ // when
+ using (var sync = new InSync(nameof(SlackClient.PostMessage)))
+ {
+ client.PostMessage(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ string.Empty,
+ blocks: SlackMother.SomeBlocks);
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ }
+
+ [Fact]
+ public void BlocksWithActions()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ PostMessageResponse actual = null;
+
+ // when
+ using (var sync = new InSync())
+ {
+ client.PostMessage(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ string.Empty,
+ blocks: SlackMother.SomeBlocksWithActions);
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ }
+
+ [Fact]
+ public void BlocksInAttachment()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ PostMessageResponse actual = null;
+
+ // when
+ using (var sync = new InSync(nameof(SlackClient.PostMessage)))
+ {
+ client.PostMessage(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ "These blocks are in an attachment",
+ attachments: new []
+ {
+ new Attachment
+ {
+ blocks = SlackMother.SomeBlocks
+ }
+ });
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/Configuration/IntegrationCollection.cs b/SlackAPI.Tests/Configuration/IntegrationCollection.cs
new file mode 100644
index 00000000..2b8c8e0c
--- /dev/null
+++ b/SlackAPI.Tests/Configuration/IntegrationCollection.cs
@@ -0,0 +1,9 @@
+using Xunit;
+
+namespace SlackAPI.Tests.Configuration
+{
+ [CollectionDefinition("Integration tests")]
+ public class IntegrationCollection : ICollectionFixture
+ {
+ }
+}
diff --git a/SlackAPI.Tests/Configuration/IntegrationFixture.cs b/SlackAPI.Tests/Configuration/IntegrationFixture.cs
new file mode 100755
index 00000000..cc542af7
--- /dev/null
+++ b/SlackAPI.Tests/Configuration/IntegrationFixture.cs
@@ -0,0 +1,148 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using Newtonsoft.Json;
+using Polly;
+using SlackAPI.Tests.Helpers;
+using SlackAPI.WebSocketMessages;
+using Xunit;
+
+namespace SlackAPI.Tests.Configuration
+{
+ public class IntegrationFixture : IDisposable
+ {
+ private const int MaxConnectionAttempts = 5;
+
+ private readonly Lazy userClient;
+ private readonly Lazy botClient;
+ private readonly Lazy userClientAsync;
+ private readonly Lazy botClientAsync;
+
+ private readonly Policy connectRetryPolicy;
+
+ public IntegrationFixture()
+ {
+ this.Config = this.GetConfig();
+
+ this.connectRetryPolicy = Policy
+ .Handle(exception => exception.Message.Contains("ratelimited"))
+ .WaitAndRetry(MaxConnectionAttempts, retryAttempt => TimeSpan.FromSeconds(ComputeExponentialBackoff(retryAttempt)),
+ (exception, timeSpan, retryCount, context) => Console.WriteLine($"Connection failed ({exception.Message}). Retrying after {timeSpan.TotalSeconds}s ({retryCount}/5)"));
+
+ this.userClient = new Lazy(() => connectRetryPolicy.Execute(() => this.CreateClient(this.Config.UserAuthToken)));
+ this.botClient = new Lazy(() => connectRetryPolicy.Execute(() => this.CreateClient(this.Config.BotAuthToken)));
+ this.userClientAsync = new Lazy(() => new SlackTaskClient(this.Config.UserAuthToken));
+ this.botClientAsync = new Lazy(() => new SlackTaskClient(this.Config.BotAuthToken));
+ }
+
+ public SlackConfig Config { get; }
+
+ public TimeSpan ConnectionTimeout => TimeSpan.FromSeconds(Enumerable.Range(1, MaxConnectionAttempts).Sum(ComputeExponentialBackoff)) + TimeSpan.FromSeconds(10); // Maximum exponential backoff + 10 seconds for connections attemps
+
+ public SlackSocketClient UserClient
+ {
+ get
+ {
+ Assert.True(userClient.Value.IsReady);
+ return userClient.Value;
+ }
+ }
+
+ public SlackSocketClient BotClient
+ {
+ get
+ {
+ Assert.True(botClient.Value.IsReady);
+ return botClient.Value;
+ }
+ }
+
+ public SlackSocketClient CreateUserClient(IWebProxy proxySettings = null, bool maintainPresenceChangesStatus = false, Action presenceChanged = null)
+ {
+ return this.connectRetryPolicy.Execute(() => this.CreateClient(this.Config.UserAuthToken, proxySettings, maintainPresenceChangesStatus, presenceChanged));
+ }
+
+ public SlackSocketClient CreateBotClient(IWebProxy proxySettings = null)
+ {
+ return this.connectRetryPolicy.Execute(() => this.CreateClient(this.Config.BotAuthToken, proxySettings));
+ }
+
+ public SlackTaskClient UserClientAsync => userClientAsync.Value;
+
+ public SlackTaskClient BotClientAsync => botClientAsync.Value;
+
+ public void Dispose()
+ {
+ if (this.userClient.IsValueCreated)
+ {
+ this.UserClient.CloseSocket();
+ }
+
+ if (this.botClient.IsValueCreated)
+ {
+ this.BotClient.CloseSocket();
+ }
+ }
+
+ private SlackConfig GetConfig()
+ {
+ var currentAssembly = this.GetType().GetTypeInfo().Assembly.Location;
+ var assemblyDirectory = Path.GetDirectoryName(currentAssembly);
+ string fileName = Path.Combine(assemblyDirectory, @"configuration\config.json");
+ string json = System.IO.File.ReadAllText(fileName);
+
+ var jsonObject = new {slack = (SlackConfig)null };
+ return JsonConvert.DeserializeAnonymousType(json, jsonObject).slack;
+ }
+
+ private SlackSocketClient CreateClient(string authToken, IWebProxy proxySettings = null, bool maintainPresenceChanges = false, Action presenceChanged = null)
+ {
+ SlackSocketClient client;
+
+ LoginResponse loginResponse = null;
+ using (var syncClient = new InSync($"{nameof(SlackClient.Connect)} - Connected callback"))
+ using (var syncClientSocket = new InSync($"{nameof(SlackClient.Connect)} - SocketConnected callback"))
+ using (var syncClientSocketHello = new InSync($"{nameof(SlackClient.Connect)} - SocketConnected hello callback"))
+ {
+ client = new SlackSocketClient(authToken, proxySettings, maintainPresenceChanges);
+
+ void OnPresenceChanged(PresenceChange x)
+ {
+ presenceChanged?.Invoke(client, x);
+ }
+
+ client.OnPresenceChanged += OnPresenceChanged;
+ client.OnHello += () => syncClientSocketHello.Proceed();
+ client.Connect(x =>
+ {
+ loginResponse = x;
+
+ Console.WriteLine($"Connected {x.ok}");
+ syncClient.Proceed();
+ if (!x.ok)
+ {
+ // If connect fails, socket connect callback is not called
+ syncClientSocket.Proceed();
+ syncClientSocketHello.Proceed();
+ }
+ }, () =>
+ {
+ Console.WriteLine("Socket Connected");
+ syncClientSocket.Proceed();
+ });
+ }
+
+ loginResponse.AssertOk();
+
+ return client;
+ }
+
+ private int ComputeExponentialBackoff(int retryAttempt)
+ {
+ // Retries after 4, 8, 16, 32, 64... seconds
+ return 2 * (int)Math.Pow(2, retryAttempt);
+ }
+ }
+}
diff --git a/SlackAPI.Tests/Configuration/SlackConfig.cs b/SlackAPI.Tests/Configuration/SlackConfig.cs
new file mode 100644
index 00000000..6f45de62
--- /dev/null
+++ b/SlackAPI.Tests/Configuration/SlackConfig.cs
@@ -0,0 +1,17 @@
+namespace SlackAPI.Tests.Configuration
+{
+ public class SlackConfig
+ {
+ public string UserAuthToken { get; set; }
+ public string BotAuthToken { get; set; }
+ public string TestChannel { get; set; }
+ public string DirectMessageUser { get; set; }
+
+ public string ClientId { get; set; }
+ public string ClientSecret { get; set; }
+ public string RedirectUrl { get; set; }
+ public string AuthUsername { get; set; }
+ public string AuthPassword { get; set; }
+ public string AuthWorkspace { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/Configuration/config.default.json b/SlackAPI.Tests/Configuration/config.default.json
new file mode 100644
index 00000000..01d4e493
--- /dev/null
+++ b/SlackAPI.Tests/Configuration/config.default.json
@@ -0,0 +1,16 @@
+{
+ "slack": {
+ "userAuthToken": "token-tokentoken-tokentoken-tokentoken-token",
+ "botAuthToken": "bottoken-bottoken-bottoken-bottoken",
+ "testChannel": "SuperSecretChannel",
+ "directMessageUser": "someUserId",
+
+ // Application GetAccessToken
+ "clientId": "your-special-id",
+ "clientSecret": "such-special-secret-key",
+ "redirectUrl": "http://redirecturl",
+ "authUsername": "user@domain.com",
+ "authPassword": "userpassword",
+ "authWorkspace": "workspace"
+ }
+}
diff --git a/SlackAPI.Tests/Connect.cs b/SlackAPI.Tests/Connect.cs
new file mode 100644
index 00000000..3aa193f8
--- /dev/null
+++ b/SlackAPI.Tests/Connect.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using SlackAPI.WebSocketMessages;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class Connect : IDisposable
+ {
+ const string TestText = "Test :D";
+ private readonly IntegrationFixture fixture;
+
+ private SlackSocketClient slackClient;
+
+ public Connect(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+
+ // Extra wait to mitigate Slack throttling
+ Thread.Sleep(2000);
+ }
+
+ public void Dispose()
+ {
+ slackClient?.CloseSocket();
+ }
+
+ [Fact]
+ public void TestConnectAsUser()
+ {
+ slackClient = this.fixture.CreateUserClient();
+ Assert.True(slackClient.IsConnected, "Invalid, doesn't think it's connected.");
+ }
+
+ [Fact]
+ public void TestConnectAsBot()
+ {
+ slackClient = this.fixture.CreateBotClient();
+ Assert.True(slackClient.IsConnected, "Invalid, doesn't think it's connected.");
+ }
+
+ [Fact]
+ public void TestConnectWithWrongProxySettings()
+ {
+ var proxySettings = new WebProxy { Address = new Uri("http://127.0.0.1:8080")};
+ Assert.Throws(() => this.fixture.CreateUserClient(proxySettings));
+ Assert.Throws(() => this.fixture.CreateBotClient(proxySettings));
+ }
+
+ [Fact]
+ public void TestConnectPostAndDelete()
+ {
+ // given
+ slackClient = this.fixture.CreateUserClient();
+ string channel = this.fixture.Config.TestChannel;
+
+ // when
+ DateTime messageTimestamp = PostMessage(slackClient, channel);
+ DeletedResponse deletedResponse = DeleteMessage(slackClient, channel, messageTimestamp);
+
+ // then
+ Assert.NotNull(deletedResponse);
+ Assert.True(deletedResponse.ok);
+ Assert.Equal(channel, deletedResponse.channel);
+ Assert.Equal(messageTimestamp, deletedResponse.ts);
+ }
+
+ [Fact]
+ public void TestConnectGetPresenceChanges()
+ {
+ // Arrange
+ int presenceChangesRaisedCount = 0;
+ using (var sync = new InSync(nameof(TestConnectGetPresenceChanges), this.fixture.ConnectionTimeout))
+ {
+ void OnPresenceChanged(SlackSocketClient sender, PresenceChange e)
+ {
+ if (++presenceChangesRaisedCount == sender.Users.Count)
+ {
+ sync.Proceed();
+ }
+ }
+
+ // Act
+ slackClient = this.fixture.CreateUserClient(maintainPresenceChangesStatus: true, presenceChanged: OnPresenceChanged);
+ }
+
+ // Assert
+ Assert.True(slackClient.Users.All(x => x.presence != null));
+ }
+
+ [Fact(Skip = "Not stable on AppVeyor")]
+ public void TestManualSubscribePresenceChangeAndManualPresenceChange()
+ {
+ // Arrange
+ slackClient = this.fixture.CreateUserClient();
+ using (var sync = new InSync())
+ {
+ slackClient.OnPresenceChanged += x =>
+ {
+ if (x.user == slackClient.MySelf.id)
+ {
+ // Assert
+ sync.Proceed();
+ }
+ };
+
+ slackClient.SubscribePresenceChange(slackClient.MySelf.id);
+ }
+
+ using (var sync = new InSync())
+ {
+ slackClient.OnPresenceChanged += x =>
+ {
+ if (x is ManualPresenceChange && x.user == slackClient.MySelf.id)
+ {
+ // Assert
+ sync.Proceed();
+ }
+ };
+
+ // Act
+ slackClient.EmitPresence(x => x.AssertOk(), Presence.away);
+ slackClient.EmitPresence(x => x.AssertOk(), Presence.auto);
+ }
+ }
+
+ private static DateTime PostMessage(SlackSocketClient client, string channel)
+ {
+ MessageReceived sendMessageResponse = null;
+
+ using (var sync = new InSync(nameof(SlackSocketClient.SendMessage)))
+ {
+ client.SendMessage(response =>
+ {
+ sendMessageResponse = response;
+ sync.Proceed();
+ }, channel, TestText);
+ }
+
+ Assert.NotNull(sendMessageResponse);
+ Assert.Equal(TestText, sendMessageResponse.text);
+
+ return sendMessageResponse.ts;
+ }
+
+ private static DeletedResponse DeleteMessage(SlackSocketClient client, string channel, DateTime messageTimestamp)
+ {
+ DeletedResponse deletedResponse = null;
+
+ using (var sync = new InSync(nameof(SlackClient.DeleteMessage)))
+ {
+ client.DeleteMessage(response =>
+ {
+ deletedResponse = response;
+ sync.Proceed();
+ }, channel, messageTimestamp);
+ }
+
+ return deletedResponse;
+ }
+ }
+}
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/Helpers/InSync.cs b/SlackAPI.Tests/Helpers/InSync.cs
new file mode 100644
index 00000000..83a77ec8
--- /dev/null
+++ b/SlackAPI.Tests/Helpers/InSync.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+namespace SlackAPI.Tests.Helpers
+{
+ public class InSync : IDisposable
+ {
+ private readonly TimeSpan DefaultWaitTimeout = TimeSpan.FromSeconds(15);
+
+ private readonly ManualResetEventSlim waiter;
+ private readonly string message;
+ private readonly TimeSpan waitTimeout;
+
+ public InSync([CallerMemberName] string message = null, TimeSpan? waitTimeout = null)
+ {
+ this.message = message;
+ this.waitTimeout = waitTimeout.GetValueOrDefault(DefaultWaitTimeout);
+ this.waiter = new ManualResetEventSlim();
+ }
+
+ public void Proceed()
+ {
+ this.waiter.Set();
+ }
+
+ public void Dispose()
+ {
+ Assert.True(this.waiter.Wait(Debugger.IsAttached ? Timeout.InfiniteTimeSpan : this.waitTimeout), $"Took too long to do '{this.message}'");
+ }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/Helpers/SlackMother.cs b/SlackAPI.Tests/Helpers/SlackMother.cs
new file mode 100644
index 00000000..70f750ef
--- /dev/null
+++ b/SlackAPI.Tests/Helpers/SlackMother.cs
@@ -0,0 +1,253 @@
+namespace SlackAPI.Tests.Helpers
+{
+ public class SlackMother
+ {
+ public static IBlock[] SomeBlocks => new IBlock[]
+ {
+ new ContextBlock
+ {
+ elements = new IElement[]{
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = ""
+
+ }
+ }
+ },
+ new SectionBlock
+ {
+ text = new Text
+ {
+ type = TextTypes.Markdown,
+ text = ""
+ },
+ accessory = new ImageElement()
+ {
+ image_url = "https://imgs.xkcd.com/comics/exploits_of_a_mom.png",
+ alt_text = "Required for image elements"
+ }
+ },
+ new DividerBlock(),
+ new SectionBlock
+ {
+ fields = new []
+ {
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = "*Priority*\nHigh"
+ },
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = "*Priority*\nHigh"
+ },
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = "*Priority*\nHigh"
+ },
+ new Text
+ {
+ type = TextTypes.PlainText,
+ text = "*Priority*\nHigh"
+ }
+ }
+ }
+ };
+
+ public static IBlock[] SomeBlocksWithActions => new IBlock[]
+ {
+ new ContextBlock
+ {
+ elements = new IElement[]{
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = ""
+ }
+ },
+
+ },
+ new SectionBlock
+ {
+ text = new Text
+ {
+ type = TextTypes.Markdown,
+ text = ""
+ },
+ accessory = new OverflowElement
+ {
+ options = new []
+ {
+ new Option
+ {
+ text = new Text
+ {
+ type = TextTypes.PlainText,
+ text = "Option 1 Text"
+ },
+ value = "option 1"
+ },
+ new Option
+ {
+ text = new Text
+ {
+ type = TextTypes.PlainText,
+ text = "Option 2 Text"
+ },
+ value = "option 2"
+ },
+ }
+ }
+ },
+ new DividerBlock(),
+ new SectionBlock
+ {
+ fields = new []
+ {
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = "*Priority*\nHigh"
+ },
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = "*Priority*\nHigh"
+ },
+ new Text
+ {
+ type = TextTypes.Markdown,
+ text = "*Priority*\nHigh"
+ },
+ new Text
+ {
+ type = TextTypes.PlainText,
+ text = "*Priority*\nHigh"
+ }
+ }
+ },
+ new SectionBlock
+ {
+ text = new Text
+ {
+ text = "Pick a date"
+ },
+ accessory = new Element
+ {
+ type = ElementTypes.DatePicker,
+ initial_date = "1977-05-25",
+ placeholder = new Text
+ {
+ text = "Select a date"
+ }
+ }
+ },
+ new ActionsBlock
+ {
+ block_id = "Optional unique identifier for a block",
+ elements = new IElement[]
+ {
+ new ButtonElement
+ {
+ text = new Text
+ {
+ text = "Button 1 Text"
+ },
+ value = "Button 1",
+ style = ButtonStyles.Danger,
+ confirm = new Confirm
+ {
+ title = new Text
+ {
+ text = "Are you sure?"
+ },
+ text = new Text
+ {
+ text = "Did you press Button 1?"
+ },
+ confirm = new Text
+ {
+ text = "I did"
+ },
+ deny = new Text
+ {
+ text = "I didn't"
+ }
+ }
+ },
+ new ButtonElement
+ {
+ text = new Text
+ {
+ text = "Button 2 Text"
+ },
+ value = "Button 2",
+ },
+ new ButtonElement
+ {
+ text = new Text
+ {
+ text = "Button 3 Text"
+ },
+ value = "Button 3",
+ style = ButtonStyles.Primary,
+ }
+ }
+ }
+ };
+ public static Attachment[] SomeAttachments => new[]
+ {
+ new Attachment()
+ {
+ fallback = "Required plain-text summary of the attachment.",
+ color = "#36a64f",
+ pretext = "Optional text that appears above the attachment block",
+ author_name = "Bobby Tables",
+ author_link = "http://flickr.com/bobby/",
+ author_icon = "http://flickr.com/icons/bobby.jpg",
+ title = "Slack API Documentation",
+ title_link = "https://api.slack.com/",
+ text = "Optional text that appears within the attachment",
+ fields = new[]
+ {
+ new Field() { title = "Priority", value = "High", @short = false },
+ new Field() { title = "Priority", value = "High", @short = true },
+ new Field() { title = "Priority", value = "High", @short = true }
+ }
+ }
+ };
+
+ public static Attachment[] SomeAttachmentsWithActions => new[]
+ {
+ new Attachment()
+ {
+ fallback = "Required plain-text summary of the attachment.",
+ color = "#36a64f",
+ pretext = "Optional text that appears above the attachment block",
+ author_name = "Bobby Tables",
+ author_link = "http://flickr.com/bobby/",
+ author_icon = "http://flickr.com/icons/bobby.jpg",
+ title = "Slack API Documentation",
+ title_link = "https://api.slack.com/",
+ text = "Optional text that appears within the attachment",
+ fields = new[]
+ {
+ new Field() { title = "Priority", value = "High", @short = false },
+ new Field() { title = "Priority", value = "High", @short = true },
+ new Field() { title = "Priority", value = "High", @short = true }
+ },
+ actions = new []
+ {
+ new AttachmentAction("Button 1", "Button 1 Text") ,
+ new AttachmentAction("Button 2", "Button 2 Text") {style = "primary"},
+ new AttachmentAction("Button 3", "Button 3 Text") {style = "danger"},
+ new AttachmentAction("Button 4", "Button 4 Text") {style = "danger", confirm = new ActionConfirm {text = "Are you sure?????"} },
+ new AttachmentAction("Button 5", "Button 5 Text") {style = "danger", confirm = new ActionConfirm {text = "Do you really want to do this", dismiss_text = "No I don't", ok_text = "Sure I do", title = "Just checking"} }
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/JoinDirectMessageChannel.cs b/SlackAPI.Tests/JoinDirectMessageChannel.cs
new file mode 100644
index 00000000..237ede30
--- /dev/null
+++ b/SlackAPI.Tests/JoinDirectMessageChannel.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Linq;
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class JoinDirectMessageChannel
+ {
+ private readonly IntegrationFixture fixture;
+
+ public JoinDirectMessageChannel(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+
+ [Fact]
+ public void ShouldJoinDirectMessageChannel()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ JoinDirectMessageChannelResponse actual = null;
+
+ string userName = this.fixture.Config.DirectMessageUser;
+ string user = client.Users.First(x => x.name.Equals(userName, StringComparison.OrdinalIgnoreCase)).id;
+
+ // when
+ using (var sync = new InSync(nameof(SlackClient.JoinDirectMessageChannel)))
+ {
+ client.JoinDirectMessageChannel(response =>
+ {
+ actual = response;
+ sync.Proceed();;
+ }, user);
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while joining user channel");
+ Assert.NotEmpty(actual.channel.id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/PostMessage.cs b/SlackAPI.Tests/PostMessage.cs
new file mode 100644
index 00000000..9766660a
--- /dev/null
+++ b/SlackAPI.Tests/PostMessage.cs
@@ -0,0 +1,125 @@
+using System;
+using SlackAPI.RPCMessages;
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using System.Linq;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class PostMessage
+ {
+ private readonly IntegrationFixture fixture;
+
+ public PostMessage(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+
+ [Fact]
+ public void SimpleMessageDelivery()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ PostMessageResponse actual = null;
+
+ // when
+ using (var sync = new InSync(nameof(SlackClient.PostMessage)))
+ {
+ client.PostMessage(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ "Hi there!");
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ Assert.Equal("Hi there!", actual.message.text);
+ Assert.Equal("message", actual.message.type);
+ }
+
+ [Fact]
+ public void Attachments()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ PostMessageResponse actual = null;
+
+ // when
+ using (var sync = new InSync(nameof(SlackClient.PostMessage)))
+ {
+ client.PostMessage(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ string.Empty,
+ attachments: SlackMother.SomeAttachments);
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ }
+
+ [Fact]
+ public void AttachmentsWithActions()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ PostMessageResponse actual = null;
+
+ // when
+ using (var sync = new InSync())
+ {
+ client.PostMessage(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ string.Empty,
+ attachments: SlackMother.SomeAttachmentsWithActions);
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ }
+
+ [Fact]
+ public void PostEphemeralMessage()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ PostEphemeralResponse actual = null;
+
+ string userName = this.fixture.Config.DirectMessageUser;
+ string userId = client.Users.First(x => x.name.Equals(userName, StringComparison.OrdinalIgnoreCase)).id;
+
+ // when
+ using (var sync = new InSync(nameof(SlackClient.PostEphemeralMessage)))
+ {
+ client.PostEphemeralMessage(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ "Hi there!",
+ userId);
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ Assert.Null(actual.error);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/SlackAPI.Tests.csproj b/SlackAPI.Tests/SlackAPI.Tests.csproj
new file mode 100644
index 00000000..bc4dc3b4
--- /dev/null
+++ b/SlackAPI.Tests/SlackAPI.Tests.csproj
@@ -0,0 +1,43 @@
+
+
+
+ net452;netcoreapp2.1
+ Library
+ Full
+ SlackAPI.Tests
+ SlackAPI.Tests
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+ Properties\GlobalAssemblyInfo.cs
+
+
+
+
+ $(DefineConstants);RELEASE
+
+
+
diff --git a/SlackAPI.Tests/Timestamp.cs b/SlackAPI.Tests/Timestamp.cs
new file mode 100644
index 00000000..8f9e2ef0
--- /dev/null
+++ b/SlackAPI.Tests/Timestamp.cs
@@ -0,0 +1,34 @@
+using Newtonsoft.Json;
+using System;
+using System.IO;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Unit tests")]
+ public class Timestamp
+ {
+ [Theory]
+ [InlineData("12345.000000")]
+ [InlineData("12345.900000")]
+ [InlineData("12345.123456")]
+ [InlineData("12345.123450")]
+ [InlineData("12345.12345")]
+ [InlineData("12345")]
+ public void TestTimestampConversion(string originalTimestamp)
+ {
+ // Arrange
+ JavascriptDateTimeConverter converter = new JavascriptDateTimeConverter();
+ var jsonReader = new JsonTextReader(new StringReader($"\"{originalTimestamp}\""));
+ jsonReader.Read();
+
+ // Act
+ DateTime timestampDateTime = (DateTime)converter.ReadJson(jsonReader, null, null, null);
+ var newTimestamp = timestampDateTime.ToProperTimeStamp();
+
+ // Assert
+ Assert.Equal(double.Parse(originalTimestamp), double.Parse(newTimestamp));
+ Assert.Equal(6, newTimestamp.Substring(newTimestamp.IndexOf(".") + 1).Length);
+ }
+ }
+}
diff --git a/SlackAPI.Tests/Update.cs b/SlackAPI.Tests/Update.cs
new file mode 100644
index 00000000..a5ed7151
--- /dev/null
+++ b/SlackAPI.Tests/Update.cs
@@ -0,0 +1,80 @@
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class Update
+ {
+ private readonly IntegrationFixture fixture;
+
+ public Update(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+
+ [Fact]
+ public void SimpleUpdate()
+ {
+ // given
+ var client = this.fixture.UserClient;
+ var messageId = PostedMessage(client);
+ UpdateResponse actual = null;
+
+ // when
+ using (var sync = new InSync(nameof(SlackClient.Update)))
+ {
+ client.Update(
+ response =>
+ {
+ actual = response;
+ sync.Proceed();
+ },
+ messageId,
+ this.fixture.Config.TestChannel,
+ "[changed]",
+ attachments: SlackMother.SomeAttachments,
+ as_user: true);
+ }
+
+ // then
+ Assert.True(actual.ok, "Error while posting message to channel. ");
+ Assert.Equal("[changed]", actual.message.text);
+ Assert.Equal("message", actual.message.type);
+ }
+
+ private string PostedMessage(SlackSocketClient client)
+ {
+ string messageId = null;
+ using (var sync = new InSync(nameof(SlackClient.PostMessage)))
+ {
+ client.PostMessage(
+ response =>
+ {
+ messageId = response.ts;
+ Assert.True(response.ok, "Error while posting message to channel. ");
+ sync.Proceed();
+ },
+ this.fixture.Config.TestChannel,
+ "Hi there!",
+ as_user: true);
+ }
+ return messageId;
+ }
+
+ [Fact]
+ public void UpdatePresence()
+ {
+ var client = this.fixture.UserClient;
+ using (var sync = new InSync(nameof(SlackClient.EmitPresence)))
+ {
+ client.EmitPresence((presence) =>
+ {
+ presence.AssertOk();
+ sync.Proceed();
+ }, Presence.away);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/UploadFile.cs b/SlackAPI.Tests/UploadFile.cs
new file mode 100644
index 00000000..28a5b943
--- /dev/null
+++ b/SlackAPI.Tests/UploadFile.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class UploadFile
+ {
+ private readonly IntegrationFixture fixture;
+
+ public UploadFile(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+
+ [Fact]
+ public void UploadFile_Succeeds()
+ {
+ // Arrange
+ const int FileSize = 500;
+ const string FileName = "MyFile.bin";
+ byte[] data = new byte[FileSize];
+
+ // Act
+ FileUploadResponse fileUploadResponse = null;
+ using (var sync = new InSync(nameof(SlackClient.UploadFile)))
+ {
+ this.fixture.UserClient.UploadFile(c =>
+ {
+ fileUploadResponse = c;
+ Assert.True(c.ok);
+ sync.Proceed();
+ },
+ data, FileName, new[] {this.fixture.Config.TestChannel});
+ }
+
+ // Assert
+ using (var sync = new InSync(nameof(SlackClient.UploadFile)))
+ {
+ this.fixture.UserClient.GetFileInfo(c =>
+ {
+ Assert.True(c.ok);
+ Assert.Equal(FileSize, c.file.size);
+ Assert.Equal(FileName, c.file.name);
+ Assert.Equal(new[] { this.fixture.Config.TestChannel}, c.file.channels);
+ sync.Proceed();
+ }, fileUploadResponse.file.id);
+ }
+ }
+
+ [Fact]
+ public async Task UploadFileAsync_Succeeds()
+ {
+ // Arrange
+ const int FileSize = 500;
+ const string FileName = "MyFile.bin";
+ byte[] data = new byte[FileSize];
+
+ // Act
+ var fileUploadResponse = await this.fixture.UserClientAsync.UploadFileAsync(data, FileName, new[] { this.fixture.Config.TestChannel });
+
+ // Assert
+ Assert.True(fileUploadResponse.ok);
+ var fileInfoResponse = await this.fixture.UserClientAsync.GetFileInfoAsync(fileUploadResponse.file.id);
+ Assert.True(fileInfoResponse.ok);
+ Assert.Equal(FileSize, fileInfoResponse.file.size);
+ Assert.Equal(FileName, fileInfoResponse.file.name);
+ Assert.Equal(new[] { this.fixture.Config.TestChannel}, fileInfoResponse.file.channels);
+ }
+ }
+}
diff --git a/SlackAPI.Tests/UserUIInteraction.cs b/SlackAPI.Tests/UserUIInteraction.cs
new file mode 100644
index 00000000..0ece63cf
--- /dev/null
+++ b/SlackAPI.Tests/UserUIInteraction.cs
@@ -0,0 +1,77 @@
+using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Threading;
+using OpenQA.Selenium;
+using OpenQA.Selenium.Chrome;
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+// Run UI tests on a single plateform to avoid Slack Captcha
+// (captcha is displayed when trying to login too often)
+#if NETFRAMEWORK
+ [Collection("Integration tests")]
+ public class UserUIInteraction
+ {
+ private readonly IntegrationFixture fixture;
+
+ public UserUIInteraction(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+
+ [Fact]
+ public void TestGetAccessToken()
+ {
+ var clientId = this.fixture.Config.ClientId;
+ var clientSecret = this.fixture.Config.ClientSecret;
+ var redirectUrl = this.fixture.Config.RedirectUrl;
+ var authUsername = this.fixture.Config.AuthUsername;
+ var authPassword = this.fixture.Config.AuthPassword;
+ var authWorkspace = this.fixture.Config.AuthWorkspace;
+
+ using (var driver = new ChromeDriver(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)))
+ {
+ driver.Navigate().GoToUrl($"https://{authWorkspace}.slack.com");
+
+ // Wait a bit to ensure we can properly fill username and password fields
+ Thread.Sleep(1000);
+ driver.FindElement(By.Id("email")).SendKeys(authUsername);
+ driver.FindElement(By.Id("password")).SendKeys(authPassword);
+ driver.FindElement(By.Id("signin_btn")).Click();
+
+ var slackClientHelpers = new SlackClientHelpers();
+ var uri = slackClientHelpers.GetAuthorizeUri(clientId, SlackScope.Identify);
+ driver.Navigate().GoToUrl(uri);
+ driver.FindElement(By.CssSelector("button[type='submit']")).Click();
+
+ var code = Regex.Match(driver.Url, "code=(?[^&]+)&state").Groups["code"].Value;
+
+ var accessTokenResponse = GetAccessToken(slackClientHelpers, clientId, clientSecret, redirectUrl, code);
+ Assert.True(accessTokenResponse.ok);
+ Assert.Contains("identify", accessTokenResponse.scope);
+ }
+ }
+
+ private AccessTokenResponse GetAccessToken(SlackClientHelpers slackClientHelpers, string clientId, string clientSecret, string redirectUri, string authCode)
+ {
+ AccessTokenResponse accessTokenResponse = null;
+
+ using (var sync = new InSync(nameof(slackClientHelpers.GetAccessToken)))
+ {
+ slackClientHelpers.GetAccessToken(response =>
+ {
+ accessTokenResponse = response;
+ sync.Proceed();
+
+ }, clientId, clientSecret, redirectUri, authCode);
+ }
+
+ return accessTokenResponse;
+ }
+ }
+#endif
+}
diff --git a/SlackAPI.Tests/Users.cs b/SlackAPI.Tests/Users.cs
new file mode 100644
index 00000000..3970f6aa
--- /dev/null
+++ b/SlackAPI.Tests/Users.cs
@@ -0,0 +1,45 @@
+using System.Linq;
+using SlackAPI.Tests.Configuration;
+using SlackAPI.Tests.Helpers;
+using Xunit;
+
+namespace SlackAPI.Tests
+{
+ [Collection("Integration tests")]
+ public class Users
+ {
+ private readonly IntegrationFixture fixture;
+
+ public Users(IntegrationFixture fixture)
+ {
+ this.fixture = fixture;
+ }
+ [Fact]
+ public void UserList()
+ {
+ var client = this.fixture.UserClient;
+ UserListResponse actual = null;
+ using (var sync = new InSync(nameof(SlackClient.UserLookup)))
+ {
+ client.GetUserList(response =>
+ {
+ actual = response;
+ sync.Proceed();
+ });
+ }
+
+ Assert.True(actual.ok, "Error while fetching user list.");
+ Assert.True(actual.members.Any());
+
+ // apparently deleted users do indeed have null values, so if the first user returned is deleted then there are failures.
+ var someMember = actual.members.Where(x => !x.deleted).First();
+ Assert.NotNull(someMember.id);
+ Assert.NotNull(someMember.color);
+ Assert.NotNull(someMember.real_name);
+ Assert.NotNull(someMember.name);
+ Assert.NotNull(someMember.team_id);
+ Assert.NotNull(someMember.tz);
+ Assert.NotNull(someMember.tz_label);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI.Tests/app.config b/SlackAPI.Tests/app.config
new file mode 100755
index 00000000..21f024a7
--- /dev/null
+++ b/SlackAPI.Tests/app.config
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SlackAPI.csproj b/SlackAPI.csproj
deleted file mode 100644
index c2d6854e..00000000
--- a/SlackAPI.csproj
+++ /dev/null
@@ -1,107 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {0C0A58A8-174E-4A4C-907B-C3569144D15D}
- Library
- Properties
- SlackAPI
- SlackAPI
- v4.5
- 512
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
- False
- ..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll
-
-
-
-
-
-
-
-
-
-
- ..\packages\WebSocket4Net.0.9\lib\net45\WebSocket4Net.dll
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/SlackAPI.sln b/SlackAPI.sln
index 2192a644..f3f74ac0 100644
--- a/SlackAPI.sln
+++ b/SlackAPI.sln
@@ -1,9 +1,20 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.21005.1
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30320.27
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlackAPI", "SlackAPI.csproj", "{0C0A58A8-174E-4A4C-907B-C3569144D15D}"
+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
@@ -11,12 +22,19 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {0C0A58A8-174E-4A4C-907B-C3569144D15D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0C0A58A8-174E-4A4C-907B-C3569144D15D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0C0A58A8-174E-4A4C-907B-C3569144D15D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0C0A58A8-174E-4A4C-907B-C3569144D15D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7EED3D9B-9B7A-49A4-AFBF-599153A47DDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7EED3D9B-9B7A-49A4-AFBF-599153A47DDA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7EED3D9B-9B7A-49A4-AFBF-599153A47DDA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7EED3D9B-9B7A-49A4-AFBF-599153A47DDA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DEFA9559-0F8F-4C38-9644-67A080EDC46D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEFA9559-0F8F-4C38-9644-67A080EDC46D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DEFA9559-0F8F-4C38-9644-67A080EDC46D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DEFA9559-0F8F-4C38-9644-67A080EDC46D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {367D3666-0DC5-4B8A-832F-57279BE4DCB0}
+ EndGlobalSection
EndGlobal
diff --git a/SlackAPI/Attachment.cs b/SlackAPI/Attachment.cs
new file mode 100644
index 00000000..64b28e1d
--- /dev/null
+++ b/SlackAPI/Attachment.cs
@@ -0,0 +1,59 @@
+namespace SlackAPI
+{
+ //See: https://api.slack.com/docs/attachments
+ public class Attachment
+ {
+ public string callback_id;
+ public string fallback;
+ public string color;
+ public string pretext;
+ public string author_name;
+ public string author_link;
+ public string author_icon;
+ public string title;
+ public string title_link;
+ public string text;
+ public Field[] fields;
+ public IBlock[] blocks;
+
+ public string image_url;
+ public string thumb_url;
+ public string[] mrkdwn_in;
+ public AttachmentAction[] actions;
+
+ public string footer;
+ public string footer_icon;
+ }
+
+ public class Field{
+ public string title;
+ public string value;
+ public bool @short;
+ }
+
+ //See: https://api.slack.com/docs/message-buttons#action_fields
+ public class AttachmentAction
+ {
+ public AttachmentAction(string name, string text)
+ {
+ this.name = name;
+ this.text = text;
+ }
+ public string name { get; }
+ public string text { get; }
+ public string style;
+ public string type = "button";
+ public string value;
+ public ActionConfirm confirm;
+ public string url;
+ }
+
+ //see: https://api.slack.com/docs/message-buttons#confirmation_fields
+ public class ActionConfirm
+ {
+ public string title;
+ public string text;
+ public string ok_text;
+ public string dismiss_text;
+ }
+}
diff --git a/SlackAPI/Block.cs b/SlackAPI/Block.cs
new file mode 100644
index 00000000..5b05099f
--- /dev/null
+++ b/SlackAPI/Block.cs
@@ -0,0 +1,236 @@
+namespace SlackAPI
+{
+ //see https://api.slack.com/reference/messaging/blocks
+ public class Block : IBlock
+ {
+ public string type { get; set; }
+ public string block_id { get; set; }
+ public Text text { get; set; }
+ public Element accessory { get; set; }
+ public Element[] elements { get; set; }
+ public Text title { get; set; }
+ public string image_url { get; set; }
+ public string alt_text { get; set; }
+ public Text[] fields { get; set; }
+ }
+ public class SectionBlock : IBlock
+ {
+ public string type { get; } = BlockTypes.Section;
+ public string block_id { get; set; }
+ public Text text { get; set; }
+ public IElement accessory { get; set; }
+ public Text[] fields { get; set; }
+ }
+ public class DividerBlock : IBlock
+ {
+ public string type { get; } = BlockTypes.Divider;
+ public string block_id { get; set; }
+ }
+ public class ImageBlock : IBlock
+ {
+ public string type { get; } = BlockTypes.Image;
+ public string block_id { get; set; }
+ public Text title { get; set; }
+ public string image_url { get; set; }
+ public string alt_text { get; set; }
+ }
+ public class ActionsBlock : IBlock
+ {
+ public string type { get; } = BlockTypes.Actions;
+ public string block_id { get; set; }
+ public IElement[] elements { get; set; }
+ }
+ public class ContextBlock : IBlock
+ {
+ public string type { get; } = BlockTypes.Context;
+ 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;
+ public string text { get; set; }
+ public bool? emoji { get; set; }
+ public bool? verbatim { get; set; }
+ }
+
+ public class Option
+ {
+ public Text text { get; set; }
+ public string value { get; set; }
+ }
+
+ public class OptionGroups
+ {
+ public Text label { get; set; }
+ public Option[] options { get; set; }
+ }
+
+ public class Confirm
+ {
+ public Text title { get; set; }
+ public Text text { get; set; }
+ public Text confirm { get; set; }
+ public Text deny { get; set; }
+ }
+
+ public class Element : IElement
+ {
+ public string type { get; set; }
+ public string action_id { get; set; }
+ public Text text { get; set; }
+ public string value { get; set; }
+ public Text placeholder { get; set; }
+ public Option[] options { get; set; }
+ public OptionGroups[] option_groups { get; set; }
+ public string image_url { get; set; }
+ public string alt_text { get; set; }
+ public string url { get; set; }
+ public string initial_date { get; set; }
+ public string initial_user { get; set; }
+ public string initial_channel { get; set; }
+ public string initial_conversation { get; set; }
+ public string initial_option { get; set; }
+ public int? min_query_length { get; set; }
+ public Confirm confirm { get; set; }
+ public string style { get; set; }
+ }
+ public class ImageElement : IElement
+ {
+ public string type { get; } = ElementTypes.Image;
+ public string image_url { get; set; }
+ public string alt_text { get; set; }
+ }
+ public class ButtonElement : IElement
+ {
+ public string type { get; } = ElementTypes.Button;
+ public string action_id { get; set; }
+ public Text text { get; set; }
+ public string value { get; set; }
+ public Text placeholder { get; set; }
+ public Option[] options { get; set; }
+ public OptionGroups[] option_groups { get; set; }
+ public string url { get; set; }
+ public Confirm confirm { get; set; }
+ public string style { get; set; }
+ }
+ public class StaticSelectElement : IElement
+ {
+ public string type { get; } = ElementTypes.StaticSelect;
+ public string action_id { get; set; }
+ public Text placeholder { get; set; }
+ public Option[] options { get; set; }
+ public OptionGroups[] option_groups { get; set; }
+ public string initial_option { get; set; }
+ public Confirm confirm { get; set; }
+ }
+ public class ExternalSelectElement : IElement
+ {
+ public string type { get; } = ElementTypes.ExternalSelect;
+ public string action_id { get; set; }
+ public Text placeholder { get; set; }
+ public string initial_option { get; set; }
+ public int min_query_length { get; set; }
+ public Confirm confirm { get; set; }
+ }
+
+
+ public class UserSelectElement : IElement
+ {
+ public string type { get; } = ElementTypes.UserSelect;
+ public string action_id { get; set; }
+ public Text placeholder { get; set; }
+ public string initial_user { get; set; }
+ public Confirm confirm { get; set; }
+ }
+ public class ConversationSelectElement : IElement
+ {
+ public string type { get; } = ElementTypes.ChannelSelect;
+ public string action_id { get; set; }
+ public Text placeholder { get; set; }
+ public string initial_conversation { get; set; }
+ public Confirm confirm { get; set; }
+ }
+ public class ChannelSelectElement : IElement
+ {
+ public string type { get; } = ElementTypes.ChannelSelect;
+ public string action_id { get; set; }
+ public Text placeholder { get; set; }
+ public string initial_channel { get; set; }
+ public Confirm confirm { get; set; }
+ }
+ public class OverflowElement : IElement
+ {
+ public string type { get; } = ElementTypes.Overflow;
+ public string action_id { get; set; }
+ public Option[] options { get; set; }
+ public Confirm confirm { get; set; }
+ }
+
+ public class DatePickerElement : IElement
+ {
+ public string type { get; } = ElementTypes.DatePicker;
+ public string action_id { get; set; }
+ public Text placeholder { get; set; }
+ public string initial_date { get; set; }
+ 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";
+ public const string Danger = "danger";
+ }
+
+ public static class BlockTypes
+ {
+ public const string Section = "section";
+ public const string Divider = "divider";
+ 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
+ {
+ public const string Markdown = "mrkdwn";
+ public const string PlainText = "plain_text";
+ }
+
+ public static class ElementTypes
+ {
+ public const string Image = "image";
+ public const string Button = "button";
+ public const string StaticSelect = "static_select";
+ public const string ExternalSelect = "external_select";
+ public const string UserSelect = "users_select";
+ public const string ChannelSelect = "channel_select";
+ public const string ConversationSelect = "conversation_select";
+ public const string Overflow = "overflow";
+ public const string DatePicker = "datepicker";
+ }
+
+ public interface IElement { }
+
+ public interface IBlock { }
+
+}
diff --git a/SlackAPI/Bot.cs b/SlackAPI/Bot.cs
new file mode 100644
index 00000000..c11ae22d
--- /dev/null
+++ b/SlackAPI/Bot.cs
@@ -0,0 +1,12 @@
+namespace SlackAPI
+{
+ public class Bot
+ {
+ public string id;
+ public bool deleted;
+ public string name;
+ public string updated;
+ public string app_id;
+ public ProfileIcons icons;
+ }
+}
diff --git a/Channel.cs b/SlackAPI/Channel.cs
similarity index 71%
rename from Channel.cs
rename to SlackAPI/Channel.cs
index bdb562ac..c45d1c23 100644
--- a/Channel.cs
+++ b/SlackAPI/Channel.cs
@@ -6,28 +6,25 @@
namespace SlackAPI
{
- public class Channel
+ public class Channel : Conversation
{
- public string id;
-
public string name;
public string creator;
- public DateTime created;
- public DateTime last_read;
+ public string user;
public bool is_archived;
public bool is_member;
public bool is_general;
- public bool is_starred;
+ 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'; } }
public int num_members;
- public int unread_count;
public OwnedStampedMessage topic;
public OwnedStampedMessage purpose;
- public Message latest;
-
public string[] members;
}
}
diff --git a/ContextMessage.cs b/SlackAPI/ContextMessage.cs
similarity index 65%
rename from ContextMessage.cs
rename to SlackAPI/ContextMessage.cs
index 27e7fd52..764c17a3 100644
--- a/ContextMessage.cs
+++ b/SlackAPI/ContextMessage.cs
@@ -8,16 +8,16 @@ namespace SlackAPI
{
public class ContextMessage : Message
{
- public string type;
+ //public string type;
///
/// Only contains partial channel data.
///
- public Channel channel;
- public string user;
- public string username;
- public DateTime ts;
- public string text;
- public string permalink;
+ //public Channel channel;
+ //public string user;
+ //public string username;
+ //public DateTime ts;
+ //public string text;
+ //public string permalink;
public Message previous_2;
public Message previous;
public Message next;
diff --git a/DirectMessageConversation.cs b/SlackAPI/Conversation.cs
similarity index 77%
rename from DirectMessageConversation.cs
rename to SlackAPI/Conversation.cs
index 57b0aad9..def8d1a2 100644
--- a/DirectMessageConversation.cs
+++ b/SlackAPI/Conversation.cs
@@ -6,16 +6,14 @@
namespace SlackAPI
{
- public class DirectMessageConversation
+ public class Conversation
{
public string id;
- public string user;
public DateTime created;
- public bool is_user_deleted;
+ public DateTime last_read;
public bool is_open;
public bool is_starred;
- public DateTime last_read;
- public Message latest;
public int unread_count;
+ public Message latest;
}
}
diff --git a/SlackAPI/Dialog.cs b/SlackAPI/Dialog.cs
new file mode 100644
index 00000000..020f9dcf
--- /dev/null
+++ b/SlackAPI/Dialog.cs
@@ -0,0 +1,80 @@
+namespace SlackAPI
+{
+ //see https://api.slack.com/dialogs
+
+ public class Dialog
+ {
+ public string callback_id { get; set; }
+ public string title { get; set; }
+ public string submit_label { get; set; }
+ public bool? notify_on_cancel { get; set; }
+ public string state { get; set; }
+ public Element[] elements { get; set; }
+
+ public abstract class Element
+ {
+ public string label { get; set; }
+ public string name { get; set; }
+ public string placeholder { get; set; }
+ public bool? optional { get; set; }
+ public string value { get; set; }
+ }
+
+ public class TextElement : Element
+ {
+ public string type { get; } = "text";
+ public string subtype { get; set; }
+ public int? max_length { get; set; }
+ public int? min_length { get; set; }
+ public string hint { get; set; }
+
+ }
+
+ public class TextAreaElement : Element
+ {
+ public string type { get; } = "textarea";
+ public int? max_length { get; set; }
+ public int? min_length { get; set; }
+ public string hint { get; set; }
+
+ }
+
+ public class SelectElement : Element
+ {
+ public string type { get; } = "select";
+ public string data_source { get; set; }
+ public Option[] options { get; set; }
+ public OptionGroup[] option_groups { get; set; }
+ public Option[] selected_options { get; set; }
+ public int? min_query_length { get; set; }
+ }
+
+ public class Option
+ {
+ public string label { get; set; }
+ public string value { get; set; }
+ }
+
+ public class OptionGroup
+ {
+ public string label { get; set; }
+ public Option[] options { get; set; }
+ }
+
+ public static class DataSourceTypes
+ {
+ public static string Users = "users";
+ public static string Channels = "channels";
+ public static string Conversations = "conversations";
+ public static string External = "external";
+ }
+
+ public static class TextElementSubTypes
+ {
+ public static string Email = "email";
+ public static string Number = "number";
+ public static string Telephone = "telephone";
+ public static string Url = "url";
+ }
+ }
+}
\ No newline at end of file
diff --git a/PresenseResponse.cs b/SlackAPI/DirectMessageConversation.cs
similarity index 51%
rename from PresenseResponse.cs
rename to SlackAPI/DirectMessageConversation.cs
index f8189cad..2241fe6b 100644
--- a/PresenseResponse.cs
+++ b/SlackAPI/DirectMessageConversation.cs
@@ -6,13 +6,9 @@
namespace SlackAPI
{
- [RequestPath("presense.set")]
- public class PresenceResponse : Response
+ public class DirectMessageConversation : Conversation
{
- }
- public enum Presence
- {
- Active,
- Away
+ public string user;
+ public bool is_user_deleted;
}
}
diff --git a/SlackAPI/Extensions.cs b/SlackAPI/Extensions.cs
new file mode 100644
index 00000000..02d8d6e4
--- /dev/null
+++ b/SlackAPI/Extensions.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using Newtonsoft.Json;
+
+namespace SlackAPI
+{
+ public static class Extensions
+ {
+ internal static readonly IList Converters = new List { new JavascriptDateTimeConverter() };
+
+ ///
+ /// Converts to a propert JavaScript timestamp interpretted by Slack. Also handles converting to UTC.
+ ///
+ ///
+ ///
+ public static string ToProperTimeStamp(this DateTime that, bool toUTC = true)
+ {
+ if (toUTC)
+ {
+ return ((that.ToUniversalTime().Ticks - 621355968000000000m) / 10000000m).ToString("F6", CultureInfo.InvariantCulture);
+ }
+ else
+ return that.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString(CultureInfo.InvariantCulture);
+ }
+
+ public static K Deserialize(this string data)
+ where K : class
+ {
+ return JsonConvert.DeserializeObject(data, CreateSettings());
+ }
+
+ public static object Deserialize(this string data, Type type)
+ {
+ return JsonConvert.DeserializeObject(data, type, CreateSettings());
+ }
+
+ private static JsonSerializerSettings CreateSettings()
+ {
+ JsonSerializerSettings settings = new JsonSerializerSettings();
+ settings.Converters = Converters;
+
+ return settings;
+ }
+ }
+}
diff --git a/File.cs b/SlackAPI/File.cs
similarity index 81%
rename from File.cs
rename to SlackAPI/File.cs
index 62b4d622..3f1dd33b 100644
--- a/File.cs
+++ b/SlackAPI/File.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace SlackAPI
{
@@ -13,6 +9,7 @@ namespace SlackAPI
public class File
{
public string id;
+ public DateTime created;
public DateTime timestamp;
public string name;
@@ -26,9 +23,10 @@ public class File
public bool editable;
public bool is_external;
public string external_type;
-
+ public string username;
+
///
- /// Looks it's in bytes?
+ /// File size in bytes
///
public int size;
@@ -39,12 +37,17 @@ public class File
public string thumb_64;
public string thumb_80;
+ public string thumb_160;
public string thumb_360;
public string thumb_360_gif;
public int thumb_360_w;
public int thumb_360_h;
+ public string thumb_480;
+ public int thumb_480_w;
+ public int thumb_480_h;
public string permalink;
+ public string permalink_public;
public string edit_link;
public string preview;
public string preview_highlight;
@@ -53,12 +56,17 @@ public class File
public bool is_public;
public bool public_url_shared;
+ public bool display_as_bot;
public string[] channels;
public string[] groups;
public string[] ims;
public FileComment initial_comment;
+ public int comments_count;
public int num_stars;
public bool is_starred;
+ public string[] pinned_to;
+
+ public Reaction[] reactions;
}
[Flags]
diff --git a/SlackAPI/JavascriptDateTimeConverter.cs b/SlackAPI/JavascriptDateTimeConverter.cs
new file mode 100644
index 00000000..438df1a8
--- /dev/null
+++ b/SlackAPI/JavascriptDateTimeConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ internal class JavascriptDateTimeConverter : Newtonsoft.Json.JsonConverter
+ {
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
+ }
+
+ public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ decimal value = decimal.Parse(reader.Value.ToString(), CultureInfo.InvariantCulture);
+ DateTime res = new DateTime(621355968000000000 + (long)(value * 10000000m)).ToLocalTime();
+ System.Diagnostics.Debug.Assert(
+ Decimal.Equals(
+ Decimal.Parse(res.ToProperTimeStamp(), CultureInfo.InvariantCulture),
+ Decimal.Parse(reader.Value.ToString(), CultureInfo.InvariantCulture)),
+ "Precision loss :(");
+ return res;
+ }
+
+ public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
+ {
+ //Not sure if this is correct :D
+ writer.WriteValue(((DateTime)value).Subtract(new DateTime(1970, 1, 1)).TotalSeconds);
+ }
+ }
+}
diff --git a/Message.cs b/SlackAPI/Message.cs
similarity index 72%
rename from Message.cs
rename to SlackAPI/Message.cs
index 81c9ba27..6077ec6c 100644
--- a/Message.cs
+++ b/SlackAPI/Message.cs
@@ -6,9 +6,9 @@
namespace SlackAPI
{
- public class Message
+ public class Message : SlackSocketMessage
{
- public string type;
+ public string channel;
public DateTime ts;
public string user;
///
@@ -16,8 +16,12 @@ public class Message
///
public string username;
public string text;
+ public Attachment[] attachments;
public bool is_starred;
public string permalink;
+ public Reaction[] reactions;
//Wibblr? Not really sure what this applies to. :<
+
+ public DateTime? thread_ts;
}
}
diff --git a/MimeTypes.cs b/SlackAPI/MimeTypes.cs
similarity index 100%
rename from MimeTypes.cs
rename to SlackAPI/MimeTypes.cs
diff --git a/OwnedStampedMessage.cs b/SlackAPI/OwnedStampedMessage.cs
similarity index 100%
rename from OwnedStampedMessage.cs
rename to SlackAPI/OwnedStampedMessage.cs
diff --git a/Preferences.cs b/SlackAPI/Preferences.cs
similarity index 96%
rename from Preferences.cs
rename to SlackAPI/Preferences.cs
index e1b91b94..f0931eec 100644
--- a/Preferences.cs
+++ b/SlackAPI/Preferences.cs
@@ -53,9 +53,9 @@ public class Preferences
public bool mark_msgs_read_immediately;
public string tz;
public string emoji_mode;
- public string hightlight_words;
+ public string highlight_words;
//public string newxp_slackbot_step; //I don't even...
- public SearchSort search_sort;
+ public string search_sort;
public string push_loud_channels;
public string push_mention_channels;
public string push_loud_channels_set;
@@ -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/ProfileIcons.cs b/SlackAPI/ProfileIcons.cs
new file mode 100644
index 00000000..db849a48
--- /dev/null
+++ b/SlackAPI/ProfileIcons.cs
@@ -0,0 +1,12 @@
+namespace SlackAPI
+{
+ public class ProfileIcons
+ {
+ public string image_24;
+ public string image_32;
+ public string image_48;
+ public string image_72;
+ public string image_192;
+ public string image_512;
+ }
+}
diff --git a/SlackAPI/Properties/InternalsVisibleTo.cs b/SlackAPI/Properties/InternalsVisibleTo.cs
new file mode 100644
index 00000000..d747d594
--- /dev/null
+++ b/SlackAPI/Properties/InternalsVisibleTo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly:InternalsVisibleTo("SlackAPI.Tests")]
\ No newline at end of file
diff --git a/SlackAPI/RPCMessages/AccessTokenResponse.cs b/SlackAPI/RPCMessages/AccessTokenResponse.cs
new file mode 100644
index 00000000..9c4266bb
--- /dev/null
+++ b/SlackAPI/RPCMessages/AccessTokenResponse.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("oauth.access")]
+ public class AccessTokenResponse : Response
+ {
+ public string access_token;
+ public string scope;
+ public string team_name;
+ public string team_id { get; set; }
+ public BotTokenResponse bot;
+ public IncomingWebhook incoming_webhook { get; set; }
+ }
+
+ public class BotTokenResponse
+ {
+ public string emoji;
+ public string image_24;
+ public string image_32;
+ public string image_48;
+ public string image_72;
+ public string image_192;
+
+ public bool deleted;
+ public UserProfile icons;
+ public string id;
+ public string name;
+ public string bot_user_id;
+ public string bot_access_token;
+ }
+
+ public class IncomingWebhook
+ {
+ public string channel { get; set; }
+ public string channel_id { get; set; }
+ public string configuration_url { get; set; }
+ public string url { get; set; }
+ }
+}
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/AuthSigninResponse.cs b/SlackAPI/RPCMessages/AuthSigninResponse.cs
similarity index 100%
rename from AuthSigninResponse.cs
rename to SlackAPI/RPCMessages/AuthSigninResponse.cs
diff --git a/AuthStartResponse.cs b/SlackAPI/RPCMessages/AuthStartResponse.cs
similarity index 90%
rename from AuthStartResponse.cs
rename to SlackAPI/RPCMessages/AuthStartResponse.cs
index 6e2e044d..93dde36d 100644
--- a/AuthStartResponse.cs
+++ b/SlackAPI/RPCMessages/AuthStartResponse.cs
@@ -8,7 +8,6 @@ public class AuthStartResponse : Response
public string email;
public string domain;
public UserTeamCombo[] users;
- //string[] teams; //Not sure?
///
/// Path to create a new team?
///
diff --git a/AuthTestResponse.cs b/SlackAPI/RPCMessages/AuthTestResponse.cs
similarity index 100%
rename from AuthTestResponse.cs
rename to SlackAPI/RPCMessages/AuthTestResponse.cs
diff --git a/SlackAPI/RPCMessages/ChannelCreateResponse.cs b/SlackAPI/RPCMessages/ChannelCreateResponse.cs
new file mode 100644
index 00000000..b5273651
--- /dev/null
+++ b/SlackAPI/RPCMessages/ChannelCreateResponse.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace SlackAPI
+{
+ [RequestPath("channels.create")]
+ public class ChannelCreateResponse : Response
+ {
+ public Channel channel;
+ }
+}
diff --git a/SlackAPI/RPCMessages/ChannelInviteResponse.cs b/SlackAPI/RPCMessages/ChannelInviteResponse.cs
new file mode 100644
index 00000000..41b2108b
--- /dev/null
+++ b/SlackAPI/RPCMessages/ChannelInviteResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("channels.invite")]
+ public class ChannelInviteResponse : Response
+ {
+ public Channel channel;
+ }
+}
diff --git a/ChannelListResponse.cs b/SlackAPI/RPCMessages/ChannelListResponse.cs
similarity index 100%
rename from ChannelListResponse.cs
rename to SlackAPI/RPCMessages/ChannelListResponse.cs
diff --git a/ChannelMessageHistory.cs b/SlackAPI/RPCMessages/ChannelMessageHistory.cs
similarity index 100%
rename from ChannelMessageHistory.cs
rename to SlackAPI/RPCMessages/ChannelMessageHistory.cs
diff --git a/SlackAPI/RPCMessages/ChannelSetTopicResponse.cs b/SlackAPI/RPCMessages/ChannelSetTopicResponse.cs
new file mode 100644
index 00000000..540f1d06
--- /dev/null
+++ b/SlackAPI/RPCMessages/ChannelSetTopicResponse.cs
@@ -0,0 +1,8 @@
+namespace SlackAPI
+{
+ [RequestPath("channels.setTopic")]
+ public class ChannelSetTopicResponse : Response
+ {
+ public string topic;
+ }
+}
\ No newline at end of file
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/DeletedResponse.cs b/SlackAPI/RPCMessages/DeletedResponse.cs
new file mode 100644
index 00000000..89b5af83
--- /dev/null
+++ b/SlackAPI/RPCMessages/DeletedResponse.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("chat.delete")]
+ public class DeletedResponse : Response
+ {
+ public string channel;
+ public DateTime ts;
+ }
+}
diff --git a/SlackAPI/RPCMessages/DialogOpenResponse.cs b/SlackAPI/RPCMessages/DialogOpenResponse.cs
new file mode 100644
index 00000000..926a339a
--- /dev/null
+++ b/SlackAPI/RPCMessages/DialogOpenResponse.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("dialog.open")]
+ public class DialogOpenResponse : Response
+ {
+ public ResponseMetadata response_metadata { get; set; }
+
+ public class ResponseMetadata
+ {
+ public string[] messages { get; set; }
+ }
+ }
+}
diff --git a/DirectMessageConversationListResponse.cs b/SlackAPI/RPCMessages/DirectMessageConversationListResponse.cs
similarity index 100%
rename from DirectMessageConversationListResponse.cs
rename to SlackAPI/RPCMessages/DirectMessageConversationListResponse.cs
diff --git a/SlackAPI/RPCMessages/FileDeleteResponse.cs b/SlackAPI/RPCMessages/FileDeleteResponse.cs
new file mode 100644
index 00000000..81c637a7
--- /dev/null
+++ b/SlackAPI/RPCMessages/FileDeleteResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("files.delete")]
+ public class FileDeleteResponse : Response
+ {
+ }
+}
diff --git a/FileInfoResponse.cs b/SlackAPI/RPCMessages/FileInfoResponse.cs
similarity index 100%
rename from FileInfoResponse.cs
rename to SlackAPI/RPCMessages/FileInfoResponse.cs
diff --git a/FileListResponse.cs b/SlackAPI/RPCMessages/FileListResponse.cs
similarity index 100%
rename from FileListResponse.cs
rename to SlackAPI/RPCMessages/FileListResponse.cs
diff --git a/FileUploadResponse.cs b/SlackAPI/RPCMessages/FileUploadResponse.cs
similarity index 100%
rename from FileUploadResponse.cs
rename to SlackAPI/RPCMessages/FileUploadResponse.cs
diff --git a/SlackAPI/RPCMessages/FindTeamResponse.cs b/SlackAPI/RPCMessages/FindTeamResponse.cs
new file mode 100644
index 00000000..d0b2d092
--- /dev/null
+++ b/SlackAPI/RPCMessages/FindTeamResponse.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ ///
+ /// This is an undocumented response from an undocumented API. If anyone finds more info on this, please create a pull request.
+ ///
+ [RequestPath("auth.findTeam")]
+ public class FindTeamResponse : Response
+ {
+ public string sso_required, sso_type, team_id, url;
+ public string[] email_domains;
+ public bool sso;
+ public SSOProvider[] sso_provider;
+
+ public static implicit operator Team(FindTeamResponse resp)
+ {
+ Team end = new Team();
+ end.sso_required = resp.sso_required;
+ end.sso_type = resp.sso_type;
+ end.id = resp.team_id;
+ end.url = resp.url;
+ end.sso = resp.sso;
+ end.email_domains = resp.email_domains;
+ end.sso_provider = resp.sso_provider;
+ return end;
+ }
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupArchiveResponse.cs b/SlackAPI/RPCMessages/GroupArchiveResponse.cs
new file mode 100644
index 00000000..d043012f
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupArchiveResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.archive")]
+ public class GroupArchiveResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupCloseResponse.cs b/SlackAPI/RPCMessages/GroupCloseResponse.cs
new file mode 100644
index 00000000..d0fdcd20
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupCloseResponse.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.close")]
+ public class GroupCloseResponse : Response
+ {
+ public string no_op;
+ public string already_closed;
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupCreateChildResponse.cs b/SlackAPI/RPCMessages/GroupCreateChildResponse.cs
new file mode 100644
index 00000000..51d495e9
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupCreateChildResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.createChild")]
+ public class GroupCreateChildResponse : GroupResponse
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupCreateResponse.cs b/SlackAPI/RPCMessages/GroupCreateResponse.cs
new file mode 100644
index 00000000..b5082e01
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupCreateResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.create")]
+ public class GroupCreateResponse : GroupResponse
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupInviteResponse.cs b/SlackAPI/RPCMessages/GroupInviteResponse.cs
new file mode 100644
index 00000000..21551786
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupInviteResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.invite")]
+ public class GroupInviteResponse : GroupResponse
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupKickResponse.cs b/SlackAPI/RPCMessages/GroupKickResponse.cs
new file mode 100644
index 00000000..3213fb22
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupKickResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.kick")]
+ public class GroupKickResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupLeaveResponse.cs b/SlackAPI/RPCMessages/GroupLeaveResponse.cs
new file mode 100644
index 00000000..9c961224
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupLeaveResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.leave")]
+ public class GroupLeaveResponse : Response
+ {
+ }
+}
diff --git a/GroupListResponse.cs b/SlackAPI/RPCMessages/GroupListResponse.cs
similarity index 100%
rename from GroupListResponse.cs
rename to SlackAPI/RPCMessages/GroupListResponse.cs
diff --git a/SlackAPI/RPCMessages/GroupMarkResponse.cs b/SlackAPI/RPCMessages/GroupMarkResponse.cs
new file mode 100644
index 00000000..b4cb7429
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupMarkResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.mark")]
+ public class GroupMarkResponse : Response
+ {
+ }
+}
diff --git a/GroupMessageHistory.cs b/SlackAPI/RPCMessages/GroupMessageHistory.cs
similarity index 100%
rename from GroupMessageHistory.cs
rename to SlackAPI/RPCMessages/GroupMessageHistory.cs
diff --git a/SlackAPI/RPCMessages/GroupOpenResponse.cs b/SlackAPI/RPCMessages/GroupOpenResponse.cs
new file mode 100644
index 00000000..70c2ffcc
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupOpenResponse.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.open")]
+ public class GroupOpenResponse : Response
+ {
+ public string no_op;
+ public string already_closed;
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupRenameResponse.cs b/SlackAPI/RPCMessages/GroupRenameResponse.cs
new file mode 100644
index 00000000..65905767
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupRenameResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.rename")]
+ public class GroupRenameResponse : Response
+ {
+ public Channel channel;
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupResponse.cs b/SlackAPI/RPCMessages/GroupResponse.cs
new file mode 100644
index 00000000..f8834bc8
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ public class GroupResponse : Response
+ {
+ public Channel group;
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupSetPurposeResponse.cs b/SlackAPI/RPCMessages/GroupSetPurposeResponse.cs
new file mode 100644
index 00000000..93a061c7
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupSetPurposeResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.setPurpose")]
+ public class GroupSetPurposeResponse : Response
+ {
+ public string purpose;
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupSetTopicResponse.cs b/SlackAPI/RPCMessages/GroupSetTopicResponse.cs
new file mode 100644
index 00000000..f8adab97
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupSetTopicResponse.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.setTopic")]
+ public class GroupSetTopicResponse : Response
+ {
+ public string topic;
+ }
+}
diff --git a/SlackAPI/RPCMessages/GroupUnarchiveResponse.cs b/SlackAPI/RPCMessages/GroupUnarchiveResponse.cs
new file mode 100644
index 00000000..d68061c2
--- /dev/null
+++ b/SlackAPI/RPCMessages/GroupUnarchiveResponse.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("groups.unarchive")]
+ public class GroupUnarchiveResponse : Response
+ {
+ }
+}
diff --git a/SlackAPI/RPCMessages/JoinDirectMessageChannelResponse.cs b/SlackAPI/RPCMessages/JoinDirectMessageChannelResponse.cs
new file mode 100644
index 00000000..a7095398
--- /dev/null
+++ b/SlackAPI/RPCMessages/JoinDirectMessageChannelResponse.cs
@@ -0,0 +1,8 @@
+namespace SlackAPI
+{
+ [RequestPath("conversations.open")]
+ public class JoinDirectMessageChannelResponse : Response
+ {
+ public Channel channel;
+ }
+}
\ No newline at end of file
diff --git a/SlackAPI/RPCMessages/LoginResponse.cs b/SlackAPI/RPCMessages/LoginResponse.cs
new file mode 100644
index 00000000..d048f316
--- /dev/null
+++ b/SlackAPI/RPCMessages/LoginResponse.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ [RequestPath("rtm.start")]
+ public class LoginResponse : Response
+ {
+ public Bot[] bots;
+ public Channel[] channels;
+ public Channel[] groups;
+ public DirectMessageConversation[] ims;
+ public Self self;
+ public int svn_rev;
+ public int min_svn_rev;
+ public Team team;
+ public string url;
+ public User[] users;
+ }
+
+ public class Self
+ {
+ public DateTime created;
+ public string id;
+ public string manual_presence;
+ public string name;
+ public Preferences prefs;
+ }
+}
diff --git a/MarkResponse.cs b/SlackAPI/RPCMessages/MarkResponse.cs
similarity index 89%
rename from MarkResponse.cs
rename to SlackAPI/RPCMessages/MarkResponse.cs
index 982fbbc7..976b0c91 100644
--- a/MarkResponse.cs
+++ b/SlackAPI/RPCMessages/MarkResponse.cs
@@ -9,7 +9,7 @@ namespace SlackAPI
///
/// This is used for moving the read cursor in the channel.
///
- [RequestPath("channels.marks")]
+ [RequestPath("channels.mark")]
public class MarkResponse : Response
{
}
diff --git a/MessageHistory.cs b/SlackAPI/RPCMessages/MessageHistory.cs
similarity index 92%
rename from MessageHistory.cs
rename to SlackAPI/RPCMessages/MessageHistory.cs
index ba2541ca..8fb797a1 100644
--- a/MessageHistory.cs
+++ b/SlackAPI/RPCMessages/MessageHistory.cs
@@ -12,6 +12,7 @@ public class MessageHistory : Response
public DateTime latest;
public Message[] messages;
public bool has_more;
+ public int unread_count_display;
public bool channel_not_found;
public bool invalid_ts_latest;
diff --git a/SlackAPI/RPCMessages/PostEphemeralResponse.cs b/SlackAPI/RPCMessages/PostEphemeralResponse.cs
new file mode 100644
index 00000000..bf48a50e
--- /dev/null
+++ b/SlackAPI/RPCMessages/PostEphemeralResponse.cs
@@ -0,0 +1,8 @@
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("chat.postEphemeral")]
+ public class PostEphemeralResponse : Response
+ {
+ public string message_ts;
+ }
+}
diff --git a/SlackAPI/RPCMessages/PostMessageResponse.cs b/SlackAPI/RPCMessages/PostMessageResponse.cs
new file mode 100644
index 00000000..c7f4b908
--- /dev/null
+++ b/SlackAPI/RPCMessages/PostMessageResponse.cs
@@ -0,0 +1,20 @@
+namespace SlackAPI
+{
+ [RequestPath("chat.postMessage")]
+ public class PostMessageResponse : Response
+ {
+ public string ts;
+ public string channel;
+ public Message message;
+
+ public class Message
+ {
+ public string text;
+ public string user;
+ public string username;
+ public string type;
+ public string subtype;
+ public string ts;
+ }
+ }
+}
diff --git a/SlackAPI/RPCMessages/PresenseResponse.cs b/SlackAPI/RPCMessages/PresenseResponse.cs
new file mode 100644
index 00000000..7b3cd5de
--- /dev/null
+++ b/SlackAPI/RPCMessages/PresenseResponse.cs
@@ -0,0 +1,14 @@
+namespace SlackAPI
+{
+ [RequestPath("users.setPresence")]
+ public class PresenceResponse : Response
+ {
+ }
+
+ public enum Presence
+ {
+ active,
+ away,
+ auto
+ }
+}
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/SearchResponseAll.cs b/SlackAPI/RPCMessages/SearchResponseAll.cs
similarity index 100%
rename from SearchResponseAll.cs
rename to SlackAPI/RPCMessages/SearchResponseAll.cs
diff --git a/SearchResponseFiles.cs b/SlackAPI/RPCMessages/SearchResponseFiles.cs
similarity index 91%
rename from SearchResponseFiles.cs
rename to SlackAPI/RPCMessages/SearchResponseFiles.cs
index 9b8b284a..4cf8ebe1 100644
--- a/SearchResponseFiles.cs
+++ b/SlackAPI/RPCMessages/SearchResponseFiles.cs
@@ -10,7 +10,7 @@ namespace SlackAPI
public class SearchResponseFiles : Response
{
public string query;
- SearchResponseMessagesContainer messages;
+ public SearchResponseFilesContainer files;
}
public class SearchResponseFilesContainer
diff --git a/SearchResponseMessages.cs b/SlackAPI/RPCMessages/SearchResponseMessages.cs
similarity index 95%
rename from SearchResponseMessages.cs
rename to SlackAPI/RPCMessages/SearchResponseMessages.cs
index 6131f949..8b1eef70 100644
--- a/SearchResponseMessages.cs
+++ b/SlackAPI/RPCMessages/SearchResponseMessages.cs
@@ -10,7 +10,6 @@ namespace SlackAPI
public class SearchResponseMessages : Response
{
public string query;
- SearchResponseMessagesContainer messages;
}
public class SearchResponseMessagesContainer
@@ -56,8 +55,10 @@ public class PaginationInformation
public enum SearchSort
{
+ not_set,
score,
- timestamp
+ timestamp,
+ not_set_new_user
}
public enum SearchSortDirection
diff --git a/StarListResponse.cs b/SlackAPI/RPCMessages/StarListResponse.cs
similarity index 100%
rename from StarListResponse.cs
rename to SlackAPI/RPCMessages/StarListResponse.cs
diff --git a/SlackAPI/RPCMessages/UpdateResponse.cs b/SlackAPI/RPCMessages/UpdateResponse.cs
new file mode 100644
index 00000000..4514edfc
--- /dev/null
+++ b/SlackAPI/RPCMessages/UpdateResponse.cs
@@ -0,0 +1,18 @@
+namespace SlackAPI
+{
+ [RequestPath("chat.update")]
+ public class UpdateResponse : Response
+ {
+ public string channel;
+ public string ts;
+ public string text;
+ public Message message;
+
+ public class Message
+ {
+ public string type;
+ public string user;
+ public string text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/UserCountsResponse.cs b/SlackAPI/RPCMessages/UserCountsResponse.cs
similarity index 100%
rename from UserCountsResponse.cs
rename to SlackAPI/RPCMessages/UserCountsResponse.cs
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/RPCMessages/UserGetPresenceResponse.cs b/SlackAPI/RPCMessages/UserGetPresenceResponse.cs
new file mode 100644
index 00000000..62b43272
--- /dev/null
+++ b/SlackAPI/RPCMessages/UserGetPresenceResponse.cs
@@ -0,0 +1,8 @@
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("users.getPresence")]
+ public class UserGetPresenceResponse : Response
+ {
+ public Presence presence;
+ }
+}
diff --git a/SlackAPI/RPCMessages/UserInfoResponse.cs b/SlackAPI/RPCMessages/UserInfoResponse.cs
new file mode 100644
index 00000000..2948f507
--- /dev/null
+++ b/SlackAPI/RPCMessages/UserInfoResponse.cs
@@ -0,0 +1,8 @@
+namespace SlackAPI.RPCMessages
+{
+ [RequestPath("users.info")]
+ public class UserInfoResponse : Response
+ {
+ public User user;
+ }
+}
diff --git a/UserListResponse.cs b/SlackAPI/RPCMessages/UserListResponse.cs
similarity index 100%
rename from UserListResponse.cs
rename to SlackAPI/RPCMessages/UserListResponse.cs
diff --git a/UserPreferencesResponse.cs b/SlackAPI/RPCMessages/UserPreferencesResponse.cs
similarity index 100%
rename from UserPreferencesResponse.cs
rename to SlackAPI/RPCMessages/UserPreferencesResponse.cs
diff --git a/SlackAPI/Reaction.cs b/SlackAPI/Reaction.cs
new file mode 100644
index 00000000..5cae7099
--- /dev/null
+++ b/SlackAPI/Reaction.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace SlackAPI
+{
+ public class Reaction
+ {
+ public string name;
+ public int count;
+ public List users;
+ }
+}
diff --git a/PostMessageResponse.cs b/SlackAPI/ReactionAddedResponse.cs
similarity index 64%
rename from PostMessageResponse.cs
rename to SlackAPI/ReactionAddedResponse.cs
index 45386c87..b641ac32 100644
--- a/PostMessageResponse.cs
+++ b/SlackAPI/ReactionAddedResponse.cs
@@ -6,8 +6,8 @@
namespace SlackAPI
{
- [RequestPath("chat.postMessage")]
- public class PostMessageResponse : Response
+ [RequestPath("reactions.add")]
+ public class ReactionAddedResponse : Response
{
}
diff --git a/Request.cs b/SlackAPI/Request.cs
similarity index 50%
rename from Request.cs
rename to SlackAPI/Request.cs
index 5a0f1d90..e7ed01a5 100644
--- a/Request.cs
+++ b/SlackAPI/Request.cs
@@ -1,6 +1,7 @@
using Newtonsoft.Json;
using System;
-using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
@@ -9,6 +10,8 @@
namespace SlackAPI
{
+
+
class RequestState
where K : Response
{
@@ -21,6 +24,7 @@ class RequestState
public RequestState(HttpWebRequest requestData, Tuple[] postParameters, Action toCallback)
{
+ if (requestData == null) throw new ArgumentNullException("requestData can not be null");
request = requestData;
Post = postParameters;
callback = toCallback;
@@ -31,12 +35,12 @@ public void Begin()
if (Post.Length == 0)
{
request.Method = "GET";
- request.BeginGetResponse(GotResponse, this);
+ IAsyncResult result = request.BeginGetResponse(GotResponse, this);
}
else
{
request.Method = "POST";
- request.BeginGetRequestStream(GotRequest, this);
+ IAsyncResult result = request.BeginGetRequestStream(GotRequest, this);
}
}
@@ -68,26 +72,61 @@ internal void GotResponse(IAsyncResult result)
{
try
{
- response = (HttpWebResponse)request.EndGetResponse(result);
+ response = (HttpWebResponse)request?.EndGetResponse(result);
Success = true;
}
catch (WebException we)
{
+ // If we don't get a response, let the exception bubble up as we can't do anything
+ if (we.Response == null)
+ {
+ var defaultResponse = CreateDefaultResponseForError(we);
+ callback?.Invoke(defaultResponse);
+ return;
+ }
+
//Anything that doesn't return error 200 throws an exception. Sucks. :l
response = (HttpWebResponse)we.Response;
//TODO: Handle timeouts, etc?
}
+ catch (Exception e)
+ {
+ var defaultResponse = CreateDefaultResponseForError(e);
+ callback?.Invoke(defaultResponse);
+ return;
+ }
K responseObj;
+ if (response == null)
+ {
+ responseObj = CreateDefaultResponseForError(new Exception("Empty response"));
+ callback?.Invoke(responseObj);
+ return;
+ }
- using(Stream responseReading = response.GetResponseStream())
- using (StreamReader reader = new StreamReader(responseReading))
+ try
+ {
+ using (Stream responseReading = response.GetResponseStream())
+ using (StreamReader reader = new StreamReader(responseReading))
+ {
+ string responseData = reader.ReadToEnd();
+ responseObj = responseData.Deserialize();
+ }
+ }
+ catch (Exception e)
{
- string responseData = reader.ReadToEnd();
- responseObj = JsonConvert.DeserializeObject(responseData, new JavascriptDateTimeConverter());
+ responseObj = CreateDefaultResponseForError(e);
}
- callback(responseObj);
+ callback?.Invoke(responseObj);
+ }
+
+ private K CreateDefaultResponseForError(Exception e)
+ {
+ var defaultResponse = (K)Activator.CreateInstance();
+ defaultResponse.ok = false;
+ defaultResponse.error = e.ToString();
+ return defaultResponse;
}
}
@@ -103,20 +142,36 @@ public RequestPath(string requestPath, bool isPrimaryAPI = true)
UsePrimaryAPI = isPrimaryAPI;
}
- static Dictionary paths = new Dictionary();
+ static ConcurrentDictionary paths = new ConcurrentDictionary();
public static RequestPath GetRequestPath()
{
Type t = typeof(K);
- if (paths.ContainsKey(t))
- return paths[t];
+ if (paths.TryGetValue(t, out var path))
+ return path;
TypeInfo info = t.GetTypeInfo();
- RequestPath path = info.GetCustomAttribute();
+ path = info.GetCustomAttribute();
if (path == null) throw new InvalidOperationException(string.Format("No valid request path for {0}", t.Name));
- paths.Add(t, path);
+ try
+ {
+ // Some other thread may have already placed the path to the dictionary,
+ // the original one will remain there and current one will be GCed once it
+ // gets out of scope of this request.
+ paths.TryAdd(t, path);
+ }
+ catch (Exception e)
+ {
+ // There is a slight chance of TryAdd throwing, we want to be extra safe
+ // so we just consume it and leave the dictionary as-is, next call of
+ // 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.
+ Trace.TraceError(e.ToString());
+ }
+
return path;
}
}
diff --git a/SlackAPI/RequestStateForTask.cs b/SlackAPI/RequestStateForTask.cs
new file mode 100644
index 00000000..f535f5a4
--- /dev/null
+++ b/SlackAPI/RequestStateForTask.cs
@@ -0,0 +1,94 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ public class RequestStateForTask
+ where K : Response
+ {
+ public HttpWebRequest request;
+ Tuple[] Post;
+ public bool Success;
+
+ public RequestStateForTask(HttpWebRequest requestData, Tuple[] postParameters)
+ {
+ request = requestData;
+ Post = postParameters;
+ }
+
+ internal Task Execute()
+ {
+ if (Post == null || Post.Length == 0)
+ {
+ request.Method = "GET";
+ return this.ExecuteResult();
+ }
+ else
+ {
+ return this.ExecutePost();
+ }
+ }
+
+ private async Task ExecuteResult()
+ {
+ HttpWebResponse response = null;
+ try
+ {
+ response = (HttpWebResponse)await this.request.GetResponseAsync().ConfigureAwait(false);
+ Success = true;
+ }
+ catch (WebException we)
+ {
+ //Anything that doesn't return error 200 throws an exception. Sucks. :l
+ response = (HttpWebResponse)we.Response;
+ //TODO: Handle timeouts, etc?
+ }
+
+ K responseObj;
+
+ using (Stream responseReading = response.GetResponseStream())
+ {
+ using (StreamReader reader = new StreamReader(responseReading))
+ {
+ string responseData = reader.ReadToEnd();
+ responseObj = responseData.Deserialize();
+ }
+ }
+
+ return responseObj;
+ }
+
+ private async Task ExecutePost()
+ {
+ request.Method = "POST";
+ request.ContentType = "application/x-www-form-urlencoded";
+ using (Stream requestStream = await request.GetRequestStreamAsync().ConfigureAwait(false))
+ {
+ if (Post.Length > 0)
+ {
+ using (StreamWriter writer = new StreamWriter(requestStream))
+ {
+ bool first = true;
+ foreach (Tuple postEntry in Post)
+ {
+ if (!first)
+ writer.Write('&');
+
+ await writer.WriteAsync(string.Format("{0}={1}", Uri.EscapeDataString(postEntry.Item1), Uri.EscapeDataString(postEntry.Item2))).ConfigureAwait(false);
+
+ first = false;
+ }
+ }
+ }
+ }
+
+ return await this.ExecuteResult().ConfigureAwait(false);
+ }
+ }
+}
diff --git a/SlackAPI/Response.cs b/SlackAPI/Response.cs
new file mode 100644
index 00000000..8cd5e2c7
--- /dev/null
+++ b/SlackAPI/Response.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SlackAPI
+{
+ public abstract class Response
+ {
+ ///
+ /// Should always be checked before trying to process a response.
+ ///
+ public bool ok;
+
+ ///
+ /// 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
new file mode 100644
index 00000000..934c5356
--- /dev/null
+++ b/SlackAPI/SlackAPI.csproj
@@ -0,0 +1,40 @@
+
+
+
+ net45;netstandard2.0
+ Full
+ SlackAPI
+ SlackAPI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Properties\GlobalAssemblyInfo.cs
+
+
+
+
+ $(DefineConstants);RELEASE
+
+
+
diff --git a/SlackAPI/SlackClient.cs b/SlackAPI/SlackClient.cs
new file mode 100644
index 00000000..e81a55c3
--- /dev/null
+++ b/SlackAPI/SlackClient.cs
@@ -0,0 +1,939 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using SlackAPI.RPCMessages;
+
+namespace SlackAPI
+{
+ ///
+ /// SlackClient is intended to solely handle RPC (HTTP-based) functionality. Does not handle WebSocket connectivity.
+ ///
+ /// For WebSocket connectivity, refer to
+ ///
+ public class SlackClient : SlackClientBase
+ {
+ private readonly string APIToken;
+
+ public Self MySelf;
+ public User MyData;
+ public Team MyTeam;
+
+ public List starredChannels;
+
+ public List Users;
+ public List Bots;
+ public List Channels;
+ public List Groups;
+ public List DirectMessages;
+
+ public Dictionary UserLookup;
+ public Dictionary ChannelLookup;
+ public Dictionary GroupLookup;
+ public Dictionary DirectMessageLookup;
+ public Dictionary ConversationLookup;
+
+ public SlackClient(string token)
+ {
+ APIToken = token;
+ }
+
+ public SlackClient(string token, IWebProxy proxySettings)
+ : base(proxySettings)
+ {
+ APIToken = token;
+ }
+
+ public virtual void Connect(Action onConnected = null, Action onSocketConnected = null)
+ {
+ EmitLogin((loginDetails) =>
+ {
+ if (loginDetails.ok)
+ Connected(loginDetails);
+
+ if (onConnected != null)
+ onConnected(loginDetails);
+ });
+ }
+
+ protected virtual void Connected(LoginResponse loginDetails)
+ {
+ MySelf = loginDetails.self;
+ MyData = loginDetails.users.First((c) => c.id == MySelf.id);
+ MyTeam = loginDetails.team;
+
+ Users = new List(loginDetails.users.Where((c) => !c.deleted));
+ Bots = new List(loginDetails.bots.Where((c) => !c.deleted));
+ Channels = new List(loginDetails.channels);
+ Groups = new List(loginDetails.groups);
+ DirectMessages = new List(loginDetails.ims.Where((c) => Users.Exists((a) => a.id == c.user) && c.id != MySelf.id));
+ starredChannels =
+ Groups.Where((c) => c.is_starred).Select((c) => c.id)
+ .Union(
+ DirectMessages.Where((c) => c.is_starred).Select((c) => c.user)
+ ).Union(
+ Channels.Where((c) => c.is_starred).Select((c) => c.id)
+ ).ToList();
+
+ UserLookup = new Dictionary();
+ foreach (User u in Users) UserLookup.Add(u.id, u);
+
+ ChannelLookup = new Dictionary();
+ ConversationLookup = new Dictionary();
+ foreach (Channel c in Channels)
+ {
+ ChannelLookup.Add(c.id, c);
+ ConversationLookup.Add(c.id, c);
+ }
+
+ GroupLookup = new Dictionary();
+ foreach (Channel g in Groups)
+ {
+ GroupLookup.Add(g.id, g);
+ ConversationLookup.Add(g.id, g);
+ }
+
+ DirectMessageLookup = new Dictionary();
+ foreach (DirectMessageConversation im in DirectMessages)
+ {
+ DirectMessageLookup.Add(im.id, im);
+ ConversationLookup.Add(im.id, im);
+ }
+ }
+
+ public void APIRequestWithToken(Action callback, params Tuple[] getParameters)
+ where K : Response
+ {
+ APIRequest(callback, getParameters, new Tuple[0], APIToken);
+ }
+
+ public void TestAuth(Action callback)
+ {
+ APIRequestWithToken(callback);
+ }
+
+ public void GetUserList(Action callback)
+ {
+ APIRequestWithToken(callback);
+ }
+
+ 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));
+ }
+
+ public void ChannelsInvite(Action callback, string userId, string channelId)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("user", 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"));
+ }
+
+ public void GetGroupsList(Action callback, bool ExcludeArchived = true)
+ {
+ APIRequestWithToken(callback, new Tuple("exclude_archived", ExcludeArchived ? "1" : "0"));
+ }
+
+ public void GetDirectMessageList(Action callback)
+ {
+ APIRequestWithToken(callback);
+ }
+
+ public void GetFiles(Action callback, string userId = null, DateTime? from = null, DateTime? to = null, int? count = null, int? page = null, FileTypes types = FileTypes.all, string channel = null)
+ {
+ List> parameters = new List>();
+
+ if (!string.IsNullOrEmpty(userId))
+ parameters.Add(new Tuple("user", userId));
+
+ if (from.HasValue)
+ parameters.Add(new Tuple("ts_from", from.Value.ToProperTimeStamp()));
+
+ if (to.HasValue)
+ parameters.Add(new Tuple("ts_to", to.Value.ToProperTimeStamp()));
+
+ if (!types.HasFlag(FileTypes.all))
+ {
+ FileTypes[] values = (FileTypes[])Enum.GetValues(typeof(FileTypes));
+
+ StringBuilder building = new StringBuilder();
+ bool first = true;
+ for (int i = 0; i < values.Length; ++i)
+ {
+ if (types.HasFlag(values[i]))
+ {
+ if (!first) building.Append(",");
+
+ building.Append(values[i].ToString());
+
+ first = false;
+ }
+ }
+
+ if (building.Length > 0)
+ parameters.Add(new Tuple("types", building.ToString()));
+ }
+
+ if (count.HasValue)
+ parameters.Add(new Tuple("count", count.Value.ToString()));
+
+ if (page.HasValue)
+ parameters.Add(new Tuple("page", page.Value.ToString()));
+
+ if (!string.IsNullOrEmpty(channel))
+ parameters.Add(new Tuple("channel", channel));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ void GetHistory(Action historyCallback, string channel, DateTime? latest = null, DateTime? oldest = null, int? count = null, bool? unreads = false)
+ where K : MessageHistory
+ {
+ List> parameters = new List>();
+ parameters.Add(new Tuple("channel", channel));
+
+ if(latest.HasValue)
+ parameters.Add(new Tuple("latest", latest.Value.ToProperTimeStamp()));
+ if(oldest.HasValue)
+ parameters.Add(new Tuple("oldest", oldest.Value.ToProperTimeStamp()));
+ if(count.HasValue)
+ parameters.Add(new Tuple("count", count.Value.ToString()));
+ if (unreads.HasValue)
+ parameters.Add(new Tuple("unreads", unreads.Value ? "1" : "0"));
+
+ APIRequestWithToken(historyCallback, parameters.ToArray());
+ }
+
+ public void GetChannelHistory(Action callback, Channel channelInfo, DateTime? latest = null, DateTime? oldest = null, int? count = null, bool? unreads = false)
+ {
+ GetHistory(callback, channelInfo.id, latest, oldest, count, unreads);
+ }
+
+ public void GetDirectMessageHistory(Action callback, DirectMessageConversation conversationInfo, DateTime? latest = null, DateTime? oldest = null, int? count = null, bool? unreads = false)
+ {
+ GetHistory(callback, conversationInfo.id, latest, oldest, count, unreads);
+ }
+
+ public void GetGroupHistory(Action callback, Channel groupInfo, DateTime? latest = null, DateTime? oldest = null, int? count = null, bool? unreads = false)
+ {
+ 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,
+ new Tuple("channel", channelId),
+ new Tuple("ts", ts.ToProperTimeStamp())
+ );
+ }
+
+ public void GetFileInfo(Action callback, string fileId, int? page = null, int? count = null)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("file", fileId));
+
+ if(count.HasValue)
+ parameters.Add(new Tuple("count", count.Value.ToString()));
+
+ if (page.HasValue)
+ parameters.Add(new Tuple("page", page.Value.ToString()));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+ #region Groups
+ public void GroupsArchive(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void GroupsClose(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void GroupsCreate(Action callback, string name)
+ {
+ APIRequestWithToken(callback, new Tuple("name", name));
+ }
+
+ public void GroupsCreateChild(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void GroupsInvite(Action callback, string userId, string channelId)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("user", userId));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void GroupsKick(Action callback, string userId, string channelId)
+ {
+ List> parameters = new List>();
+
+ parameters.Add(new Tuple("channel", channelId));
+ parameters.Add(new Tuple("user", userId));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void GroupsLeave(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void GroupsMark(Action callback, string channelId, DateTime ts)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId), new Tuple("ts", ts.ToProperTimeStamp()));
+ }
+
+ public void GroupsOpen(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ public void GroupsRename(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 GroupsSetPurpose(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 GroupsSetTopic(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 GroupsUnarchive(Action callback, string channelId)
+ {
+ APIRequestWithToken(callback, new Tuple("channel", channelId));
+ }
+
+ #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>();
+ parameters.Add(new Tuple("query", query));
+
+ if (sorting != null)
+ parameters.Add(new Tuple("sort", sorting));
+
+ if (direction.HasValue)
+ parameters.Add(new Tuple("sort_dir", direction.Value.ToString()));
+
+ if (enableHighlights)
+ parameters.Add(new Tuple("highlight", "1"));
+
+ if (count.HasValue)
+ parameters.Add(new Tuple("count", count.Value.ToString()));
+
+ if (page.HasValue)
+ parameters.Add(new Tuple("page", page.Value.ToString()));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void SearchMessages(Action callback, string query, string sorting = null, SearchSortDirection? direction = null, bool enableHighlights = false, int? count = null, int? page = null)
+ {
+ List> parameters = new List>();
+ parameters.Add(new Tuple("query", query));
+
+ if (sorting != null)
+ parameters.Add(new Tuple("sort", sorting));
+
+ if (direction.HasValue)
+ parameters.Add(new Tuple("sort_dir", direction.Value.ToString()));
+
+ if (enableHighlights)
+ parameters.Add(new Tuple("highlight", "1"));
+
+ if (count.HasValue)
+ parameters.Add(new Tuple("count", count.Value.ToString()));
+
+ if (page.HasValue)
+ parameters.Add(new Tuple("page", page.Value.ToString()));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void SearchFiles(Action callback, string query, string sorting = null, SearchSortDirection? direction = null, bool enableHighlights = false, int? count = null, int? page = null)
+ {
+ List> parameters = new List>();
+ parameters.Add(new Tuple("query", query));
+
+ if (sorting != null)
+ parameters.Add(new Tuple("sort", sorting));
+
+ if (direction.HasValue)
+ parameters.Add(new Tuple("sort_dir", direction.Value.ToString()));
+
+ if (enableHighlights)
+ parameters.Add(new Tuple("highlight", "1"));
+
+ if (count.HasValue)
+ parameters.Add(new Tuple("count", count.Value.ToString()));
+
+ if (page.HasValue)
+ parameters.Add(new Tuple("page", page.Value.ToString()));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void GetStars(Action callback, string userId = null, int? count = null, int? page = null){
+ List> parameters = new List>();
+
+ if(!string.IsNullOrEmpty(userId))
+ parameters.Add(new Tuple("user", userId));
+
+ if(count.HasValue)
+ parameters.Add(new Tuple("count", count.Value.ToString()));
+
+ if(page.HasValue)
+ parameters.Add(new Tuple("page", page.Value.ToString()));
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void DeleteMessage(Action callback, string channelId, DateTime ts)
+ {
+ List> parameters = new List>()
+ {
+ new Tuple("ts", ts.ToProperTimeStamp()),
+ new Tuple("channel", channelId)
+ };
+
+ APIRequestWithToken(callback, parameters.ToArray());
+ }
+
+ public void EmitPresence(Action callback, Presence status)
+ {
+ APIRequestWithToken(callback, new Tuple