Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
BrammyS committed Aug 10, 2021
2 parents 477903e + 47b7d94 commit 6398de0
Show file tree
Hide file tree
Showing 93 changed files with 1,043 additions and 478 deletions.
4 changes: 2 additions & 2 deletions samples/RoleManager/Commands/RoleCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
using Color_Chan.Discord.Commands.Attributes;
using Color_Chan.Discord.Commands.Attributes.ProvidedRequirements;
using Color_Chan.Discord.Commands.Modules;
using Color_Chan.Discord.Core.Common.API.DataModels;
using Color_Chan.Discord.Core.Common.API.DataModels.Application;
using Color_Chan.Discord.Core.Common.API.DataModels.Guild;
using Color_Chan.Discord.Core.Common.API.Params.Guild;
using Color_Chan.Discord.Core.Common.API.Rest;
using Color_Chan.Discord.Core.Common.Models.Interaction;
Expand Down Expand Up @@ -52,7 +52,7 @@ string roleName
var roleConfig = new DiscordCreateGuildRole
{
Name = roleName,
Permissions = DiscordGuildPermission.None
Permissions = DiscordPermission.None
};
var newRoleResponse = await _restGuild.CreateGuildRoleAsync(SlashContext.GuildId!.Value, roleConfig).ConfigureAwait(false);

Expand Down
43 changes: 43 additions & 0 deletions samples/RoleManager/Pipelines/PerformancePipeline.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Diagnostics;
using System.Threading.Tasks;
using Color_Chan.Discord.Commands;
using Color_Chan.Discord.Commands.Models.Contexts;
using Color_Chan.Discord.Core.Common.Models.Interaction;
using Color_Chan.Discord.Core.Results;
using Microsoft.Extensions.Logging;

namespace RoleManager.Pipelines
{
/// <summary>
/// A pipeline that will measure the performance of the slash commands.
/// </summary>
public class PerformancePipeline : ISlashCommandPipeline
{
private readonly ILogger<PerformancePipeline> _logger;

/// <summary>
/// Initializes a new instance of <see cref="PerformancePipeline" />.
/// </summary>
/// <param name="logger">The logger that will log the performance of the slash commands to the console.</param>
public PerformancePipeline(ILogger<PerformancePipeline> logger)
{
_logger = logger;
}

/// <inheritdoc />
public async Task<Result<IDiscordInteractionResponse>> HandleAsync(ISlashCommandContext context, SlashCommandHandlerDelegate next)
{
var sw = new Stopwatch();

sw.Start();

// Run the command
var result = await next().ConfigureAwait(false);

sw.Stop();

_logger.Log(sw.ElapsedMilliseconds > 500 ? LogLevel.Warning : LogLevel.Information ,"Command {Name} ran for {Time}ms.", context.MethodName, sw.ElapsedMilliseconds.ToString());
return result;
}
}
}
6 changes: 6 additions & 0 deletions samples/RoleManager/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Color_Chan.Discord.Extensions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace RoleManager
{
Expand All @@ -22,6 +23,11 @@ public static async Task Main(string[] args)
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
})
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}
Expand Down
2 changes: 1 addition & 1 deletion samples/RoleManager/RoleManager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Color-Chan.Discord\Color-Chan.Discord.csproj"/>
<ProjectReference Include="..\..\src\Color-Chan.Discord\Color-Chan.Discord.csproj" />
</ItemGroup>

</Project>
9 changes: 7 additions & 2 deletions samples/RoleManager/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Color_Chan.Discord.Commands.Extensions;
using Color_Chan.Discord.Configurations;
using Color_Chan.Discord.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using RoleManager.Pipelines;

namespace RoleManager
{
Expand All @@ -24,15 +26,18 @@ public void ConfigureServices(IServiceCollection services)
{
SlashCommandConfigs = slashOptions =>
{
slashOptions.EnableAutoSync = true;
slashOptions.EnableAutoGetGuild = true;
slashOptions.EnableAutoSync = true; // <---
slashOptions.EnableAutoGetGuild = true; // <---
}
};

// Replace the arguments with the data of your bot.
// Note: It is not recommended to hardcode them in, loading them from an environment variable or from a json file is better.
services.AddColorChanDiscord("TOKEN", "PUBLIC_KEY", 999999999999999, config); // <---

// Register your custom pipelines if any.
services.AddSlashCommandPipeline<PerformancePipeline>(); // <---

services.AddControllers();
}

Expand Down
1 change: 1 addition & 0 deletions samples/RoleManager/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"Default": "Warning",
"Microsoft": "Warning",
"Color_Chan.Discord": "Information",
"RoleManager": "Information",
"Microsoft.Hosting.Lifetime": "Warning"
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ namespace Color_Chan.Discord.Caching.Configurations
public class CacheConfiguration
{
/// <summary>
/// Gets or sets an absolute expiration time, relative to now.
/// Gets or sets an absolute expiration time, relative to now. Default is 30 seconds.
/// </summary>
public TimeSpan AbsoluteExpiration { get; set; }
public TimeSpan AbsoluteExpiration { get; set; } = TimeSpan.FromSeconds(30);

/// <summary>
/// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed.
/// This will not extend the entry lifetime beyond the absolute expiration (if set).
/// This will not extend the entry lifetime beyond the absolute expiration (if set). Default is 15 seconds.
/// </summary>
public TimeSpan SlidingExpiration { get; set; }
public TimeSpan SlidingExpiration { get; set; } = TimeSpan.FromSeconds(15);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System;
using System.Threading.Tasks;
using Color_Chan.Discord.Caching.Services;
using Color_Chan.Discord.Commands.Contexts;
using Color_Chan.Discord.Commands.Models;
using Color_Chan.Discord.Commands.Results;
using Color_Chan.Discord.Commands.Models.Contexts;
using Color_Chan.Discord.Commands.Models.Results;
using Color_Chan.Discord.Core.Results;
using Microsoft.Extensions.DependencyInjection;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Color_Chan.Discord.Commands.Contexts;
using Color_Chan.Discord.Commands.Results;
using Color_Chan.Discord.Commands.Models.Contexts;
using Color_Chan.Discord.Commands.Models.Results;
using Color_Chan.Discord.Core;
using Color_Chan.Discord.Core.Common.API.DataModels.Guild;
using Color_Chan.Discord.Core.Common.API.DataModels;
using Color_Chan.Discord.Core.Common.API.Rest;
using Color_Chan.Discord.Core.Common.Models;
using Color_Chan.Discord.Core.Common.Models.Guild;
using Color_Chan.Discord.Core.Extensions;
using Color_Chan.Discord.Core.Results;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -16,8 +18,8 @@ namespace Color_Chan.Discord.Commands.Attributes.ProvidedRequirements
/// Requires the bot that requested the slash command to have a certain permissions.
/// </summary>
/// <example>
/// This example requires the bot to have the <see cref="DiscordGuildPermission.BanMembers" /> and
/// <see cref="DiscordGuildPermission.KickMembers" /> permission.
/// This example requires the bot to have the <see cref="DiscordPermission.BanMembers" /> and
/// <see cref="DiscordPermission.KickMembers" /> permission.
/// You can also put the <see cref="SlashCommandRequireBotPermissionAttribute" /> on a method if you only want to have
/// it on a specific command.
/// <code language="cs">
Expand All @@ -35,18 +37,18 @@ namespace Color_Chan.Discord.Commands.Attributes.ProvidedRequirements
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class SlashCommandRequireBotPermissionAttribute : SlashCommandRequirementAttribute
{
private readonly DiscordGuildPermission _requiredGuildPermission;
private readonly DiscordPermission _requiredPermission;

/// <summary>
/// Initializes a new instance of <see cref="SlashCommandRateLimitAttribute" />.
/// </summary>
/// <param name="requiredGuildPermission">
/// The <see cref="DiscordGuildPermission" /> the bot is required to have for this
/// <param name="requiredPermission">
/// The <see cref="DiscordPermission" /> the bot is required to have for this
/// command/command group.
/// </param>
public SlashCommandRequireBotPermissionAttribute(DiscordGuildPermission requiredGuildPermission)
public SlashCommandRequireBotPermissionAttribute(DiscordPermission requiredPermission)
{
_requiredGuildPermission = requiredGuildPermission;
_requiredPermission = requiredPermission;
}

/// <inheritdoc />
Expand All @@ -58,37 +60,77 @@ public override async Task<Result> CheckRequirementAsync(ISlashCommandContext co
}

var restGuild = services.GetRequiredService<IDiscordRestGuild>();
var discordTokens = services.GetRequiredService<DiscordTokens>();

// Get the guild.
IDiscordGuild? guild;
if (context.Guild is not null)
{
guild = context.Guild;
}
else
{
var guildResult = await restGuild.GetGuildAsync(context.GuildId.Value).ConfigureAwait(false);
guild = guildResult.Entity;
}

if (guild is null)
{
throw new NullReferenceException("Failed to get guild");
}

// Get the bot user.
var discordTokens = services.GetRequiredService<DiscordTokens>();
var botMember = await restGuild.GetGuildMemberAsync(context.GuildId.Value, discordTokens.ApplicationId);
if (!botMember.IsSuccessful)
{
return Result.FromError(new SlashCommandRequireBotPermissionErrorResult("Guild member does not exist", default));
}

// Get the bot role permissions.
var botRoles = guild.Roles.Where(x => botMember.Entity!.Roles.Contains(x.Id));
var rolePerms = botRoles.Aggregate(DiscordPermission.None, (current, botRole) => current | botRole.Permissions);

if (botMember.Entity!.Permissions is null)

// Get the channel.
IDiscordChannel? channel;
if (context.Guild is not null)
{
channel = context.Channel;
}
else
{
throw new ArgumentNullException(nameof(botMember.Entity.Permissions), "No permission found");
var restChannel = services.GetRequiredService<IDiscordRestChannel>();
var channelResult = await restChannel.GetChannelAsync(context.ChannelId).ConfigureAwait(false);
channel = channelResult.Entity;
}

if (botMember.Entity.Permissions.Value.HasFlag(_requiredGuildPermission))
if (channel is null)
{
return Result.FromSuccess();
throw new NullReferenceException("Failed to get channel");
}

var botPerms = botMember.Entity!.Permissions.ToList();
var requiredPerms = _requiredGuildPermission.ToList();
var missingPerms = new List<DiscordGuildPermission>();
if (channel.PermissionOverwrites is null)
{
throw new NullReferenceException("Missing permission overwrites");
}

foreach (var requiredPerm in requiredPerms)
// Removed the denied permissions from the role perms.
foreach (var permissionOverwrite in channel.PermissionOverwrites)
{
if (!botPerms.Contains(requiredPerm))
if (permissionOverwrite.TargetId == discordTokens.ApplicationId || botMember.Entity!.Roles.Contains(permissionOverwrite.TargetId))
{
missingPerms.Add(requiredPerm);
rolePerms |= permissionOverwrite.Allow;
rolePerms &= ~permissionOverwrite.Deny;
}
}

return Result.FromError(new SlashCommandRequireBotPermissionErrorResult("Bot did not meet permission requirements", missingPerms));

var missingPerms = _requiredPermission.ToList().Where(requiredPerm => !rolePerms.HasFlag(requiredPerm)).ToList();
if (missingPerms.Any())
{
return Result.FromError(new SlashCommandRequireBotPermissionErrorResult("Bot did not meet permission requirements", missingPerms));
}

return Result.FromSuccess();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using Color_Chan.Discord.Commands.Contexts;
using Color_Chan.Discord.Commands.Results;
using Color_Chan.Discord.Commands.Models.Contexts;
using Color_Chan.Discord.Commands.Models.Results;
using Color_Chan.Discord.Core.Results;

namespace Color_Chan.Discord.Commands.Attributes.ProvidedRequirements
Expand Down Expand Up @@ -40,7 +40,7 @@ public SlashCommandRequireChannelAttribute(ulong channelId)
public override Task<Result> CheckRequirementAsync(ISlashCommandContext context, IServiceProvider services)
{
return Task.FromResult(context.ChannelId != _channelId
? Result.FromError(new SlashCommandRequirementErrorResult($"Channel with ID {_channelId.ToString()} is required to use this command."))
? Result.FromError(new SlashCommandRequireChannelErrorResult($"Channel with ID {_channelId.ToString()} is required to use this command."))
: Result.FromSuccess());
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using Color_Chan.Discord.Commands.Contexts;
using Color_Chan.Discord.Commands.Results;
using Color_Chan.Discord.Commands.Models.Contexts;
using Color_Chan.Discord.Commands.Models.Results;
using Color_Chan.Discord.Core.Results;

namespace Color_Chan.Discord.Commands.Attributes.ProvidedRequirements
Expand Down Expand Up @@ -36,7 +36,7 @@ public override Task<Result> CheckRequirementAsync(ISlashCommandContext context,
return Task.FromResult(Result.FromSuccess());
}

return Task.FromResult(Result.FromError(new SlashCommandRequirementErrorResult("Command can not be executed in a guild")));
return Task.FromResult(Result.FromError(new SlashCommandRequireDmErrorResult("Command can not be executed in a guild")));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using Color_Chan.Discord.Commands.Contexts;
using Color_Chan.Discord.Commands.Results;
using Color_Chan.Discord.Commands.Models.Contexts;
using Color_Chan.Discord.Commands.Models.Results;
using Color_Chan.Discord.Core.Results;

namespace Color_Chan.Discord.Commands.Attributes.ProvidedRequirements
Expand Down Expand Up @@ -36,7 +36,7 @@ public override Task<Result> CheckRequirementAsync(ISlashCommandContext context,
return Task.FromResult(Result.FromSuccess());
}

return Task.FromResult(Result.FromError(new SlashCommandRequirementErrorResult("Command can not be executed in DMs")));
return Task.FromResult(Result.FromError(new SlashCommandRequireGuildErrorResult("Command can not be executed in DMs")));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using Color_Chan.Discord.Commands.Contexts;
using Color_Chan.Discord.Commands.Results;
using Color_Chan.Discord.Commands.Models.Contexts;
using Color_Chan.Discord.Commands.Models.Results;
using Color_Chan.Discord.Core.Common.API.Rest;
using Color_Chan.Discord.Core.Common.Models.Guild;
using Color_Chan.Discord.Core.Results;
Expand Down Expand Up @@ -36,7 +36,7 @@ public override async Task<Result> CheckRequirementAsync(ISlashCommandContext co
{
if (context.GuildId is null)
{
return Result.FromError(new SlashCommandRequirementErrorResult("Command can not be executed in DMs"));
return Result.FromError(new SlashCommandRequireGuildOwnerErrorResult("Command can not be executed in DMs"));
}

IDiscordGuild? guild;
Expand All @@ -61,7 +61,7 @@ public override async Task<Result> CheckRequirementAsync(ISlashCommandContext co
return Result.FromSuccess();
}

return Result.FromError(new SlashCommandRequirementErrorResult("Guild owner is required"));
return Result.FromError(new SlashCommandRequireGuildOwnerErrorResult("Guild owner is required"));
}
}
}
Loading

0 comments on commit 6398de0

Please sign in to comment.