Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an image gallery interaction #7

Merged
merged 3 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,6 @@ $RECYCLE.BIN/
##
## Local environment
##
kubeconfig.yaml
.env
localconfig.*
localconfig.*
local/
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
6 changes: 6 additions & 0 deletions src/ServerManagerDiscordBot/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public class AppSettings

public LargeFileDownloadHandlerType LargeFileDownloadHandler { get; set; } = LargeFileDownloadHandlerType.Disabled;

public bool EnableGallery { get; set; } = true;

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
2 changes: 2 additions & 0 deletions src/ServerManagerDiscordBot/ServerInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ public class ServerInfo

public string? FilesPath { get; set; }

public string? GalleryPath { get; set; }

public Dictionary<string, string> Fields { get; set; } = new Dictionary<string, string>();
}
45 changes: 45 additions & 0 deletions src/ServerManagerDiscordBot/ServerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,51 @@ public async Task<FileInfo> GetServerFileAsync(string name, string fileName, Can
return new FileInfo(filePath);
}

public async Task<IEnumerable<FileInfo>> GetServerGalleryFilesAsync(string name, 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.");
}

var directory = new DirectoryInfo(server.GalleryPath);
if (!directory.Exists)
{
throw new DirectoryNotFoundException($"The `{name}` server gallery directory `{server.GalleryPath}` does not exist.");
}

var files = directory.EnumerateFiles()
.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
Loading
Loading