soxtoby / SlackNet

A comprehensive Slack API client for .NET
MIT License
208 stars 65 forks source link

Dispatch_failled, can't manage to use commands #179

Closed Dabendorf closed 10 months ago

Dabendorf commented 10 months ago

I might not be the first asking this, but I am just entirely failing to use slack commands, while the rest of this amazing framework works.

I set up a slack command /echo routing to https://mywebsite.net/Slack/echo/slack/command

The code is the following:

[HttpPost]
        [Route("[Controller]/echo/slack/command")]
        [Produces("application/json")]
        public async Task<SlashCommandResponse> Handle(SlashCommand command) {
            Console.WriteLine($"{command.UserName} used the /echo slash command in the {command.ChannelName} channel");

            return new SlashCommandResponse {
                Message = new Message
                {
                    Text = command.Text
                }
            };
        }

I literally tried everything. Changing the route, changing the body, having other arguments, literally everything I do gets me to "/echo failed with the error dispatch_failed". I used this as manual: https://github.com/soxtoby/SlackNet/tree/master/Examples/SlackNetDemo

Anything obvious I did wrong here? I tested the URL, it is in fact existing

Dabendorf commented 10 months ago

Edit: I also tried it with socket mode instead:

using SlackNet.Interaction;
using SlackNet.WebApi;

namespace SlackAPI.Controllers; 

class SlashCommands : ISlashCommandHandler {
    public const string SlashCommand = "/echo";

    public async Task<SlashCommandResponse> Handle(SlashCommand command) {
        Console.WriteLine($"{command.UserName} used the {SlashCommand} slash command in the {command.ChannelName} channel");

        return new SlashCommandResponse {
            Message = new Message
            {
                Text = command.Text
            }
        };
    }
}
builder.Services.AddSlackNet(c => c
        .UseApiToken(slackConfig.SlackAccessToken)
        .UseAppLevelToken(
            slackConfig.AppLevelToken)
        .RegisterEventHandler<MessageEvent, PingHandler>()
    .RegisterSlashCommandHandler<SlashCommands>(SlashCommands.SlashCommand));

But same error

soxtoby commented 10 months ago

Thanks for the question, @Dabendorf. The best bet here is to let SlackNet handle the slash command request for you, rather than setting up your own route. SlackNet sets up its own endpoints, so you can tell Slack to send all slash command requests to /slack/command (without the preceding /Slack/echo that you've got in your first example) and register a slash command handler like you have in your socket mode example.

If you go down the socket mode route, then make sure you enable socket mode on your Slack app, which will remove the need to specify any URLs for handling slash commands etc.

Hope that helps.

Dabendorf commented 10 months ago

Hi, Thank you for your answer. Yeah I trashed the original routing functionality and just run your version with sockets. I am now not getting any errors any longer, but nothing happens. Maybe I just misunderstand the concept.

My class looks like this:

public class SlashCommands : ISlashCommandHandler {
    public const string SlashCommand = "/echo";
    private readonly ISlackApiClient _slack;
    private readonly IConfiguration _configuration;

    public SlashCommands(ISlackApiClient slack, IConfiguration configuration) {
        _slack = slack;
        _configuration = configuration;
    }

    public async Task<SlashCommandResponse> Handle(SlashCommand command) {
        Console.WriteLine("TEST TEST");
        Console.WriteLine($"{command.UserName} used the {SlashCommand} slash command in the {command.ChannelName} channel");
        await _slack.Chat.PostMessage(new Message() { Text = "Test Test Hello", Channel = _configuration.GetValue<string>("Slack:ChannelToSend")}, null);
        Console.WriteLine(command.UserName);

        return new SlashCommandResponse {
            Message = new Message {
                Text = command.Text
            }
        };
    }
}

Its not the final code, its just for testing purpose. I tried to do anything, sending /echo, /echo with text, any other command. It just doesn't happen anything. Also the printing does not happen and in debugging, I never get into the Handle method. So I think something is wrong there I guess.

My Program.cs looks like that:

Console.WriteLine("Configuring...");

var settings = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .AddUserSecrets<Program>()
    .Build();

var serviceCollection = new ServiceCollection();
serviceCollection.AddSlackNet(c => c
    // Configure the tokens used to authenticate with Slack
    .UseApiToken("removed") // This gets used by the API client
    .UseAppLevelToken("removed") // This gets used by the socket mode client

    // Echo demo - a slash command for telling you what you already know
    .RegisterSlashCommandHandler<SlashCommands>(SlashCommands.SlashCommand)

);

var client = new SlackServiceBuilder()
    .UseAppLevelToken("removed")
    .GetSocketModeClient();
await client.Connect();

Console.WriteLine("Connected. Press any key to exit...");
await Task.Run(Console.ReadKey);

I tested like literally every configuration (except the one who will work, haha). Do you see anything obvious what is weird here?

soxtoby commented 10 months ago

I think the problem here is that you've configured SlackNet with a ServiceCollection, but then resolved the socket mode client from a separate SlackServiceBuilder, so the client connects, but slash commands won't go anywhere because they were configured somewhere else.

I'd suggest resolving the socket mode client from the ServiceProvider like this:

var serviceProvider = serviceCollection.BuildServiceProvider();
var client = serviceProvider.SlackServices().GetSocketModeClient();

That way the socket mode client will get the configuration from the ServiceCollection.

Dabendorf commented 10 months ago

Hey, Thanks for your answer. I am not sure how to resolve that. I tried these two lines before, since they are also part of the manual, but I get a warning in line one and an error in line 2. It marks SlackServices() red and says Cannot resolve symbol. The warning in the first one is that BuildServiceProvider generates an additional copy of a singleton. I am not sure why it cannot find any SlackServices. Maybe its just missing an import, but I guess I used all you used and my IDE wont give me any recommendations

soxtoby commented 10 months ago

Hmm odd. SlackServices() is in the same namespace as AddSlackNet(): SlackNet.Extensions.DependencyInjection. Make sure you're calling it on serviceProvider, not serviceCollection. Alternatively you could resolve the service directly with:

var client = serviceProvider.GetRequiredService<ISlackSocketModeClient>();

As for the "additional copy of singleton services" warning, it looks like that warning shows up when you use BuildServiceProvider() in an ASP.NET project, since they're expecting you to use an app builder instead of creating the ServiceProvider directly.

soxtoby commented 10 months ago

Oops finger slipped. If you're not going to use ASP.NET, then change the top of your project file from:

<Project Sdk="Microsoft.NET.Sdk.Web">

to

<Project Sdk="Microsoft.NET.Sdk">

and the warning on BuildServiceProvider should disappear.

Dabendorf commented 10 months ago

Thank you very much for you answering here, that helps a lot! I am not sure if I maybe debug my problem wrong, but I don't know. The code is know free for errors in the IDE, but still, nothing is running.

using SlackAPI.Handlers;
using SlackNet;

using SlackNet.Extensions.DependencyInjection;

Console.WriteLine("Configuring...");

var settings = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .AddUserSecrets<Program>()
    .Build();

var serviceCollection = new ServiceCollection();
serviceCollection.AddSlackNet(c => c
    // Configure the tokens used to authenticate with Slack
    .UseApiToken("removed") // This gets used by the API client
    .UseAppLevelToken("removed") // This gets used by the socket mode client

    // Echo demo - a slash command for telling you what you already know
    .RegisterSlashCommandHandler<SlashCommands>(SlashCommands.SlashCommand)

);

var serviceProvider = serviceCollection.BuildServiceProvider();
var client = serviceProvider.GetRequiredService<ISlackSocketModeClient>();

await client.Connect();

Console.WriteLine("Connected. Press any key to exit...");
await Task.Run(Console.ReadKey);

When I use it locally, I do not have any output whatsover. The bot just don't react to anything. If I push it into production and run it there, then I get the old dispatch failed messages for literally every command I defined.

I personally always wondered how the SlashCommands class actually knows which command I use? Is the handler for all commands? Maybe that is my problem.

using SlackNet;
using SlackNet.Interaction;
using SlackNet.WebApi;

namespace SlackAPI.Handlers; 

public class SlashCommands : ISlashCommandHandler {
    public const string SlashCommand = "/echo";
    private readonly ISlackApiClient _slack;
    private readonly IConfiguration _configuration;

    public SlashCommands(ISlackApiClient slack, IConfiguration configuration) {
        _slack = slack;
        _configuration = configuration;
    }

    public async Task<SlashCommandResponse> Handle(SlashCommand command) {
        Console.WriteLine("TEST TEST");
        Console.WriteLine($"{command.UserName} used the {SlashCommand} slash command in the {command.ChannelName} channel");
        await _slack.Chat.PostMessage(new Message() { Text = "Test Test Hello", Channel = _configuration.GetValue<string>("Slack:ChannelToSend")}, null);
        Console.WriteLine(command.UserName);

        return new SlashCommandResponse {
            Message = new Message {
                Text = command.Text
            }
        };
    }
}

But I don't know. Its just weird that I cannot debug it properly

Dabendorf commented 10 months ago

Interesting fact: your PingHandler demo works perfectly in my programme, its just the slash commands failing

soxtoby commented 10 months ago

RegisterSlashCommandHandler takes in the command that the handler is mapped to, so the handler will end up handling every command you register it for (which in this case should just be /echo).

Running your code, I got an error constructing SlashCommands because there was no IConfiguration registered with the service provider. It was probably registered automatically when you were using ASP.NET, but now you'll need to register it manually with:

serviceCollection.AddSingleton<IConfiguration>(settings);

(make sure to include the generic argument, because settings will be IConfigurationRoot, not IConfiguration specifically)

Only other things I can think of would be to double-check that the slash command you've set up in your Slack app exactly matches what's being registered with SlackNet, and that it's the only app you have with that slash command.

Hope that helps.

Dabendorf commented 10 months ago

Hey Thank you for all your help. I have over the weekend just refactored the entire code and now use your asp.net example code instead. I have no idea what was wrong with my old code, but the new version works nice for me :) Thanks for all the help and the great project