Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public ReactionCallbackData(string text, Embed embed = null, bool expiresAfterUs
SingleUsePerUser = singleUsePerUser;
ExpiresAfterUse = expiresAfterUse;
ReactorIDs = new List<ulong>();
Text = text ?? "";
Text = text ?? string.Empty;
Embed = embed;
Timeout = timeout;
TimeoutCallback = timeoutCallback;
Expand Down
4 changes: 2 additions & 2 deletions Floofbot/Configs/BotConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ class BotConfig
public string BackupScript { get; set; }
public int NumberOfBackups { get; set; }
public List<ulong> AnnouncementChannels { get; set; }
public Dictionary <string, int> RaidProtection { get; set; }
public Dictionary <string, string> RulesGate { get; set; }
public Dictionary<string, int> RaidProtection { get; set; }
public Dictionary<string, string> RulesGate { get; set; }

}
}
19 changes: 11 additions & 8 deletions Floofbot/Configs/BotConfigFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ public static BotConfig Config
[MethodImpl(MethodImplOptions.Synchronized)]
get
{
if (_config == null)
{
_config = BotConfigParser.ParseFromFile(_filename);
_token = _config.Token;
}
if (_config != null) return _config;

_config = BotConfigParser.ParseFromFile(_filename);
_token = _config.Token;

return _config;
}
[MethodImpl(MethodImplOptions.Synchronized)]
Expand All @@ -38,8 +38,9 @@ public static void Initialize(string filename)

public static void Reinitialize()
{
BotConfig config = BotConfigParser.ParseFromFile(_filename);
// sanity check to make sure the token was not changed upon reload
var config = BotConfigParser.ParseFromFile(_filename);

// Sanity check to make sure the token was not changed upon reload
if (config.Token == _token)
{
_config = config;
Expand All @@ -50,7 +51,7 @@ public static void Reinitialize()
}
}

class BotConfigParser
private class BotConfigParser
{
public static BotConfig ParseFromFile(string filename)
{
Expand All @@ -59,11 +60,13 @@ public static BotConfig ParseFromFile(string filename)
var fileContents = new StringReader(File.ReadAllText(filename));
var deserializer = new Deserializer();
var config = deserializer.Deserialize<BotConfig>(fileContents);

return config;
}
catch (Exception e)
{
Log.Error(e.ToString());

return new BotConfig();
}
}
Expand Down
128 changes: 76 additions & 52 deletions Floofbot/Handlers/CommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,101 +24,114 @@ public class CommandHandler

public CommandHandler(DiscordSocketClient client)
{
_client = client;
_services = BuildServiceProvider(client);
var context = _services.GetRequiredService<FloofDataContext>();
context.Database.Migrate(); // apply all migrations

_client = client;
_services = BuildServiceProvider(client);

_commands = new CommandService(new CommandServiceConfig { CaseSensitiveCommands = false });
_commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);

_client.MessageReceived += HandleCommandAsync;
_client.MessageUpdated += OnMessageUpdatedHandler;
}

private IServiceProvider BuildServiceProvider(DiscordSocketClient client)
{
InteractiveService interactiveService = new InteractiveService(client);
var interactiveService = new InteractiveService(client);

return new ServiceCollection()
.AddSingleton<InteractiveService>(interactiveService)
.AddSingleton(interactiveService)
.AddDbContext<FloofDataContext>()
.BuildServiceProvider();
}

private Embed generateErrorEmbed(SocketUser user, IResult result, SocketUserMessage msg)
private Embed GenerateErrorEmbed(SocketUser user, IResult result, SocketUserMessage msg)
{
EmbedAuthorBuilder author = new EmbedAuthorBuilder();
var author = new EmbedAuthorBuilder();

author.Name = user.Username + "#" + user.Discriminator;

if (Uri.IsWellFormedUriString(user.GetAvatarUrl(), UriKind.Absolute))
author.IconUrl = user.GetAvatarUrl();

if (msg.Channel.GetType() != typeof(SocketDMChannel))
author.Url = msg.GetJumpUrl();

EmbedBuilder builder = new EmbedBuilder
{
Author = author,
Title = "A fatal error has occured. User message content: " + msg.Content,
Description = result.Error + "\n```" + result.ErrorReason + "```",
Color = Color.Red
};
builder.AddField("Channel Type", (msg.Channel.GetType() == typeof(SocketDMChannel) ? "DM" : "Guild") + " Channel");

builder.WithCurrentTimestamp();
var builder = new EmbedBuilder
{
Author = author,
Title = "A fatal error has occured. User message content: " + msg.Content,
Description = result.Error + "\n```" + result.ErrorReason + "```",
Color = Color.Red
}.AddField("Channel Type", (msg.Channel.GetType() == typeof(SocketDMChannel) ? "DM" : "Guild") + " Channel")
.WithCurrentTimestamp();

return builder.Build();
}

private async Task LogErrorInDiscordChannel(IResult result, SocketMessage originalMessage)
{
FloofDataContext _floofDb = new FloofDataContext();

var userMsg = originalMessage as SocketUserMessage; // the original command
var channel = userMsg.Channel as ITextChannel; // the channel of the original command
if (channel == null)
return;

var serverConfig = _floofDb.ErrorLoggingConfigs.Find(channel.GuildId); // no db result
if (serverConfig == null)
return;

if ((!serverConfig.IsOn) || (serverConfig.ChannelId == null)) // not configured or disabled
return;

Discord.ITextChannel errorLoggingChannel = await channel.Guild.GetTextChannelAsync((ulong)serverConfig.ChannelId); // can return null if channel invalid
if (errorLoggingChannel == null)
return;


Embed embed = generateErrorEmbed(userMsg.Author, result, userMsg);
await errorLoggingChannel.SendMessageAsync("", false, embed);
return;
await using (var floofDb = new FloofDataContext())
{
var userMsg = originalMessage as SocketUserMessage; // the original command
var channel = userMsg.Channel as ITextChannel; // the channel of the original command

if (channel == null)
return;

var serverConfig = await floofDb.ErrorLoggingConfigs.FindAsync(channel.GuildId); // no db result

if (serverConfig == null)
return;

if (!serverConfig.IsOn || (serverConfig.ChannelId == null)) // not configured or disabled
return;

var errorLoggingChannel = await channel.Guild.GetTextChannelAsync((ulong)serverConfig.ChannelId); // can return null if channel invalid

if (errorLoggingChannel == null)
return;

var embed = GenerateErrorEmbed(userMsg.Author, result, userMsg);

await errorLoggingChannel.SendMessageAsync(string.Empty, false, embed);
}
}

private async Task OnMessageUpdatedHandler(Cacheable<IMessage, ulong> before, SocketMessage after, ISocketMessageChannel chan)
{
var messageBefore = before.Value as IUserMessage;

if (messageBefore == null)
return;

if (messageBefore.Content == after.Content)
return;

if (messageBefore.EditedTimestamp == null) // user has never edited their message
if (messageBefore.EditedTimestamp == null) // User has never edited their message
{
var timeDifference = DateTimeOffset.Now - messageBefore.Timestamp;

if (timeDifference.TotalSeconds < 30)
await HandleCommandAsync(after);

}
}
private async Task HandleCommandAsync(SocketMessage s)

private async Task HandleCommandAsync(SocketMessage socketMessage)
{
var msg = s as SocketUserMessage;
var msg = socketMessage as SocketUserMessage;

if (msg == null)
return;

if (msg.Author.IsBot)
return;

var context = new SocketCommandContext(_client, msg);
int argPos = 0;
string prefix;
var argPos = 0;
var prefix = string.Empty;

if (string.IsNullOrEmpty(BotConfigFactory.Config.Prefix))
{
Expand All @@ -130,55 +143,66 @@ private async Task HandleCommandAsync(SocketMessage s)
prefix = BotConfigFactory.Config.Prefix;
}

bool hasValidPrefix = msg.HasMentionPrefix(_client.CurrentUser, ref argPos) || msg.HasStringPrefix(prefix, ref argPos);
string strippedCommandName = msg.Content.Substring(argPos).Split()[0];
bool hasValidStart = !string.IsNullOrEmpty(strippedCommandName) && Regex.IsMatch(strippedCommandName, @"^[0-9]?[a-z]+\??$", RegexOptions.IgnoreCase);
var hasValidPrefix = msg.HasMentionPrefix(_client.CurrentUser, ref argPos) || msg.HasStringPrefix(prefix, ref argPos);
var strippedCommandName = msg.Content.Substring(argPos).Split()[0];
var hasValidStart = !string.IsNullOrEmpty(strippedCommandName) && Regex.IsMatch(strippedCommandName, @"^[0-9]?[a-z]+\??$", RegexOptions.IgnoreCase);

if (hasValidPrefix && hasValidStart)
{
var result = await _commands.ExecuteAsync(context, argPos, _services);

if (!result.IsSuccess)
{
string errorMessage = "An unknown exception occured. I have notified the administrators.";
bool isCriticalFailure = false;
var errorMessage = "An unknown exception occured. I have notified the administrators.";
var isCriticalFailure = false;

switch (result.Error)
{
case CommandError.BadArgCount:
errorMessage = result.ErrorReason;
break;

case CommandError.MultipleMatches:
errorMessage = "Multiple results. I don't know which one you want! Please try to be more specific.";
break;

case CommandError.ObjectNotFound:
errorMessage = "One or more of your command parameters could not be resolved - " + result.ErrorReason;
break;

case CommandError.ParseFailed:
errorMessage = "For some reason, I can't understand your command.";
break;

case CommandError.UnknownCommand:
// check 8ball response
// Check 8ball response
if (msg.HasMentionPrefix(_client.CurrentUser, ref argPos) && msg.Content.EndsWith("?"))
{
string eightBallResponse = Floofbot.Modules.Helpers.EightBall.GetRandomResponse();
var eightBallResponse = Floofbot.Modules.Helpers.EightBall.GetRandomResponse();

Embed embed = new EmbedBuilder()
var embed = new EmbedBuilder()
{
Description = msg.Content
}.Build();

await msg.Channel.SendMessageAsync($"{msg.Author.Mention} {eightBallResponse}", false, embed);
return;
}
errorMessage = "Unknown command '" + strippedCommandName + "'. Please check your spelling and try again.";
break;

case CommandError.UnmetPrecondition:
errorMessage = "A command condition has not been met - " + result.ErrorReason;
break;

default:
await LogErrorInDiscordChannel(result, msg);
isCriticalFailure = true;
break;
}

await msg.Channel.SendMessageAsync("ERROR: ``" + errorMessage + "``");

if (isCriticalFailure)
Log.Error(result.Error + "\nMessage Content: " + msg.Content + "\nError Reason: " + result.ErrorReason);
else
Expand Down
19 changes: 12 additions & 7 deletions Floofbot/Handlers/RandomResponseGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,45 @@ class RandomResponseGenerator
{
public static string GenerateResponse(SocketUserMessage userMessage)
{

// System messages (e.x. pin notifications)
if (userMessage == null)
{
return string.Empty;
}

List<BotRandomResponse> responses = BotConfigFactory.Config.RandomResponses;
var responses = BotConfigFactory.Config.RandomResponses;

if (responses == null || responses.Count == 0)
{
return string.Empty;
}

Random rand = new Random();
double val = rand.NextDouble();
var rand = new Random();
var val = rand.NextDouble();

foreach (var response in responses)
{
Regex requiredInput = new Regex(response.Input, RegexOptions.IgnoreCase);
Match match = requiredInput.Match(userMessage.Content);
var requiredInput = new Regex(response.Input, RegexOptions.IgnoreCase);
var match = requiredInput.Match(userMessage.Content);

if (match.Success && val < response.Probability)
{
if (match.Groups.Count == 1) {
// no regex needed for the output
return response.Response;
}

List<string> matchedValues = new List<string>(match.Groups.Count - 1);
var matchedValues = new List<string>(match.Groups.Count - 1);

for (int i = 1; i < match.Groups.Count; i++)
{
matchedValues.Add(match.Groups[i].Value);
}

return string.Format(response.Response, matchedValues.ToArray());
}
}

return string.Empty;
}
}
Loading