@@ -61,6 +61,39 @@ | |||||
<Compile Include="..\Discord.Net.Commands\CommandServiceConfig.cs"> | <Compile Include="..\Discord.Net.Commands\CommandServiceConfig.cs"> | ||||
<Link>CommandServiceConfig.cs</Link> | <Link>CommandServiceConfig.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Commands\Permissions\IPermissionChecker.cs"> | |||||
<Link>Permissions\IPermissionChecker.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Levels\PermissionLevelChecker.cs"> | |||||
<Link>Permissions\Levels\PermissionLevelChecker.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Levels\PermissionLevelExtensions.cs"> | |||||
<Link>Permissions\Levels\PermissionLevelExtensions.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Levels\PermissionLevelService.cs"> | |||||
<Link>Permissions\Levels\PermissionLevelService.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Userlist\BlacklistChecker.cs"> | |||||
<Link>Permissions\Userlist\BlacklistChecker.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Userlist\BlacklistExtensions.cs"> | |||||
<Link>Permissions\Userlist\BlacklistExtensions.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Userlist\BlacklistService.cs"> | |||||
<Link>Permissions\Userlist\BlacklistService.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Userlist\UserlistService.cs"> | |||||
<Link>Permissions\Userlist\UserlistService.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Userlist\WhitelistChecker.cs"> | |||||
<Link>Permissions\Userlist\WhitelistChecker.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Userlist\WhitelistExtensions.cs"> | |||||
<Link>Permissions\Userlist\WhitelistExtensions.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Commands\Permissions\Userlist\WhitelistService.cs"> | |||||
<Link>Permissions\Userlist\WhitelistService.cs</Link> | |||||
</Compile> | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | |||||
using Discord.Commands.Permissions; | |||||
using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -41,8 +42,9 @@ namespace Discord.Commands | |||||
public IEnumerable<CommandParameter> Parameters => _parameters; | public IEnumerable<CommandParameter> Parameters => _parameters; | ||||
internal CommandParameter[] _parameters; | internal CommandParameter[] _parameters; | ||||
private Func<CommandEventArgs, Task> _handler; | |||||
private IPermissionChecker[] _checks; | |||||
private Func<CommandEventArgs, Task> _runFunc; | |||||
internal Command(string text) | internal Command(string text) | ||||
{ | { | ||||
@@ -56,7 +58,6 @@ namespace Discord.Commands | |||||
{ | { | ||||
_aliases = aliases; | _aliases = aliases; | ||||
} | } | ||||
internal void SetParameters(CommandParameter[] parameters) | internal void SetParameters(CommandParameter[] parameters) | ||||
{ | { | ||||
_parameters = parameters; | _parameters = parameters; | ||||
@@ -89,24 +90,32 @@ namespace Discord.Commands | |||||
} | } | ||||
} | } | ||||
} | } | ||||
internal void SetHandler(Func<CommandEventArgs, Task> func) | |||||
{ | |||||
_handler = func; | |||||
} | |||||
internal void SetHandler(Action<CommandEventArgs> func) | |||||
internal void SetChecks(IPermissionChecker[] checks) | |||||
{ | { | ||||
_handler = e => { func(e); return TaskHelper.CompletedTask; }; | |||||
_checks = checks; | |||||
} | } | ||||
internal bool CanRun(User user, Channel channel) | internal bool CanRun(User user, Channel channel) | ||||
{ | { | ||||
for (int i = 0; i < _checks.Length; i++) | |||||
{ | |||||
if (!_checks[i].CanRun(this, user, channel)) | |||||
return false; | |||||
} | |||||
return true; | return true; | ||||
} | } | ||||
internal void SetRunFunc(Func<CommandEventArgs, Task> func) | |||||
{ | |||||
_runFunc = func; | |||||
} | |||||
internal void SetRunFunc(Action<CommandEventArgs> func) | |||||
{ | |||||
_runFunc = e => { func(e); return TaskHelper.CompletedTask; }; | |||||
} | |||||
internal Task Run(CommandEventArgs args) | internal Task Run(CommandEventArgs args) | ||||
{ | { | ||||
var task = _handler(args); | |||||
var task = _runFunc(args); | |||||
if (task != null) | if (task != null) | ||||
return task; | return task; | ||||
else | else | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | |||||
using Discord.Commands.Permissions; | |||||
using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
@@ -9,19 +10,27 @@ namespace Discord.Commands | |||||
{ | { | ||||
private readonly CommandService _service; | private readonly CommandService _service; | ||||
private readonly Command _command; | private readonly Command _command; | ||||
private List<CommandParameter> _params; | |||||
private bool _allowRequired, _isClosed; | |||||
private string _prefix; | |||||
private readonly List<CommandParameter> _params; | |||||
private readonly List<IPermissionChecker> _checks; | |||||
private readonly string _prefix; | |||||
private bool _allowRequiredParams, _areParamsClosed; | |||||
public CommandService Service => _service; | |||||
internal CommandBuilder(CommandService service, Command command, string prefix, string category) | |||||
internal CommandBuilder(CommandService service, Command command, string prefix = "", string category = "", IEnumerable<IPermissionChecker> initialChecks = null) | |||||
{ | { | ||||
_service = service; | _service = service; | ||||
_command = command; | _command = command; | ||||
_command.Category = category; | _command.Category = category; | ||||
_params = new List<CommandParameter>(); | _params = new List<CommandParameter>(); | ||||
if (initialChecks != null) | |||||
_checks = new List<IPermissionChecker>(initialChecks); | |||||
else | |||||
_checks = new List<IPermissionChecker>(); | |||||
_prefix = prefix; | _prefix = prefix; | ||||
_allowRequired = true; | |||||
_isClosed = false; | |||||
_allowRequiredParams = true; | |||||
_areParamsClosed = false; | |||||
} | } | ||||
public CommandBuilder Alias(params string[] aliases) | public CommandBuilder Alias(params string[] aliases) | ||||
@@ -35,24 +44,24 @@ namespace Discord.Commands | |||||
_command.Category = category; | _command.Category = category; | ||||
return this; | return this; | ||||
}*/ | }*/ | ||||
public CommandBuilder Info(string description) | |||||
public CommandBuilder Description(string description) | |||||
{ | { | ||||
_command.Description = description; | _command.Description = description; | ||||
return this; | return this; | ||||
} | } | ||||
public CommandBuilder Parameter(string name, ParameterType type = ParameterType.Required) | public CommandBuilder Parameter(string name, ParameterType type = ParameterType.Required) | ||||
{ | { | ||||
if (_isClosed) | |||||
if (_areParamsClosed) | |||||
throw new Exception($"No parameters may be added after a {nameof(ParameterType.Multiple)} or {nameof(ParameterType.Unparsed)} parameter."); | throw new Exception($"No parameters may be added after a {nameof(ParameterType.Multiple)} or {nameof(ParameterType.Unparsed)} parameter."); | ||||
if (!_allowRequired && type == ParameterType.Required) | |||||
if (!_allowRequiredParams && type == ParameterType.Required) | |||||
throw new Exception($"{nameof(ParameterType.Required)} parameters may not be added after an optional one"); | throw new Exception($"{nameof(ParameterType.Required)} parameters may not be added after an optional one"); | ||||
_params.Add(new CommandParameter(name, type)); | _params.Add(new CommandParameter(name, type)); | ||||
if (type == ParameterType.Optional) | if (type == ParameterType.Optional) | ||||
_allowRequired = false; | |||||
_allowRequiredParams = false; | |||||
if (type == ParameterType.Multiple || type == ParameterType.Unparsed) | if (type == ParameterType.Multiple || type == ParameterType.Unparsed) | ||||
_isClosed = true; | |||||
_areParamsClosed = true; | |||||
return this; | return this; | ||||
} | } | ||||
public CommandBuilder Hide() | public CommandBuilder Hide() | ||||
@@ -60,20 +69,26 @@ namespace Discord.Commands | |||||
_command.IsHidden = true; | _command.IsHidden = true; | ||||
return this; | return this; | ||||
} | } | ||||
public CommandBuilder AddCheck(IPermissionChecker check) | |||||
{ | |||||
_checks.Add(check); | |||||
return this; | |||||
} | |||||
public void Do(Func<CommandEventArgs, Task> func) | public void Do(Func<CommandEventArgs, Task> func) | ||||
{ | { | ||||
_command.SetHandler(func); | |||||
_command.SetRunFunc(func); | |||||
Build(); | Build(); | ||||
} | } | ||||
public void Do(Action<CommandEventArgs> func) | public void Do(Action<CommandEventArgs> func) | ||||
{ | { | ||||
_command.SetHandler(func); | |||||
_command.SetRunFunc(func); | |||||
Build(); | Build(); | ||||
} | } | ||||
private void Build() | private void Build() | ||||
{ | { | ||||
_command.SetParameters(_params.ToArray()); | _command.SetParameters(_params.ToArray()); | ||||
_command.SetChecks(_checks.ToArray()); | |||||
_service.AddCommand(_command); | _service.AddCommand(_command); | ||||
} | } | ||||
@@ -97,14 +112,21 @@ namespace Discord.Commands | |||||
} | } | ||||
public sealed class CommandGroupBuilder | public sealed class CommandGroupBuilder | ||||
{ | { | ||||
internal readonly CommandService _service; | |||||
private readonly CommandService _service; | |||||
private readonly string _prefix; | private readonly string _prefix; | ||||
private readonly List<IPermissionChecker> _checks; | |||||
private string _category; | private string _category; | ||||
internal CommandGroupBuilder(CommandService service, string prefix) | |||||
public CommandService Service => _service; | |||||
internal CommandGroupBuilder(CommandService service, string prefix, IEnumerable<IPermissionChecker> initialChecks = null) | |||||
{ | { | ||||
_service = service; | _service = service; | ||||
_prefix = prefix; | _prefix = prefix; | ||||
if (initialChecks != null) | |||||
_checks = new List<IPermissionChecker>(initialChecks); | |||||
else | |||||
_checks = new List<IPermissionChecker>(); | |||||
} | } | ||||
public CommandGroupBuilder Category(string category) | public CommandGroupBuilder Category(string category) | ||||
@@ -112,10 +134,14 @@ namespace Discord.Commands | |||||
_category = category; | _category = category; | ||||
return this; | return this; | ||||
} | } | ||||
public void AddCheck(IPermissionChecker check) | |||||
{ | |||||
_checks.Add(check); | |||||
} | |||||
public CommandGroupBuilder CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) | public CommandGroupBuilder CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) | ||||
{ | |||||
config(new CommandGroupBuilder(_service, _prefix + ' ' + cmd)); | |||||
{ | |||||
config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _checks)); | |||||
return this; | return this; | ||||
} | } | ||||
public CommandBuilder CreateCommand() | public CommandBuilder CreateCommand() | ||||
@@ -123,7 +149,7 @@ namespace Discord.Commands | |||||
public CommandBuilder CreateCommand(string cmd) | public CommandBuilder CreateCommand(string cmd) | ||||
{ | { | ||||
var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd)); | var command = new Command(CommandBuilder.AppendPrefix(_prefix, cmd)); | ||||
return new CommandBuilder(_service, command, _prefix, _category); | |||||
return new CommandBuilder(_service, command, _prefix, _category, _checks); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -7,24 +7,26 @@ namespace Discord.Commands | |||||
internal class CommandMap | internal class CommandMap | ||||
{ | { | ||||
private readonly CommandMap _parent; | private readonly CommandMap _parent; | ||||
private readonly string _text; | |||||
private readonly string _name, _fullName; | |||||
private Command _command; | private Command _command; | ||||
private readonly Dictionary<string, CommandMap> _items; | private readonly Dictionary<string, CommandMap> _items; | ||||
private bool _isHidden; | private bool _isHidden; | ||||
public string Text => _text; | |||||
public string Name => _name; | |||||
public string FullName => _fullName; | |||||
public bool IsHidden => _isHidden; | public bool IsHidden => _isHidden; | ||||
public Command Command => _command; | public Command Command => _command; | ||||
public IEnumerable<CommandMap> SubGroups => _items.Values; | public IEnumerable<CommandMap> SubGroups => _items.Values; | ||||
/*public IEnumerable<Command> SubCommands => _items.Select(x => x.Value._command).Where(x => x != null); | /*public IEnumerable<Command> SubCommands => _items.Select(x => x.Value._command).Where(x => x != null); | ||||
public IEnumerable<CommandMap> SubGroups => _items.Select(x => x.Value).Where(x => x._items.Count > 0);*/ | public IEnumerable<CommandMap> SubGroups => _items.Select(x => x.Value).Where(x => x._items.Count > 0);*/ | ||||
public CommandMap(CommandMap parent, string text) | |||||
public CommandMap(CommandMap parent, string name, string fullName) | |||||
{ | { | ||||
_parent = parent; | _parent = parent; | ||||
_text = text; | |||||
_items = new Dictionary<string, CommandMap>(); | |||||
_name = name; | |||||
_fullName = fullName; | |||||
_items = new Dictionary<string, CommandMap>(); | |||||
_isHidden = true; | _isHidden = true; | ||||
} | } | ||||
@@ -82,19 +84,20 @@ namespace Discord.Commands | |||||
{ | { | ||||
AddCommand(0, text.Split(' '), command); | AddCommand(0, text.Split(' '), command); | ||||
} | } | ||||
public void AddCommand(int index, string[] parts, Command command) | |||||
private void AddCommand(int index, string[] parts, Command command) | |||||
{ | { | ||||
if (!command.IsHidden && _isHidden) | if (!command.IsHidden && _isHidden) | ||||
_isHidden = false; | _isHidden = false; | ||||
if (index != parts.Length) | if (index != parts.Length) | ||||
{ | { | ||||
string nextPart = parts[index]; | |||||
CommandMap nextGroup; | CommandMap nextGroup; | ||||
if (!_items.TryGetValue(nextPart, out nextGroup)) | |||||
string name = parts[index]; | |||||
string fullName = string.Join(" ", parts, 0, index + 1); | |||||
if (!_items.TryGetValue(name, out nextGroup)) | |||||
{ | { | ||||
nextGroup = new CommandMap(this, nextPart); | |||||
_items.Add(nextPart, nextGroup); | |||||
nextGroup = new CommandMap(this, name, fullName); | |||||
_items.Add(name, nextGroup); | |||||
} | } | ||||
nextGroup.AddCommand(index + 1, parts, command); | nextGroup.AddCommand(index + 1, parts, command); | ||||
} | } | ||||
@@ -9,8 +9,12 @@ namespace Discord.Commands | |||||
/// <summary> A Discord.Net client with extensions for handling common bot operations like text commands. </summary> | /// <summary> A Discord.Net client with extensions for handling common bot operations like text commands. </summary> | ||||
public partial class CommandService : IService | public partial class CommandService : IService | ||||
{ | { | ||||
private readonly CommandServiceConfig _config; | |||||
private readonly CommandGroupBuilder _root; | |||||
private DiscordClient _client; | private DiscordClient _client; | ||||
public CommandServiceConfig Config { get; } | |||||
public DiscordClient Client => _client; | |||||
public CommandGroupBuilder Root => _root; | |||||
//AllCommands store a flattened collection of all commands | //AllCommands store a flattened collection of all commands | ||||
public IEnumerable<Command> AllCommands => _allCommands; | public IEnumerable<Command> AllCommands => _allCommands; | ||||
@@ -23,40 +27,47 @@ namespace Discord.Commands | |||||
internal IEnumerable<CommandMap> Categories => _categories.Values; | internal IEnumerable<CommandMap> Categories => _categories.Values; | ||||
private readonly Dictionary<string, CommandMap> _categories; | private readonly Dictionary<string, CommandMap> _categories; | ||||
public CommandService(CommandServiceConfig config) | public CommandService(CommandServiceConfig config) | ||||
{ | { | ||||
Config = config; | |||||
_config = config; | |||||
_allCommands = new List<Command>(); | _allCommands = new List<Command>(); | ||||
_map = new CommandMap(null, null); | |||||
_map = new CommandMap(null, "", ""); | |||||
_categories = new Dictionary<string, CommandMap>(); | _categories = new Dictionary<string, CommandMap>(); | ||||
} | |||||
_root = new CommandGroupBuilder(this, "", null); | |||||
} | |||||
void IService.Install(DiscordClient client) | void IService.Install(DiscordClient client) | ||||
{ | { | ||||
_client = client; | _client = client; | ||||
Config.Lock(); | |||||
_config.Lock(); | |||||
if (Config.HelpMode != HelpMode.Disable) | |||||
if (_config.HelpMode != HelpMode.Disable) | |||||
{ | { | ||||
CreateCommand("help") | |||||
CreateCommand("help") | |||||
.Parameter("command", ParameterType.Multiple) | .Parameter("command", ParameterType.Multiple) | ||||
.Hide() | .Hide() | ||||
.Info("Returns information about commands.") | |||||
.Do(async e => | |||||
.Description("Returns information about commands.") | |||||
.Do((Func<CommandEventArgs, Task>)(async e => | |||||
{ | { | ||||
Channel channel = Config.HelpMode == HelpMode.Public ? e.Channel : await client.CreatePMChannel(e.User); | |||||
Channel replyChannel = _config.HelpMode == HelpMode.Public ? e.Channel : await client.CreatePMChannel(e.User); | |||||
if (e.Args.Length > 0) //Show command help | if (e.Args.Length > 0) //Show command help | ||||
{ | { | ||||
var map = _map.GetItem(string.Join(" ", e.Args)); | var map = _map.GetItem(string.Join(" ", e.Args)); | ||||
if (map != null) | if (map != null) | ||||
await ShowHelp(map, e.User, channel); | |||||
await ShowCommandHelp(map, e.User, e.Channel, replyChannel); | |||||
else | else | ||||
await client.SendMessage(channel, "Unable to display help: Unknown command."); | |||||
await client.SendMessage(replyChannel, "Unable to display help: Unknown command."); | |||||
} | } | ||||
else //Show general help | else //Show general help | ||||
await ShowHelp(e.User, channel); | |||||
}); | |||||
/* Unmerged change from project 'Discord.Net.Commands' | |||||
Before: | |||||
await ShowHelp(e.User, e.Channel, replyChannel); | |||||
After: | |||||
await this.ShowHelp((User)e.User, e.Channel, replyChannel); | |||||
*/ | |||||
await this.ShowGeneralHelp(e.User, (Channel)e.Channel, (Channel)replyChannel); | |||||
})); | |||||
} | } | ||||
client.MessageReceived += async (s, e) => | client.MessageReceived += async (s, e) => | ||||
@@ -68,7 +79,7 @@ namespace Discord.Commands | |||||
if (msg.Length == 0) return; | if (msg.Length == 0) return; | ||||
//Check for command char if one is provided | //Check for command char if one is provided | ||||
var chars = Config.CommandChars; | |||||
var chars = _config.CommandChars; | |||||
if (chars.Length > 0) | if (chars.Length > 0) | ||||
{ | { | ||||
if (!chars.Contains(msg[0])) | if (!chars.Contains(msg[0])) | ||||
@@ -121,7 +132,7 @@ namespace Discord.Commands | |||||
}; | }; | ||||
} | } | ||||
public Task ShowHelp(User user, Channel channel) | |||||
public Task ShowGeneralHelp(User user, Channel channel, Channel replyChannel = null) | |||||
{ | { | ||||
StringBuilder output = new StringBuilder(); | StringBuilder output = new StringBuilder(); | ||||
/*output.AppendLine("These are the commands you can use:"); | /*output.AppendLine("These are the commands you can use:"); | ||||
@@ -163,7 +174,7 @@ namespace Discord.Commands | |||||
else | else | ||||
output.Append(", "); | output.Append(", "); | ||||
output.Append('`'); | output.Append('`'); | ||||
output.Append(group.Text); | |||||
output.Append(group.Name); | |||||
if (group.SubGroups.Any()) | if (group.SubGroups.Any()) | ||||
output.Append("*"); | output.Append("*"); | ||||
output.Append('`'); | output.Append('`'); | ||||
@@ -177,7 +188,7 @@ namespace Discord.Commands | |||||
{ | { | ||||
output.Append("\n\n"); | output.Append("\n\n"); | ||||
var chars = Config.CommandChars; | |||||
var chars = _config.CommandChars; | |||||
if (chars.Length > 0) | if (chars.Length > 0) | ||||
{ | { | ||||
if (chars.Length == 1) | if (chars.Length == 1) | ||||
@@ -190,20 +201,20 @@ namespace Discord.Commands | |||||
output.AppendLine($"`help <command>` can tell you more about how to use a command."); | output.AppendLine($"`help <command>` can tell you more about how to use a command."); | ||||
} | } | ||||
return _client.SendMessage(channel, output.ToString()); | |||||
return _client.SendMessage(replyChannel ?? channel, output.ToString()); | |||||
} | } | ||||
private Task ShowHelp(CommandMap map, User user, Channel channel) | |||||
private Task ShowCommandHelp(CommandMap map, User user, Channel channel, Channel replyChannel = null) | |||||
{ | { | ||||
StringBuilder output = new StringBuilder(); | StringBuilder output = new StringBuilder(); | ||||
Command cmd = map.Command; | Command cmd = map.Command; | ||||
if (cmd != null) | if (cmd != null) | ||||
ShowHelpInternal(cmd, user, channel, output); | |||||
ShowCommandHelpInternal(cmd, user, channel, output); | |||||
else | else | ||||
{ | { | ||||
output.Append('`'); | output.Append('`'); | ||||
output.Append(map.Text); | |||||
output.Append(map.FullName); | |||||
output.Append("`\n"); | output.Append("`\n"); | ||||
} | } | ||||
@@ -218,21 +229,21 @@ namespace Discord.Commands | |||||
else | else | ||||
output.Append(", "); | output.Append(", "); | ||||
output.Append('`'); | output.Append('`'); | ||||
output.Append(subCmd.Text); | |||||
output.Append(subCmd.Name); | |||||
if (subCmd.SubGroups.Any()) | if (subCmd.SubGroups.Any()) | ||||
output.Append("*"); | output.Append("*"); | ||||
output.Append('`'); | output.Append('`'); | ||||
} | } | ||||
return _client.SendMessage(channel, output.ToString()); | |||||
return _client.SendMessage(replyChannel ?? channel, output.ToString()); | |||||
} | } | ||||
public Task ShowHelp(Command command, User user, Channel channel) | |||||
public Task ShowCommandHelp(Command command, User user, Channel channel, Channel replyChannel = null) | |||||
{ | { | ||||
StringBuilder output = new StringBuilder(); | StringBuilder output = new StringBuilder(); | ||||
ShowHelpInternal(command, user, channel, output); | |||||
return _client.SendMessage(channel, output.ToString()); | |||||
ShowCommandHelpInternal(command, user, channel, output); | |||||
return _client.SendMessage(replyChannel ?? channel, output.ToString()); | |||||
} | } | ||||
private void ShowHelpInternal(Command command, User user, Channel channel, StringBuilder output) | |||||
private void ShowCommandHelpInternal(Command command, User user, Channel channel, StringBuilder output) | |||||
{ | { | ||||
output.Append('`'); | output.Append('`'); | ||||
output.Append(command.Text); | output.Append(command.Text); | ||||
@@ -261,17 +272,8 @@ namespace Discord.Commands | |||||
output.AppendLine($"Aliases: `" + string.Join("`, `", command.Aliases) + '`'); | output.AppendLine($"Aliases: `" + string.Join("`, `", command.Aliases) + '`'); | ||||
} | } | ||||
public void CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) | |||||
{ | |||||
var builder = new CommandGroupBuilder(this, cmd); | |||||
if (config != null) | |||||
config(builder); | |||||
} | |||||
public CommandBuilder CreateCommand(string cmd) | |||||
{ | |||||
var command = new Command(cmd); | |||||
return new CommandBuilder(this, command, "", ""); | |||||
} | |||||
public void CreateGroup(string cmd, Action<CommandGroupBuilder> config = null) => _root.CreateGroup(cmd, config); | |||||
public CommandBuilder CreateCommand(string cmd) => _root.CreateCommand(cmd); | |||||
internal void AddCommand(Command command) | internal void AddCommand(Command command) | ||||
{ | { | ||||
@@ -282,7 +284,7 @@ namespace Discord.Commands | |||||
string categoryName = command.Category ?? ""; | string categoryName = command.Category ?? ""; | ||||
if (!_categories.TryGetValue(categoryName, out category)) | if (!_categories.TryGetValue(categoryName, out category)) | ||||
{ | { | ||||
category = new CommandMap(null, ""); | |||||
category = new CommandMap(null, "", ""); | |||||
_categories.Add(categoryName, category); | _categories.Add(categoryName, category); | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
using System; | |||||
using Discord.Commands.Permissions; | |||||
using System; | |||||
namespace Discord.Commands | namespace Discord.Commands | ||||
{ | { | ||||
@@ -13,9 +14,6 @@ namespace Discord.Commands | |||||
} | } | ||||
public class CommandServiceConfig | public class CommandServiceConfig | ||||
{ | { | ||||
/*public Func<User, int> PermissionResolver { get { return _permissionsResolver; } set { SetValue(ref _permissionsResolver, value); } } | |||||
private Func<User, int> _permissionsResolver;*/ | |||||
public char? CommandChar | public char? CommandChar | ||||
{ | { | ||||
get | get | ||||
@@ -7,7 +7,7 @@ | |||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> | <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> | ||||
<PropertyGroup Label="Globals"> | <PropertyGroup Label="Globals"> | ||||
<ProjectGuid>19793545-ef89-48f4-8100-3ebaad0a9141</ProjectGuid> | <ProjectGuid>19793545-ef89-48f4-8100-3ebaad0a9141</ProjectGuid> | ||||
<RootNamespace>Discord</RootNamespace> | |||||
<RootNamespace>Discord.Commands</RootNamespace> | |||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> | ||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> | <OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
@@ -0,0 +1,7 @@ | |||||
namespace Discord.Commands.Permissions | |||||
{ | |||||
public interface IPermissionChecker | |||||
{ | |||||
bool CanRun(Command command, User user, Channel channel); | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
using System; | |||||
namespace Discord.Commands.Permissions.Levels | |||||
{ | |||||
public class PermissionLevelChecker : IPermissionChecker | |||||
{ | |||||
private readonly PermissionLevelService _service; | |||||
private readonly int _minPermissions; | |||||
public PermissionLevelService Service => _service; | |||||
public int MinPermissions => _minPermissions; | |||||
internal PermissionLevelChecker(DiscordClient client, int minPermissions) | |||||
{ | |||||
_service = client.GetService<PermissionLevelService>(); | |||||
_minPermissions = minPermissions; | |||||
if (_service == null) | |||||
throw new InvalidOperationException($"{nameof(PermissionLevelService)} must be added to {nameof(DiscordClient)} before this function is called."); | |||||
} | |||||
public bool CanRun(Command command, User user, Channel channel) | |||||
{ | |||||
int permissions = _service.GetPermissionLevel(user, channel); | |||||
return permissions >= _minPermissions; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
namespace Discord.Commands.Permissions.Levels | |||||
{ | |||||
public static class PermissionLevelExtensions | |||||
{ | |||||
public static CommandBuilder MinPermissions(this CommandBuilder builder, int minPermissions) | |||||
{ | |||||
builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); | |||||
return builder; | |||||
} | |||||
public static CommandGroupBuilder MinPermissions(this CommandGroupBuilder builder, int minPermissions) | |||||
{ | |||||
builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); | |||||
return builder; | |||||
} | |||||
public static CommandService MinPermissions(this CommandService service, int minPermissions) | |||||
{ | |||||
service.Root.AddCheck(new PermissionLevelChecker(service.Client, minPermissions)); | |||||
return service; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,23 @@ | |||||
using System; | |||||
namespace Discord.Commands.Permissions.Levels | |||||
{ | |||||
public class PermissionLevelService : IService | |||||
{ | |||||
private readonly Func<User, Channel, int> _getPermissionsFunc; | |||||
private DiscordClient _client; | |||||
public DiscordClient Client => _client; | |||||
public PermissionLevelService(Func<User, Channel, int> getPermissionsFunc) | |||||
{ | |||||
_getPermissionsFunc = getPermissionsFunc; | |||||
} | |||||
public void Install(DiscordClient client) | |||||
{ | |||||
_client = client; | |||||
} | |||||
public int GetPermissionLevel(User user, Channel channel) => _getPermissionsFunc(user, channel); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
using System; | |||||
namespace Discord.Commands.Permissions.Userlist | |||||
{ | |||||
public class BlacklistChecker : IPermissionChecker | |||||
{ | |||||
private readonly BlacklistService _service; | |||||
internal BlacklistChecker(DiscordClient client) | |||||
{ | |||||
_service = client.GetService<BlacklistService>(); | |||||
if (_service == null) | |||||
throw new InvalidOperationException($"{nameof(BlacklistService)} must be added to {nameof(DiscordClient)} before this function is called."); | |||||
} | |||||
public bool CanRun(Command command, User user, Channel channel) | |||||
{ | |||||
return _service.CanRun(user); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
namespace Discord.Commands.Permissions.Userlist | |||||
{ | |||||
public static class BlacklistExtensions | |||||
{ | |||||
public static CommandBuilder UseGlobalBlacklist(this CommandBuilder builder) | |||||
{ | |||||
builder.AddCheck(new BlacklistChecker(builder.Service.Client)); | |||||
return builder; | |||||
} | |||||
public static CommandGroupBuilder UseGlobalBlacklist(this CommandGroupBuilder builder) | |||||
{ | |||||
builder.AddCheck(new BlacklistChecker(builder.Service.Client)); | |||||
return builder; | |||||
} | |||||
public static CommandService UseGlobalBlacklist(this CommandService service) | |||||
{ | |||||
service.Root.AddCheck(new BlacklistChecker(service.Client)); | |||||
return service; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
using System.Collections.Generic; | |||||
namespace Discord.Commands.Permissions.Userlist | |||||
{ | |||||
public class BlacklistService : UserlistService | |||||
{ | |||||
public BlacklistService(IEnumerable<string> initialList = null) | |||||
: base(initialList) | |||||
{ | |||||
} | |||||
public bool CanRun(User user) | |||||
{ | |||||
return !_userList.ContainsKey(user.Id); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,52 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
namespace Discord.Commands.Permissions.Userlist | |||||
{ | |||||
public class UserlistService : IService | |||||
{ | |||||
protected readonly ConcurrentDictionary<string, bool> _userList; | |||||
private DiscordClient _client; | |||||
public DiscordClient Client => _client; | |||||
public IEnumerable<string> UserIds => _userList.Select(x => x.Key); | |||||
public UserlistService(IEnumerable<string> initialList = null) | |||||
{ | |||||
if (initialList != null) | |||||
_userList = new ConcurrentDictionary<string, bool>(initialList.Select(x => new KeyValuePair<string, bool>(x, true))); | |||||
else | |||||
_userList = new ConcurrentDictionary<string, bool>(); | |||||
} | |||||
public void Add(User user) | |||||
{ | |||||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
_userList[user.Id] = true; | |||||
} | |||||
public void Add(string userId) | |||||
{ | |||||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||||
_userList[userId] = true; | |||||
} | |||||
public bool Remove(User user) | |||||
{ | |||||
if (user == null) throw new ArgumentNullException(nameof(user)); | |||||
bool ignored; | |||||
return _userList.TryRemove(user.Id, out ignored); | |||||
} | |||||
public bool Remove(string userId) | |||||
{ | |||||
if (userId == null) throw new ArgumentNullException(nameof(userId)); | |||||
bool ignored; | |||||
return _userList.TryRemove(userId, out ignored); | |||||
} | |||||
public void Install(DiscordClient client) | |||||
{ | |||||
_client = client; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
using System; | |||||
namespace Discord.Commands.Permissions.Userlist | |||||
{ | |||||
public class WhitelistChecker : IPermissionChecker | |||||
{ | |||||
private readonly WhitelistService _service; | |||||
internal WhitelistChecker(DiscordClient client) | |||||
{ | |||||
_service = client.GetService<WhitelistService>(); | |||||
if (_service == null) | |||||
throw new InvalidOperationException($"{nameof(WhitelistService)} must be added to {nameof(DiscordClient)} before this function is called."); | |||||
} | |||||
public bool CanRun(Command command, User user, Channel channel) | |||||
{ | |||||
return _service.CanRun(user); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
namespace Discord.Commands.Permissions.Userlist | |||||
{ | |||||
public static class WhitelistExtensions | |||||
{ | |||||
public static CommandBuilder UseGlobalWhitelist(this CommandBuilder builder) | |||||
{ | |||||
builder.AddCheck(new WhitelistChecker(builder.Service.Client)); | |||||
return builder; | |||||
} | |||||
public static CommandGroupBuilder UseGlobalWhitelist(this CommandGroupBuilder builder) | |||||
{ | |||||
builder.AddCheck(new WhitelistChecker(builder.Service.Client)); | |||||
return builder; | |||||
} | |||||
public static CommandService UseGlobalWhitelist(this CommandService service) | |||||
{ | |||||
service.Root.AddCheck(new BlacklistChecker(service.Client)); | |||||
return service; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
using System.Collections.Generic; | |||||
namespace Discord.Commands.Permissions.Userlist | |||||
{ | |||||
public class WhitelistService : UserlistService | |||||
{ | |||||
public WhitelistService(IEnumerable<string> initialList = null) | |||||
: base(initialList) | |||||
{ | |||||
} | |||||
public bool CanRun(User user) | |||||
{ | |||||
return _userList.ContainsKey(user.Id); | |||||
} | |||||
} | |||||
} |
@@ -7,7 +7,7 @@ | |||||
<ProjectGuid>{3091164F-66AE-4543-A63D-167C1116241D}</ProjectGuid> | <ProjectGuid>{3091164F-66AE-4543-A63D-167C1116241D}</ProjectGuid> | ||||
<OutputType>Library</OutputType> | <OutputType>Library</OutputType> | ||||
<AppDesignerFolder>Properties</AppDesignerFolder> | <AppDesignerFolder>Properties</AppDesignerFolder> | ||||
<RootNamespace>Discord</RootNamespace> | |||||
<RootNamespace>Discord.Modules</RootNamespace> | |||||
<AssemblyName>Discord.Net.Commands</AssemblyName> | <AssemblyName>Discord.Net.Commands</AssemblyName> | ||||
<FileAlignment>512</FileAlignment> | <FileAlignment>512</FileAlignment> | ||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> | ||||
@@ -40,6 +40,9 @@ | |||||
<Compile Include="..\Discord.Net.Modules\IModule.cs"> | <Compile Include="..\Discord.Net.Modules\IModule.cs"> | ||||
<Link>IModule.cs</Link> | <Link>IModule.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net.Modules\ModuleChecker.cs"> | |||||
<Link>ModuleChecker.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net.Modules\ModuleExtensions.cs"> | <Compile Include="..\Discord.Net.Modules\ModuleExtensions.cs"> | ||||
<Link>ModuleExtensions.cs</Link> | <Link>ModuleExtensions.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -7,7 +7,7 @@ | |||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> | <Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" /> | ||||
<PropertyGroup Label="Globals"> | <PropertyGroup Label="Globals"> | ||||
<ProjectGuid>01584e8a-78da-486f-9ef9-a894e435841b</ProjectGuid> | <ProjectGuid>01584e8a-78da-486f-9ef9-a894e435841b</ProjectGuid> | ||||
<RootNamespace>Discord</RootNamespace> | |||||
<RootNamespace>Discord.Modules</RootNamespace> | |||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> | <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath> | ||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> | <OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
@@ -0,0 +1,20 @@ | |||||
using Discord.Commands; | |||||
using Discord.Commands.Permissions; | |||||
namespace Discord.Modules | |||||
{ | |||||
public class ModuleChecker : IPermissionChecker | |||||
{ | |||||
private readonly ModuleManager _manager; | |||||
internal ModuleChecker(ModuleManager manager) | |||||
{ | |||||
_manager = manager; | |||||
} | |||||
public bool CanRun(Command command, User user, Channel channel) | |||||
{ | |||||
return _manager.FilterType.HasFlag(FilterType.Unrestricted) || _manager.HasChannel(channel); | |||||
} | |||||
} | |||||
} |
@@ -64,8 +64,8 @@ namespace Discord.Modules | |||||
_name = name; | _name = name; | ||||
_id = name.ToLowerInvariant(); | _id = name.ToLowerInvariant(); | ||||
_filterType = filterType; | _filterType = filterType; | ||||
_allowServerWhitelist = filterType.HasFlag(FilterType.Server); | |||||
_allowChannelWhitelist = filterType.HasFlag(FilterType.Channel); | |||||
_allowServerWhitelist = filterType.HasFlag(FilterType.ServerWhitelist); | |||||
_allowChannelWhitelist = filterType.HasFlag(FilterType.ChannelWhitelist); | |||||
_allowPrivate = filterType.HasFlag(FilterType.AllowPrivate); | _allowPrivate = filterType.HasFlag(FilterType.AllowPrivate); | ||||
_enabledServers = new ConcurrentDictionary<string, Server>(); | _enabledServers = new ConcurrentDictionary<string, Server>(); | ||||
@@ -115,6 +115,7 @@ namespace Discord.Modules | |||||
commandService.CreateGroup(prefix, x => | commandService.CreateGroup(prefix, x => | ||||
{ | { | ||||
x.Category(_name); | x.Category(_name); | ||||
x.AddCheck(new ModuleChecker(this)); | |||||
config(x); | config(x); | ||||
}); | }); | ||||
@@ -244,12 +245,12 @@ namespace Discord.Modules | |||||
DisableAllChannels(); | DisableAllChannels(); | ||||
} | } | ||||
private bool HasServer(Server server) => | |||||
internal bool HasServer(Server server) => | |||||
_allowServerWhitelist && _enabledServers.ContainsKey(server.Id); | _allowServerWhitelist && _enabledServers.ContainsKey(server.Id); | ||||
private bool HasIndirectServer(Server server) => | |||||
internal bool HasIndirectServer(Server server) => | |||||
(_allowServerWhitelist && _enabledServers.ContainsKey(server.Id)) || | (_allowServerWhitelist && _enabledServers.ContainsKey(server.Id)) || | ||||
(_allowChannelWhitelist && _indirectServers.ContainsKey(server.Id)); | (_allowChannelWhitelist && _indirectServers.ContainsKey(server.Id)); | ||||
private bool HasChannel(Channel channel) | |||||
internal bool HasChannel(Channel channel) | |||||
{ | { | ||||
if (channel.IsPrivate) return _allowPrivate; | if (channel.IsPrivate) return _allowPrivate; | ||||
@@ -5,12 +5,12 @@ namespace Discord.Modules | |||||
[Flags] | [Flags] | ||||
public enum FilterType | public enum FilterType | ||||
{ | { | ||||
/// <summary> Disables the event filter. </summary> | |||||
Disabled = 0x0, | |||||
/// <summary> Uses the server whitelist to filter events. </summary> | |||||
Server = 0x1, | |||||
/// <summary> Uses the channel whitelist to filter events. </summary> | |||||
Channel = 0x2, | |||||
/// <summary> Disables the event and command filtesr. </summary> | |||||
Unrestricted = 0x0, | |||||
/// <summary> Uses the server whitelist to filter events and commands. </summary> | |||||
ServerWhitelist = 0x1, | |||||
/// <summary> Uses the channel whitelist to filter events and commands. </summary> | |||||
ChannelWhitelist = 0x2, | |||||
/// <summary> Enables this module in all private messages. </summary> | /// <summary> Enables this module in all private messages. </summary> | ||||
AllowPrivate = 0x4 | AllowPrivate = 0x4 | ||||
} | } | ||||