JEREDEK
JEREDEK

Reputation: 147

Discord.net Problem with announcing new users joining

I used C# and Discord.net to make my own Discord bot, but something doesn't work when announcing a new user. When someone joins, it sends the message, and all is good, but right after, the bot crashes with the error

    System.NullReferenceException: 'Object reference not set to an instance of an object.'

at line

    var context = new SocketCommandContext(_client, message);

Any ideas as to why?

Here is the full code:

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.IO;
using Discord.Rest;

namespace DiscordBot2
{
    class Program
    {
        static void Main()
        {
            Thread t = new Thread(new ThreadStart(input));
            t.Start();
            new Program().RunBotAsync().GetAwaiter().GetResult();
        }


        private DiscordSocketClient _client;
        private CommandService _commands;
        private IServiceProvider _services;

        public async Task RunBotAsync()
        {
            _client = new DiscordSocketClient();
            _commands = new CommandService();

            _services = new ServiceCollection()
                .AddSingleton(_client)
                .AddSingleton(_commands)
                .BuildServiceProvider();

            string token = "lol you wish";
            _client.Log += _client_Log;

            await HandleNewUser();

            await RegisterCommandsAsync();

            await _client.LoginAsync(TokenType.Bot, token);

            await _client.StartAsync();

            await _client.SetGameAsync("with the flock!");
                
            await _client.SetStatusAsync(UserStatus.Online);

            await Task.Delay(-1);
        }

        private Task _client_Log(LogMessage arg)
        {
            Console.WriteLine(arg);
            return Task.CompletedTask;
        }

        public async Task RegisterCommandsAsync()
        {
            _client.MessageReceived += HandleCommandAsync; //Handle incoming commands
            await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
        }

        public async Task HandleNewUser()
        {
            _client.UserJoined += AnnounceJoinedUser;
            await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
        }

        public async Task AnnounceJoinedUser(SocketGuildUser user)
        {
            var channel = _client.GetChannel(743578569476931626) as SocketTextChannel; // Gets the channel to send the message in
            await channel.SendMessageAsync($"Welcome {user.Mention} to {channel.Guild.Name}"); //Welcomes the new user
            return;
        }

        private async Task HandleCommandAsync(SocketMessage arg)
        {
            var message = arg as SocketUserMessage;
            var context = new SocketCommandContext(_client, message);
            //Prevent looping
            if (message.Author.IsBot) return;

            //Check for prefix
            int argPos = 0;
            await BadWordsWarn(message);
            if (message.HasStringPrefix("c!", ref argPos))
            {
                //Execute command
                var result = await _commands.ExecuteAsync(context, argPos, _services);
                //If it failed, log the error
                if (!result.IsSuccess)
                { 
                    Console.WriteLine(@"'" + message.Content + @"' has thrown an error: " + result.ErrorReason);
                    //Handle errors in commands
                    result = await _commands.ExecuteAsync(context, argPos, _services);
                    if (result.ErrorReason != null)
                        switch (result.ErrorReason)
                        {
                            //Not enough paramaters
                            case "The input text has too few parameters.":
                                await context.Channel.SendMessageAsync(
                                    "This command lacks parameters. Check the command description for more details.");
                                break;
                            //Bad command
                            case "Unknown command.":
                                await context.Channel.SendMessageAsync(
                                    "I don't understand this command. :frowning: You can type **c!list** to see the list of avaliable commands.");
                                break;
                            //Some other shenanigans
                            default:
                                await context.Channel.SendMessageAsync(
                                    $"{result.ErrorReason}");
                                break;
                        }
                }
            }
        }
        private async Task BadWordsWarn(SocketMessage message)
        {
            string[] badWords = File.ReadAllLines("Banned.txt");

            if (badWords.Any(word => message.Content.IndexOf(word, 0, message.Content.Length, StringComparison.OrdinalIgnoreCase) >= 0))
            {
                var m = (RestUserMessage)await message.Channel.GetMessageAsync(message.Id);
                await m.DeleteAsync();
            }
        }

Upvotes: 0

Views: 2042

Answers (1)

Daniel
Daniel

Reputation: 1481

I'm not sure where the variable input is coming from in Main(), but the way I start my bot is as follows:

static void Main() => new Program().RunBotAsync().GetAwaiter().GetResult();

You only need to call AddModulesAsync() once. You were calling it in RegisterCommandsAsync() and HandleNewUser(). Calling it more than once may cause problems, so I've moved it to the RunBotAsync() method and removed it from the other two methods. I've also moved the lines _client.MessageReceived += HandleCommandAsync; and _client.UserJoined += AnnounceJoinedUser; into theRunBotAsync() method as I was left with two separate methods each running a single line of code which only ever need to be called once, and I don't feel the extra methods are necessary for this. The RegisterCommandsAsync() and HandleNewUser() methods can be deleted.

public async Task RunBotAsync()
{
    _client = new DiscordSocketClient();
    _commands = new CommandService();

    _services = new ServiceCollection()
        .AddSingleton(_client)
        .AddSingleton(_commands)
        .BuildServiceProvider();

    await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); // This should only be called once. You were calling it in RegisterCommandsAsync() and RegisterCommandsAsync()

    string token = "lol you wish";
    _client.Log += _client_Log;

    _client.UserJoined += AnnounceJoinedUser;

    _client.MessageReceived += HandleCommandAsync; //Handle incoming commands

    await _client.LoginAsync(TokenType.Bot, token);

    await _client.StartAsync();

    await _client.SetGameAsync("with the flock!");

    await _client.SetStatusAsync(UserStatus.Online);

    await Task.Delay(-1);
}

The line var message = arg as SocketUserMessage; in your HandleCommandAsync() method can result in null, which looks like the cause of your problem. To handle this I've replaced that line with if (arg is SocketUserMessage message) and wrapped all of your code inside it. This way you are checking for null before executing the rest of your code. Alternatively you could keep the original line and add if (message == null) return; underneath it.

You are also executing your commands with _commands.ExecuteAsync and then if they fail, you're executing them again before sending an error response to the Discord channel. If a command failed the first time, then chances are it's also going to fail the second time so I'm not sure why your're executing them again. I've commented the second line so it now only executes them once.

private async Task HandleCommandAsync(SocketMessage arg)
{
    if (arg is SocketUserMessage message)
    {
        var context = new SocketCommandContext(_client, message);
        //Prevent looping
        if (message.Author.IsBot) return;

        //Check for prefix
        int argPos = 0;
        await BadWordsWarn(message);
        if (message.HasStringPrefix("c!", ref argPos))
        {
            //Execute command
            var result = await _commands.ExecuteAsync(context, argPos, _services);
            //If it failed, log the error
            if (!result.IsSuccess)
            {
                Console.WriteLine(@"'" + message.Content + @"' has thrown an error: " + result.ErrorReason);
                //Handle errors in commands
                //result = await _commands.ExecuteAsync(context, argPos, _services); // Why are you executing the command again after it failed?
                if (result.ErrorReason != null)
                    switch (result.ErrorReason)
                    {
                        //Not enough paramaters
                        case "The input text has too few parameters.":
                            await context.Channel.SendMessageAsync(
                                "This command lacks parameters. Check the command description for more details.");
                            break;
                        //Bad command
                        case "Unknown command.":
                            await context.Channel.SendMessageAsync(
                                "I don't understand this command. :frowning: You can type **c!list** to see the list of avaliable commands.");
                            break;
                        //Some other shenanigans
                        default:
                            await context.Channel.SendMessageAsync(
                                $"{result.ErrorReason}");
                            break;
                    }
            }
        }
    }
}

This YouTube video helped me to get started with Discord.Net. Maybe it will help you too: https://www.youtube.com/watch?v=ccsf5Rcu9mM

Upvotes: 1

Related Questions