Skip to content

Commit

Permalink
Merge pull request #18 from CurseForgeCommunity/feat-discord
Browse files Browse the repository at this point in the history
wip: Discord bot
  • Loading branch information
itssimple authored Feb 1, 2024
2 parents 34e5627 + 5547321 commit 3027e2e
Show file tree
Hide file tree
Showing 16 changed files with 416 additions and 325 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*.userosscache
*.sln.docstates

appsettings.Dev.json

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

Expand Down
26 changes: 26 additions & 0 deletions CFDiscordBot/CFDiscordBot.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CurseForge.APIClient" Version="2.2.0" />
<PackageReference Include="Discord.Net" Version="3.13.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.Dev.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
39 changes: 39 additions & 0 deletions CFDiscordBot/Commands/DebugCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Discord;
using Discord.Interactions;

namespace CFDiscordBot.Commands
{
[DefaultMemberPermissions(GuildPermission.Administrator)]
public class DebugCommands : InteractionModuleBase<ShardedInteractionContext>
{
[SlashCommand("ping", "Returns the latency of the bot")]
public async Task PingAsync()
{
await RespondAsync($"Pong! {Context.Client.Latency}ms", ephemeral: true);
await Task.Delay(2000);

await Context.Interaction.DeleteOriginalResponseAsync();
}

[SlashCommand("shardinfo", "Returns information about the shard")]
public async Task ShardInfoAsync()
{
var shardId = Context.Client.GetShardIdFor(Context.Guild);
var shard = Context.Client.GetShard(shardId);

var embed = new EmbedBuilder()
.WithTitle("Shard Information")
.WithDescription($"""
Shard ID: {shardId}
Guilds: {shard.Guilds.Count}
Users: {shard.Guilds.Sum(x => x.MemberCount)}
Channels: {shard.Guilds.Sum(x => x.Channels.Count)}
Latency: {shard.Latency}ms
""")
.WithColor(Color.Blue)
.Build();

await RespondAsync(embeds: new[] { embed }, ephemeral: true);
}
}
}
43 changes: 43 additions & 0 deletions CFDiscordBot/Commands/GameLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Discord;
using Discord.Interactions;

namespace CFDiscordBot.Commands
{
public partial class Lookup
{
[SlashCommand("game", "Looks up a game by its ID")]
public async Task GameIdAsync(
[Summary("id", "The ID of the game to look up")]
int gameId
)
{
var game = await apiClient.GetGameAsync(gameId);

if (game is null)
{
await RespondAsync($"Game with id {gameId} was not found.", ephemeral: true);

await Task.Delay(2000);
await Context.Interaction.DeleteOriginalResponseAsync();
return;
}

var embed = new EmbedBuilder()
.WithTitle(game.Data.Name)
.WithUrl($"https://curseforge.com/{game.Data.Slug}")
.WithColor(Color.Blue);

if (!string.IsNullOrWhiteSpace(game.Data.Assets.IconUrl))
{
embed.WithThumbnailUrl(game.Data.Assets.IconUrl);
}

if (!string.IsNullOrWhiteSpace(game.Data.Assets.CoverUrl))
{
embed.WithImageUrl(game.Data.Assets.CoverUrl);
}

await RespondAsync(embeds: new[] { embed.Build() }, ephemeral: true);
}
}
}
8 changes: 8 additions & 0 deletions CFDiscordBot/Commands/Lookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using CurseForge.APIClient;
using Discord.Interactions;

namespace CFDiscordBot.Commands
{
[Group("lookup", "Does lookups against CurseForge")]
public partial class Lookup(ApiClient apiClient) : InteractionModuleBase<ShardedInteractionContext> { }
}
35 changes: 35 additions & 0 deletions CFDiscordBot/Commands/ProjectLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Discord;
using Discord.Interactions;

namespace CFDiscordBot.Commands
{
public partial class Lookup
{
[SlashCommand("projectid", "Looks up a project by its ID")]
public async Task ProjectIdAsync(
[Summary("id", "The ID of the project to look up")]
int projectId
)
{
var project = await apiClient.GetModAsync(projectId);

if (project is null)
{
await RespondAsync($"Project with id {projectId} was not found.", ephemeral: true);

await Task.Delay(2000);
await Context.Interaction.DeleteOriginalResponseAsync();
return;
}

var embed = new EmbedBuilder()
.WithTitle(project.Data.Name)
.WithUrl(project.Data.Links.WebsiteUrl)
.WithDescription(project.Data.Summary)
.WithColor(Color.Blue)
.Build();

await RespondAsync(embeds: new[] { embed }, ephemeral: true);
}
}
}
83 changes: 83 additions & 0 deletions CFDiscordBot/DiscordBot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using CurseForge.APIClient;
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Reflection;

namespace CFDiscordBot
{
public partial class DiscordBot(
ILogger logger,
DiscordShardedClient discordClient,
string botToken,
ApiClient apiClient,
IServiceProvider serviceProvider
) : BackgroundService
{
private InteractionService? interactionService;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
discordClient.ShardReady += DiscordClient_ShardReady;

discordClient.Log += Log;

await discordClient.LoginAsync(TokenType.Bot, botToken);
await discordClient.StartAsync();

await Task.Delay(-1, stoppingToken);

await discordClient.StopAsync();
await discordClient.LogoutAsync();
}

private async Task DiscordClient_ShardReady(DiscordSocketClient _shardClient)
{
discordClient.ShardReady -= DiscordClient_ShardReady;

interactionService = new InteractionService(_shardClient);

interactionService.Log += Log;

logger.LogInformation("Registering slash commands");
await interactionService.AddModulesAsync(Assembly.GetEntryAssembly(), serviceProvider);
await interactionService.RegisterCommandsGloballyAsync(true);

discordClient.InteractionCreated += DiscordClient_InteractionCreated; ;

logger.LogInformation("Slash commands registered");
}

private async Task Log(LogMessage msg)
{
switch (msg.Severity)
{
case LogSeverity.Critical:
case LogSeverity.Error:
logger.LogError(msg.Exception, msg.Message);
break;
case LogSeverity.Warning:
logger.LogWarning(msg.Exception, msg.Message);
break;
case LogSeverity.Info:
logger.LogInformation(msg.Exception, msg.Message);
break;
case LogSeverity.Verbose:
case LogSeverity.Debug:
logger.LogDebug(msg.Exception, msg.Message);
break;
}
await Task.CompletedTask;
}

private async Task DiscordClient_InteractionCreated(SocketInteraction interaction)
{
logger.LogDebug("Interaction received: {Interaction}", interaction);
var ctx = new ShardedInteractionContext(discordClient, interaction);
await interactionService!.ExecuteCommandAsync(ctx, serviceProvider);
logger.LogDebug("Interaction handled");
}
}
}
37 changes: 37 additions & 0 deletions CFDiscordBot/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using CFDiscordBot;
using CurseForge.APIClient;
using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.Dev.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables("CFLOOKUP_")
.Build();

await Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddLogging(services => services.AddConsole());
services.AddSingleton(x => new ApiClient(configuration["CurseForge:ApiKey"]));
services.AddSingleton(x => new DiscordSocketConfig
{
UseInteractionSnowflakeDate = false
});
services.AddSingleton<DiscordShardedClient>();
services.AddHostedService(x =>
new DiscordBot(
x.GetRequiredService<ILogger<DiscordBot>>(),
x.GetRequiredService<DiscordShardedClient>(),
configuration["Discord:BotToken"]!,
x.GetRequiredService<ApiClient>(),
x
)
);
})
.Build()
.RunAsync();
15 changes: 15 additions & 0 deletions CFDiscordBot/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"CurseForge": {
"ApiKey": "..."
},
"Discord": {
"AppId": "...",
"PublicKey": "...",
"BotToken": "..."
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
6 changes: 6 additions & 0 deletions CFLookup.sln
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CFDiscordBot", "CFDiscordBot\CFDiscordBot.csproj", "{1565E57C-2043-4C9A-BD79-3855092DC239}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -25,6 +27,10 @@ Global
{234AF4A6-AF38-4514-8F86-410A0F546860}.Debug|Any CPU.Build.0 = Debug|Any CPU
{234AF4A6-AF38-4514-8F86-410A0F546860}.Release|Any CPU.ActiveCfg = Release|Any CPU
{234AF4A6-AF38-4514-8F86-410A0F546860}.Release|Any CPU.Build.0 = Release|Any CPU
{1565E57C-2043-4C9A-BD79-3855092DC239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1565E57C-2043-4C9A-BD79-3855092DC239}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1565E57C-2043-4C9A-BD79-3855092DC239}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1565E57C-2043-4C9A-BD79-3855092DC239}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
8 changes: 4 additions & 4 deletions CFLookup/CFLookup.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.9" />
<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.3" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.2" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.5" />
<PackageReference Include="NSec.Cryptography" Version="22.4.0" />
<PackageReference Include="CurseForge.APIClient" Version="2.1.0" />
<PackageReference Include="StackExchange.Redis" Version="2.7.10" />
<PackageReference Include="CurseForge.APIClient" Version="2.2.0" />
<PackageReference Include="StackExchange.Redis" Version="2.7.17" />
</ItemGroup>

<ProjectExtensions><VisualStudio><UserProperties wwwroot_4manifest_1json__JsonSchema="https://json.schemastore.org/web-manifest-combined.json" /></VisualStudio></ProjectExtensions>
Expand Down
6 changes: 0 additions & 6 deletions CFLookup/DiscordController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public async Task<IActionResult> InteractionsAsync()
{
case DiscordInteractionType.Ping:
{
await RegisterDiscordCommandsAsync(discordAppId);
return new JsonResult(new DiscordPongResult());
}
case DiscordInteractionType.ApplicationCommand:
Expand Down Expand Up @@ -262,11 +261,6 @@ private async Task<object> ProjectLookupAsync(DiscordInteractionRequest request)
};
}

private async Task RegisterDiscordCommandsAsync(string discordAppId)
{
var _hc = _httpClientFactory.CreateClient();
}

private static byte[] GetBytesFromHexString(string hex)
{
var length = hex.Length;
Expand Down
Loading

0 comments on commit 3027e2e

Please sign in to comment.