Skip to content

Commit

Permalink
Add an image gallery interaction
Browse files Browse the repository at this point in the history
Fixes #4
  • Loading branch information
cruikshj committed May 22, 2024
1 parent 5abf8bb commit 7b7a18e
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 21 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ Each example will show how to host the bot application itself as well as integra
| HostUri | The bot application host URI. Only used if `LargeFileDownloadHandler.BuiltIn` is used. | https://localhost:5000 |
| EnableFileDownloadHandler | This is a global setting for whether to enable the file downloads feature. Configured servers must still opt-in by providing a `FilesPath` value. | true |
| LargeFileDownloadHandler | Enable file downloads larger than 25MB. See [Handling Large File Downloads](#handlinglargefiledownloads). | Disabled |
| EnableGallery | Enable server gallery. | true |
| EnableGalleryUploads | Enable uploads to the gallery. | false |
| GalleryFileExtensions | Gallery file extensions. | [ png, jpg, jpeg, gif, webp ] |
| ServersCacheExpiration | The server info cache expiration. | 5 minutes |
| DownloadLinkExpiration | The lifetime of a large file download link. | 24 hours |
| ServerStatusWaitTimeout | The timeout for waiting for server status after a start or stop interaction. | 10 minutes |
Expand Down
2 changes: 2 additions & 0 deletions src/ServerManagerDiscordBot/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class AppSettings

public bool EnableGalleryUploads { get; set; } = false;

public string[] GalleryFileExtensions { get; set; } = [ "png", "jpg", "jpeg", "gif", "webp" ];

public TimeSpan ServersCacheExpiration { get; set; } = TimeSpan.FromMinutes(5);

public TimeSpan DownloadLinkExpiration { get; set; } = TimeSpan.FromDays(1);
Expand Down
23 changes: 4 additions & 19 deletions src/ServerManagerDiscordBot/BotService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Reflection;
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
Expand All @@ -7,6 +6,7 @@
public class BotService(
IOptions<AppSettings> appSettings,
DiscordSocketClient client,
CommandManager commandManager,
InteractionService interactionService,
ILogger<BotService> logger,
IServiceProvider serviceProvider)
Expand All @@ -17,6 +17,8 @@ public class BotService(
public ILogger Logger { get; } = logger;
public IServiceProvider ServiceProvider { get; } = serviceProvider;
public DiscordSocketClient Client { get; } = client;
public CommandManager CommandManager { get; } = commandManager;
public IReadOnlyCollection<IApplicationCommand> Commands { get; private set; } = [];

public async Task StartAsync(CancellationToken cancellationToken)
{
Expand All @@ -30,8 +32,6 @@ public async Task StartAsync(CancellationToken cancellationToken)
Client.SelectMenuExecuted += CommandHandler;
Client.AutocompleteExecuted += CommandHandler;

await InteractionService.AddModulesAsync(Assembly.GetEntryAssembly(), ServiceProvider);

await Client.LoginAsync(TokenType.Bot, AppSettings.BotToken);

await Client.StartAsync();
Expand All @@ -56,7 +56,7 @@ public void Dispose()

private async Task Ready()
{
await RegisterCommandsAsync();
await CommandManager.RegisterCommandsAsync();

await Client.SetStatusAsync(UserStatus.Online);
}
Expand All @@ -67,21 +67,6 @@ private async Task CommandHandler(SocketInteraction interaction)
await InteractionService.ExecuteCommandAsync(context, ServiceProvider);
}

private async Task RegisterCommandsAsync()
{
if (!AppSettings.GuildIds.Any())
{
await InteractionService.RegisterCommandsGloballyAsync(true);
}
else
{
foreach (var guildId in AppSettings.GuildIds)
{
await InteractionService.RegisterCommandsToGuildAsync(guildId, true);
}
}
}

private Task LogAsync(LogMessage message)
{
Logger.Log(
Expand Down
43 changes: 43 additions & 0 deletions src/ServerManagerDiscordBot/CommandManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Reflection;
using Discord;
using Discord.Interactions;
using Microsoft.Extensions.Options;

public class CommandManager(
IOptions<AppSettings> appSettings,
InteractionService interactionService,
IServiceProvider serviceProvider)
{
public AppSettings AppSettings { get; } = appSettings.Value;
public InteractionService InteractionService { get; } = interactionService;
public IServiceProvider ServiceProvider { get; } = serviceProvider;

public IReadOnlyCollection<IApplicationCommand> Commands { get; private set; } = [];

public IApplicationCommand GetCommand(string name)
{
var command = Commands.FirstOrDefault(c => c.Name == name);
if (command is null)
{
throw new ArgumentException($"Command '{name}' not found.", nameof(name));
}
return command;
}

public async Task RegisterCommandsAsync()
{
await InteractionService.AddModulesAsync(Assembly.GetEntryAssembly(), ServiceProvider);

if (!AppSettings.GuildIds.Any())
{
Commands = await InteractionService.RegisterCommandsGloballyAsync(true);
}
else
{
foreach (var guildId in AppSettings.GuildIds)
{
Commands = await InteractionService.RegisterCommandsToGuildAsync(guildId, true);
}
}
}
}
1 change: 1 addition & 0 deletions src/ServerManagerDiscordBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
LogLevel = Discord.LogSeverity.Verbose
});
});
builder.Services.AddSingleton<CommandManager>();
builder.Services.AddHostedService<BotService>();

builder.Services.AddSingleton<ServerManager>();
Expand Down
27 changes: 25 additions & 2 deletions src/ServerManagerDiscordBot/ServerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,36 @@ public async Task<IEnumerable<FileInfo>> GetServerGalleryFilesAsync(string name,
throw new DirectoryNotFoundException($"The `{name}` server gallery directory `{server.GalleryPath}` does not exist.");
}

var extensions = new[] { ".png", ".jpg", ".jpeg", ".gif", ".webp" };
var files = directory.EnumerateFiles()
.Where(file => extensions.Contains(file.Extension, StringComparer.OrdinalIgnoreCase));
.Where(file => AppSettings.GalleryFileExtensions.Contains(file.Extension.Substring(1), StringComparer.OrdinalIgnoreCase));

return files;
}

public async Task UploadServerGalleryFileAsync(string name, string sourceUrl, string fileName, CancellationToken cancellationToken = default)
{
var server = await GetServerInfoAsync(name, cancellationToken);

if (string.IsNullOrWhiteSpace(server.GalleryPath))
{
throw new InvalidOperationException($"The `{name}` server does not support this operation.");
}

if (!AppSettings.GalleryFileExtensions.Contains(Path.GetExtension(fileName).Substring(1), StringComparer.OrdinalIgnoreCase))
{
throw new ArgumentException($"The gallery does not support the file type `{Path.GetExtension(fileName)}`.");
}

fileName = $"{Path.GetFileNameWithoutExtension(fileName)}_{DateTime.UtcNow:yyyyMMddHHmmss}{Path.GetExtension(fileName)}";

var filePath = Path.Combine(server.GalleryPath, fileName);

using var client = new HttpClient();
using var stream = await client.GetStreamAsync(sourceUrl, cancellationToken);
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
await stream.CopyToAsync(fileStream, cancellationToken);
}

public async Task<IDictionary<string, Stream>> GetServerLogsAsync(string name, CancellationToken cancellationToken = default)
{
var server = await GetServerInfoAsync(name, cancellationToken);
Expand Down
49 changes: 49 additions & 0 deletions src/ServerManagerDiscordBot/ServersCommandModule.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using System.Text;
using Discord;
using Discord.Interactions;
using Discord.Interactions.Builders;
using Microsoft.Extensions.Options;
using SmartFormat;

public class ServersCommandModule(
IOptions<AppSettings> appSettings,
CommandManager commandManager,
ServerManager serverManager,
ILargeFileDownloadHandler largeFileDownloadHandler,
ILogger<ServersCommandModule> logger)
: InteractionModuleBase
{
public AppSettings AppSettings { get; } = appSettings.Value;
public CommandManager CommandManager { get; } = commandManager;
public ServerManager ServerManager { get; } = serverManager;
public ILargeFileDownloadHandler LargeFileDownloadHandler { get; } = largeFileDownloadHandler;
public ILogger Logger { get; } = logger;
Expand Down Expand Up @@ -399,4 +402,50 @@ await FollowupWithFilesAsync(
await FollowupAsync($"Error: {ex.Message}", ephemeral: true);
}
}

[ComponentInteraction("galleryupload|*")]
public async Task GalleryUpload(string name)
{
if (!AppSettings.EnableGalleryUploads)
{
await FollowupAsync("Gallery uploads are disabled.", ephemeral: true);
return;
}

await DeferAsync(ephemeral: true);
try
{
var command = CommandManager.GetCommand("servers-gallup");
await FollowupAsync($"Upload a file to the gallery using the </servers-gallup:{command.Id}> command.", ephemeral: true);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error in gallery upload interaction for server '{Name}'.", name);
await FollowupAsync($"Error: {ex.Message}", ephemeral: true);
}
}

[SlashCommand("servers-gallup", "Upload a file to a server gallery.")]
public async Task GalleryUpload([Autocomplete(typeof(ServersAutocompleteHandler))] string name, IAttachment file)
{
if (!AppSettings.EnableGalleryUploads)
{
await FollowupAsync("Gallery uploads are disabled.", ephemeral: true);
return;
}

await DeferAsync(ephemeral: true);

try
{
await ServerManager.UploadServerGalleryFileAsync(name, file.Url, file.Filename);

await FollowupAsync($"Uploaded file to the `{name}` server gallery.");
}
catch (Exception ex)
{
Logger.LogError(ex, "Error in gallery upload command for server '{Name}'.", name);
await FollowupAsync($"Error: {ex.Message}", ephemeral: true);
}
}
}

0 comments on commit 7b7a18e

Please sign in to comment.