| @@ -19,6 +19,10 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Utils", "src\Di | |||
| 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 | |||
| Global | |||
| GlobalSection(SharedMSBuildProjectFiles) = preSolution | |||
| src\Discord.Net.Utils\Discord.Net.Utils.projitems*{2b75119c-9893-4aaa-8d38-6176eeb09060}*SharedItemsImports = 13 | |||
| @@ -40,11 +44,21 @@ Global | |||
| {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 | |||
| EndGlobalSection | |||
| GlobalSection(SolutionProperties) = preSolution | |||
| HideSolutionNode = FALSE | |||
| 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 | |||
| @@ -0,0 +1,15 @@ | |||
| # Contributing to Docs | |||
| I don't really have any strict conditions for writing documentation, but just keep these few guidelines in mind: | |||
| * Keep code samples in the `guides/samples` folder | |||
| * When referencing an object in the API, link to it's page in the API documentation. | |||
| * Documentation should be written in clear and proper English* | |||
| \* If anyone is interested in translating documentation into other languages, please open an issue or contact me on Discord (`foxbot#0282`). | |||
| ### Compiling | |||
| Documentation is compiled into a static site using [DocFx](dotnet.github.io/docfx/). You **must** install a version of DocFx that supports .NET Core. The latest build of that is [2.1.0-cli-alpha](https://github.com/dotnet/docfx/releases/tag/v2.1.0-cli-alpha). | |||
| After making changes, compile your changes into the static site with `docfx`. You can also view your changes live with `docfx --serve`. | |||
| @@ -6,9 +6,9 @@ using Discord.WebSocket; | |||
| public class Info | |||
| { | |||
| // ~say hello -> hello | |||
| [Command("say"), Description("Echos a message.")] | |||
| [Command("say"), Summary("Echos a message.")] | |||
| public async Task Say(IUserMessage msg, | |||
| [Unparsed, Description("The text to echo")] string echo) | |||
| [Unparsed, Summary("The text to echo")] string echo) | |||
| { | |||
| await msg.Channel.SendMessageAsync(echo); | |||
| } | |||
| @@ -19,9 +19,9 @@ public class Info | |||
| public class Sample | |||
| { | |||
| // ~sample square 20 -> | |||
| [Command("square"), Description("Squares a number.")] | |||
| [Command("square"), Summary("Squares a number.")] | |||
| public async Task Square(IUserMessage msg, | |||
| [Description("The number to square.")] int num) | |||
| [Summary("The number to square.")] int num) | |||
| { | |||
| await msg.Channel.SendMessageAsync($"{num}^2 = {Math.Pow(num, 2)}"); | |||
| } | |||
| @@ -32,10 +32,10 @@ public class Sample | |||
| // ~sample userinfo Khionu --> Khionu#8708 | |||
| // ~sample userinfo 96642168176807936 --> Khionu#8708 | |||
| // ~sample whois 96642168176807936 --> Khionu#8708 | |||
| [Command("userinfo"), Description("Returns info about the current user, or the user parameter, if one passed.")] | |||
| [Command("userinfo"), Summary("Returns info about the current user, or the user parameter, if one passed.")] | |||
| [Alias("user", "whois")] | |||
| public async Task UserInfo(IUserMessage msg, | |||
| [Description("The (optional) user to get info for")] IUser user = null) | |||
| [Summary("The (optional) user to get info for")] IUser user = null) | |||
| { | |||
| var userInfo = user ?? await Program.Client.GetCurrentUserAsync(); | |||
| await msg.Channel.SendMessageAsync($"{userInfo.Username}#{userInfo.Discriminator}"); | |||
| @@ -1,6 +1,6 @@ | |||
| # Migrating from 0.9 | |||
| **1.0.0 is the biggest breaking change the library has gone through, do to massive | |||
| **1.0.0 is the biggest breaking change the library has gone through, due to massive | |||
| changes in the design of the library.** | |||
| >A medium to advanced understanding is recommended when working with this library. | |||
| @@ -78,4 +78,4 @@ provide java-esque, synchronus `GetXXX` methods to replace the asynchronus metho | |||
| This functionality may be changed at a later date, we are currently reviewing this implementation and | |||
| alternative methods. | |||
| For your reference, you may want to look through the extension classes located in @Discord.WebSocket | |||
| For your reference, you may want to look through the extension classes located in @Discord.WebSocket | |||
| @@ -0,0 +1,18 @@ | |||
| using System; | |||
| namespace Discord.Commands | |||
| { | |||
| /// <summary> Sets priority of commands </summary> | |||
| [AttributeUsage(AttributeTargets.Method)] | |||
| public class PriorityAttribute : Attribute | |||
| { | |||
| /// <summary> The priority which has been set for the command </summary> | |||
| public int Priority { get; } | |||
| /// <summary> Creates a new <see cref="PriorityAttribute"/> with the given priority. </summary> | |||
| public PriorityAttribute(int priority) | |||
| { | |||
| Priority = priority; | |||
| } | |||
| } | |||
| } | |||
| @@ -2,13 +2,13 @@ | |||
| namespace Discord.Commands | |||
| { | |||
| // Full summary of method | |||
| // Extension of the Cosmetic Summary, for Groups, Commands, and Parameters | |||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] | |||
| public class DescriptionAttribute : Attribute | |||
| public class RemarksAttribute : Attribute | |||
| { | |||
| public string Text { get; } | |||
| public DescriptionAttribute(string text) | |||
| public RemarksAttribute(string text) | |||
| { | |||
| Text = text; | |||
| } | |||
| @@ -2,7 +2,7 @@ | |||
| namespace Discord.Commands | |||
| { | |||
| // Brief summary of method/module/parameter | |||
| // Cosmetic Summary, for Groups and Commands | |||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter)] | |||
| public class SummaryAttribute : Attribute | |||
| { | |||
| @@ -21,9 +21,10 @@ namespace Discord.Commands | |||
| public MethodInfo Source { get; } | |||
| public Module Module { get; } | |||
| public string Name { get; } | |||
| public string Description { get; } | |||
| public string Summary { get; } | |||
| public string Remarks { get; } | |||
| public string Text { get; } | |||
| public int Priority { get; } | |||
| public bool HasVarArgs { get; } | |||
| public IReadOnlyList<string> Aliases { get; } | |||
| public IReadOnlyList<CommandParameter> Parameters { get; } | |||
| @@ -38,7 +39,15 @@ namespace Discord.Commands | |||
| _instance = instance; | |||
| Name = source.Name; | |||
| Text = groupPrefix + attribute.Text; | |||
| if (attribute.Text == null) | |||
| Text = groupPrefix; | |||
| if (groupPrefix != "") | |||
| groupPrefix += " "; | |||
| if (attribute.Text != null) | |||
| Text = groupPrefix + attribute.Text; | |||
| var aliasesBuilder = ImmutableArray.CreateBuilder<string>(); | |||
| @@ -54,14 +63,17 @@ namespace Discord.Commands | |||
| if (nameAttr != null) | |||
| Name = nameAttr.Text; | |||
| var description = source.GetCustomAttribute<DescriptionAttribute>(); | |||
| if (description != null) | |||
| Description = description.Text; | |||
| var summary = source.GetCustomAttribute<SummaryAttribute>(); | |||
| if (summary != null) | |||
| Summary = summary.Text; | |||
| var remarksAttr = source.GetCustomAttribute<RemarksAttribute>(); | |||
| if (remarksAttr != null) | |||
| Remarks = remarksAttr.Text; | |||
| var priorityAttr = source.GetCustomAttribute<PriorityAttribute>(); | |||
| Priority = priorityAttr?.Priority ?? 0; | |||
| Parameters = BuildParameters(source); | |||
| HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false; | |||
| Preconditions = BuildPreconditions(source); | |||
| @@ -150,6 +150,8 @@ namespace Discord.Commands | |||
| for (int i = argList.Count; i < command.Parameters.Count; i++) | |||
| { | |||
| var param = command.Parameters[i]; | |||
| if (param.IsMultiple) | |||
| continue; | |||
| if (!param.IsOptional) | |||
| return ParseResult.FromError(CommandError.BadArgCount, "The input text has too few parameters."); | |||
| argList.Add(TypeReaderResult.FromSuccess(param.DefaultValue)); | |||
| @@ -180,7 +180,7 @@ namespace Discord.Commands | |||
| public SearchResult Search(IUserMessage message, string input) | |||
| { | |||
| string lowerInput = input.ToLowerInvariant(); | |||
| var matches = _map.GetCommands(input).ToImmutableArray(); | |||
| var matches = _map.GetCommands(input).OrderByDescending(x => x.Priority).ToImmutableArray(); | |||
| if (matches.Length > 0) | |||
| return SearchResult.FromSuccess(input, matches); | |||
| @@ -7,6 +7,7 @@ namespace Discord.Commands | |||
| internal class CommandMap | |||
| { | |||
| static readonly char[] _whitespaceChars = new char[] { ' ', '\r', '\n' }; | |||
| private readonly object _lockObj = new object(); | |||
| private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | |||
| @@ -23,11 +24,11 @@ namespace Discord.Commands | |||
| string name; | |||
| if (nextSpace == -1) | |||
| name = command.Text; | |||
| name = text; | |||
| else | |||
| name = command.Text.Substring(0, nextSpace); | |||
| name = text.Substring(0, nextSpace); | |||
| lock (this) | |||
| lock (_lockObj) | |||
| { | |||
| var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(x)); | |||
| nextNode.AddCommand(nextSpace == -1 ? "" : text, nextSpace + 1, command); | |||
| @@ -42,11 +43,11 @@ namespace Discord.Commands | |||
| string name; | |||
| if (nextSpace == -1) | |||
| name = command.Text; | |||
| name = text; | |||
| else | |||
| name = command.Text.Substring(0, nextSpace); | |||
| name = text.Substring(0, nextSpace); | |||
| lock (this) | |||
| lock (_lockObj) | |||
| { | |||
| CommandMapNode nextNode; | |||
| if (_nodes.TryGetValue(name, out nextNode)) | |||
| @@ -69,7 +70,7 @@ namespace Discord.Commands | |||
| else | |||
| name = text.Substring(0, nextSpace); | |||
| lock (this) | |||
| lock (_lockObj) | |||
| { | |||
| CommandMapNode nextNode; | |||
| if (_nodes.TryGetValue(name, out nextNode)) | |||
| @@ -8,6 +8,7 @@ namespace Discord.Commands | |||
| { | |||
| private readonly ConcurrentDictionary<string, CommandMapNode> _nodes; | |||
| private readonly string _name; | |||
| private readonly object _lockObj = new object(); | |||
| private ImmutableArray<Command> _commands; | |||
| public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; | |||
| @@ -24,7 +25,7 @@ namespace Discord.Commands | |||
| int nextSpace = text.IndexOf(' ', index); | |||
| string name; | |||
| lock (this) | |||
| lock (_lockObj) | |||
| { | |||
| if (text == "") | |||
| _commands = _commands.Add(command); | |||
| @@ -45,7 +46,7 @@ namespace Discord.Commands | |||
| int nextSpace = text.IndexOf(' ', index); | |||
| string name; | |||
| lock (this) | |||
| lock (_lockObj) | |||
| { | |||
| if (text == "") | |||
| _commands = _commands.Remove(command); | |||
| @@ -13,7 +13,7 @@ namespace Discord.Commands | |||
| public string Name { get; } | |||
| public string Prefix { get; } | |||
| public string Summary { get; } | |||
| public string Description { get; } | |||
| public string Remarks { get; } | |||
| public IEnumerable<Command> Commands { get; } | |||
| internal object Instance { get; } | |||
| @@ -35,9 +35,9 @@ namespace Discord.Commands | |||
| if (summaryAttr != null) | |||
| Summary = summaryAttr.Text; | |||
| var descriptionAttr = source.GetCustomAttribute<DescriptionAttribute>(); | |||
| if (descriptionAttr != null) | |||
| Description = descriptionAttr.Text; | |||
| var remarksAttr = source.GetCustomAttribute<RemarksAttribute>(); | |||
| if (remarksAttr != null) | |||
| Remarks = remarksAttr.Text; | |||
| List<Command> commands = new List<Command>(); | |||
| SearchClass(source, instance, commands, Prefix, dependencyMap); | |||
| @@ -48,8 +48,6 @@ namespace Discord.Commands | |||
| private void SearchClass(TypeInfo parentType, object instance, List<Command> commands, string groupPrefix, IDependencyMap dependencyMap) | |||
| { | |||
| if (groupPrefix != "") | |||
| groupPrefix += " "; | |||
| foreach (var method in parentType.DeclaredMethods) | |||
| { | |||
| var cmdAttr = method.GetCustomAttribute<CommandAttribute>(); | |||
| @@ -62,10 +60,12 @@ namespace Discord.Commands | |||
| if (groupAttrib != null) | |||
| { | |||
| string nextGroupPrefix; | |||
| if (groupAttrib.Prefix != null) | |||
| nextGroupPrefix = groupPrefix + groupAttrib.Prefix ?? type.Name; | |||
| if (groupPrefix != "") | |||
| nextGroupPrefix = groupPrefix + " " + (groupAttrib.Prefix ?? type.Name.ToLowerInvariant()); | |||
| else | |||
| nextGroupPrefix = groupPrefix; | |||
| nextGroupPrefix = groupAttrib.Prefix ?? type.Name.ToLowerInvariant(); | |||
| SearchClass(type, ReflectionUtils.CreateObject(type, Service, dependencyMap), commands, nextGroupPrefix, dependencyMap); | |||
| } | |||
| } | |||
| @@ -2,6 +2,9 @@ | |||
| { | |||
| public static class Format | |||
| { | |||
| // Characters which need escaping | |||
| private static string[] SensitiveCharacters = { "*", "_", "~", "`", "\\" }; | |||
| /// <summary> Returns a markdown-formatted string with bold formatting. </summary> | |||
| public static string Bold(string text) => $"**{text}**"; | |||
| /// <summary> Returns a markdown-formatted string with italics formatting. </summary> | |||
| @@ -19,5 +22,15 @@ | |||
| else | |||
| return $"`{text}`"; | |||
| } | |||
| /// <summary> Sanitizes the string, safely escaping any Markdown sequences. </summary> | |||
| public static string Sanitize(string text) | |||
| { | |||
| foreach (string unsafeChar in SensitiveCharacters) | |||
| { | |||
| text = text.Replace(unsafeChar, $"\\{unsafeChar}"); | |||
| } | |||
| return text; | |||
| } | |||
| } | |||
| } | |||
| @@ -38,7 +38,7 @@ namespace Discord.WebSocket | |||
| private int _nextAudioId; | |||
| private bool _canReconnect; | |||
| /// <summary> Gets the shard if of this client. </summary> | |||
| /// <summary> Gets the shard of of this client. </summary> | |||
| public int ShardId { get; } | |||
| /// <summary> Gets the current connection state of this client. </summary> | |||
| public ConnectionState ConnectionState { get; private set; } | |||
| @@ -793,6 +793,7 @@ namespace Discord.WebSocket | |||
| if (guild != null) | |||
| { | |||
| guild.AddChannel(data, DataStore); | |||
| channel = guild.AddChannel(data, DataStore); | |||
| if (!guild.IsSynced) | |||
| { | |||
| @@ -1263,6 +1264,12 @@ namespace Discord.WebSocket | |||
| } | |||
| if (after != null) | |||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create(before), after).ConfigureAwait(false); | |||
| { | |||
| if (before != null) | |||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create<IMessage>(before), after).ConfigureAwait(false); | |||
| else | |||
| await _messageUpdatedEvent.InvokeAsync(Optional.Create<IMessage>(), after).ConfigureAwait(false); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| @@ -148,11 +148,12 @@ namespace Discord.WebSocket | |||
| public override Task<IGuildChannel> GetChannelAsync(ulong id) => Task.FromResult<IGuildChannel>(GetChannel(id)); | |||
| public override Task<IReadOnlyCollection<IGuildChannel>> GetChannelsAsync() => Task.FromResult<IReadOnlyCollection<IGuildChannel>>(Channels); | |||
| public void AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null) | |||
| public ISocketGuildChannel AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet<ulong> channels = null) | |||
| { | |||
| var channel = ToChannel(model); | |||
| (channels ?? _channels).TryAdd(model.Id); | |||
| dataStore.AddChannel(channel); | |||
| return channel; | |||
| } | |||
| public ISocketGuildChannel GetChannel(ulong id) | |||
| { | |||
| @@ -8,6 +8,7 @@ namespace Discord.WebSocket | |||
| internal class SocketGlobalUser : User, ISocketUser | |||
| { | |||
| internal override bool IsAttached => true; | |||
| private readonly object _lockObj = new object(); | |||
| private ushort _references; | |||
| @@ -25,13 +26,13 @@ namespace Discord.WebSocket | |||
| { | |||
| checked | |||
| { | |||
| lock (this) | |||
| lock (_lockObj) | |||
| _references++; | |||
| } | |||
| } | |||
| public void RemoveRef(DiscordSocketClient discord) | |||
| { | |||
| lock (this) | |||
| lock (_lockObj) | |||
| { | |||
| if (--_references == 0) | |||
| discord.RemoveUser(Id); | |||
| @@ -40,16 +41,16 @@ namespace Discord.WebSocket | |||
| public override void Update(Model model) | |||
| { | |||
| lock (this) | |||
| lock (_lockObj) | |||
| base.Update(model, source); | |||
| } | |||
| public void Update(PresenceModel model) | |||
| { | |||
| //Race conditions are okay here. Multiple shards racing already cant guarantee presence in order. | |||
| //lock (this) | |||
| //lock (_lockObj) | |||
| //{ | |||
| var game = model.Game != null ? new Game(model.Game) : null; | |||
| var game = model.Game != null ? new Game(model.Game) : null; | |||
| Presence = new Presence(game, model.Status); | |||
| //} | |||
| } | |||
| @@ -1,11 +1,10 @@ | |||
| { | |||
| "version": "1.0.0-*", | |||
| "version": "1.0.0-beta2-*", | |||
| "buildOptions": { | |||
| "compile": { | |||
| "include": [ "../Discord.Net.Entities/**.cs", "../Discord.Net.Utils/**.cs" ] | |||
| }, | |||
| "define": [ "WEBSOCKET" ] | |||
| "include": [ "../Discord.Net.Utils/**.cs" ] | |||
| } | |||
| }, | |||
| "dependencies": { | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "version": "1.0.0-beta2-*", | |||
| "description": "An aynchronous API wrapper for Discord using .NET. This package includes all of the optional Discord.Net components", | |||
| { | |||
| "version": "1.0.0-beta-*", | |||
| "description": "An unofficial .Net API wrapper for the Discord service.", | |||
| "authors": [ "RogueException" ], | |||
| "packOptions": { | |||
| @@ -13,19 +13,38 @@ | |||
| } | |||
| }, | |||
| "dependencies": { | |||
| "Discord.Net.Rest": { | |||
| "target": "project" | |||
| "buildOptions": { | |||
| "allowUnsafe": true, | |||
| "warningsAsErrors": false, | |||
| "xmlDoc": true | |||
| }, | |||
| "configurations": { | |||
| "Release": { | |||
| "buildOptions": { | |||
| "define": [ "RELEASE" ], | |||
| "nowarn": [ "CS1573", "CS1591" ], | |||
| "optimize": true | |||
| } | |||
| } | |||
| //"Discord.Net.WebSocket": { | |||
| // "target": "project" | |||
| //}, | |||
| //"Discord.Net.Rpc": { | |||
| // "target": "project" | |||
| //}, | |||
| //"Discord.Net.Commands": { | |||
| // "target": "project" | |||
| //} | |||
| }, | |||
| "dependencies": { | |||
| "Microsoft.Win32.Primitives": "4.0.1", | |||
| "Newtonsoft.Json": "8.0.3", | |||
| "System.Collections.Concurrent": "4.0.12", | |||
| "System.Collections.Immutable": "1.2.0", | |||
| "System.IO.Compression": "4.1.0", | |||
| "System.IO.FileSystem": "4.0.1", | |||
| "System.Net.Http": "4.1.0", | |||
| "System.Net.NameResolution": "4.0.0", | |||
| "System.Net.Sockets": "4.1.0", | |||
| "System.Net.WebSockets.Client": "4.0.0", | |||
| "System.Reflection.Extensions": "4.0.1", | |||
| "System.Runtime.InteropServices": "4.1.0", | |||
| "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", | |||
| "System.Runtime.Serialization.Primitives": "4.1.1", | |||
| "System.Text.RegularExpressions": "4.1.0" | |||
| }, | |||
| "frameworks": { | |||