Coder Social home page Coder Social logo

slackbotnet's Introduction

SlackBotNet

Build status Nuget

.NET Standard compatible client for the Real Time Messaging API.

NOTE: This library is not currently full-featured and may have unexpected issues. Please open issues when they are discovered. Pull requests are greatly appreciated.

Getting Started

Installation:

nuget> Install-Package SlackBotNet

Initialize a new instance of the bot:

var bot = await SlackBot.InitializeAsync([slack authentication token]);

Listen for messages sent to the bot:

using static SlackBotNet.MatchFactory;

bot.When(Matches.Text("hello"), async conv =>
{
    await conv.PostMessage($"Hi {conv.From.Username}!");
});

Or publish simple messages to a Slack channel/group/DM:

var hub = bot.State.GetHub("@username");
await bot.SendAsync(hub, "Hello!");

Bot Configuration

Optional configuration of the bot is set through the second argument to SlackBot.InitializeAsync

var bot = await SlackBot.InitializeAsync([slack authentication token], cfg =>
{
    cfg.LoggerFactory = ...;
    cfg.OnSendMessageFailure = (queue, msg, logger, e) => { };
});

Logging

Provide an implementation of the Microsoft.Extensions.Logging.ILoggerFactory class and the SlackBotNet library will output diagnostic logs through it.

Send Message Failure Handling

Sending messages through the library is done on a background thread to ensure the rate of sent messages is at most 1 per second (see the Slack API Rate Limits documentation)

A callback matching the signature Action<ISendMessageQueue, IMessage, ILogger, Exception> can be provided that gets executed each time a failure happens when the bot attempts to post a message back to Slack.

You can log these failures or set up a retry mechanism to attempt a resend of the message.

Example that retries sending a message up to 5 times before giving up:

cfg.OnSendMessageFailure = async (queue, msg, logger, e) =>
{
    if (msg.SendAttempts <= 5)
    {
        logger?.LogWarning($"Failed to send message {msg.Text}. Tried {msg.SendAttempts} times");
        await Task.Delay(1000 * msg.SendAttempts);
        queue.Enqueue(msg);
        return;
    }
    
    logger?.LogError($"Gave up trying to send message {msg.Text}");
};

Matching

Use MessageMatchers when you want your bot to listen for various message content.

Example:

using static SlackBotNet.MatchFactory;

bot.When("knock knock", Modes.StartThread, async conv =>
{
    await conv.PostMessage("Who's there?");

    var who = await conv.WaitForReply();
    await conv.PostMessage($"{who.Text} who?");

    var punchline = await conv.WaitForReply();
    await conv.PostMessage($"{punchline.Text}, lol :laughing:");
});

Multiple MessageMatchers can be combined by using the Or or And matcher or the double pipes/double ampersand operators:

Matches.Text("hello").Or(Matches.Regex("^world$"))
Matches.Text("text").And(Matches.Message(m => m.RawThreadTimestamp == null))

Equivalent:

Matches.Text("hello") || Matches.Regex("^world$")
Matches.Text("text") && Matches.Message(m => m.RawThreadTimestamp == null)

When multiple MessageMatchers are used, they short-circuit, meaning if the first one matches the message then the second MessageMatcher will not be tried.

Hubs

Note: Private channels are considered to be Groups. If your bot needs to listen on a private channel, make sure that the HubType.Group is included.

Bots can be configured to listen for messages on Channels they are joined to, groups they are a part of or when they receive direct messages. They will default to listening on all hub types.

bot.When(
    Matches.Text("hello"), 
    HubType.DirectMessage | HubType.Channel, 
    async conv =>
    {
        ...
    });

Observing all messages in a channel

When the bot is observing a channel or group, by default it will only listen for messages that contain the name of the bot in the message text. If you want the bot to listen to all messages in a channel, the Modes.ObserveAllMessages flag can be set.

bot.When(
    Matches.Text("hello"),
    Modes.ObserveAllMessages,
    async conv =>
    {
        ...
    });

Replying with a thread

By default the bot will send its reply to the channel. It can also start a thread by using the Modes.StartThread flag.

bot.When(
    Matches.Text("hello"),
    Modes.StartThread,
    async conv =>
    {
        ...
    });

If the StartThread mode is not set and a Slack user replies to a bot message with a thread, the bot will automatically swith to the thread mode.

Error Handling

Callbacks can be registered with the bot that will be fired in the event of an unhandled exception occuring in either the MessageMatcher(s) or the conversation delegate:

Example:

using static SlackBotNet.MatchFactory;

bot
    .When(Matches.Text("hello"),
        async conv =>
    {
            // exception thrown
    })
    .OnException((msg, ex) =>
    {
            // log exception
    });

Multiple Handlers

If there are multiple .When(...) setups configured for the bot, the bot will respect the WhenHandlerMatchMode as configured when incoming messages can be handled by more than one .When(...).

var bot = await SlackBot.InitializeAsync([slack authentication token], cfg =>
{
    cfg.WhenHandlerMatchMode = WhenHandlerMatchMode.FirstMatch;
});

WhenHandlerMatchMode.FirstMatch

Only the first When handler that matches the message will be used. The position of the When handler is determined by the order it is registered with the bot.

WhenHandlerMatchMode.BestMatch

MessageMatchers can assign a score when they match a message. In this mode, the When handler that scores the highest will be used.

WhenHandlerMatchMode.AllMatches

All When handlers that match the incoming message will be fired.

Available Message Matchers

Text

Matches a message that contains the given text.

The string type is implicitly converted to the text matcher.

Example:

bot.When("hello", async conv =>
{
    ...
})

Regex

Matches a message based on a regular expression. Capture groups in the regular expression are present in the Conversation.

Message

Takes a predicate that can inspect the raw instance of the SlackBotNet.Messages.Message object.

LuisIntent

Hooks up to the Language Understanding Intelligent Service (luis) for processing natural language. Up to LuisConfig.CacheSize results will be maintained in local cache (defaults to 100).

Installation:

nuget> Install-Package SlackBotNet.Matchers.Luis

Configuration:

LuisConfig.SubscriptionKey = "...";
LuisConfig.AppKey = "...";

LuisConfig.CacheSize = 100;

Using:

Matches.LuisIntent(intentName: "Intent", confidenceThreshold: 0.9m)

Examples

Tell a Joke

Tells a knock-knock joke. The conversation can be ended by the user typing nevermind at any point.

bot.When(
    Matches.Text("tell me a joke"),
    Modes.StartThread,
    async conv =>
    {
        await conv.PostMessage("okay! knock knock");

        async Task Continue()
        {
            await conv.WaitForReply("who's there?");

            await conv.PostMessage("broken pencil");

            await conv.WaitForReply(
                "broken pencil who?", 
                async _ => await conv.PostMessage("nope, try again"));

            await conv.PostMessage("nevermind, it's pointless");

            await Task.Delay(TimeSpan.FromSeconds(2));
            await conv.PostMessage(":joy:");
        }

        async Task Quit()
        {
            await conv.WaitForReply("nevermind");
            await conv.PostMessage("okay then :expressionless:");
        }

        await Task.WhenAny(Continue(), Quit());
    });

License

Apache 2.0

slackbotnet's People

Contributors

jpedretti-ciandt avatar jweber avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

slackbotnet's Issues

Unhandled Exception when network is unstable

Hello! I use your library in my project https://github.com/KonH/DotNetCoreBuildServer and ran into a problem, which I can resolve on my side (as I know it). I use your library there - https://github.com/KonH/DotNetCoreBuildServer/blob/master/Server/Integrations/SlackService.cs.

Steps to reproduce:

  1. Initialize bot normally
  2. Disable internet connection (simulate network problems)
  3. Try to send any message
  4. Observe results

Actual result:

Unhandled Exception: System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.Http.WinHttpException: Не удается разрешить имя или адрес сервера
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.WinHttpHandler.<StartRequest>d__105.MoveNext()
   --- End of inner exception stack trace ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpClient.<FinishSendAsync>d__58.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpClient.<GetContentAsync>d__32`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at SlackBotNet.Drivers.SlackRtmDriver.<ConnectRtmAsync>d__6.MoveNext() in C:\projects\thirdparty\slackbotnet\src\SlackBotNet\Drivers\SlackRtmDriver.cs:line 84
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at SlackBotNet.Drivers.SlackRtmDriver.<>c__DisplayClass5_0.<<ConnectAsync>b__0>d.MoveNext() in C:\projects\thirdparty\slackbotnet\src\SlackBotNet\Drivers\SlackRtmDriver.cs:line 58
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.QueueUserWorkItemCallbackDefaultContext.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

When "Не удается разрешить имя или адрес сервера" is something like "Cannot resolve host name".

Excepted result:
Exceptions like this can be handled by library or provide some methods to handle it to library user code

Is something wrong with my integration? Or it can be fixed/improved inside library?
Waiting for your feedback, maybe I can help with it.

Websocket connection sometimes hangs in Aborted state

Hello!
We faced with a problem, which can be reproduced in our environment when bot is running for a long time. Sometimes it denied to respond and as I see, it's related to websocket connection, which hangs in Aborted state.
I have made dirty workaround with reflection, because don't want to modify sources (now it used in several apps), I hope this commit helps to understand the problem - KonH/DotNetCoreBuildServer@7b16dfe.

System.Net.Http.HttpRequestException occured in ping-pong process

Hi! We faced to another issue with unhandled exception in ping-pong progress, and as I see we can't handle it on our side.

Steps to reproduce:

  • Start bot
  • Disable network connection
  • Wait several seconds

Details:
Exception is at SlackRtmDriver.cs:75:
await this.ReconnectRtmAsync(bus, logger); <== here

System.Net.Http.HttpRequestException occurred
HResult=0x80072EE7
Message=An error occurred while sending the request.
Source=
StackTrace:
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable1.ConfiguredTaskAwaiter.GetResult() at System.Net.Http.HttpClient.<FinishSendAsync>d__58.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.ConfiguredTaskAwaitable1.ConfiguredTaskAwaiter.GetResult()
at System.Net.Http.HttpClient.d__321.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
at SlackBotNet.Drivers.SlackRtmDriver.d__8.MoveNext() in C:\projects\other\slackbotnet\src\SlackBotNet\Drivers\SlackRtmDriver.cs:line 116
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at SlackBotNet.Drivers.SlackRtmDriver.<>c__DisplayClass6_0.<b__0>d.MoveNext() in C:\projects\other\slackbotnet\src\SlackBotNet\Drivers\SlackRtmDriver.cs:line 75

Inner Exception 1:
WinHttpException: Can not resolve server name or address

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.