@@ -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": { | |||