reorganized administration

This commit is contained in:
Master Kwoth 2016-03-29 15:20:18 +02:00
parent a6ae2701f7
commit 0d8598c845
14 changed files with 454 additions and 260 deletions

View File

@ -1,16 +1,19 @@
using Discord;
using Discord.Commands;
using NadekoBot.Extensions;
using NadekoBot.Modules;
using NadekoBot.Modules.Administration.Commands;
using System;
using System.Diagnostics;
using System.Linq;
using NadekoBot.Extensions;
using System.Threading.Tasks;
using System.Reflection;
using System.Threading.Tasks;
using System.Timers;
using NadekoBot.Modules;
namespace NadekoBot {
public class NadekoStats {
namespace NadekoBot
{
public class NadekoStats
{
public static NadekoStats Instance { get; } = new NadekoStats();
public string BotVersion => $"{Assembly.GetExecutingAssembly().GetName().Name} v{Assembly.GetExecutingAssembly().GetName().Version}";
@ -27,7 +30,8 @@ namespace NadekoBot {
static NadekoStats() { }
private NadekoStats() {
private NadekoStats()
{
var commandService = NadekoBot.Client.GetService<CommandService>();
statsStopwatch.Start();
@ -43,52 +47,66 @@ namespace NadekoBot {
TextChannelsCount = channelsArray.Count(c => c.Type == ChannelType.Text);
VoiceChannelsCount = channelsArray.Count() - TextChannelsCount;
NadekoBot.Client.JoinedServer += (s, e) => {
try {
NadekoBot.Client.JoinedServer += (s, e) =>
{
try
{
ServerCount++;
TextChannelsCount += e.Server.TextChannels.Count();
VoiceChannelsCount += e.Server.VoiceChannels.Count();
} catch { }
}
catch { }
};
NadekoBot.Client.LeftServer += (s, e) => {
try {
NadekoBot.Client.LeftServer += (s, e) =>
{
try
{
ServerCount--;
TextChannelsCount -= e.Server.TextChannels.Count();
VoiceChannelsCount -= e.Server.VoiceChannels.Count();
} catch { }
}
catch { }
};
NadekoBot.Client.ChannelCreated += (s, e) => {
try {
NadekoBot.Client.ChannelCreated += (s, e) =>
{
try
{
if (e.Channel.IsPrivate)
return;
if (e.Channel.Type == ChannelType.Text)
TextChannelsCount++;
else if (e.Channel.Type == ChannelType.Voice)
VoiceChannelsCount++;
} catch { }
}
catch { }
};
NadekoBot.Client.ChannelDestroyed += (s, e) => {
try {
NadekoBot.Client.ChannelDestroyed += (s, e) =>
{
try
{
if (e.Channel.IsPrivate)
return;
if (e.Channel.Type == ChannelType.Text)
VoiceChannelsCount++;
else if (e.Channel.Type == ChannelType.Voice)
VoiceChannelsCount--;
} catch { }
}
catch { }
};
}
public TimeSpan GetUptime() =>
DateTime.Now - Process.GetCurrentProcess().StartTime;
public string GetUptimeString() {
public string GetUptimeString()
{
var time = (DateTime.Now - Process.GetCurrentProcess().StartTime);
return time.Days + " days, " + time.Hours + " hours, and " + time.Minutes + " minutes.";
}
public Task LoadStats() =>
Task.Run(() => {
Task.Run(() =>
{
var songs = Music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null);
var sb = new System.Text.StringBuilder();
sb.AppendLine("`Author: Kwoth` `Library: Discord.Net`");
@ -102,7 +120,7 @@ namespace NadekoBot {
sb.AppendLine($" | VoiceChannels: {VoiceChannelsCount}`");
sb.AppendLine($"`Commands Ran this session: {commandsRan}`");
sb.AppendLine($"`Message queue size: {NadekoBot.Client.MessageQueue.Count}`");
sb.Append($"`Greeted {Commands.ServerGreetCommand.Greeted} times.`");
sb.Append($"`Greeted {ServerGreetCommand.Greeted} times.`");
sb.AppendLine($" `| Playing {songs} songs, ".SnPl(songs) +
$"{Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count)} queued.`");
sb.AppendLine($"`Heap: {Heap(false)}`");
@ -111,7 +129,8 @@ namespace NadekoBot {
public string Heap(bool pass = true) => Math.Round((double)GC.GetTotalMemory(pass) / 1.MiB(), 2).ToString();
public async Task<string> GetStats() {
public async Task<string> GetStats()
{
if (statsStopwatch.Elapsed.Seconds < 4 &&
!string.IsNullOrWhiteSpace(statsCache)) return statsCache;
await LoadStats();
@ -119,35 +138,45 @@ namespace NadekoBot {
return statsCache;
}
private async Task StartCollecting() {
while (true) {
private async Task StartCollecting()
{
while (true)
{
await Task.Delay(new TimeSpan(0, 30, 0));
try {
try
{
var onlineUsers = await Task.Run(() => NadekoBot.Client.Servers.Sum(x => x.Users.Count()));
var realOnlineUsers = await Task.Run(() => NadekoBot.Client.Servers
.Sum(x => x.Users.Count(u => u.Status == UserStatus.Online)));
var connectedServers = NadekoBot.Client.Servers.Count();
Classes.DbHandler.Instance.InsertData(new Classes._DataModels.Stats {
Classes.DbHandler.Instance.InsertData(new Classes._DataModels.Stats
{
OnlineUsers = onlineUsers,
RealOnlineUsers = realOnlineUsers,
Uptime = GetUptime(),
ConnectedServers = connectedServers,
DateAdded = DateTime.Now
});
} catch {
}
catch
{
Console.WriteLine("DB Exception in stats collecting.");
break;
}
}
}
private async void StatsCollector_RanCommand(object sender, CommandEventArgs e) {
private async void StatsCollector_RanCommand(object sender, CommandEventArgs e)
{
Console.WriteLine($">>Command {e.Command.Text}");
await Task.Run(() => {
try {
await Task.Run(() =>
{
try
{
commandsRan++;
Classes.DbHandler.Instance.InsertData(new Classes._DataModels.Command {
Classes.DbHandler.Instance.InsertData(new Classes._DataModels.Command
{
ServerId = (long)e.Server.Id,
ServerName = e.Server.Name,
ChannelId = (long)e.Channel.Id,
@ -157,7 +186,9 @@ namespace NadekoBot {
CommandName = e.Command.Text,
DateAdded = DateTime.Now
});
} catch {
}
catch
{
Console.WriteLine("Error in ran command DB write.");
}
});

View File

@ -1,27 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Discord;
using Discord;
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes.Permissions;
using NadekoBot.Modules;
using System;
using System.Linq;
using ServerPermissions = NadekoBot.Classes.Permissions.ServerPermissions;
namespace NadekoBot.Commands {
internal class FilterWords : DiscordCommand {
public FilterWords(DiscordModule module) : base(module) {
NadekoBot.Client.MessageReceived += async (sender, args) => {
namespace NadekoBot.Commands
{
internal class FilterWords : DiscordCommand
{
public FilterWords(DiscordModule module) : base(module)
{
NadekoBot.Client.MessageReceived += async (sender, args) =>
{
if (args.Channel.IsPrivate || args.User.Id == NadekoBot.Client.CurrentUser.Id) return;
try {
try
{
ServerPermissions serverPerms;
if (!IsChannelOrServerFiltering(args.Channel, out serverPerms)) return;
var wordsInMessage = args.Message.RawText.ToLowerInvariant().Split(' ');
if (serverPerms.Words.Any(w => wordsInMessage.Contains(w))) {
if (serverPerms.Words.Any(w => wordsInMessage.Contains(w)))
{
await args.Message.Delete();
IncidentsHandler.Add(args.Server.Id, $"User [{args.User.Name}/{args.User.Id}] posted " +
$"BANNED WORD in [{args.Channel.Name}/{args.Channel.Id}] channel. " +
@ -30,11 +32,13 @@ namespace NadekoBot.Commands {
await args.Channel.SendMessage($"{args.User.Mention} One or more of the words you used " +
$"in that sentence are not allowed here.");
}
} catch { }
}
catch { }
};
}
private static bool IsChannelOrServerFiltering(Channel channel, out ServerPermissions serverPerms) {
private static bool IsChannelOrServerFiltering(Channel channel, out ServerPermissions serverPerms)
{
if (!PermissionsHandler.PermissionsDict.TryGetValue(channel.Server.Id, out serverPerms)) return false;
if (serverPerms.Permissions.FilterWords)
@ -44,7 +48,8 @@ namespace NadekoBot.Commands {
return serverPerms.ChannelPermissions.TryGetValue(channel.Id, out perms) && perms.FilterWords;
}
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "cfw")
.Alias(Module.Prefix + "channelfilterwords")
.Description("Enables or disables automatic deleting of messages containing banned words on the channel." +
@ -52,12 +57,15 @@ namespace NadekoBot.Commands {
"\n**Usage**: ;cfi enable #general-chat")
.Parameter("bool")
.Parameter("channel", ParameterType.Optional)
.Do(async e => {
try {
.Do(async e =>
{
try
{
var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
var chanStr = e.GetArg("channel")?.ToLowerInvariant().Trim();
if (chanStr != "all") {
if (chanStr != "all")
{
var chan = string.IsNullOrWhiteSpace(chanStr)
? e.Channel
: PermissionHelper.ValidateChannel(e.Server, chanStr);
@ -67,11 +75,14 @@ namespace NadekoBot.Commands {
}
//all channels
foreach (var curChannel in e.Server.TextChannels) {
foreach (var curChannel in e.Server.TextChannels)
{
PermissionsHandler.SetChannelWordPermission(curChannel, state);
}
await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.");
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage($"💢 Error: {ex.Message}");
}
});
@ -81,15 +92,19 @@ namespace NadekoBot.Commands {
.Description("Adds a new word to the list of filtered words" +
"\n**Usage**: ;aw poop")
.Parameter("word", ParameterType.Unparsed)
.Do(async e => {
try {
.Do(async e =>
{
try
{
var word = e.GetArg("word");
if (string.IsNullOrWhiteSpace(word))
return;
PermissionsHandler.AddFilteredWord(e.Server, word.ToLowerInvariant().Trim());
await e.Channel.SendMessage($"Successfully added new filtered word.");
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage($"💢 Error: {ex.Message}");
}
});
@ -99,15 +114,19 @@ namespace NadekoBot.Commands {
.Description("Removes the word from the list of filtered words" +
"\n**Usage**: ;rw poop")
.Parameter("word", ParameterType.Unparsed)
.Do(async e => {
try {
.Do(async e =>
{
try
{
var word = e.GetArg("word");
if (string.IsNullOrWhiteSpace(word))
return;
PermissionsHandler.RemoveFilteredWord(e.Server, word.ToLowerInvariant().Trim());
await e.Channel.SendMessage($"Successfully removed filtered word.");
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage($"💢 Error: {ex.Message}");
}
});
@ -116,14 +135,18 @@ namespace NadekoBot.Commands {
.Alias(Module.Prefix + "listfilteredwords")
.Description("Shows a list of filtered words" +
"\n**Usage**: ;lfw")
.Do(async e => {
try {
.Do(async e =>
{
try
{
ServerPermissions serverPerms;
if (!PermissionsHandler.PermissionsDict.TryGetValue(e.Server.Id, out serverPerms))
return;
await e.Channel.SendMessage($"There are `{serverPerms.Words.Count}` filtered words.\n" +
string.Join("\n", serverPerms.Words));
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage($"💢 Error: {ex.Message}");
}
});
@ -132,13 +155,17 @@ namespace NadekoBot.Commands {
.Alias(Module.Prefix + "serverfilterwords")
.Description("Enables or disables automatic deleting of messages containing forbidden words on the server.\n**Usage**: ;sfi disable")
.Parameter("bool")
.Do(async e => {
try {
.Do(async e =>
{
try
{
var state = PermissionHelper.ValidateBool(e.GetArg("bool"));
PermissionsHandler.SetServerWordPermission(e.Server, state);
await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** on this server.");
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage($"💢 Error: {ex.Message}");
}
});

View File

@ -4,19 +4,19 @@ using Discord.Modules;
using NadekoBot.Classes;
using NadekoBot.Classes._DataModels;
using NadekoBot.Classes.Permissions;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using NadekoBot.Modules.Administration.Commands;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules
namespace NadekoBot.Modules.Administration
{
internal class Administration : DiscordModule
internal class AdministrationModule : DiscordModule
{
public Administration()
public AdministrationModule()
{
commands.Add(new ServerGreetCommand(this));
commands.Add(new LogCommand(this));

View File

@ -1,36 +1,48 @@
using System;
using Discord;
using Discord.Commands;
using NadekoBot.Classes.Permissions;
using NadekoBot.Commands;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Discord;
using Discord.Commands;
using NadekoBot.Classes.Permissions;
using NadekoBot.Modules;
namespace NadekoBot.Commands {
class CrossServerTextChannel : DiscordCommand {
public CrossServerTextChannel(DiscordModule module) : base(module) {
NadekoBot.Client.MessageReceived += async (s, e) => {
try {
namespace NadekoBot.Modules.Administration.Commands
{
class CrossServerTextChannel : DiscordCommand
{
public CrossServerTextChannel(DiscordModule module) : base(module)
{
NadekoBot.Client.MessageReceived += async (s, e) =>
{
try
{
if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return;
foreach (var subscriber in Subscribers) {
foreach (var subscriber in Subscribers)
{
var set = subscriber.Value;
if (!set.Contains(e.Channel))
continue;
foreach (var chan in set.Except(new[] { e.Channel })) {
foreach (var chan in set.Except(new[] { e.Channel }))
{
await chan.SendMessage(GetText(e.Server, e.Channel, e.User, e.Message));
}
}
} catch { }
}
catch { }
};
NadekoBot.Client.MessageUpdated += async (s, e) => {
try {
NadekoBot.Client.MessageUpdated += async (s, e) =>
{
try
{
if (e.After?.User?.Id == null || e.After.User.Id == NadekoBot.Client.CurrentUser.Id) return;
foreach (var subscriber in Subscribers) {
foreach (var subscriber in Subscribers)
{
var set = subscriber.Value;
if (!set.Contains(e.Channel))
continue;
foreach (var chan in set.Except(new[] { e.Channel })) {
foreach (var chan in set.Except(new[] { e.Channel }))
{
var msg = chan.Messages
.FirstOrDefault(m =>
m.RawText == GetText(e.Server, e.Channel, e.User, e.Before));
@ -39,7 +51,8 @@ namespace NadekoBot.Commands {
}
}
} catch { }
}
catch { }
};
}
@ -48,15 +61,18 @@ namespace NadekoBot.Commands {
public static readonly ConcurrentDictionary<int, HashSet<Channel>> Subscribers = new ConcurrentDictionary<int, HashSet<Channel>>();
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "scsc")
.Description("Starts an instance of cross server channel. You will get a token as a DM" +
"that other people will use to tune in to the same instance")
.AddCheck(SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
var token = new Random().Next();
var set = new HashSet<Channel>();
if (Subscribers.TryAdd(token, set)) {
if (Subscribers.TryAdd(token, set))
{
set.Add(e.Channel);
await e.User.SendMessage("This is your CSC token:" + token.ToString());
}
@ -66,7 +82,8 @@ namespace NadekoBot.Commands {
.Description("Joins current channel to an instance of cross server channel using the token.")
.Parameter("token")
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e => {
.Do(async e =>
{
int token;
if (!int.TryParse(e.GetArg("token"), out token))
return;
@ -80,8 +97,10 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(Module.Prefix + "lcsc")
.Description("Leaves Cross server channel instance from this channel")
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e => {
foreach (var subscriber in Subscribers) {
.Do(async e =>
{
foreach (var subscriber in Subscribers)
{
subscriber.Value.Remove(e.Channel);
}
await e.Channel.SendMessage(":ok:");

View File

@ -2,13 +2,13 @@
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes.Permissions;
using NadekoBot.Modules;
using NadekoBot.Commands;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Commands
namespace NadekoBot.Modules.Administration.Commands
{
internal class LogCommand : DiscordCommand
{

View File

@ -1,15 +1,18 @@
using System;
using System.Timers;
using System.Collections.Concurrent;
using Discord;
using NadekoBot.Classes.Permissions;
using Discord;
using Discord.Commands;
using NadekoBot.Modules;
using NadekoBot.Classes.Permissions;
using NadekoBot.Commands;
using System;
using System.Collections.Concurrent;
using System.Timers;
namespace NadekoBot.Commands {
class MessageRepeater : DiscordCommand {
namespace NadekoBot.Modules.Administration.Commands
{
class MessageRepeater : DiscordCommand
{
private readonly ConcurrentDictionary<Server, Repeater> repeaters = new ConcurrentDictionary<Server, Repeater>();
private class Repeater {
private class Repeater
{
[Newtonsoft.Json.JsonIgnore]
public Timer MessageTimer { get; set; }
[Newtonsoft.Json.JsonIgnore]
@ -20,21 +23,27 @@ namespace NadekoBot.Commands {
public string RepeatingMessage { get; set; }
public int Interval { get; set; }
public Repeater Start() {
public Repeater Start()
{
MessageTimer = new Timer { Interval = Interval };
MessageTimer.Elapsed += async (s, e) => {
MessageTimer.Elapsed += async (s, e) =>
{
var ch = RepeatingChannel;
var msg = RepeatingMessage;
if (ch != null && !string.IsNullOrWhiteSpace(msg)) {
try {
if (ch != null && !string.IsNullOrWhiteSpace(msg))
{
try
{
await ch.SendMessage(msg);
} catch { }
}
catch { }
}
};
return this;
}
}
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "repeat")
.Description("Repeat a message every X minutes. If no parameters are specified, " +
@ -42,12 +51,14 @@ namespace NadekoBot.Commands {
.Parameter("minutes", ParameterType.Optional)
.Parameter("msg", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.ManageMessages())
.Do(async e => {
.Do(async e =>
{
var minutesStr = e.GetArg("minutes");
var msg = e.GetArg("msg");
// if both null, disable
if (string.IsNullOrWhiteSpace(msg) && string.IsNullOrWhiteSpace(minutesStr)) {
if (string.IsNullOrWhiteSpace(msg) && string.IsNullOrWhiteSpace(minutesStr))
{
await e.Channel.SendMessage("Repeating disabled");
Repeater rep;
if (repeaters.TryGetValue(e.Server, out rep))
@ -55,14 +66,16 @@ namespace NadekoBot.Commands {
return;
}
int minutes;
if (!int.TryParse(minutesStr, out minutes) || minutes < 1 || minutes > 720) {
if (!int.TryParse(minutesStr, out minutes) || minutes < 1 || minutes > 720)
{
await e.Channel.SendMessage("Invalid value");
return;
}
var repeater = repeaters.GetOrAdd(
e.Server,
s => new Repeater {
s => new Repeater
{
Interval = minutes * 60 * 1000,
RepeatingChannel = e.Channel,
RepeatingChannelId = e.Channel.Id,

View File

@ -1,15 +1,17 @@
using System;
using Discord.Commands;
using NadekoBot.Classes.JSONModels;
using NadekoBot.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Discord.Commands;
using System.Timers;
using NadekoBot.Classes.JSONModels;
using NadekoBot.Modules;
namespace NadekoBot.Commands {
internal class PlayingRotate : DiscordCommand {
namespace NadekoBot.Modules.Administration.Commands
{
internal class PlayingRotate : DiscordCommand
{
private static readonly Timer timer = new Timer(12000);
public static Dictionary<string, Func<string>> PlayingPlaceholders { get; } =
@ -34,16 +36,21 @@ namespace NadekoBot.Commands {
private readonly object playingPlaceholderLock = new object();
public PlayingRotate(DiscordModule module) : base(module) {
public PlayingRotate(DiscordModule module) : base(module)
{
var i = -1;
timer.Elapsed += (s, e) => {
try {
timer.Elapsed += (s, e) =>
{
try
{
i++;
var status = "";
lock (playingPlaceholderLock) {
lock (playingPlaceholderLock)
{
if (PlayingPlaceholders.Count == 0)
return;
if (i >= PlayingPlaceholders.Count) {
if (i >= PlayingPlaceholders.Count)
{
i = -1;
return;
}
@ -54,14 +61,17 @@ namespace NadekoBot.Commands {
if (string.IsNullOrWhiteSpace(status))
return;
Task.Run(() => { NadekoBot.Client.SetGame(status); });
} catch { }
}
catch { }
};
timer.Enabled = NadekoBot.Config.IsRotatingStatus;
}
public Func<CommandEventArgs, Task> DoFunc() => async e => {
lock (playingPlaceholderLock) {
public Func<CommandEventArgs, Task> DoFunc() => async e =>
{
lock (playingPlaceholderLock)
{
if (timer.Enabled)
timer.Stop();
else
@ -72,7 +82,8 @@ namespace NadekoBot.Commands {
await e.Channel.SendMessage($"❗`Rotating playing status has been {(timer.Enabled ? "enabled" : "disabled")}.`");
};
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "rotateplaying")
.Alias(Module.Prefix + "ropl")
.Description("Toggles rotation of playing status of the dynamic strings you specified earlier.")
@ -85,11 +96,13 @@ namespace NadekoBot.Commands {
"Supported placeholders: " + string.Join(", ", PlayingPlaceholders.Keys))
.Parameter("text", ParameterType.Unparsed)
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
var arg = e.GetArg("text");
if (string.IsNullOrWhiteSpace(arg))
return;
lock (playingPlaceholderLock) {
lock (playingPlaceholderLock)
{
NadekoBot.Config.RotatingStatuses.Add(arg);
ConfigHandler.SaveConfig();
}
@ -100,12 +113,14 @@ namespace NadekoBot.Commands {
.Alias(Module.Prefix + "lipl")
.Description("Lists all playing statuses with their corresponding number.")
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
if (NadekoBot.Config.RotatingStatuses.Count == 0)
await e.Channel.SendMessage("`There are no playing strings. " +
"Add some with .addplaying [text] command.`");
var sb = new StringBuilder();
for (var i = 0; i < NadekoBot.Config.RotatingStatuses.Count; i++) {
for (var i = 0; i < NadekoBot.Config.RotatingStatuses.Count; i++)
{
sb.AppendLine($"`{i + 1}.` {NadekoBot.Config.RotatingStatuses[i]}");
}
await e.Channel.SendMessage(sb.ToString());
@ -116,11 +131,13 @@ namespace NadekoBot.Commands {
.Description("Removes a playing string on a given number.")
.Parameter("number", ParameterType.Required)
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
.Do(async e => {
.Do(async e =>
{
var arg = e.GetArg("number");
int num;
string str;
lock (playingPlaceholderLock) {
lock (playingPlaceholderLock)
{
if (!int.TryParse(arg.Trim(), out num) || num <= 0 || num > NadekoBot.Config.RotatingStatuses.Count)
return;
str = NadekoBot.Config.RotatingStatuses[num - 1];

View File

@ -1,28 +1,36 @@
using System;
using System.Collections.Concurrent;
using Discord.Commands;
using NadekoBot.Classes.Permissions;
using NadekoBot.Modules;
using NadekoBot.Commands;
using System;
using System.Collections.Concurrent;
namespace NadekoBot.Commands {
internal class RatelimitCommand : DiscordCommand {
namespace NadekoBot.Modules.Administration.Commands
{
internal class RatelimitCommand : DiscordCommand
{
public static ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, DateTime>> RatelimitingChannels = new ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, DateTime>>();
private static readonly TimeSpan ratelimitTime = new TimeSpan(0, 0, 0, 5);
public RatelimitCommand(DiscordModule module) : base(module) {
NadekoBot.Client.MessageReceived += async (s, e) => {
public RatelimitCommand(DiscordModule module) : base(module)
{
NadekoBot.Client.MessageReceived += async (s, e) =>
{
if (e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id)
return;
ConcurrentDictionary<ulong, DateTime> userTimePair;
if (!RatelimitingChannels.TryGetValue(e.Channel.Id, out userTimePair)) return;
DateTime lastMessageTime;
if (userTimePair.TryGetValue(e.User.Id, out lastMessageTime)) {
if (DateTime.Now - lastMessageTime < ratelimitTime) {
try {
if (userTimePair.TryGetValue(e.User.Id, out lastMessageTime))
{
if (DateTime.Now - lastMessageTime < ratelimitTime)
{
try
{
await e.Message.Delete();
} catch { }
}
catch { }
return;
}
}
@ -30,23 +38,27 @@ namespace NadekoBot.Commands {
};
}
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "slowmode")
.Description("Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds.")
.Parameter("minutes", ParameterType.Optional)
.AddCheck(SimpleCheckers.ManageMessages())
.Do(async e => {
.Do(async e =>
{
//var minutesStr = e.GetArg("minutes");
//if (string.IsNullOrWhiteSpace(minutesStr)) {
// RatelimitingChannels.Remove(e.Channel.Id);
// return;
//}
ConcurrentDictionary<ulong, DateTime> throwaway;
if (RatelimitingChannels.TryRemove(e.Channel.Id, out throwaway)) {
if (RatelimitingChannels.TryRemove(e.Channel.Id, out throwaway))
{
await e.Channel.SendMessage("Slow mode disabled.");
return;
}
if (RatelimitingChannels.TryAdd(e.Channel.Id, new ConcurrentDictionary<ulong, DateTime>())) {
if (RatelimitingChannels.TryAdd(e.Channel.Id, new ConcurrentDictionary<ulong, DateTime>()))
{
await e.Channel.SendMessage("Slow mode initiated. " +
"Users can't send more than 1 message every 5 seconds.");
}

View File

@ -1,29 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Discord.Commands;
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes.Permissions;
using NadekoBot.Modules;
using NadekoBot.Commands;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NadekoBot.Commands {
internal class SelfAssignedRolesCommand : DiscordCommand {
namespace NadekoBot.Modules.Administration.Commands
{
internal class SelfAssignedRolesCommand : DiscordCommand
{
public SelfAssignedRolesCommand(DiscordModule module) : base(module) { }
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(".asar")
.Description("Adds a role, or list of roles separated by whitespace" +
"(use quotations for multiword roles) to the list of self-assignable roles.\n**Usage**: .asar Gamer")
.Parameter("roles", ParameterType.Multiple)
.AddCheck(SimpleCheckers.CanManageRoles)
.Do(async e => {
.Do(async e =>
{
var config = SpecificConfigurations.Default.Of(e.Server.Id);
var msg = new StringBuilder();
foreach (var arg in e.Args) {
foreach (var arg in e.Args)
{
var role = e.Server.FindRoles(arg.Trim()).FirstOrDefault();
if (role == null)
msg.AppendLine($":anger:Role **{arg}** not found.");
else {
if (config.ListOfSelfAssignableRoles.Contains(role.Id)) {
if (config.ListOfSelfAssignableRoles.Contains(role.Id))
{
msg.AppendLine($":anger:Role **{role.Name}** is already in the list.");
continue;
}
@ -38,17 +44,20 @@ namespace NadekoBot.Commands {
.Description("Removes a specified role from the list of self-assignable roles.")
.Parameter("role", ParameterType.Unparsed)
.AddCheck(SimpleCheckers.CanManageRoles)
.Do(async e => {
.Do(async e =>
{
var roleName = e.GetArg("role")?.Trim();
if (string.IsNullOrWhiteSpace(roleName))
return;
var role = e.Server.FindRoles(roleName).FirstOrDefault();
if (role == null) {
if (role == null)
{
await e.Channel.SendMessage(":anger:That role does not exist.");
return;
}
var config = SpecificConfigurations.Default.Of(e.Server.Id);
if (!config.ListOfSelfAssignableRoles.Contains(role.Id)) {
if (!config.ListOfSelfAssignableRoles.Contains(role.Id))
{
await e.Channel.SendMessage(":anger:That role is not self-assignable.");
return;
}
@ -59,20 +68,25 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(".lsar")
.Description("Lits all self-assignable roles.")
.Parameter("roles", ParameterType.Multiple)
.Do(async e => {
.Do(async e =>
{
var config = SpecificConfigurations.Default.Of(e.Server.Id);
var msg = new StringBuilder($"There are `{config.ListOfSelfAssignableRoles.Count}` self assignable roles:\n");
var toRemove = new HashSet<ulong>();
foreach (var roleId in config.ListOfSelfAssignableRoles) {
foreach (var roleId in config.ListOfSelfAssignableRoles)
{
var role = e.Server.GetRole(roleId);
if (role == null) {
if (role == null)
{
msg.Append($"`{roleId} not found. Cleaned up.`, ");
toRemove.Add(roleId);
} else {
}
else {
msg.Append($"**{role.Name}**, ");
}
}
foreach (var id in toRemove) {
foreach (var id in toRemove)
{
config.ListOfSelfAssignableRoles.Remove(id);
}
await e.Channel.SendMessage(msg.ToString());
@ -83,21 +97,25 @@ namespace NadekoBot.Commands {
"Role must be on a list of self-assignable roles." +
"\n**Usage**: .iam Gamer")
.Parameter("role", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
var roleName = e.GetArg("role")?.Trim();
if (string.IsNullOrWhiteSpace(roleName))
return;
var role = e.Server.FindRoles(roleName).FirstOrDefault();
if (role == null) {
if (role == null)
{
await e.Channel.SendMessage(":anger:That role does not exist.");
return;
}
var config = SpecificConfigurations.Default.Of(e.Server.Id);
if (!config.ListOfSelfAssignableRoles.Contains(role.Id)) {
if (!config.ListOfSelfAssignableRoles.Contains(role.Id))
{
await e.Channel.SendMessage(":anger:That role is not self-assignable.");
return;
}
if (e.User.HasRole(role)) {
if (e.User.HasRole(role))
{
await e.Channel.SendMessage($":anger:You already have {role.Name} role.");
return;
}
@ -111,21 +129,25 @@ namespace NadekoBot.Commands {
"Role must be on a list of self-assignable roles." +
"\n**Usage**: .iamn Gamer")
.Parameter("role", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
var roleName = e.GetArg("role")?.Trim();
if (string.IsNullOrWhiteSpace(roleName))
return;
var role = e.Server.FindRoles(roleName).FirstOrDefault();
if (role == null) {
if (role == null)
{
await e.Channel.SendMessage(":anger:That role does not exist.");
return;
}
var config = SpecificConfigurations.Default.Of(e.Server.Id);
if (!config.ListOfSelfAssignableRoles.Contains(role.Id)) {
if (!config.ListOfSelfAssignableRoles.Contains(role.Id))
{
await e.Channel.SendMessage(":anger:That role is not self-assignable.");
return;
}
if (!e.User.HasRole(role)) {
if (!e.User.HasRole(role))
{
await e.Channel.SendMessage($":anger:You don't have {role.Name} role.");
return;
}

View File

@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using System.Collections.Concurrent;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using Discord;
using NadekoBot.Modules;
using System.Collections.Concurrent;
using System.Linq;
/* Voltana's legacy
public class AsyncLazy<T> : Lazy<Task<T>>
@ -21,14 +18,17 @@ public class AsyncLazy<T> : Lazy<Task<T>>
}
*/
namespace NadekoBot.Commands {
internal class ServerGreetCommand : DiscordCommand {
namespace NadekoBot.Modules.Administration.Commands
{
internal class ServerGreetCommand : DiscordCommand
{
public static ConcurrentDictionary<ulong, AnnounceControls> AnnouncementsDictionary;
public static long Greeted = 0;
public ServerGreetCommand(DiscordModule module) : base(module) {
public ServerGreetCommand(DiscordModule module) : base(module)
{
AnnouncementsDictionary = new ConcurrentDictionary<ulong, AnnounceControls>();
NadekoBot.Client.UserJoined += UserJoined;
@ -41,8 +41,10 @@ namespace NadekoBot.Commands {
AnnouncementsDictionary.TryAdd((ulong)obj.ServerId, new AnnounceControls(obj));
}
private async void UserLeft(object sender, UserEventArgs e) {
try {
private async void UserLeft(object sender, UserEventArgs e)
{
try
{
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id) ||
!AnnouncementsDictionary[e.Server.Id].Bye) return;
@ -52,9 +54,11 @@ namespace NadekoBot.Commands {
if (string.IsNullOrEmpty(msg))
return;
if (controls.ByePM) {
if (controls.ByePM)
{
Greeted++;
try {
try
{
await e.User.SendMessage($"`Farewell Message From {e.Server?.Name}`\n" + msg);
}
catch { }
@ -68,8 +72,10 @@ namespace NadekoBot.Commands {
catch { }
}
private async void UserJoined(object sender, Discord.UserEventArgs e) {
try {
private async void UserJoined(object sender, Discord.UserEventArgs e)
{
try
{
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id) ||
!AnnouncementsDictionary[e.Server.Id].Greet) return;
@ -79,7 +85,8 @@ namespace NadekoBot.Commands {
var msg = controls.GreetText.Replace("%user%", e.User.Mention).Trim();
if (string.IsNullOrEmpty(msg))
return;
if (controls.GreetPM) {
if (controls.GreetPM)
{
Greeted++;
await e.User.SendMessage($"`Welcome Message From {e.Server.Name}`\n" + msg);
}
@ -92,7 +99,8 @@ namespace NadekoBot.Commands {
catch { }
}
public class AnnounceControls {
public class AnnounceControls
{
private Classes._DataModels.Announcement _model { get; }
public bool Greet {
@ -139,28 +147,36 @@ namespace NadekoBot.Commands {
set { _model.ServerId = (long)value; }
}
public AnnounceControls(Classes._DataModels.Announcement model) {
public AnnounceControls(Classes._DataModels.Announcement model)
{
this._model = model;
}
public AnnounceControls(ulong serverId) {
public AnnounceControls(ulong serverId)
{
this._model = new Classes._DataModels.Announcement();
ServerId = serverId;
}
internal bool ToggleBye(ulong id) {
if (Bye) {
internal bool ToggleBye(ulong id)
{
if (Bye)
{
return Bye = false;
} else {
}
else {
ByeChannel = id;
return Bye = true;
}
}
internal bool ToggleGreet(ulong id) {
if (Greet) {
internal bool ToggleGreet(ulong id)
{
if (Greet)
{
return Greet = false;
} else {
}
else {
GreetChannel = id;
return Greet = true;
}
@ -168,16 +184,19 @@ namespace NadekoBot.Commands {
internal bool ToggleGreetPM() => GreetPM = !GreetPM;
internal bool ToggleByePM() => ByePM = !ByePM;
private void Save() {
private void Save()
{
Classes.DbHandler.Instance.Save(_model);
}
}
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "greet")
.Description("Enables or Disables anouncements on the current channel when someone joins the server.")
.Do(async e => {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageServer) return;
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id))
AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id));
@ -193,7 +212,8 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(Module.Prefix + "greetmsg")
.Description("Sets a new announce message. Type %user% if you want to mention the new member.\n**Usage**: .greetmsg Welcome to the server, %user%.")
.Parameter("msg", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageServer) return;
if (e.GetArg("msg") == null) return;
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id))
@ -207,7 +227,8 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(Module.Prefix + "bye")
.Description("Enables or Disables anouncements on the current channel when someone leaves the server.")
.Do(async e => {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageServer) return;
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id))
AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id));
@ -223,7 +244,8 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(Module.Prefix + "byemsg")
.Description("Sets a new announce leave message. Type %user% if you want to mention the new member.\n**Usage**: .byemsg %user% has left the server.")
.Parameter("msg", ParameterType.Unparsed)
.Do(async e => {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageServer) return;
if (e.GetArg("msg") == null) return;
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id))
@ -237,7 +259,8 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(Module.Prefix + "byepm")
.Description("Toggles whether the good bye messages will be sent in a PM or in the text channel.")
.Do(async e => {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageServer) return;
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id))
AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id));
@ -253,7 +276,8 @@ namespace NadekoBot.Commands {
cgb.CreateCommand(Module.Prefix + "greetpm")
.Description("Toggles whether the greet messages will be sent in a PM or in the text channel.")
.Do(async e => {
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageServer) return;
if (!AnnouncementsDictionary.ContainsKey(e.Server.Id))
AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id));

View File

@ -1,34 +1,40 @@
using System;
using Discord;
using Discord.Commands;
using NadekoBot.Commands;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Discord.Commands;
using System.Collections.Concurrent;
using Discord;
using NadekoBot.Modules;
namespace NadekoBot.Commands {
internal class VoiceNotificationCommand : DiscordCommand {
namespace NadekoBot.Modules.Administration.Commands
{
internal class VoiceNotificationCommand : DiscordCommand
{
//voicechannel/text channel
private readonly ConcurrentDictionary<Channel, Channel> subscribers = new ConcurrentDictionary<Channel, Channel>();
public Func<CommandEventArgs, Task> DoFunc() => async e => {
public Func<CommandEventArgs, Task> DoFunc() => async e =>
{
var arg = e.GetArg("voice_name");
if (string.IsNullOrWhiteSpace("voice_name"))
return;
var voiceChannel = e.Server.FindChannels(arg, ChannelType.Voice).FirstOrDefault();
if (voiceChannel == null)
return;
if (subscribers.ContainsKey(voiceChannel)) {
if (subscribers.ContainsKey(voiceChannel))
{
await e.Channel.SendMessage("`Voice channel notifications disabled.`");
return;
}
if (subscribers.TryAdd(voiceChannel, e.Channel)) {
if (subscribers.TryAdd(voiceChannel, e.Channel))
{
await e.Channel.SendMessage("`Voice channel notifications enabled.`");
}
};
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "voicenotif")
.Description("Enables notifications on who joined/left the voice channel.\n**Usage**:.voicenotif Karaoke club")
.Parameter("voice_name", ParameterType.Unparsed)

View File

@ -1,20 +1,24 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Discord;
using Discord;
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes.Permissions;
using NadekoBot.Modules;
using NadekoBot.Commands;
using System;
using System.Linq;
using ChPermOverride = Discord.ChannelPermissionOverrides;
namespace NadekoBot.Commands {
internal class VoicePlusTextCommand : DiscordCommand {
namespace NadekoBot.Modules.Administration.Commands
{
internal class VoicePlusTextCommand : DiscordCommand
{
public VoicePlusTextCommand(DiscordModule module) : base(module) {
public VoicePlusTextCommand(DiscordModule module) : base(module)
{
// changing servers may cause bugs
NadekoBot.Client.UserUpdated += async (sender, e) => {
try {
NadekoBot.Client.UserUpdated += async (sender, e) =>
{
try
{
if (e.Server == null)
return;
var config = SpecificConfigurations.Default.Of(e.Server.Id);
@ -24,20 +28,24 @@ namespace NadekoBot.Commands {
var serverPerms = e.Server.GetUser(NadekoBot.Client.CurrentUser.Id)?.ServerPermissions;
if (serverPerms == null)
return;
if (!serverPerms.Value.ManageChannels || !serverPerms.Value.ManageRoles) {
if (!serverPerms.Value.ManageChannels || !serverPerms.Value.ManageRoles)
{
try {
try
{
await e.Server.Owner.SendMessage(
"I don't have manage server and/or Manage Channels permission," +
$" so I cannot run voice+text on **{e.Server.Name}** server.");
} catch { } // meh
}
catch { } // meh
config.VoicePlusTextEnabled = false;
return;
}
var beforeVch = e.Before.VoiceChannel;
if (beforeVch != null) {
if (beforeVch != null)
{
var textChannel =
e.Server.FindChannels(GetChannelName(beforeVch.Name), ChannelType.Text).FirstOrDefault();
if (textChannel != null)
@ -46,12 +54,14 @@ namespace NadekoBot.Commands {
sendMessages: PermValue.Deny));
}
var afterVch = e.After.VoiceChannel;
if (afterVch != null) {
if (afterVch != null)
{
var textChannel = e.Server.FindChannels(
GetChannelName(afterVch.Name),
ChannelType.Text)
.FirstOrDefault();
if (textChannel == null) {
if (textChannel == null)
{
textChannel = (await e.Server.CreateChannel(GetChannelName(afterVch.Name), ChannelType.Text));
await textChannel.AddPermissionsRule(e.Server.EveryoneRole,
new ChPermOverride(readMessages: PermValue.Deny,
@ -61,7 +71,9 @@ namespace NadekoBot.Commands {
new ChPermOverride(readMessages: PermValue.Allow,
sendMessages: PermValue.Allow));
}
} catch (Exception ex) {
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
};
@ -70,22 +82,30 @@ namespace NadekoBot.Commands {
private string GetChannelName(string voiceName) =>
voiceName.Replace(" ", "-").Trim() + "-voice";
internal override void Init(CommandGroupBuilder cgb) {
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "v+t")
.Alias(Module.Prefix + "voice+text")
.Description("Creates a text channel for each voice channel only users in that voice channel can see." +
"If you are server owner, keep in mind you will see them all the time regardless.")
.AddCheck(SimpleCheckers.ManageChannels())
.AddCheck(SimpleCheckers.CanManageRoles)
.Do(async e => {
try {
.Do(async e =>
{
try
{
var config = SpecificConfigurations.Default.Of(e.Server.Id);
if (config.VoicePlusTextEnabled == true) {
if (config.VoicePlusTextEnabled == true)
{
config.VoicePlusTextEnabled = false;
foreach (var textChannel in e.Server.TextChannels.Where(c => c.Name.EndsWith("-voice"))) {
try {
foreach (var textChannel in e.Server.TextChannels.Where(c => c.Name.EndsWith("-voice")))
{
try
{
await textChannel.Delete();
} catch {
}
catch
{
await
e.Channel.SendMessage(
":anger: Error: Most likely i don't have permissions to do this.");
@ -99,7 +119,9 @@ namespace NadekoBot.Commands {
await e.Channel.SendMessage("Successfuly enabled voice + text feature. " +
"**Make sure the bot has manage roles and manage channels permissions**");
} catch (Exception ex) {
}
catch (Exception ex)
{
await e.Channel.SendMessage(ex.ToString());
}
});

View File

@ -5,6 +5,7 @@ using Discord.Modules;
using NadekoBot.Classes.JSONModels;
using NadekoBot.Commands;
using NadekoBot.Modules;
using NadekoBot.Modules.Administration;
using NadekoBot.Modules.Gambling;
using NadekoBot.Modules.Pokemon;
using NadekoBot.Modules.Translator;
@ -157,7 +158,7 @@ namespace NadekoBot
}));
//install modules
modules.Add(new Administration(), "Administration", ModuleFilter.None);
modules.Add(new AdministrationModule(), "Administration", ModuleFilter.None);
modules.Add(new Help(), "Help", ModuleFilter.None);
modules.Add(new PermissionModule(), "Permissions", ModuleFilter.None);
modules.Add(new Conversations(), "Conversations", ModuleFilter.None);

View File

@ -150,22 +150,22 @@
<Compile Include="Classes\_DataModels\TypingArticleModel.cs" />
<Compile Include="Classes\_DataModels\UserQuoteModel.cs" />
<Compile Include="Commands\BetrayGame.cs" />
<Compile Include="Commands\CrossServerTextChannel.cs" />
<Compile Include="Commands\SelfAssignedRolesCommand.cs" />
<Compile Include="Modules\Administration\Commands\CrossServerTextChannel.cs" />
<Compile Include="Modules\Administration\Commands\SelfAssignedRolesCommand.cs" />
<Compile Include="Modules\ClashOfClans.cs" />
<Compile Include="Commands\FilterWordsCommand.cs" />
<Compile Include="Commands\FilterInvitesCommand.cs" />
<Compile Include="Commands\LogCommand.cs" />
<Compile Include="Modules\Administration\Commands\LogCommand.cs" />
<Compile Include="Commands\LoLCommands.cs" />
<Compile Include="Commands\MessageRepeater.cs" />
<Compile Include="Commands\PlayingRotate.cs" />
<Compile Include="Modules\Administration\Commands\MessageRepeater.cs" />
<Compile Include="Modules\Administration\Commands\PlayingRotate.cs" />
<Compile Include="Commands\StreamNotifications.cs" />
<Compile Include="Commands\TriviaCommand.cs" />
<Compile Include="Classes\Trivia\TriviaGame.cs" />
<Compile Include="Classes\Trivia\TriviaQuestion.cs" />
<Compile Include="Classes\Trivia\TriviaQuestionPool.cs" />
<Compile Include="Commands\RequestsCommand.cs" />
<Compile Include="Commands\ServerGreetCommand.cs" />
<Compile Include="Modules\Administration\Commands\ServerGreetCommand.cs" />
<Compile Include="Commands\SpeedTyping.cs" />
<Compile Include="Modules\Gambling\Helpers\Cards.cs" />
<Compile Include="Classes\Extensions.cs" />
@ -175,9 +175,9 @@
<Compile Include="Modules\Gambling\DrawCommand.cs" />
<Compile Include="Modules\Gambling\FlipCoinCommand.cs" />
<Compile Include="Commands\HelpCommand.cs" />
<Compile Include="Commands\VoiceNotificationCommand.cs" />
<Compile Include="Commands\VoicePlusTextCommand.cs" />
<Compile Include="Modules\Administration.cs" />
<Compile Include="Modules\Administration\Commands\VoiceNotificationCommand.cs" />
<Compile Include="Modules\Administration\Commands\VoicePlusTextCommand.cs" />
<Compile Include="Modules\Administration\AdministrationModule.cs" />
<Compile Include="Modules\Conversations.cs" />
<Compile Include="Modules\DiscordModule.cs" />
<Compile Include="Modules\Gambling\GamblingModule.cs" />
@ -187,7 +187,7 @@
<Compile Include="Commands\PollCommand.cs" />
<Compile Include="Modules\NSFW.cs" />
<Compile Include="Modules\Permissions.cs" />
<Compile Include="Commands\RatelimitCommand.cs" />
<Compile Include="Modules\Administration\Commands\RatelimitCommand.cs" />
<Compile Include="Modules\Pokemon\DefaultMoves.cs" />
<Compile Include="Modules\Pokemon\PokemonModule.cs" />
<Compile Include="Modules\Pokemon\PokemonTypes\IPokeTypeExtensions.cs" />