| @@ -3,7 +3,23 @@ Microsoft Visual Studio Solution File, Format Version 12.00 | |||||
| # Visual Studio 14 | # Visual Studio 14 | ||||
| VisualStudioVersion = 14.0.25420.1 | VisualStudioVersion = 14.0.25420.1 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F7F3E124-93C7-4846-AE87-9CE12BD82859}" | |||||
| ProjectSection(SolutionItems) = preProject | |||||
| global.json = global.json | |||||
| README.md = README.md | |||||
| EndProjectSection | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{496DB20A-A455-4D01-B6BC-90FE6D7C6B81}" | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" | |||||
| EndProject | |||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Impls", "Impls", "{288C363D-A636-4EAE-9AC1-4698B641B26E}" | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rest", "src\Discord.Net.Rest\Discord.Net.Rest.xproj", "{BFC6DC28-0351-4573-926A-D4124244C04F}" | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.WebSocket", "src\Discord.Net.WebSocket\Discord.Net.WebSocket.xproj", "{22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}" | |||||
| EndProject | |||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Rpc", "src\Discord.Net.Rpc\Discord.Net.Rpc.xproj", "{5688A353-121E-40A1-8BFA-B17B91FB48FB}" | |||||
| EndProject | EndProject | ||||
| Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" | ||||
| EndProject | EndProject | ||||
| @@ -13,10 +29,26 @@ Global | |||||
| Release|Any CPU = Release|Any CPU | Release|Any CPU = Release|Any CPU | ||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {496DB20A-A455-4D01-B6BC-90FE6D7C6B81}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU | {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {5688A353-121E-40A1-8BFA-B17B91FB48FB}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU | {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU | {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| @@ -25,4 +57,9 @@ Global | |||||
| GlobalSection(SolutionProperties) = preSolution | GlobalSection(SolutionProperties) = preSolution | ||||
| HideSolutionNode = FALSE | HideSolutionNode = FALSE | ||||
| EndGlobalSection | EndGlobalSection | ||||
| GlobalSection(NestedProjects) = preSolution | |||||
| {BFC6DC28-0351-4573-926A-D4124244C04F} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||||
| {22AB6C66-536C-4AC2-BBDB-A8BC4EB6B14D} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||||
| {5688A353-121E-40A1-8BFA-B17B91FB48FB} = {288C363D-A636-4EAE-9AC1-4698B641B26E} | |||||
| EndGlobalSection | |||||
| EndGlobal | EndGlobal | ||||
| @@ -0,0 +1,3 @@ | |||||
| using System.Runtime.CompilerServices; | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Tests")] | |||||
| @@ -6,6 +6,7 @@ namespace Discord.Commands | |||||
| public class CommandAttribute : Attribute | public class CommandAttribute : Attribute | ||||
| { | { | ||||
| public string Text { get; } | public string Text { get; } | ||||
| public RunMode RunMode { get; set; } = RunMode.Sync; | |||||
| public CommandAttribute() | public CommandAttribute() | ||||
| { | { | ||||
| @@ -0,0 +1,9 @@ | |||||
| using System; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| [AttributeUsage(AttributeTargets.Class)] | |||||
| public class DontAutoLoadAttribute : Attribute | |||||
| { | |||||
| } | |||||
| } | |||||
| @@ -1,22 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| [AttributeUsage(AttributeTargets.Class)] | |||||
| public class ModuleAttribute : Attribute | |||||
| { | |||||
| public string Prefix { get; } | |||||
| public bool AutoLoad { get; set; } | |||||
| public ModuleAttribute() | |||||
| { | |||||
| Prefix = null; | |||||
| AutoLoad = true; | |||||
| } | |||||
| public ModuleAttribute(string prefix) | |||||
| { | |||||
| Prefix = prefix; | |||||
| AutoLoad = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -6,6 +6,6 @@ namespace Discord.Commands | |||||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | ||||
| public abstract class PreconditionAttribute : Attribute | public abstract class PreconditionAttribute : Attribute | ||||
| { | { | ||||
| public abstract Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance); | |||||
| public abstract Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map); | |||||
| } | } | ||||
| } | } | ||||
| @@ -21,7 +21,7 @@ namespace Discord.Commands | |||||
| Contexts = contexts; | Contexts = contexts; | ||||
| } | } | ||||
| public override Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) | |||||
| public override Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) | |||||
| { | { | ||||
| bool isValid = false; | bool isValid = false; | ||||
| @@ -20,9 +20,9 @@ namespace Discord.Commands | |||||
| GuildPermission = null; | GuildPermission = null; | ||||
| } | } | ||||
| public override Task<PreconditionResult> CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) | |||||
| public override Task<PreconditionResult> CheckPermissions(CommandContext context, CommandInfo command, IDependencyMap map) | |||||
| { | { | ||||
| var guildUser = context.Author as IGuildUser; | |||||
| var guildUser = context.User as IGuildUser; | |||||
| if (GuildPermission.HasValue) | if (GuildPermission.HasValue) | ||||
| { | { | ||||
| @@ -0,0 +1,18 @@ | |||||
| namespace Discord.Commands | |||||
| { | |||||
| public struct CommandContext | |||||
| { | |||||
| public IGuild Guild { get; } | |||||
| public IMessageChannel Channel { get; } | |||||
| public IUser User { get; } | |||||
| public IUserMessage Message { get; } | |||||
| public CommandContext(IGuild guild, IMessageChannel channel, IUser user, IUserMessage msg) | |||||
| { | |||||
| Guild = guild; | |||||
| Channel = channel; | |||||
| User = user; | |||||
| Message = msg; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -10,38 +10,38 @@ using System.Threading.Tasks; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class Command | |||||
| public class CommandInfo | |||||
| { | { | ||||
| private static readonly MethodInfo _convertParamsMethod = typeof(Command).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); | |||||
| private static readonly MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); | |||||
| private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | ||||
| private readonly object _instance; | |||||
| private readonly Func<IUserMessage, IReadOnlyList<object>, Task> _action; | |||||
| private readonly Func<CommandContext, object[], Task> _action; | |||||
| public MethodInfo Source { get; } | public MethodInfo Source { get; } | ||||
| public Module Module { get; } | |||||
| public ModuleInfo Module { get; } | |||||
| public string Name { get; } | public string Name { get; } | ||||
| public string Summary { get; } | public string Summary { get; } | ||||
| public string Remarks { get; } | public string Remarks { get; } | ||||
| public string Text { get; } | public string Text { get; } | ||||
| public int Priority { get; } | public int Priority { get; } | ||||
| public bool HasVarArgs { get; } | public bool HasVarArgs { get; } | ||||
| public RunMode RunMode { get; } | |||||
| public IReadOnlyList<string> Aliases { get; } | public IReadOnlyList<string> Aliases { get; } | ||||
| public IReadOnlyList<CommandParameter> Parameters { get; } | public IReadOnlyList<CommandParameter> Parameters { get; } | ||||
| public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | ||||
| internal Command(MethodInfo source, Module module, object instance, CommandAttribute attribute, string groupPrefix) | |||||
| internal CommandInfo(MethodInfo source, ModuleInfo module, CommandAttribute attribute, string groupPrefix) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| Source = source; | Source = source; | ||||
| Module = module; | Module = module; | ||||
| _instance = instance; | |||||
| Name = source.Name; | Name = source.Name; | ||||
| if (attribute.Text == null) | if (attribute.Text == null) | ||||
| Text = groupPrefix; | Text = groupPrefix; | ||||
| RunMode = attribute.RunMode; | |||||
| if (groupPrefix != "") | if (groupPrefix != "") | ||||
| groupPrefix += " "; | groupPrefix += " "; | ||||
| @@ -85,18 +85,18 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| public async Task<PreconditionResult> CheckPreconditions(IUserMessage context) | |||||
| public async Task<PreconditionResult> CheckPreconditions(CommandContext context, IDependencyMap map = null) | |||||
| { | { | ||||
| foreach (PreconditionAttribute precondition in Module.Preconditions) | foreach (PreconditionAttribute precondition in Module.Preconditions) | ||||
| { | { | ||||
| var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false); | |||||
| var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| return result; | return result; | ||||
| } | } | ||||
| foreach (PreconditionAttribute precondition in Preconditions) | foreach (PreconditionAttribute precondition in Preconditions) | ||||
| { | { | ||||
| var result = await precondition.CheckPermissions(context, this, Module.Instance).ConfigureAwait(false); | |||||
| var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -104,7 +104,7 @@ namespace Discord.Commands | |||||
| return PreconditionResult.FromSuccess(); | return PreconditionResult.FromSuccess(); | ||||
| } | } | ||||
| public async Task<ParseResult> Parse(IUserMessage context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | |||||
| public async Task<ParseResult> Parse(CommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | |||||
| { | { | ||||
| if (!searchResult.IsSuccess) | if (!searchResult.IsSuccess) | ||||
| return ParseResult.FromError(searchResult); | return ParseResult.FromError(searchResult); | ||||
| @@ -125,7 +125,7 @@ namespace Discord.Commands | |||||
| return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); | return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); | ||||
| } | } | ||||
| public Task<ExecuteResult> Execute(IUserMessage context, ParseResult parseResult) | |||||
| public Task<ExecuteResult> Execute(CommandContext context, ParseResult parseResult) | |||||
| { | { | ||||
| if (!parseResult.IsSuccess) | if (!parseResult.IsSuccess) | ||||
| return Task.FromResult(ExecuteResult.FromError(parseResult)); | return Task.FromResult(ExecuteResult.FromError(parseResult)); | ||||
| @@ -148,11 +148,23 @@ namespace Discord.Commands | |||||
| return Execute(context, argList, paramList); | return Execute(context, argList, paramList); | ||||
| } | } | ||||
| public async Task<ExecuteResult> Execute(IUserMessage context, IEnumerable<object> argList, IEnumerable<object> paramList) | |||||
| public async Task<ExecuteResult> Execute(CommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| await _action.Invoke(context, GenerateArgs(argList, paramList)).ConfigureAwait(false);//Note: This code may need context | |||||
| var args = GenerateArgs(argList, paramList); | |||||
| switch (RunMode) | |||||
| { | |||||
| case RunMode.Sync: //Always sync | |||||
| await _action(context, args).ConfigureAwait(false); | |||||
| break; | |||||
| case RunMode.Mixed: //Sync until first await statement | |||||
| var t1 = _action(context, args); | |||||
| break; | |||||
| case RunMode.Async: //Always async | |||||
| var t2 = Task.Run(() => _action(context, args)); | |||||
| break; | |||||
| } | |||||
| return ExecuteResult.FromSuccess(); | return ExecuteResult.FromSuccess(); | ||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| @@ -169,11 +181,9 @@ namespace Discord.Commands | |||||
| private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) | private IReadOnlyList<CommandParameter> BuildParameters(MethodInfo methodInfo) | ||||
| { | { | ||||
| var parameters = methodInfo.GetParameters(); | var parameters = methodInfo.GetParameters(); | ||||
| if (parameters.Length == 0 || parameters[0].ParameterType != typeof(IUserMessage)) | |||||
| throw new InvalidOperationException($"The first parameter of a command must be {nameof(IUserMessage)}."); | |||||
| var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length - 1); | |||||
| for (int i = 1; i < parameters.Length; i++) | |||||
| var paramBuilder = ImmutableArray.CreateBuilder<CommandParameter>(parameters.Length); | |||||
| for (int i = 0; i < parameters.Length; i++) | |||||
| { | { | ||||
| var parameter = parameters[i]; | var parameter = parameters[i]; | ||||
| var type = parameter.ParameterType; | var type = parameter.ParameterType; | ||||
| @@ -209,19 +219,23 @@ namespace Discord.Commands | |||||
| } | } | ||||
| return paramBuilder.ToImmutable(); | return paramBuilder.ToImmutable(); | ||||
| } | } | ||||
| private Func<IUserMessage, IReadOnlyList<object>, Task> BuildAction(MethodInfo methodInfo) | |||||
| private Func<CommandContext, object[], Task> BuildAction(MethodInfo methodInfo) | |||||
| { | { | ||||
| if (methodInfo.ReturnType != typeof(Task)) | if (methodInfo.ReturnType != typeof(Task)) | ||||
| throw new InvalidOperationException("Commands must return a non-generic Task."); | throw new InvalidOperationException("Commands must return a non-generic Task."); | ||||
| return (msg, args) => | |||||
| return (context, args) => | |||||
| { | { | ||||
| object[] newArgs = new object[args.Count + 1]; | |||||
| newArgs[0] = msg; | |||||
| for (int i = 0; i < args.Count; i++) | |||||
| newArgs[i + 1] = args[i]; | |||||
| var result = methodInfo.Invoke(_instance, newArgs); | |||||
| return result as Task ?? Task.CompletedTask; | |||||
| var instance = Module.CreateInstance(); | |||||
| instance.Context = context; | |||||
| try | |||||
| { | |||||
| return methodInfo.Invoke(instance, args) as Task ?? Task.CompletedTask; | |||||
| } | |||||
| finally | |||||
| { | |||||
| (instance as IDisposable)?.Dispose(); | |||||
| } | |||||
| }; | }; | ||||
| } | } | ||||
| @@ -32,7 +32,7 @@ namespace Discord.Commands | |||||
| DefaultValue = defaultValue; | DefaultValue = defaultValue; | ||||
| } | } | ||||
| public async Task<TypeReaderResult> Parse(IUserMessage context, string input) | |||||
| public async Task<TypeReaderResult> Parse(CommandContext context, string input) | |||||
| { | { | ||||
| return await _reader.Read(context, input).ConfigureAwait(false); | return await _reader.Read(context, input).ConfigureAwait(false); | ||||
| } | } | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Commands | |||||
| QuotedParameter | QuotedParameter | ||||
| } | } | ||||
| public static async Task<ParseResult> ParseArgs(Command command, IUserMessage context, string input, int startPos) | |||||
| public static async Task<ParseResult> ParseArgs(CommandInfo command, CommandContext context, string input, int startPos) | |||||
| { | { | ||||
| CommandParameter curParam = null; | CommandParameter curParam = null; | ||||
| StringBuilder argBuilder = new StringBuilder(input.Length); | StringBuilder argBuilder = new StringBuilder(input.Length); | ||||
| @@ -11,21 +11,25 @@ namespace Discord.Commands | |||||
| { | { | ||||
| public class CommandService | public class CommandService | ||||
| { | { | ||||
| private static readonly TypeInfo _moduleTypeInfo = typeof(ModuleBase).GetTypeInfo(); | |||||
| private readonly SemaphoreSlim _moduleLock; | private readonly SemaphoreSlim _moduleLock; | ||||
| private readonly ConcurrentDictionary<Type, Module> _modules; | |||||
| private readonly ConcurrentDictionary<Type, ModuleInfo> _moduleDefs; | |||||
| private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | private readonly ConcurrentDictionary<Type, TypeReader> _typeReaders; | ||||
| private readonly CommandMap _map; | private readonly CommandMap _map; | ||||
| public IEnumerable<Module> Modules => _modules.Select(x => x.Value); | |||||
| public IEnumerable<Command> Commands => _modules.SelectMany(x => x.Value.Commands); | |||||
| public IEnumerable<ModuleInfo> Modules => _moduleDefs.Select(x => x.Value); | |||||
| public IEnumerable<CommandInfo> Commands => _moduleDefs.SelectMany(x => x.Value.Commands); | |||||
| public CommandService() | public CommandService() | ||||
| { | { | ||||
| _moduleLock = new SemaphoreSlim(1, 1); | _moduleLock = new SemaphoreSlim(1, 1); | ||||
| _modules = new ConcurrentDictionary<Type, Module>(); | |||||
| _moduleDefs = new ConcurrentDictionary<Type, ModuleInfo>(); | |||||
| _map = new CommandMap(); | _map = new CommandMap(); | ||||
| _typeReaders = new ConcurrentDictionary<Type, TypeReader> | _typeReaders = new ConcurrentDictionary<Type, TypeReader> | ||||
| { | { | ||||
| [typeof(bool)] = new SimpleTypeReader<bool>(), | |||||
| [typeof(char)] = new SimpleTypeReader<char>(), | |||||
| [typeof(string)] = new SimpleTypeReader<string>(), | [typeof(string)] = new SimpleTypeReader<string>(), | ||||
| [typeof(byte)] = new SimpleTypeReader<byte>(), | [typeof(byte)] = new SimpleTypeReader<byte>(), | ||||
| [typeof(sbyte)] = new SimpleTypeReader<sbyte>(), | [typeof(sbyte)] = new SimpleTypeReader<sbyte>(), | ||||
| @@ -43,7 +47,6 @@ namespace Discord.Commands | |||||
| [typeof(IMessage)] = new MessageTypeReader<IMessage>(), | [typeof(IMessage)] = new MessageTypeReader<IMessage>(), | ||||
| [typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(), | [typeof(IUserMessage)] = new MessageTypeReader<IUserMessage>(), | ||||
| //[typeof(ISystemMessage)] = new MessageTypeReader<ISystemMessage>(), | |||||
| [typeof(IChannel)] = new ChannelTypeReader<IChannel>(), | [typeof(IChannel)] = new ChannelTypeReader<IChannel>(), | ||||
| [typeof(IDMChannel)] = new ChannelTypeReader<IDMChannel>(), | [typeof(IDMChannel)] = new ChannelTypeReader<IDMChannel>(), | ||||
| [typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(), | [typeof(IGroupChannel)] = new ChannelTypeReader<IGroupChannel>(), | ||||
| @@ -53,120 +56,99 @@ namespace Discord.Commands | |||||
| [typeof(ITextChannel)] = new ChannelTypeReader<ITextChannel>(), | [typeof(ITextChannel)] = new ChannelTypeReader<ITextChannel>(), | ||||
| [typeof(IVoiceChannel)] = new ChannelTypeReader<IVoiceChannel>(), | [typeof(IVoiceChannel)] = new ChannelTypeReader<IVoiceChannel>(), | ||||
| //[typeof(IGuild)] = new GuildTypeReader<IGuild>(), | |||||
| [typeof(IRole)] = new RoleTypeReader<IRole>(), | [typeof(IRole)] = new RoleTypeReader<IRole>(), | ||||
| //[typeof(IInvite)] = new InviteTypeReader<IInvite>(), | |||||
| //[typeof(IInviteMetadata)] = new InviteTypeReader<IInviteMetadata>(), | |||||
| [typeof(IUser)] = new UserTypeReader<IUser>(), | [typeof(IUser)] = new UserTypeReader<IUser>(), | ||||
| [typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), | [typeof(IGroupUser)] = new UserTypeReader<IGroupUser>(), | ||||
| [typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(), | [typeof(IGuildUser)] = new UserTypeReader<IGuildUser>(), | ||||
| }; | }; | ||||
| } | } | ||||
| public void AddTypeReader<T>(TypeReader reader) | |||||
| { | |||||
| _typeReaders[typeof(T)] = reader; | |||||
| } | |||||
| public void AddTypeReader(Type type, TypeReader reader) | |||||
| { | |||||
| _typeReaders[type] = reader; | |||||
| } | |||||
| internal TypeReader GetTypeReader(Type type) | |||||
| { | |||||
| TypeReader reader; | |||||
| if (_typeReaders.TryGetValue(type, out reader)) | |||||
| return reader; | |||||
| return null; | |||||
| } | |||||
| public async Task<Module> Load(object moduleInstance) | |||||
| //Modules | |||||
| public async Task<ModuleInfo> AddModule<T>(IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| if (_modules.ContainsKey(moduleInstance.GetType())) | |||||
| throw new ArgumentException($"This module has already been loaded."); | |||||
| if (_moduleDefs.ContainsKey(typeof(T))) | |||||
| throw new ArgumentException($"This module has already been added."); | |||||
| var typeInfo = moduleInstance.GetType().GetTypeInfo(); | |||||
| var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>(); | |||||
| if (moduleAttr == null) | |||||
| throw new ArgumentException($"Modules must be marked with ModuleAttribute."); | |||||
| var typeInfo = typeof(T).GetTypeInfo(); | |||||
| if (!_moduleTypeInfo.IsAssignableFrom(typeInfo)) | |||||
| throw new ArgumentException($"Modules must inherit ModuleBase."); | |||||
| return LoadInternal(moduleInstance, moduleAttr, typeInfo, null); | |||||
| return AddModuleInternal(typeInfo, dependencyMap); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _moduleLock.Release(); | _moduleLock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| private Module LoadInternal(object moduleInstance, ModuleAttribute moduleAttr, TypeInfo typeInfo, IDependencyMap dependencyMap) | |||||
| { | |||||
| if (_modules.ContainsKey(moduleInstance.GetType())) | |||||
| return _modules[moduleInstance.GetType()]; | |||||
| var loadedModule = new Module(typeInfo, this, moduleInstance, moduleAttr, dependencyMap); | |||||
| _modules[moduleInstance.GetType()] = loadedModule; | |||||
| foreach (var cmd in loadedModule.Commands) | |||||
| _map.AddCommand(cmd); | |||||
| return loadedModule; | |||||
| } | |||||
| public async Task<IEnumerable<Module>> LoadAssembly(Assembly assembly, IDependencyMap dependencyMap = null) | |||||
| public async Task<IEnumerable<ModuleInfo>> AddModules(Assembly assembly, IDependencyMap dependencyMap = null) | |||||
| { | { | ||||
| var modules = ImmutableArray.CreateBuilder<Module>(); | |||||
| var moduleDefs = ImmutableArray.CreateBuilder<ModuleInfo>(); | |||||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| foreach (var type in assembly.ExportedTypes) | foreach (var type in assembly.ExportedTypes) | ||||
| { | { | ||||
| var typeInfo = type.GetTypeInfo(); | |||||
| var moduleAttr = typeInfo.GetCustomAttribute<ModuleAttribute>(); | |||||
| if (moduleAttr != null && moduleAttr.AutoLoad) | |||||
| if (!_moduleDefs.ContainsKey(type)) | |||||
| { | { | ||||
| var moduleInstance = ReflectionUtils.CreateObject(typeInfo, this, dependencyMap); | |||||
| modules.Add(LoadInternal(moduleInstance, moduleAttr, typeInfo, dependencyMap)); | |||||
| var typeInfo = type.GetTypeInfo(); | |||||
| if (_moduleTypeInfo.IsAssignableFrom(typeInfo)) | |||||
| { | |||||
| var dontAutoLoad = typeInfo.GetCustomAttribute<DontAutoLoadAttribute>(); | |||||
| if (dontAutoLoad == null) | |||||
| moduleDefs.Add(AddModuleInternal(typeInfo, dependencyMap)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| return modules.ToImmutable(); | |||||
| return moduleDefs.ToImmutable(); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _moduleLock.Release(); | _moduleLock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| private ModuleInfo AddModuleInternal(TypeInfo typeInfo, IDependencyMap dependencyMap) | |||||
| { | |||||
| var moduleDef = new ModuleInfo(typeInfo, this, dependencyMap); | |||||
| _moduleDefs[typeInfo.BaseType] = moduleDef; | |||||
| foreach (var cmd in moduleDef.Commands) | |||||
| _map.AddCommand(cmd); | |||||
| return moduleDef; | |||||
| } | |||||
| public async Task<bool> Unload(Module module) | |||||
| public async Task<bool> RemoveModule(ModuleInfo module) | |||||
| { | { | ||||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| return UnloadInternal(module.Instance); | |||||
| return RemoveModuleInternal(module.Source.BaseType); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _moduleLock.Release(); | _moduleLock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| public async Task<bool> Unload(object moduleInstance) | |||||
| public async Task<bool> RemoveModule<T>() | |||||
| { | { | ||||
| await _moduleLock.WaitAsync().ConfigureAwait(false); | await _moduleLock.WaitAsync().ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| return UnloadInternal(moduleInstance); | |||||
| return RemoveModuleInternal(typeof(T)); | |||||
| } | } | ||||
| finally | finally | ||||
| { | { | ||||
| _moduleLock.Release(); | _moduleLock.Release(); | ||||
| } | } | ||||
| } | } | ||||
| private bool UnloadInternal(object module) | |||||
| private bool RemoveModuleInternal(Type type) | |||||
| { | { | ||||
| Module unloadedModule; | |||||
| if (_modules.TryRemove(module.GetType(), out unloadedModule)) | |||||
| ModuleInfo unloadedModule; | |||||
| if (_moduleDefs.TryRemove(type, out unloadedModule)) | |||||
| { | { | ||||
| foreach (var cmd in unloadedModule.Commands) | foreach (var cmd in unloadedModule.Commands) | ||||
| _map.RemoveCommand(cmd); | _map.RemoveCommand(cmd); | ||||
| @@ -176,8 +158,26 @@ namespace Discord.Commands | |||||
| return false; | return false; | ||||
| } | } | ||||
| public SearchResult Search(IUserMessage message, int argPos) => Search(message, message.Content.Substring(argPos)); | |||||
| public SearchResult Search(IUserMessage message, string input) | |||||
| //Type Readers | |||||
| public void AddTypeReader<T>(TypeReader reader) | |||||
| { | |||||
| _typeReaders[typeof(T)] = reader; | |||||
| } | |||||
| public void AddTypeReader(Type type, TypeReader reader) | |||||
| { | |||||
| _typeReaders[type] = reader; | |||||
| } | |||||
| internal TypeReader GetTypeReader(Type type) | |||||
| { | |||||
| TypeReader reader; | |||||
| if (_typeReaders.TryGetValue(type, out reader)) | |||||
| return reader; | |||||
| return null; | |||||
| } | |||||
| //Execution | |||||
| public SearchResult Search(CommandContext context, int argPos) => Search(context, context.Message.Content.Substring(argPos)); | |||||
| public SearchResult Search(CommandContext context, string input) | |||||
| { | { | ||||
| string lowerInput = input.ToLowerInvariant(); | string lowerInput = input.ToLowerInvariant(); | ||||
| var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); | var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); | ||||
| @@ -188,18 +188,18 @@ namespace Discord.Commands | |||||
| return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | ||||
| } | } | ||||
| public Task<IResult> Execute(IUserMessage message, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| => Execute(message, message.Content.Substring(argPos), multiMatchHandling); | |||||
| public async Task<IResult> Execute(IUserMessage message, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| public Task<IResult> Execute(CommandContext context, int argPos, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| => Execute(context, context.Message.Content.Substring(argPos), multiMatchHandling); | |||||
| public async Task<IResult> Execute(CommandContext context, string input, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| { | { | ||||
| var searchResult = Search(message, input); | |||||
| var searchResult = Search(context, input); | |||||
| if (!searchResult.IsSuccess) | if (!searchResult.IsSuccess) | ||||
| return searchResult; | return searchResult; | ||||
| var commands = searchResult.Commands; | var commands = searchResult.Commands; | ||||
| for (int i = commands.Count - 1; i >= 0; i--) | for (int i = commands.Count - 1; i >= 0; i--) | ||||
| { | { | ||||
| var preconditionResult = await commands[i].CheckPreconditions(message); | |||||
| var preconditionResult = await commands[i].CheckPreconditions(context).ConfigureAwait(false); | |||||
| if (!preconditionResult.IsSuccess) | if (!preconditionResult.IsSuccess) | ||||
| { | { | ||||
| if (commands.Count == 1) | if (commands.Count == 1) | ||||
| @@ -208,17 +208,17 @@ namespace Discord.Commands | |||||
| continue; | continue; | ||||
| } | } | ||||
| var parseResult = await commands[i].Parse(message, searchResult, preconditionResult); | |||||
| var parseResult = await commands[i].Parse(context, searchResult, preconditionResult).ConfigureAwait(false); | |||||
| if (!parseResult.IsSuccess) | if (!parseResult.IsSuccess) | ||||
| { | { | ||||
| if (parseResult.Error == CommandError.MultipleMatches) | if (parseResult.Error == CommandError.MultipleMatches) | ||||
| { | { | ||||
| TypeReaderValue[] argList, paramList; | |||||
| IReadOnlyList<TypeReaderValue> argList, paramList; | |||||
| switch (multiMatchHandling) | switch (multiMatchHandling) | ||||
| { | { | ||||
| case MultiMatchHandling.Best: | case MultiMatchHandling.Best: | ||||
| argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); | |||||
| paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); | |||||
| argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); | |||||
| paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); | |||||
| parseResult = ParseResult.FromSuccess(argList, paramList); | parseResult = ParseResult.FromSuccess(argList, paramList); | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -233,7 +233,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| return await commands[i].Execute(message, parseResult); | |||||
| return await commands[i].Execute(context, parseResult).ConfigureAwait(false); | |||||
| } | } | ||||
| return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); | return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); | ||||
| @@ -32,7 +32,7 @@ | |||||
| if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> " | if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> " | ||||
| ulong userId; | ulong userId; | ||||
| if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 2), out userId)) return false; | |||||
| if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out userId)) return false; | |||||
| if (userId == user.Id) | if (userId == user.Id) | ||||
| { | { | ||||
| argPos = endPos + 2; | argPos = endPos + 2; | ||||
| @@ -16,7 +16,7 @@ namespace Discord.Commands | |||||
| _nodes = new ConcurrentDictionary<string, CommandMapNode>(); | _nodes = new ConcurrentDictionary<string, CommandMapNode>(); | ||||
| } | } | ||||
| public void AddCommand(Command command) | |||||
| public void AddCommand(CommandInfo command) | |||||
| { | { | ||||
| foreach (string text in command.Aliases) | foreach (string text in command.Aliases) | ||||
| { | { | ||||
| @@ -35,7 +35,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public void RemoveCommand(Command command) | |||||
| public void RemoveCommand(CommandInfo command) | |||||
| { | { | ||||
| foreach (string text in command.Aliases) | foreach (string text in command.Aliases) | ||||
| { | { | ||||
| @@ -60,7 +60,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| public IEnumerable<Command> GetCommands(string text) | |||||
| public IEnumerable<CommandInfo> GetCommands(string text) | |||||
| { | { | ||||
| int nextSpace = NextWhitespace(text); | int nextSpace = NextWhitespace(text); | ||||
| string name; | string name; | ||||
| @@ -76,7 +76,7 @@ namespace Discord.Commands | |||||
| if (_nodes.TryGetValue(name, out nextNode)) | if (_nodes.TryGetValue(name, out nextNode)) | ||||
| return nextNode.GetCommands(text, nextSpace + 1); | return nextNode.GetCommands(text, nextSpace + 1); | ||||
| else | else | ||||
| return Enumerable.Empty<Command>(); | |||||
| return Enumerable.Empty<CommandInfo>(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -9,7 +9,7 @@ namespace Discord.Commands | |||||
| private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | ||||
| private readonly string _name; | private readonly string _name; | ||||
| private readonly object _lockObj = new object(); | private readonly object _lockObj = new object(); | ||||
| private ImmutableArray<Command> _commands; | |||||
| private ImmutableArray<CommandInfo> _commands; | |||||
| public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; | public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; | ||||
| @@ -17,10 +17,10 @@ namespace Discord.Commands | |||||
| { | { | ||||
| _name = name; | _name = name; | ||||
| _nodes = new ConcurrentDictionary<string, CommandMapNode>(); | _nodes = new ConcurrentDictionary<string, CommandMapNode>(); | ||||
| _commands = ImmutableArray.Create<Command>(); | |||||
| _commands = ImmutableArray.Create<CommandInfo>(); | |||||
| } | } | ||||
| public void AddCommand(string text, int index, Command command) | |||||
| public void AddCommand(string text, int index, CommandInfo command) | |||||
| { | { | ||||
| int nextSpace = text.IndexOf(' ', index); | int nextSpace = text.IndexOf(' ', index); | ||||
| string name; | string name; | ||||
| @@ -41,7 +41,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| public void RemoveCommand(string text, int index, Command command) | |||||
| public void RemoveCommand(string text, int index, CommandInfo command) | |||||
| { | { | ||||
| int nextSpace = text.IndexOf(' ', index); | int nextSpace = text.IndexOf(' ', index); | ||||
| string name; | string name; | ||||
| @@ -68,7 +68,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| public IEnumerable<Command> GetCommands(string text, int index) | |||||
| public IEnumerable<CommandInfo> GetCommands(string text, int index) | |||||
| { | { | ||||
| int nextSpace = text.IndexOf(' ', index); | int nextSpace = text.IndexOf(' ', index); | ||||
| string name; | string name; | ||||
| @@ -0,0 +1,15 @@ | |||||
| using System.Threading.Tasks; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| public abstract class ModuleBase | |||||
| { | |||||
| public IDiscordClient Client { get; internal set; } | |||||
| public CommandContext Context { get; internal set; } | |||||
| protected virtual async Task ReplyAsync(string message, bool isTTS = false, RequestOptions options = null) | |||||
| { | |||||
| await Context.Channel.SendMessageAsync(message, isTTS, options).ConfigureAwait(false); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| using System.Collections.Generic; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.Reflection; | using System.Reflection; | ||||
| @@ -6,26 +7,31 @@ using System.Reflection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | ||||
| public class Module | |||||
| public class ModuleInfo | |||||
| { | { | ||||
| internal readonly Func<ModuleBase> _builder; | |||||
| public TypeInfo Source { get; } | public TypeInfo Source { get; } | ||||
| public CommandService Service { get; } | public CommandService Service { get; } | ||||
| public string Name { get; } | public string Name { get; } | ||||
| public string Prefix { get; } | public string Prefix { get; } | ||||
| public string Summary { get; } | public string Summary { get; } | ||||
| public string Remarks { get; } | public string Remarks { get; } | ||||
| public IEnumerable<Command> Commands { get; } | |||||
| internal object Instance { get; } | |||||
| public IEnumerable<CommandInfo> Commands { get; } | |||||
| public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | public IReadOnlyList<PreconditionAttribute> Preconditions { get; } | ||||
| internal Module(TypeInfo source, CommandService service, object instance, ModuleAttribute moduleAttr, IDependencyMap dependencyMap) | |||||
| internal ModuleInfo(TypeInfo source, CommandService service, IDependencyMap dependencyMap) | |||||
| { | { | ||||
| Source = source; | Source = source; | ||||
| Service = service; | Service = service; | ||||
| Name = source.Name; | Name = source.Name; | ||||
| Prefix = moduleAttr.Prefix ?? ""; | |||||
| Instance = instance; | |||||
| _builder = ReflectionUtils.CreateBuilder<ModuleBase>(source, Service, dependencyMap); | |||||
| var groupAttr = source.GetCustomAttribute<GroupAttribute>(); | |||||
| if (groupAttr != null) | |||||
| Prefix = groupAttr.Prefix; | |||||
| else | |||||
| Prefix = ""; | |||||
| var nameAttr = source.GetCustomAttribute<NameAttribute>(); | var nameAttr = source.GetCustomAttribute<NameAttribute>(); | ||||
| if (nameAttr != null) | if (nameAttr != null) | ||||
| @@ -39,20 +45,19 @@ namespace Discord.Commands | |||||
| if (remarksAttr != null) | if (remarksAttr != null) | ||||
| Remarks = remarksAttr.Text; | Remarks = remarksAttr.Text; | ||||
| List<Command> commands = new List<Command>(); | |||||
| SearchClass(source, instance, commands, Prefix, dependencyMap); | |||||
| List<CommandInfo> commands = new List<CommandInfo>(); | |||||
| SearchClass(source, commands, Prefix, dependencyMap); | |||||
| Commands = commands; | Commands = commands; | ||||
| Preconditions = BuildPreconditions(); | |||||
| Preconditions = Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | |||||
| } | } | ||||
| private void SearchClass(TypeInfo parentType, object instance, List<Command> commands, string groupPrefix, IDependencyMap dependencyMap) | |||||
| private void SearchClass(TypeInfo parentType, List<CommandInfo> commands, string groupPrefix, IDependencyMap dependencyMap) | |||||
| { | { | ||||
| foreach (var method in parentType.DeclaredMethods) | foreach (var method in parentType.DeclaredMethods) | ||||
| { | { | ||||
| var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); | var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); | ||||
| if (cmdAttr != null) | if (cmdAttr != null) | ||||
| commands.Add(new Command(method, this, instance, cmdAttr, groupPrefix)); | |||||
| commands.Add(new CommandInfo(method, this, cmdAttr, groupPrefix)); | |||||
| } | } | ||||
| foreach (var type in parentType.DeclaredNestedTypes) | foreach (var type in parentType.DeclaredNestedTypes) | ||||
| { | { | ||||
| @@ -66,15 +71,13 @@ namespace Discord.Commands | |||||
| else | else | ||||
| nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); | nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); | ||||
| SearchClass(type, ReflectionUtils.CreateObject(type, Service, dependencyMap), commands, nextGroupPrefix, dependencyMap); | |||||
| SearchClass(type, commands, nextGroupPrefix, dependencyMap); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| private IReadOnlyList<PreconditionAttribute> BuildPreconditions() | |||||
| { | |||||
| return Source.GetCustomAttributes<PreconditionAttribute>().ToImmutableArray(); | |||||
| } | |||||
| internal ModuleBase CreateInstance() | |||||
| => _builder(); | |||||
| public override string ToString() => Name; | public override string ToString() => Name; | ||||
| private string DebuggerDisplay => Name; | private string DebuggerDisplay => Name; | ||||
| @@ -13,7 +13,7 @@ namespace Discord.Commands | |||||
| static PrimitiveParsers() | static PrimitiveParsers() | ||||
| { | { | ||||
| var parserBuilder = ImmutableDictionary.CreateBuilder<Type, Delegate>(); | var parserBuilder = ImmutableDictionary.CreateBuilder<Type, Delegate>(); | ||||
| parserBuilder[typeof(string)] = (TryParseDelegate<string>)delegate(string str, out string value) { value = str; return true; }; | |||||
| parserBuilder[typeof(bool)] = (TryParseDelegate<bool>)bool.TryParse; | |||||
| parserBuilder[typeof(sbyte)] = (TryParseDelegate<sbyte>)sbyte.TryParse; | parserBuilder[typeof(sbyte)] = (TryParseDelegate<sbyte>)sbyte.TryParse; | ||||
| parserBuilder[typeof(byte)] = (TryParseDelegate<byte>)byte.TryParse; | parserBuilder[typeof(byte)] = (TryParseDelegate<byte>)byte.TryParse; | ||||
| parserBuilder[typeof(short)] = (TryParseDelegate<short>)short.TryParse; | parserBuilder[typeof(short)] = (TryParseDelegate<short>)short.TryParse; | ||||
| @@ -27,6 +27,12 @@ namespace Discord.Commands | |||||
| parserBuilder[typeof(decimal)] = (TryParseDelegate<decimal>)decimal.TryParse; | parserBuilder[typeof(decimal)] = (TryParseDelegate<decimal>)decimal.TryParse; | ||||
| parserBuilder[typeof(DateTime)] = (TryParseDelegate<DateTime>)DateTime.TryParse; | parserBuilder[typeof(DateTime)] = (TryParseDelegate<DateTime>)DateTime.TryParse; | ||||
| parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate<DateTimeOffset>)DateTimeOffset.TryParse; | parserBuilder[typeof(DateTimeOffset)] = (TryParseDelegate<DateTimeOffset>)DateTimeOffset.TryParse; | ||||
| parserBuilder[typeof(char)] = (TryParseDelegate<char>)char.TryParse; | |||||
| parserBuilder[typeof(string)] = (TryParseDelegate<string>)delegate (string str, out string value) | |||||
| { | |||||
| value = str; | |||||
| return true; | |||||
| }; | |||||
| _parsers = parserBuilder.ToImmutable(); | _parsers = parserBuilder.ToImmutable(); | ||||
| } | } | ||||
| @@ -9,23 +9,21 @@ namespace Discord.Commands | |||||
| internal class ChannelTypeReader<T> : TypeReader | internal class ChannelTypeReader<T> : TypeReader | ||||
| where T : class, IChannel | where T : class, IChannel | ||||
| { | { | ||||
| public override async Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| public override async Task<TypeReaderResult> Read(CommandContext context, string input) | |||||
| { | { | ||||
| var guild = (context.Channel as IGuildChannel)?.Guild; | |||||
| if (guild != null) | |||||
| if (context.Guild != null) | |||||
| { | { | ||||
| var results = new Dictionary<ulong, TypeReaderValue>(); | var results = new Dictionary<ulong, TypeReaderValue>(); | ||||
| var channels = await guild.GetChannelsAsync().ConfigureAwait(false); | |||||
| var channels = await context.Guild.GetChannelsAsync(CacheMode.CacheOnly).ConfigureAwait(false); | |||||
| ulong id; | ulong id; | ||||
| //By Mention (1.0) | //By Mention (1.0) | ||||
| if (MentionUtils.TryParseChannel(input, out id)) | if (MentionUtils.TryParseChannel(input, out id)) | ||||
| AddResult(results, await guild.GetChannelAsync(id).ConfigureAwait(false) as T, 1.00f); | |||||
| AddResult(results, await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); | |||||
| //By Id (0.9) | //By Id (0.9) | ||||
| if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | ||||
| AddResult(results, await guild.GetChannelAsync(id).ConfigureAwait(false) as T, 0.90f); | |||||
| AddResult(results, await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); | |||||
| //By Name (0.7-0.8) | //By Name (0.7-0.8) | ||||
| foreach (var channel in channels.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) | foreach (var channel in channels.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) | ||||
| @@ -42,7 +42,7 @@ namespace Discord.Commands | |||||
| _enumsByValue = byValueBuilder.ToImmutable(); | _enumsByValue = byValueBuilder.ToImmutable(); | ||||
| } | } | ||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| public override Task<TypeReaderResult> Read(CommandContext context, string input) | |||||
| { | { | ||||
| T baseValue; | T baseValue; | ||||
| object enumValue; | object enumValue; | ||||
| @@ -6,19 +6,19 @@ namespace Discord.Commands | |||||
| internal class MessageTypeReader<T> : TypeReader | internal class MessageTypeReader<T> : TypeReader | ||||
| where T : class, IMessage | where T : class, IMessage | ||||
| { | { | ||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| public override async Task<TypeReaderResult> Read(CommandContext context, string input) | |||||
| { | { | ||||
| ulong id; | ulong id; | ||||
| //By Id (1.0) | //By Id (1.0) | ||||
| if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | ||||
| { | { | ||||
| var msg = context.Channel.GetCachedMessage(id) as T; | |||||
| var msg = await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T; | |||||
| if (msg != null) | if (msg != null) | ||||
| return Task.FromResult(TypeReaderResult.FromSuccess(msg)); | |||||
| return TypeReaderResult.FromSuccess(msg); | |||||
| } | } | ||||
| return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found.")); | |||||
| return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found."); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -9,23 +9,22 @@ namespace Discord.Commands | |||||
| internal class RoleTypeReader<T> : TypeReader | internal class RoleTypeReader<T> : TypeReader | ||||
| where T : class, IRole | where T : class, IRole | ||||
| { | { | ||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| public override Task<TypeReaderResult> Read(CommandContext context, string input) | |||||
| { | { | ||||
| var guild = (context.Channel as IGuildChannel)?.Guild; | |||||
| ulong id; | ulong id; | ||||
| if (guild != null) | |||||
| if (context.Guild != null) | |||||
| { | { | ||||
| var results = new Dictionary<ulong, TypeReaderValue>(); | var results = new Dictionary<ulong, TypeReaderValue>(); | ||||
| var roles = guild.Roles; | |||||
| var roles = context.Guild.Roles; | |||||
| //By Mention (1.0) | //By Mention (1.0) | ||||
| if (MentionUtils.TryParseRole(input, out id)) | if (MentionUtils.TryParseRole(input, out id)) | ||||
| AddResult(results, guild.GetRole(id) as T, 1.00f); | |||||
| AddResult(results, context.Guild.GetRole(id) as T, 1.00f); | |||||
| //By Id (0.9) | //By Id (0.9) | ||||
| if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | ||||
| AddResult(results, guild.GetRole(id) as T, 0.90f); | |||||
| AddResult(results, context.Guild.GetRole(id) as T, 0.90f); | |||||
| //By Name (0.7-0.8) | //By Name (0.7-0.8) | ||||
| foreach (var role in roles.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) | foreach (var role in roles.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) | ||||
| @@ -11,7 +11,7 @@ namespace Discord.Commands | |||||
| _tryParse = PrimitiveParsers.Get<T>(); | _tryParse = PrimitiveParsers.Get<T>(); | ||||
| } | } | ||||
| public override Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| public override Task<TypeReaderResult> Read(CommandContext context, string input) | |||||
| { | { | ||||
| T value; | T value; | ||||
| if (_tryParse(input, out value)) | if (_tryParse(input, out value)) | ||||
| @@ -4,6 +4,6 @@ namespace Discord.Commands | |||||
| { | { | ||||
| public abstract class TypeReader | public abstract class TypeReader | ||||
| { | { | ||||
| public abstract Task<TypeReaderResult> Read(IUserMessage context, string input); | |||||
| public abstract Task<TypeReaderResult> Read(CommandContext context, string input); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | |||||
| using System.Globalization; | using System.Globalization; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| @@ -9,33 +10,32 @@ namespace Discord.Commands | |||||
| internal class UserTypeReader<T> : TypeReader | internal class UserTypeReader<T> : TypeReader | ||||
| where T : class, IUser | where T : class, IUser | ||||
| { | { | ||||
| public override async Task<TypeReaderResult> Read(IUserMessage context, string input) | |||||
| public override async Task<TypeReaderResult> Read(CommandContext context, string input) | |||||
| { | { | ||||
| var results = new Dictionary<ulong, TypeReaderValue>(); | var results = new Dictionary<ulong, TypeReaderValue>(); | ||||
| var guild = (context.Channel as IGuildChannel)?.Guild; | |||||
| IReadOnlyCollection<IUser> channelUsers = await context.Channel.GetUsersAsync().ConfigureAwait(false); | |||||
| IReadOnlyCollection<IUser> channelUsers = (await context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten().ConfigureAwait(false)).ToArray(); //TODO: must be a better way? | |||||
| IReadOnlyCollection<IGuildUser> guildUsers = null; | IReadOnlyCollection<IGuildUser> guildUsers = null; | ||||
| ulong id; | ulong id; | ||||
| if (guild != null) | |||||
| guildUsers = await guild.GetUsersAsync().ConfigureAwait(false); | |||||
| if (context.Guild != null) | |||||
| guildUsers = await context.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false); | |||||
| //By Mention (1.0) | //By Mention (1.0) | ||||
| if (MentionUtils.TryParseUser(input, out id)) | if (MentionUtils.TryParseUser(input, out id)) | ||||
| { | { | ||||
| if (guild != null) | |||||
| AddResult(results, await guild.GetUserAsync(id).ConfigureAwait(false) as T, 1.00f); | |||||
| if (context.Guild != null) | |||||
| AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); | |||||
| else | else | ||||
| AddResult(results, await context.Channel.GetUserAsync(id).ConfigureAwait(false) as T, 1.00f); | |||||
| AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); | |||||
| } | } | ||||
| //By Id (0.9) | //By Id (0.9) | ||||
| if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) | ||||
| { | { | ||||
| if (guild != null) | |||||
| AddResult(results, await guild.GetUserAsync(id).ConfigureAwait(false) as T, 0.90f); | |||||
| if (context.Guild != null) | |||||
| AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); | |||||
| else | else | ||||
| AddResult(results, await context.Channel.GetUserAsync(id).ConfigureAwait(false) as T, 0.90f); | |||||
| AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); | |||||
| } | } | ||||
| //By Username + Discriminator (0.7-0.85) | //By Username + Discriminator (0.7-0.85) | ||||
| @@ -75,7 +75,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| if (results.Count > 0) | if (results.Count > 0) | ||||
| return TypeReaderResult.FromSuccess(results.Values.ToArray()); | |||||
| return TypeReaderResult.FromSuccess(results.Values.ToImmutableArray()); | |||||
| return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); | return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); | ||||
| } | } | ||||
| @@ -6,7 +6,10 @@ namespace Discord.Commands | |||||
| { | { | ||||
| internal class ReflectionUtils | internal class ReflectionUtils | ||||
| { | { | ||||
| internal static object CreateObject(TypeInfo typeInfo, CommandService service, IDependencyMap map = null) | |||||
| internal static T CreateObject<T>(TypeInfo typeInfo, CommandService service, IDependencyMap map = null) | |||||
| => CreateBuilder<T>(typeInfo, service, map)(); | |||||
| internal static Func<T> CreateBuilder<T>(TypeInfo typeInfo, CommandService service, IDependencyMap map = null) | |||||
| { | { | ||||
| var constructors = typeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); | var constructors = typeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); | ||||
| if (constructors.Length == 0) | if (constructors.Length == 0) | ||||
| @@ -14,7 +17,7 @@ namespace Discord.Commands | |||||
| else if (constructors.Length > 1) | else if (constructors.Length > 1) | ||||
| throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); | throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); | ||||
| var constructor = constructors[0]; | |||||
| var constructor = constructors[0]; | |||||
| ParameterInfo[] parameters = constructor.GetParameters(); | ParameterInfo[] parameters = constructor.GetParameters(); | ||||
| object[] args = new object[parameters.Length]; | object[] args = new object[parameters.Length]; | ||||
| @@ -34,14 +37,17 @@ namespace Discord.Commands | |||||
| args[i] = arg; | args[i] = arg; | ||||
| } | } | ||||
| try | |||||
| { | |||||
| return constructor.Invoke(args); | |||||
| } | |||||
| catch (Exception ex) | |||||
| return () => | |||||
| { | { | ||||
| throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex); | |||||
| } | |||||
| try | |||||
| { | |||||
| return (T)constructor.Invoke(args); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex); | |||||
| } | |||||
| }; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -7,14 +7,14 @@ namespace Discord.Commands | |||||
| public struct SearchResult : IResult | public struct SearchResult : IResult | ||||
| { | { | ||||
| public string Text { get; } | public string Text { get; } | ||||
| public IReadOnlyList<Command> Commands { get; } | |||||
| public IReadOnlyList<CommandInfo> Commands { get; } | |||||
| public CommandError? Error { get; } | public CommandError? Error { get; } | ||||
| public string ErrorReason { get; } | public string ErrorReason { get; } | ||||
| public bool IsSuccess => !Error.HasValue; | public bool IsSuccess => !Error.HasValue; | ||||
| private SearchResult(string text, IReadOnlyList<Command> commands, CommandError? error, string errorReason) | |||||
| private SearchResult(string text, IReadOnlyList<CommandInfo> commands, CommandError? error, string errorReason) | |||||
| { | { | ||||
| Text = text; | Text = text; | ||||
| Commands = commands; | Commands = commands; | ||||
| @@ -22,7 +22,7 @@ namespace Discord.Commands | |||||
| ErrorReason = errorReason; | ErrorReason = errorReason; | ||||
| } | } | ||||
| public static SearchResult FromSuccess(string text, IReadOnlyList<Command> commands) | |||||
| public static SearchResult FromSuccess(string text, IReadOnlyList<CommandInfo> commands) | |||||
| => new SearchResult(text, commands, null, null); | => new SearchResult(text, commands, null, null); | ||||
| public static SearchResult FromError(CommandError error, string reason) | public static SearchResult FromError(CommandError error, string reason) | ||||
| => new SearchResult(null, null, error, reason); | => new SearchResult(null, null, error, reason); | ||||
| @@ -2,7 +2,6 @@ | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.Linq; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -0,0 +1,9 @@ | |||||
| namespace Discord.Commands | |||||
| { | |||||
| public enum RunMode | |||||
| { | |||||
| Sync, | |||||
| Mixed, | |||||
| Async | |||||
| } | |||||
| } | |||||
| @@ -13,24 +13,22 @@ | |||||
| } | } | ||||
| }, | }, | ||||
| "buildOptions": { | |||||
| "allowUnsafe": true, | |||||
| "warningsAsErrors": false, | |||||
| "xmlDoc": true | |||||
| }, | |||||
| "configurations": { | "configurations": { | ||||
| "Release": { | "Release": { | ||||
| "buildOptions": { | "buildOptions": { | ||||
| "define": [ "RELEASE" ], | "define": [ "RELEASE" ], | ||||
| "nowarn": [ "CS1573", "CS1591" ], | "nowarn": [ "CS1573", "CS1591" ], | ||||
| "optimize": true | |||||
| "optimize": true, | |||||
| "warningsAsErrors": true, | |||||
| "xmlDoc": true | |||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "Discord.Net": "1.0.0-*" | |||||
| "Discord.Net.Core": { | |||||
| "target": "project" | |||||
| } | |||||
| }, | }, | ||||
| "frameworks": { | "frameworks": { | ||||
| @@ -1,6 +1,6 @@ | |||||
| namespace Discord.API | namespace Discord.API | ||||
| { | { | ||||
| internal static class CDN | |||||
| public static class CDN | |||||
| { | { | ||||
| public static string GetApplicationIconUrl(ulong appId, string iconId) | public static string GetApplicationIconUrl(ulong appId, string iconId) | ||||
| => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; | => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; | ||||
| @@ -12,5 +12,7 @@ | |||||
| => splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null; | => splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null; | ||||
| public static string GetChannelIconUrl(ulong channelId, string iconId) | public static string GetChannelIconUrl(ulong channelId, string iconId) | ||||
| => iconId != null ? $"{DiscordConfig.CDNUrl}channel-icons/{channelId}/{iconId}.jpg" : null; | => iconId != null ? $"{DiscordConfig.CDNUrl}channel-icons/{channelId}/{iconId}.jpg" : null; | ||||
| public static string GetEmojiUrl(ulong emojiId) | |||||
| => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.png"; | |||||
| } | } | ||||
| } | } | ||||
| @@ -11,13 +11,14 @@ namespace Discord.API | |||||
| public string[] RPCOrigins { get; set; } | public string[] RPCOrigins { get; set; } | ||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| public string Name { get; set; } | public string Name { get; set; } | ||||
| [JsonProperty("flags"), Int53] | |||||
| public ulong Flags { get; set; } | |||||
| [JsonProperty("owner")] | |||||
| public User Owner { get; set; } | |||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
| [JsonProperty("icon")] | [JsonProperty("icon")] | ||||
| public string Icon { get; set; } | public string Icon { get; set; } | ||||
| [JsonProperty("flags"), Int53] | |||||
| public Optional<ulong> Flags { get; set; } | |||||
| [JsonProperty("owner")] | |||||
| public Optional<User> Owner { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -12,6 +12,8 @@ namespace Discord.API | |||||
| public MessageType Type { get; set; } | public MessageType Type { get; set; } | ||||
| [JsonProperty("channel_id")] | [JsonProperty("channel_id")] | ||||
| public ulong ChannelId { get; set; } | public ulong ChannelId { get; set; } | ||||
| [JsonProperty("webhook_id")] | |||||
| public Optional<ulong> WebhookId { get; set; } | |||||
| [JsonProperty("author")] | [JsonProperty("author")] | ||||
| public Optional<User> Author { get; set; } | public Optional<User> Author { get; set; } | ||||
| [JsonProperty("content")] | [JsonProperty("content")] | ||||
| @@ -25,7 +27,9 @@ namespace Discord.API | |||||
| [JsonProperty("mention_everyone")] | [JsonProperty("mention_everyone")] | ||||
| public Optional<bool> MentionEveryone { get; set; } | public Optional<bool> MentionEveryone { get; set; } | ||||
| [JsonProperty("mentions")] | [JsonProperty("mentions")] | ||||
| public Optional<User[]> Mentions { get; set; } | |||||
| public Optional<ObjectOrId<User>[]> UserMentions { get; set; } | |||||
| [JsonProperty("mention_roles")] | |||||
| public Optional<ulong[]> RoleMentions { get; set; } | |||||
| [JsonProperty("attachments")] | [JsonProperty("attachments")] | ||||
| public Optional<Attachment[]> Attachments { get; set; } | public Optional<Attachment[]> Attachments { get; set; } | ||||
| [JsonProperty("embeds")] | [JsonProperty("embeds")] | ||||
| @@ -5,6 +5,7 @@ namespace Discord.API | |||||
| { | { | ||||
| Friend = 1, | Friend = 1, | ||||
| Blocked = 2, | Blocked = 2, | ||||
| Pending = 4 | |||||
| IncomingPending = 3, | |||||
| OutgoingPending = 4 | |||||
| } | } | ||||
| } | } | ||||
| @@ -13,6 +13,8 @@ namespace Discord.API | |||||
| public uint Color { get; set; } | public uint Color { get; set; } | ||||
| [JsonProperty("hoist")] | [JsonProperty("hoist")] | ||||
| public bool Hoist { get; set; } | public bool Hoist { get; set; } | ||||
| [JsonProperty("mentionable")] | |||||
| public bool Mentionable { get; set; } | |||||
| [JsonProperty("position")] | [JsonProperty("position")] | ||||
| public int Position { get; set; } | public int Position { get; set; } | ||||
| [JsonProperty("permissions"), Int53] | [JsonProperty("permissions"), Int53] | ||||
| @@ -2,7 +2,7 @@ | |||||
| namespace Discord.API | namespace Discord.API | ||||
| { | { | ||||
| internal struct Image | |||||
| public struct Image | |||||
| { | { | ||||
| public Stream Stream { get; } | public Stream Stream { get; } | ||||
| public string Hash { get; } | public string Hash { get; } | ||||
| @@ -0,0 +1,19 @@ | |||||
| namespace Discord.API | |||||
| { | |||||
| public struct ObjectOrId<T> | |||||
| { | |||||
| public ulong Id { get; } | |||||
| public T Object { get; } | |||||
| public ObjectOrId(ulong id) | |||||
| { | |||||
| Id = id; | |||||
| Object = default(T); | |||||
| } | |||||
| public ObjectOrId(T obj) | |||||
| { | |||||
| Id = 0; | |||||
| Object = obj; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class CreateChannelInviteParams | |||||
| { | |||||
| [JsonProperty("max_age")] | |||||
| public Optional<int> MaxAge { get; set; } | |||||
| [JsonProperty("max_uses")] | |||||
| public Optional<int> MaxUses { get; set; } | |||||
| [JsonProperty("temporary")] | |||||
| public Optional<bool> IsTemporary { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,8 +7,11 @@ namespace Discord.API.Rest | |||||
| public class CreateDMChannelParams | public class CreateDMChannelParams | ||||
| { | { | ||||
| [JsonProperty("recipient_id")] | [JsonProperty("recipient_id")] | ||||
| internal ulong _recipientId { get; set; } | |||||
| public ulong RecipientId { set { _recipientId = value; } } | |||||
| public IUser Recipient { set { _recipientId = value.Id; } } | |||||
| public ulong RecipientId { get; } | |||||
| public CreateDMChannelParams(ulong recipientId) | |||||
| { | |||||
| RecipientId = recipientId; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,8 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class CreateGuildBanParams | |||||
| { | |||||
| public Optional<int> DeleteMessageDays { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class CreateGuildChannelParams | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public string Name { get; } | |||||
| [JsonProperty("type")] | |||||
| public ChannelType Type { get; } | |||||
| [JsonProperty("bitrate")] | |||||
| public Optional<int> Bitrate { get; set; } | |||||
| public CreateGuildChannelParams(string name, ChannelType type) | |||||
| { | |||||
| Name = name; | |||||
| Type = type; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -7,9 +7,14 @@ namespace Discord.API.Rest | |||||
| public class CreateGuildIntegrationParams | public class CreateGuildIntegrationParams | ||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { internal get; set; } | |||||
| public ulong Id { get; } | |||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public string Type { internal get; set; } | |||||
| public string Type { get; } | |||||
| public CreateGuildIntegrationParams(ulong id, string type) | |||||
| { | |||||
| Id = id; | |||||
| Type = type; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,5 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.IO; | |||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| { | { | ||||
| @@ -8,13 +7,17 @@ namespace Discord.API.Rest | |||||
| public class CreateGuildParams | public class CreateGuildParams | ||||
| { | { | ||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| public string Name { internal get; set; } | |||||
| public string Name { get; } | |||||
| [JsonProperty("region")] | [JsonProperty("region")] | ||||
| public string Region { internal get; set; } | |||||
| public string RegionId { get; } | |||||
| [JsonProperty("icon")] | [JsonProperty("icon")] | ||||
| internal Optional<Image?> _icon { get; set; } | |||||
| public Stream Icon { set { _icon = value != null ? new Image(value) : (Image?)null; } } | |||||
| public Optional<Image?> Icon { get; set; } | |||||
| public CreateGuildParams(string name, string regionId) | |||||
| { | |||||
| Name = name; | |||||
| RegionId = regionId; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,22 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class CreateMessageParams | |||||
| { | |||||
| [JsonProperty("content")] | |||||
| public string Content { get; } | |||||
| [JsonProperty("nonce")] | |||||
| public Optional<string> Nonce { get; set; } | |||||
| [JsonProperty("tts")] | |||||
| public Optional<bool> IsTTS { get; set; } | |||||
| public CreateMessageParams(string content) | |||||
| { | |||||
| Content = content; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class DeleteMessagesParams | |||||
| { | |||||
| [JsonProperty("messages")] | |||||
| public ulong[] MessageIds { get; } | |||||
| public DeleteMessagesParams(ulong[] messageIds) | |||||
| { | |||||
| MessageIds = messageIds; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,10 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class GetChannelMessagesParams | |||||
| { | |||||
| public Optional<int> Limit { get; set; } | |||||
| public Optional<Direction> RelativeDirection { get; set; } | |||||
| public Optional<ulong> RelativeMessageId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| #pragma warning disable CS1591 | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class GetGuildMembersParams | |||||
| { | |||||
| public Optional<int> Limit { get; set; } | |||||
| public Optional<ulong> AfterUserId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,6 +7,11 @@ namespace Discord.API.Rest | |||||
| public class GuildPruneParams | public class GuildPruneParams | ||||
| { | { | ||||
| [JsonProperty("days")] | [JsonProperty("days")] | ||||
| public int Days { internal get; set; } | |||||
| public int Days { get; } | |||||
| public GuildPruneParams(int days) | |||||
| { | |||||
| Days = days; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,10 +7,17 @@ namespace Discord.API.Rest | |||||
| public class ModifyChannelPermissionsParams | public class ModifyChannelPermissionsParams | ||||
| { | { | ||||
| [JsonProperty("type")] | [JsonProperty("type")] | ||||
| public string Type { internal get; set; } | |||||
| public string Type { get; } | |||||
| [JsonProperty("allow")] | [JsonProperty("allow")] | ||||
| public ulong Allow { internal get; set; } | |||||
| public ulong Allow { get; } | |||||
| [JsonProperty("deny")] | [JsonProperty("deny")] | ||||
| public ulong Deny { internal get; set; } | |||||
| public ulong Deny { get; } | |||||
| public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) | |||||
| { | |||||
| Type = type; | |||||
| Allow = allow; | |||||
| Deny = deny; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,6 +7,11 @@ namespace Discord.API.Rest | |||||
| public class ModifyCurrentUserNickParams | public class ModifyCurrentUserNickParams | ||||
| { | { | ||||
| [JsonProperty("nick")] | [JsonProperty("nick")] | ||||
| public string Nickname { internal get; set; } | |||||
| public string Nickname { get; } | |||||
| public ModifyCurrentUserNickParams(string nickname) | |||||
| { | |||||
| Nickname = nickname; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,6 +1,5 @@ | |||||
| #pragma warning disable CS1591 | #pragma warning disable CS1591 | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.IO; | |||||
| namespace Discord.API.Rest | namespace Discord.API.Rest | ||||
| { | { | ||||
| @@ -8,11 +7,8 @@ namespace Discord.API.Rest | |||||
| public class ModifyCurrentUserParams | public class ModifyCurrentUserParams | ||||
| { | { | ||||
| [JsonProperty("username")] | [JsonProperty("username")] | ||||
| internal Optional<string> _username { get; set; } | |||||
| public string Username { set { _username = value; } } | |||||
| public Optional<string> Username { get; set; } | |||||
| [JsonProperty("avatar")] | [JsonProperty("avatar")] | ||||
| internal Optional<Image> _avatar { get; set; } | |||||
| public Stream Avatar { set { _avatar = new Image(value); } } | |||||
| public Optional<Image> Avatar { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,11 +7,8 @@ namespace Discord.API.Rest | |||||
| public class ModifyGuildChannelParams | public class ModifyGuildChannelParams | ||||
| { | { | ||||
| [JsonProperty("name")] | [JsonProperty("name")] | ||||
| internal Optional<string> _name { get; set; } | |||||
| public string Name { set { _name = value; } } | |||||
| public Optional<string> Name { get; set; } | |||||
| [JsonProperty("position")] | [JsonProperty("position")] | ||||
| internal Optional<int> _position { get; set; } | |||||
| public int Position { set { _position = value; } } | |||||
| public Optional<int> Position { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,9 +7,14 @@ namespace Discord.API.Rest | |||||
| public class ModifyGuildChannelsParams | public class ModifyGuildChannelsParams | ||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { internal get; set; } | |||||
| public ulong Id { get; set; } | |||||
| [JsonProperty("position")] | [JsonProperty("position")] | ||||
| public int Position { internal get; set; } | |||||
| public int Position { get; set; } | |||||
| public ModifyGuildChannelsParams(ulong id, int position) | |||||
| { | |||||
| Id = id; | |||||
| Position = position; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,14 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildEmbedParams | |||||
| { | |||||
| [JsonProperty("enabled")] | |||||
| public Optional<bool> Enabled { get; set; } | |||||
| [JsonProperty("channel")] | |||||
| public Optional<ulong?> ChannelId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildIntegrationParams | |||||
| { | |||||
| [JsonProperty("expire_behavior")] | |||||
| public Optional<int> ExpireBehavior { get; set; } | |||||
| [JsonProperty("expire_grace_period")] | |||||
| public Optional<int> ExpireGracePeriod { get; set; } | |||||
| [JsonProperty("enable_emoticons")] | |||||
| public Optional<bool> EnableEmoticons { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildMemberParams | |||||
| { | |||||
| [JsonProperty("mute")] | |||||
| public Optional<bool> Mute { get; set; } | |||||
| [JsonProperty("deaf")] | |||||
| public Optional<bool> Deaf { get; set; } | |||||
| [JsonProperty("nick")] | |||||
| public Optional<string> Nickname { get; set; } | |||||
| [JsonProperty("roles")] | |||||
| public Optional<ulong[]> RoleIds { get; set; } | |||||
| [JsonProperty("channel_id")] | |||||
| public Optional<ulong> ChannelId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildParams | |||||
| { | |||||
| [JsonProperty("username")] | |||||
| public Optional<string> Username { get; set; } | |||||
| [JsonProperty("name")] | |||||
| public Optional<string> Name { get; set; } | |||||
| [JsonProperty("region")] | |||||
| public Optional<string> RegionId { get; set; } | |||||
| [JsonProperty("verification_level")] | |||||
| public Optional<VerificationLevel> VerificationLevel { get; set; } | |||||
| [JsonProperty("default_message_notifications")] | |||||
| public Optional<DefaultMessageNotifications> DefaultMessageNotifications { get; set; } | |||||
| [JsonProperty("afk_timeout")] | |||||
| public Optional<int> AfkTimeout { get; set; } | |||||
| [JsonProperty("icon")] | |||||
| public Optional<Image?> Icon { get; set; } | |||||
| [JsonProperty("splash")] | |||||
| public Optional<Image?> Splash { get; set; } | |||||
| [JsonProperty("afk_channel_id")] | |||||
| public Optional<ulong?> AfkChannelId { get; set; } | |||||
| [JsonProperty("owner_id")] | |||||
| public Optional<ulong> OwnerId { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Newtonsoft.Json; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| [JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||||
| public class ModifyGuildRoleParams | |||||
| { | |||||
| [JsonProperty("name")] | |||||
| public Optional<string> Name { get; set; } | |||||
| [JsonProperty("permissions")] | |||||
| public Optional<ulong> Permissions { get; set; } | |||||
| [JsonProperty("position")] | |||||
| public Optional<int> Position { get; set; } | |||||
| [JsonProperty("color")] | |||||
| public Optional<uint> Color { get; set; } | |||||
| [JsonProperty("hoist")] | |||||
| public Optional<bool> Hoist { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,6 +7,11 @@ namespace Discord.API.Rest | |||||
| public class ModifyGuildRolesParams : ModifyGuildRoleParams | public class ModifyGuildRolesParams : ModifyGuildRoleParams | ||||
| { | { | ||||
| [JsonProperty("id")] | [JsonProperty("id")] | ||||
| public ulong Id { internal get; set; } | |||||
| public ulong Id { get; } | |||||
| public ModifyGuildRolesParams(ulong id) | |||||
| { | |||||
| Id = id; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,7 +7,6 @@ namespace Discord.API.Rest | |||||
| public class ModifyMessageParams | public class ModifyMessageParams | ||||
| { | { | ||||
| [JsonProperty("content")] | [JsonProperty("content")] | ||||
| internal Optional<string> _content { get; set; } | |||||
| public string Content { set { _content = value; } } | |||||
| public Optional<string> Content { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,11 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using System; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class ModifyPresenceParams | |||||
| { | |||||
| public Optional<UserStatus> Status { get; set; } | |||||
| public Optional<Game> Game { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -7,7 +7,6 @@ namespace Discord.API.Rest | |||||
| public class ModifyTextChannelParams : ModifyGuildChannelParams | public class ModifyTextChannelParams : ModifyGuildChannelParams | ||||
| { | { | ||||
| [JsonProperty("topic")] | [JsonProperty("topic")] | ||||
| internal Optional<string> _topic { get; set; } | |||||
| public string Topic { set { _topic = value; } } | |||||
| public Optional<string> Topic { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,11 +7,8 @@ namespace Discord.API.Rest | |||||
| public class ModifyVoiceChannelParams : ModifyGuildChannelParams | public class ModifyVoiceChannelParams : ModifyGuildChannelParams | ||||
| { | { | ||||
| [JsonProperty("bitrate")] | [JsonProperty("bitrate")] | ||||
| internal Optional<int> _bitrate { get; set; } | |||||
| public int Bitrate { set { _bitrate = value; } } | |||||
| public Optional<int> Bitrate { get; set; } | |||||
| [JsonProperty("user_limit")] | [JsonProperty("user_limit")] | ||||
| internal Optional<int> _userLimit { get; set; } | |||||
| public int UserLimit { set { _userLimit = value; } } | |||||
| public Optional<int> UserLimit { get; set; } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,35 @@ | |||||
| #pragma warning disable CS1591 | |||||
| using Discord.Net.Rest; | |||||
| using System.Collections.Generic; | |||||
| using System.IO; | |||||
| namespace Discord.API.Rest | |||||
| { | |||||
| public class UploadFileParams | |||||
| { | |||||
| public Stream File { get; } | |||||
| public Optional<string> Filename { get; set; } | |||||
| public Optional<string> Content { get; set; } | |||||
| public Optional<string> Nonce { get; set; } | |||||
| public Optional<bool> IsTTS { get; set; } | |||||
| public UploadFileParams(Stream file) | |||||
| { | |||||
| File = file; | |||||
| } | |||||
| public IReadOnlyDictionary<string, object> ToDictionary() | |||||
| { | |||||
| var d = new Dictionary<string, object>(); | |||||
| d["file"] = new MultipartFile(File, Filename.GetValueOrDefault("unknown.dat")); | |||||
| if (Content.IsSpecified) | |||||
| d["content"] = Content.Value; | |||||
| if (IsTTS.IsSpecified) | |||||
| d["tts"] = IsTTS.Value.ToString(); | |||||
| if (Nonce.IsSpecified) | |||||
| d["nonce"] = Nonce.Value; | |||||
| return d; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| using System.Runtime.CompilerServices; | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Rest")] | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Rpc")] | |||||
| [assembly: InternalsVisibleTo("Discord.Net.WebSocket")] | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Commands")] | |||||
| [assembly: InternalsVisibleTo("Discord.Net.Tests")] | |||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | using System; | ||||
| using System.IO; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Audio | namespace Discord.Audio | ||||
| @@ -8,8 +9,7 @@ namespace Discord.Audio | |||||
| event Func<Task> Connected; | event Func<Task> Connected; | ||||
| event Func<Exception, Task> Disconnected; | event Func<Exception, Task> Disconnected; | ||||
| event Func<int, int, Task> LatencyUpdated; | event Func<int, int, Task> LatencyUpdated; | ||||
| DiscordVoiceAPIClient ApiClient { get; } | |||||
| /// <summary> Gets the current connection state of this client. </summary> | /// <summary> Gets the current connection state of this client. </summary> | ||||
| ConnectionState ConnectionState { get; } | ConnectionState ConnectionState { get; } | ||||
| /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | /// <summary> Gets the estimated round-trip latency, in milliseconds, to the gateway server. </summary> | ||||
| @@ -17,7 +17,7 @@ namespace Discord.Audio | |||||
| Task DisconnectAsync(); | Task DisconnectAsync(); | ||||
| RTPWriteStream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||||
| OpusEncodeStream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | |||||
| Stream CreateOpusStream(int samplesPerFrame, int bufferSize = 4000); | |||||
| Stream CreatePCMStream(int samplesPerFrame, int? bitrate = null, OpusApplication application = OpusApplication.MusicOrMixed, int bufferSize = 4000); | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,19 @@ | |||||
| <?xml version="1.0" encoding="utf-8"?> | |||||
| <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||||
| <PropertyGroup> | |||||
| <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> | |||||
| <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath> | |||||
| </PropertyGroup> | |||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" /> | |||||
| <PropertyGroup Label="Globals"> | |||||
| <ProjectGuid>91e9e7bd-75c9-4e98-84aa-2c271922e5c2</ProjectGuid> | |||||
| <RootNamespace>Discord</RootNamespace> | |||||
| <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath> | |||||
| <OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath> | |||||
| <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> | |||||
| </PropertyGroup> | |||||
| <PropertyGroup> | |||||
| <SchemaVersion>2.0</SchemaVersion> | |||||
| </PropertyGroup> | |||||
| <Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" /> | |||||
| </Project> | |||||