diff --git a/Discord.Net.sln b/Discord.Net.sln index 4ce5a23fc..a24b833e3 100644 --- a/Discord.Net.sln +++ b/Discord.Net.sln @@ -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 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 000000000..468ff7bd4 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -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`. \ No newline at end of file diff --git a/docs/guides/samples/module.cs b/docs/guides/samples/module.cs index 9de35aa6e..282a322c9 100644 --- a/docs/guides/samples/module.cs +++ b/docs/guides/samples/module.cs @@ -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}"); diff --git a/docs/migrating.md b/docs/migrating.md index f73d5e8c0..c69b8a59c 100644 --- a/docs/migrating.md +++ b/docs/migrating.md @@ -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 \ No newline at end of file +For your reference, you may want to look through the extension classes located in @Discord.WebSocket diff --git a/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs b/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs new file mode 100644 index 000000000..5120bb7d1 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord.Commands +{ + /// Sets priority of commands + [AttributeUsage(AttributeTargets.Method)] + public class PriorityAttribute : Attribute + { + /// The priority which has been set for the command + public int Priority { get; } + + /// Creates a new with the given priority. + public PriorityAttribute(int priority) + { + Priority = priority; + } + } +} diff --git a/src/Discord.Net.Commands/Attributes/DescriptionAttribute.cs b/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs similarity index 55% rename from src/Discord.Net.Commands/Attributes/DescriptionAttribute.cs rename to src/Discord.Net.Commands/Attributes/RemarksAttribute.cs index 9940c40f3..44db18a79 100644 --- a/src/Discord.Net.Commands/Attributes/DescriptionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs @@ -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; } diff --git a/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs b/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs index b7a4e3d43..46d52f3d9 100644 --- a/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs @@ -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 { diff --git a/src/Discord.Net.Commands/Command.cs b/src/Discord.Net.Commands/Command.cs index 61eff4877..b7b7da401 100644 --- a/src/Discord.Net.Commands/Command.cs +++ b/src/Discord.Net.Commands/Command.cs @@ -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 Aliases { get; } public IReadOnlyList 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(); @@ -54,14 +63,17 @@ namespace Discord.Commands if (nameAttr != null) Name = nameAttr.Text; - var description = source.GetCustomAttribute(); - if (description != null) - Description = description.Text; - var summary = source.GetCustomAttribute(); if (summary != null) Summary = summary.Text; + var remarksAttr = source.GetCustomAttribute(); + if (remarksAttr != null) + Remarks = remarksAttr.Text; + + var priorityAttr = source.GetCustomAttribute(); + Priority = priorityAttr?.Priority ?? 0; + Parameters = BuildParameters(source); HasVarArgs = Parameters.Count > 0 ? Parameters[Parameters.Count - 1].IsMultiple : false; Preconditions = BuildPreconditions(source); diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 7338f6995..77e19109a 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -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)); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 814277b04..344950e68 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -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); diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index f75a0f12f..a5a1f8bc4 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -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 _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)) diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 1ce0b4724..5ef42544e 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -8,6 +8,7 @@ namespace Discord.Commands { private readonly ConcurrentDictionary _nodes; private readonly string _name; + private readonly object _lockObj = new object(); private ImmutableArray _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); diff --git a/src/Discord.Net.Commands/Module.cs b/src/Discord.Net.Commands/Module.cs index 0624e6ef3..9d61ca522 100644 --- a/src/Discord.Net.Commands/Module.cs +++ b/src/Discord.Net.Commands/Module.cs @@ -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 Commands { get; } internal object Instance { get; } @@ -35,9 +35,9 @@ namespace Discord.Commands if (summaryAttr != null) Summary = summaryAttr.Text; - var descriptionAttr = source.GetCustomAttribute(); - if (descriptionAttr != null) - Description = descriptionAttr.Text; + var remarksAttr = source.GetCustomAttribute(); + if (remarksAttr != null) + Remarks = remarksAttr.Text; List commands = new List(); SearchClass(source, instance, commands, Prefix, dependencyMap); @@ -48,8 +48,6 @@ namespace Discord.Commands private void SearchClass(TypeInfo parentType, object instance, List commands, string groupPrefix, IDependencyMap dependencyMap) { - if (groupPrefix != "") - groupPrefix += " "; foreach (var method in parentType.DeclaredMethods) { var cmdAttr = method.GetCustomAttribute(); @@ -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); } } diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index 8b1d06bf8..d31f0ad0a 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -2,6 +2,9 @@ { public static class Format { + // Characters which need escaping + private static string[] SensitiveCharacters = { "*", "_", "~", "`", "\\" }; + /// Returns a markdown-formatted string with bold formatting. public static string Bold(string text) => $"**{text}**"; /// Returns a markdown-formatted string with italics formatting. @@ -19,5 +22,15 @@ else return $"`{text}`"; } + + /// Sanitizes the string, safely escaping any Markdown sequences. + public static string Sanitize(string text) + { + foreach (string unsafeChar in SensitiveCharacters) + { + text = text.Replace(unsafeChar, $"\\{unsafeChar}"); + } + return text; + } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index c90e9d7c6..00b53843d 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -38,7 +38,7 @@ namespace Discord.WebSocket private int _nextAudioId; private bool _canReconnect; - /// Gets the shard if of this client. + /// Gets the shard of of this client. public int ShardId { get; } /// Gets the current connection state of this client. 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(before), after).ConfigureAwait(false); + else + await _messageUpdatedEvent.InvokeAsync(Optional.Create(), after).ConfigureAwait(false); + } } else { diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 7ddab5ad6..1870a703c 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -148,11 +148,12 @@ namespace Discord.WebSocket public override Task GetChannelAsync(ulong id) => Task.FromResult(GetChannel(id)); public override Task> GetChannelsAsync() => Task.FromResult>(Channels); - public void AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet channels = null) + public ISocketGuildChannel AddChannel(ChannelModel model, DataStore dataStore, ConcurrentHashSet channels = null) { var channel = ToChannel(model); (channels ?? _channels).TryAdd(model.Id); dataStore.AddChannel(channel); + return channel; } public ISocketGuildChannel GetChannel(ulong id) { diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 53180ebfc..f08a2656e 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -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); //} } diff --git a/src/Discord.Net.WebSocket/project.json b/src/Discord.Net.WebSocket/project.json index c52db6ba9..9a7de9a07 100644 --- a/src/Discord.Net.WebSocket/project.json +++ b/src/Discord.Net.WebSocket/project.json @@ -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": { diff --git a/src/Discord.Net/project.json b/src/Discord.Net/project.json index 8185a792d..16fdb8aef 100644 --- a/src/Discord.Net/project.json +++ b/src/Discord.Net/project.json @@ -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": {