Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/dev' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
acid-chicken committed Apr 10, 2018
2 parents 3be8e40 + b8b59d9 commit f5bb99c
Show file tree
Hide file tree
Showing 112 changed files with 2,604 additions and 550 deletions.
17 changes: 16 additions & 1 deletion Discord.Net.sln
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2009
VisualStudioVersion = 15.0.27130.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Core", "src\Discord.Net.Core\Discord.Net.Core.csproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}"
EndProject
Expand All @@ -22,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Tests", "test\D
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Webhook", "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj", "{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Analyzers", "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj", "{BBA8E7FB-C834-40DC-822F-B112CB7F0140}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -116,6 +118,18 @@ Global
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x64.Build.0 = Release|Any CPU
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.ActiveCfg = Release|Any CPU
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}.Release|x86.Build.0 = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Debug|x64.ActiveCfg = Debug|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Debug|x64.Build.0 = Debug|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Debug|x86.ActiveCfg = Debug|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Debug|x86.Build.0 = Debug|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|Any CPU.Build.0 = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x64.ActiveCfg = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x64.Build.0 = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.ActiveCfg = Release|Any CPU
{BBA8E7FB-C834-40DC-822F-B112CB7F0140}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -126,6 +140,7 @@ Global
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {288C363D-A636-4EAE-9AC1-4698B641B26E}
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {B0657AAE-DCC5-4FBF-8E5D-1FB578CF3012}
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
{BBA8E7FB-C834-40DC-822F-B112CB7F0140} = {CC3D4B1C-9DE0-448B-8AE7-F3F1F3EC5C3A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495}
Expand Down
2 changes: 1 addition & 1 deletion Discord.Net.targets
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VersionPrefix>2.0.0</VersionPrefix>
<VersionSuffix>beta</VersionSuffix>
<VersionSuffix>beta2</VersionSuffix>
<Authors>RogueException</Authors>
<PackageTags>discord;discordapp</PackageTags>
<PackageProjectUrl>https://github.com/RogueException/Discord.Net</PackageProjectUrl>
Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ after_build:
- ps: dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
- ps: dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
- ps: dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
- ps: dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" -c "Release" -o "../../artifacts" --no-build /p:BuildNumber="$Env:BUILD" /p:IsTagBuild="$Env:APPVEYOR_REPO_TAG"
- ps: >-
if ($Env:APPVEYOR_REPO_TAG -eq "true") {
nuget pack src\Discord.Net\Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix=""
Expand Down
16 changes: 16 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Instructions for Building Documentation

The documentation for the Discord.NET library uses [DocFX][docfx-main]. [Instructions for installing this tool can be found here.][docfx-installing]

1. Navigate to the root of the repository.
2. (Optional) If you intend to target a specific version, ensure that you
have the correct version checked out.
3. Build the library. Run `dotnet build` in the root of this repository.
Ensure that the build passes without errors.
4. Build the docs using `docfx .\docs\docfx.json`. Add the `--serve` parameter
to preview the site locally. Some elements of the page may appear incorrect
when not hosted by a server.
- Remarks: According to the docfx website, this tool does work on Linux under mono.

[docfx-main]: https://dotnet.github.io/docfx/
[docfx-installing]: https://dotnet.github.io/docfx/tutorial/docfx_getting_started.html
4 changes: 2 additions & 2 deletions docs/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@
"default"
],
"globalMetadata": {
"_appFooter": "Discord.Net (c) 2015-2017"
"_appFooter": "Discord.Net (c) 2015-2018 2.0.0-beta"
},
"noLangKeyword": false
}
}
}
4 changes: 2 additions & 2 deletions docs/guides/commands/samples/module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ public class Info : ModuleBase<SocketCommandContext>
{
// ~say hello -> hello
[Command("say")]
[Summary("Echos a message.")]
[Summary("Echoes a message.")]
public async Task SayAsync([Remainder] [Summary("The text to echo")] string echo)
{
// ReplyAsync is a method on ModuleBase
Expand Down Expand Up @@ -38,4 +38,4 @@ public async Task UserInfoAsync([Summary("The (optional) user to get info for")]
var userInfo = user ?? Context.Client.CurrentUser;
await ReplyAsync($"{userInfo.Username}#{userInfo.Discriminator}");
}
}
}
68 changes: 45 additions & 23 deletions docs/guides/getting_started/samples/intro/structure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ static void Main(string[] args)

private readonly DiscordSocketClient _client;

// Keep the CommandService and IServiceCollection around for use with commands.
// Keep the CommandService and DI container around for use with commands.
// These two types require you install the Discord.Net.Commands package.
private readonly IServiceCollection _map = new ServiceCollection();
private readonly CommandService _commands = new CommandService();
private readonly CommandService _commands;
private readonly IServiceProvider _services;

private Program()
{
Expand All @@ -41,14 +41,45 @@ private Program()
// add the `using` at the top, and uncomment this line:
//WebSocketProvider = WS4NetProvider.Instance
});

_commands = new CommandService(new CommandServiceConfig
{
// Again, log level:
LogLevel = LogSeverity.Info,

// There's a few more properties you can set,
// for example, case-insensitive commands.
CaseSensitiveCommands = false,
});

// Subscribe the logging handler to both the client and the CommandService.
_client.Log += Logger;
_commands.Log += Logger;
_client.Log += Log;
_commands.Log += Log;

// Setup your DI container.
_services = ConfigureServices(),

}

// If any services require the client, or the CommandService, or something else you keep on hand,
// pass them as parameters into this method as needed.
// If this method is getting pretty long, you can seperate it out into another file using partials.
private static IServiceProvider ConfigureServices()
{
var map = new ServiceCollection()
// Repeat this for all the service classes
// and other dependencies that your commands might need.
.AddSingleton(new SomeServiceClass());

// When all your required services are in the collection, build the container.
// Tip: There's an overload taking in a 'validateScopes' bool to make sure
// you haven't made any mistakes in your dependency graph.
return map.BuildServiceProvider();
}

// Example of a logging handler. This can be re-used by addons
// that ask for a Func<LogMessage, Task>.
private static Task Logger(LogMessage message)
private static Task Log(LogMessage message)
{
switch (message.Severity)
{
Expand Down Expand Up @@ -92,24 +123,15 @@ private async Task MainAsync()
await Task.Delay(Timeout.Infinite);
}

private IServiceProvider _services;

private async Task InitCommands()
{
// Repeat this for all the service classes
// and other dependencies that your commands might need.
_map.AddSingleton(new SomeServiceClass());

// When all your required services are in the collection, build the container.
// Tip: There's an overload taking in a 'validateScopes' bool to make sure
// you haven't made any mistakes in your dependency graph.
_services = _map.BuildServiceProvider();

// Either search the program and add all Module classes that can be found.
// Module classes MUST be marked 'public' or they will be ignored.
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
// You also need to pass your 'IServiceProvider' instance now,
// so make sure that's done before you get here.
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
// Or add Modules manually if you prefer to be a little more explicit:
await _commands.AddModuleAsync<SomeModule>();
await _commands.AddModuleAsync<SomeModule>(_services);
// Note that the first one is 'Modules' (plural) and the second is 'Module' (singular).

// Subscribe a handler to see if a message invokes a command.
Expand All @@ -123,8 +145,6 @@ private async Task HandleCommandAsync(SocketMessage arg)
if (msg == null) return;

// We don't want the bot to respond to itself or other bots.
// NOTE: Selfbots should invert this first check and remove the second
// as they should ONLY be allowed to respond to messages from the same account.
if (msg.Author.Id == _client.CurrentUser.Id || msg.Author.IsBot) return;

// Create a number to track where the prefix ends and the command begins
Expand All @@ -140,10 +160,12 @@ private async Task HandleCommandAsync(SocketMessage arg)

// Execute the command. (result does not indicate a return value,
// rather an object stating if the command executed successfully).
var result = await _commands.ExecuteAsync(context, pos, _services);
var result = await _commands.ExecuteAsync(context, pos);

// Uncomment the following lines if you want the bot
// to send a message if it failed (not advised for most situations).
// to send a message if it failed.
// This does not catch errors from commands with 'RunMode.Async',
// subscribe a handler for '_commands.CommandExecuted' to see those.
//if (!result.IsSuccess && result.Error != CommandError.UnknownCommand)
// await msg.Channel.SendMessageAsync(result.ErrorReason);
}
Expand Down
15 changes: 15 additions & 0 deletions src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../Discord.Net.targets" />
<PropertyGroup>
<AssemblyName>Discord.Net.Analyzers</AssemblyName>
<RootNamespace>Discord.Analyzers</RootNamespace>
<Description>A Discord.Net extension adding support for design-time analysis of the API usage.</Description>
<TargetFramework>netstandard1.3</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="2.6.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Discord.Net.Commands\Discord.Net.Commands.csproj" />
</ItemGroup>
</Project>
70 changes: 70 additions & 0 deletions src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Discord.Commands;

namespace Discord.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class GuildAccessAnalyzer : DiagnosticAnalyzer
{
private const string DiagnosticId = "DNET0001";
private const string Title = "Limit command to Guild contexts.";
private const string MessageFormat = "Command method '{0}' is accessing 'Context.Guild' but is not restricted to Guild contexts.";
private const string Description = "Accessing 'Context.Guild' in a command without limiting the command to run only in guilds.";
private const string Category = "API Usage";

private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression);
}

private static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context)
{
// Bail out if the accessed member isn't named 'Guild'
var memberAccessSymbol = context.SemanticModel.GetSymbolInfo(context.Node).Symbol;
if (memberAccessSymbol.Name != "Guild")
return;

// Bail out if it happens to be 'ContextType.Guild' in the '[RequireContext]' argument
if (context.Node.Parent is AttributeArgumentSyntax)
return;

// Bail out if the containing class doesn't derive from 'ModuleBase<T>'
var typeNode = context.Node.FirstAncestorOrSelf<TypeDeclarationSyntax>();
var typeSymbol = context.SemanticModel.GetDeclaredSymbol(typeNode);
if (!typeSymbol.DerivesFromModuleBase())
return;

// Bail out if the containing method isn't marked with '[Command]'
var methodNode = context.Node.FirstAncestorOrSelf<MethodDeclarationSyntax>();
var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodNode);
var methodAttributes = methodSymbol.GetAttributes();
if (!methodAttributes.Any(a => a.AttributeClass.Name == nameof(CommandAttribute)))
return;

// Is the '[RequireContext]' attribute not applied to either the
// method or the class, or its argument isn't 'ContextType.Guild'?
var ctxAttribute = methodAttributes.SingleOrDefault(_attributeDataPredicate)
?? typeSymbol.GetAttributes().SingleOrDefault(_attributeDataPredicate);

if (ctxAttribute == null || ctxAttribute.ConstructorArguments.Any(arg => !arg.Value.Equals((int)ContextType.Guild)))
{
// Report the diagnostic
var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(), methodSymbol.Name);
context.ReportDiagnostic(diagnostic);
}
}

private static readonly Func<AttributeData, bool> _attributeDataPredicate =
(a => a.AttributeClass.Name == nameof(RequireContextAttribute));
}
}
21 changes: 21 additions & 0 deletions src/Discord.Net.Analyzers/SymbolExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using Microsoft.CodeAnalysis;
using Discord.Commands;

namespace Discord.Analyzers
{
internal static class SymbolExtensions
{
private static readonly string _moduleBaseName = typeof(ModuleBase<>).Name;

public static bool DerivesFromModuleBase(this ITypeSymbol symbol)
{
for (var bType = symbol.BaseType; bType != null; bType = bType.BaseType)
{
if (bType.MetadataName == _moduleBaseName)
return true;
}
return false;
}
}
}
30 changes: 30 additions & 0 deletions src/Discord.Net.Analyzers/docs/DNET0001.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# DNET0001

<table>
<tr>
<td>TypeName</td>
<td>GuildAccessAnalyzer</td>
</tr>
<tr>
<td>CheckId</td>
<td>DNET0001</td>
</tr>
<tr>
<td>Category</td>
<td>API Usage</td>
</tr>
</table>

## Cause

A method identified as a command is accessing `Context.Guild` without the requisite precondition.

## Rule description

The value of `Context.Guild` is `null` if a command is invoked in a DM channel. Attempting to access
guild properties in such a case will result in a `NullReferenceException` at runtime.
This exception is entirely avoidable by using the library's provided preconditions.

## How to fix violations

Add the precondition `[RequireContext(ContextType.Guild)]` to the command or module class.
2 changes: 1 addition & 1 deletion src/Discord.Net.Commands/Attributes/AliasAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Discord.Commands
{
/// <summary> Provides aliases for a command. </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AliasAttribute : Attribute
{
/// <summary> The aliases which have been defined for the command. </summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Discord.Net.Commands/Attributes/CommandAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System;

namespace Discord.Commands
{
[AttributeUsage(AttributeTargets.Method)]
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CommandAttribute : Attribute
{
public string Text { get; }
Expand Down
4 changes: 2 additions & 2 deletions src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System;

namespace Discord.Commands
{
[AttributeUsage(AttributeTargets.Class)]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class DontAutoLoadAttribute : Attribute
{
}
Expand Down
Loading

0 comments on commit f5bb99c

Please sign in to comment.