RegexBot/Module/ModCommands/Commands/BanKick.cs

211 lines
8.5 KiB
C#
Raw Normal View History

using Discord.WebSocket;
using Newtonsoft.Json.Linq;
using Noikoio.RegexBot.ConfigItem;
using System;
using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.ModCommands.Commands
{
// Ban and kick commands are highly similar in implementation, and thus are handled in a single class.
class BanKick : Command
{
protected enum CommandMode { Ban, Kick }
private readonly CommandMode _mode;
private readonly bool _forceReason;
private readonly int _purgeDays;
private readonly string _successMsg;
private readonly string _notifyMsg;
// Configuration:
// "forcereason" - boolean; Force a reason to be given. Defaults to false.
// "purgedays" - integer; Number of days of target's post history to delete, if banning.
// Must be between 0-7 inclusive. Defaults to 0.
// "successmsg" - Message to display on command success. Overrides default.
// "notifymsg" - Message to send to the target user being acted upon. Default message is used
// if the value is not specified. If a blank value is given, the feature is disabled.
// Takes the special values $s for server name and $r for reason text.
protected BanKick(CommandListener l, string label, JObject conf, CommandMode mode) : base(l, label, conf)
{
_mode = mode;
_forceReason = conf["forcereason"]?.Value<bool>() ?? false;
_purgeDays = conf["purgedays"]?.Value<int>() ?? 0;
if (_mode == CommandMode.Ban && (_purgeDays > 7 || _purgeDays < 0))
{
throw new RuleImportException("The value of 'purgedays' must be between 0 and 7.");
}
_successMsg = conf["successmsg"]?.Value<string>();
if (conf["notifymsg"] == null)
{
// Message not specified - use default
2018-01-19 08:57:36 +00:00
_notifyMsg = string.Format(NotifyDefault, mode == CommandMode.Ban ? "banned" : "kicked");
}
else
{
string val = conf["notifymsg"].Value<string>();
if (string.IsNullOrWhiteSpace(val)) _notifyMsg = null; // empty value - disable message
else _notifyMsg = val;
}
// Building usage message here
DefaultUsageMsg = $"{this.Trigger} [user or user ID] " + (_forceReason ? "[reason]" : "*[reason]*") + "\n"
+ "Removes the given user from this server"
+ (_mode == CommandMode.Ban ? " and prevents the user from rejoining" : "") + ". "
+ (_forceReason ? "L" : "Optionally l") + "ogs the reason for the "
+ (_mode == CommandMode.Ban ? "ban" : "kick") + " to the Audit Log.";
if (_purgeDays > 0)
DefaultUsageMsg += $"\nAdditionally removes the user's post history for the last {_purgeDays} day(s).";
}
2018-01-19 08:57:36 +00:00
#region Strings
const string FailPrefix = ":x: **Failed to {0} user:** ";
const string Fail404 = "The specified user is no longer in the server.";
2018-01-19 08:57:36 +00:00
const string NotifyDefault = "You have been {0} from $s for the following reason:\n$r";
const string NotifyReasonNone = "No reason specified.";
const string NotifyFailed = "\n(User was unable to receive notification message.)";
const string ReasonRequired = ":x: **You must specify a reason.**";
const string TargetNotFound = ":x: **Unable to determine the target user.**";
#endregion
// Usage: (command) (mention) (reason)
public override async Task Invoke(SocketGuild g, SocketMessage msg)
{
string[] line = msg.Content.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
string targetstr;
string reason;
if (line.Length < 2)
{
await SendUsageMessageAsync(msg.Channel, null);
return;
}
targetstr = line[1];
if (line.Length == 3)
{
// Reason given - keep it
reason = line[2];
}
else
{
// No reason given
if (_forceReason)
{
await SendUsageMessageAsync(msg.Channel, ReasonRequired);
return;
}
reason = null;
}
// Retrieve target user
var (targetId, targetData) = await GetUserDataFromString(g.Id, targetstr);
if (targetId == 1)
2018-03-05 02:04:10 +00:00
{
await msg.Channel.SendMessageAsync(FailPrefix + FailDefault);
return;
}
if (targetId == 0)
{
await SendUsageMessageAsync(msg.Channel, TargetNotFound);
return;
}
SocketGuildUser targetobj = g.GetUser(targetId);
string targetdisp;
if (targetData != null)
targetdisp = $"{targetData.Username}#{targetData.Discriminator}";
else
targetdisp = $"ID {targetId}";
if (_mode == CommandMode.Kick && targetobj == null)
{
// Can't kick without obtaining the user object
await SendUsageMessageAsync(msg.Channel, TargetNotFound);
return;
}
// Send out message
2018-01-19 08:57:36 +00:00
var notifyTask = SendNotificationMessage(targetobj, reason);
// Do the action
try
{
string reasonlog = $"Invoked by {msg.Author.ToString()}.";
if (reason != null) reasonlog += $" Reason: {reason}";
reasonlog = Uri.EscapeDataString(reasonlog);
#warning Remove EscapeDataString call on next Discord.Net update
#if !DEBUG
if (_mode == CommandMode.Ban) await g.AddBanAsync(targetId, _purgeDays, reasonlog);
else await targetobj.KickAsync(reason);
#else
#warning "Actual kick/ban action is DISABLED during debug."
#endif
string resultmsg = BuildSuccessMessage(targetdisp);
2018-01-19 08:57:36 +00:00
if (await notifyTask == false) resultmsg += NotifyFailed;
await msg.Channel.SendMessageAsync(resultmsg);
}
catch (Discord.Net.HttpException ex)
{
2018-01-19 08:57:36 +00:00
string err = string.Format(FailPrefix, (_mode == CommandMode.Ban ? "ban" : "kick"));
if (ex.HttpCode == System.Net.HttpStatusCode.Forbidden)
{
2018-01-19 08:57:36 +00:00
await msg.Channel.SendMessageAsync(err + Fail403);
}
else if (ex.HttpCode == System.Net.HttpStatusCode.NotFound)
{
2018-01-19 08:57:36 +00:00
await msg.Channel.SendMessageAsync(err + Fail404);
}
else
{
2018-01-19 08:57:36 +00:00
await msg.Channel.SendMessageAsync(err + FailDefault);
await Log(ex.ToString());
}
}
}
2018-01-19 08:57:36 +00:00
// Returns true on message send success
private async Task<bool> SendNotificationMessage(SocketGuildUser target, string reason)
{
if (_notifyMsg == null) return true;
if (target == null) return false;
var ch = await target.GetOrCreateDMChannelAsync();
string outresult = _notifyMsg;
2018-02-07 20:47:51 +00:00
outresult = outresult.Replace("$s", target.Guild.Name);
2018-01-19 08:57:36 +00:00
outresult = outresult.Replace("$r", reason ?? NotifyReasonNone);
try
{
await ch.SendMessageAsync(outresult);
}
catch (Discord.Net.HttpException ex)
{
await Log("Failed to send out notification to target over DM: "
+ Enum.GetName(typeof(System.Net.HttpStatusCode), ex.HttpCode));
return false;
}
return true;
}
private string BuildSuccessMessage(string targetstr)
{
const string defaultmsgBan = ":white_check_mark: Banned user **$target**.";
const string defaultmsgKick = ":white_check_mark: Kicked user **$target**.";
string msg = _successMsg ?? (_mode == CommandMode.Ban ? defaultmsgBan : defaultmsgKick);
return msg.Replace("$target", targetstr);
}
}
class Ban : BanKick
{
public Ban(CommandListener l, string label, JObject conf)
: base(l, label, conf, CommandMode.Ban) { }
}
class Kick : BanKick
{
public Kick(CommandListener l, string label, JObject conf)
: base(l, label, conf, CommandMode.Kick) { }
}
}