Reputation: 147
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
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