Skip to content
Closed
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
90 changes: 90 additions & 0 deletions DiscordBot/Modules/TipModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.IO;
using Discord.Commands;
using DiscordBot.Attributes;
using DiscordBot.Services;
using DiscordBot.Services.Tips;
using DiscordBot.Settings;

// ReSharper disable all UnusedMember.Local
namespace DiscordBot.Modules;

public class TipModule : ModuleBase
{
#region Dependency Injection

public CommandHandlingService CommandHandlingService { get; set; }
public BotSettings Settings { get; set; }
public TipService TipService { get; set; }

#endregion

[Command("Tip")]
[Summary("Find and provide pre-authored tips (images or text) by their keywords.")]
public async Task Tip(string keywords)
{
var tips = TipService.GetTips(keywords);
if (tips.Count == 0)
{
await ReplyAsync("No tips for the keywords provided were found.");
return;
}

var isAnyTextTips = tips.Any(tip => !string.IsNullOrEmpty(tip.Content));
EmbedBuilder builder = new EmbedBuilder();
if (isAnyTextTips)
{
// Loop through tips in order, have dot point list of the .Content property in an embed
builder
.WithTitle("Tip List")
.WithDescription("Here are the tips for your keywords:");
foreach (var tip in tips)
{
builder.AddField(tip.Keywords.Count == 1 ? tip.Keywords[0] : "Multiple Keywords", tip.Content);
}
}

var attachments = tips
.Where(tip => tip.ImagePaths != null && tip.ImagePaths.Any())
.SelectMany(tip => tip.ImagePaths)
.Select(imagePath => new FileAttachment(Path.Combine(Settings.TipImageDirectory, imagePath)))

Check failure on line 49 in DiscordBot/Modules/TipModule.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipImageDirectory' and no accessible extension method 'TipImageDirectory' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 49 in DiscordBot/Modules/TipModule.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipImageDirectory' and no accessible extension method 'TipImageDirectory' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)
.ToList();

if (attachments.Count > 0)

Check failure on line 52 in DiscordBot/Modules/TipModule.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Operator '>' cannot be applied to operands of type 'method group' and 'int'

Check failure on line 52 in DiscordBot/Modules/TipModule.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Operator '>' cannot be applied to operands of type 'method group' and 'int'
{
if (isAnyTextTips)
{
await Context.Channel.SendFilesAsync(attachments, embed: builder.Build());
}
else
{
await Context.Channel.SendFilesAsync(attachments);
}
}
else
{
await ReplyAsync(embed: builder.Build());
}
}

[Command("AddTip")]
[Summary("Add a tip to the database.")]
[RequireModerator]
public async Task AddTip(string keywords, string content = "")
{
await TipService.AddTip(Context.Message, keywords, content);
}

#region CommandList

[Summary("Does what you see now.")]
[Command("Ticket Help")]
public async Task TicketHelp()
{
foreach (var message in CommandHandlingService.GetCommandListMessages("TipModule", true, true, false))
{
await ReplyAsync(message);
}
}
#endregion

}
4 changes: 3 additions & 1 deletion DiscordBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Discord.WebSocket;
using DiscordBot.Service;
using DiscordBot.Services;
using DiscordBot.Services.Tips;
using DiscordBot.Settings;
using DiscordBot.Utils;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -104,6 +105,7 @@ private IServiceProvider ConfigureServices() =>
.AddSingleton<ReminderService>()
.AddSingleton<WeatherService>()
.AddSingleton<AirportService>()
.AddSingleton<TipService>()
.AddSingleton<CannedResponseService>()
.AddSingleton<UserExtendedService>()
.BuildServiceProvider();
Expand All @@ -114,4 +116,4 @@ private static void DeserializeSettings()
_rules = SerializeUtil.DeserializeFile<Rules>(@"Settings/Rules.json");
_userSettings = SerializeUtil.DeserializeFile<UserSettings>(@"Settings/UserSettings.json");
}
}
}
8 changes: 8 additions & 0 deletions DiscordBot/Services/Tips/Components/Tip.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace DiscordBot.Services.Tips.Components;

public class Tip
{
public string Content { get; set; }
public List<string> Keywords { get; set; }
public List<string> ImagePaths { get; set; }
}
143 changes: 143 additions & 0 deletions DiscordBot/Services/Tips/TipService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using Discord;
using Discord.WebSocket;
using DiscordBot.Services.Tips.Components;
using DiscordBot.Settings;
using Newtonsoft.Json;

namespace DiscordBot.Services.Tips;

public class TipService
{
private const string ServiceName = "TipService";

private readonly BotSettings _settings;
private readonly ILoggingService _loggingService;
private readonly string _imageDirectory;

private ConcurrentDictionary<string, List<Tip>> _tips = new();
private bool _isRunning = false;

public TipService(BotSettings settings, ILoggingService loggingService)
{
_settings = settings;
_loggingService = loggingService;

if (string.IsNullOrEmpty(_settings.TipImageDirectory))

Check failure on line 30 in DiscordBot/Services/Tips/TipService.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipImageDirectory' and no accessible extension method 'TipImageDirectory' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)
{
_loggingService.LogAction($"[{ServiceName}] TipImageDirectory not set, service will not run.", ExtendedLogSeverity.Warning);
_isRunning = false;
return;
}

_imageDirectory = Path.Combine(Directory.GetCurrentDirectory(), _settings.TipImageDirectory);

Check failure on line 37 in DiscordBot/Services/Tips/TipService.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipImageDirectory' and no accessible extension method 'TipImageDirectory' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)

Initialize();
}

private void Initialize()
{
if (_isRunning) return;

if (!Directory.Exists(_imageDirectory))
{
Directory.CreateDirectory(_imageDirectory);
File.WriteAllText(Path.Combine(_imageDirectory, "tips.json"), "{}");
}
else
{
var directorySize = new DirectoryInfo(_imageDirectory).EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(file => file.Length);
if (directorySize > _settings.TipMaxDirectoryFileSize)

Check failure on line 54 in DiscordBot/Services/Tips/TipService.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipMaxDirectoryFileSize' and no accessible extension method 'TipMaxDirectoryFileSize' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)
{
_loggingService.LogAction($"[{ServiceName}] Tip directory size is {directorySize / 1024 / 1024} MB, exceeding the limit of {_settings.TipMaxDirectoryFileSize / 1024 / 1024} MB, no additional content will be added during this session.", ExtendedLogSeverity.Warning);

Check failure on line 56 in DiscordBot/Services/Tips/TipService.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipMaxDirectoryFileSize' and no accessible extension method 'TipMaxDirectoryFileSize' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)
}
else
{
_loggingService.LogAction($"[{ServiceName}] Tip directory size is {directorySize / 1024 / 1024} MB, within the limit of {_settings.TipMaxDirectoryFileSize / 1024 / 1024} MB.", ExtendedLogSeverity.Info);

Check failure on line 60 in DiscordBot/Services/Tips/TipService.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipMaxDirectoryFileSize' and no accessible extension method 'TipMaxDirectoryFileSize' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)
_loggingService.LogAction($"[{ServiceName}] Tip directory contains {new DirectoryInfo(_imageDirectory).EnumerateFiles("*.*", SearchOption.AllDirectories).Count()} files.",
ExtendedLogSeverity.Info);
}

var jsonPath = Path.Combine(_imageDirectory, "tips.json");
if (File.Exists(jsonPath))
{
var json = File.ReadAllText(jsonPath);
_tips = JsonConvert.DeserializeObject<ConcurrentDictionary<string, List<Tip>>>(json);
}
}

_isRunning = true;
}

public async Task AddTip(IUserMessage message, string keywords, string content)
{
var keywordList = keywords.Split(',').Select(k => k.Trim()).ToList();
var imagePaths = new List<string>();

foreach (var attachment in message.Attachments)
{
if (!attachment.Filename.EndsWith(".png") && !attachment.Filename.EndsWith(".webp") && !attachment.Filename.EndsWith(".jpg")) continue;
var newFileName = Guid.NewGuid().ToString() + attachment.Filename.Substring(attachment.Filename.LastIndexOf('.'));
var filePath = Path.Combine(_imageDirectory, newFileName);
if (attachment.Size > _settings.TipMaxImageFileSize)

Check failure on line 86 in DiscordBot/Services/Tips/TipService.cs

View workflow job for this annotation

GitHub Actions / Build & Test

'BotSettings' does not contain a definition for 'TipMaxImageFileSize' and no accessible extension method 'TipMaxImageFileSize' accepting a first argument of type 'BotSettings' could be found (are you missing a using directive or an assembly reference?)
{
continue;
}

using var client = new HttpClient();
await using var stream = await client.GetStreamAsync(attachment.Url);
await using var file = File.Create(filePath);
await stream.CopyToAsync(file);

imagePaths.Add(newFileName);
}

var tip = new Tip
{
Content = content,
Keywords = keywordList,
ImagePaths = imagePaths
};

foreach (var keyword in keywordList)
{
_tips.AddOrUpdate(keyword, new List<Tip> { tip }, (key, list) =>
{
list.Add(tip);
return list;
});
}

// In same folder, we save json files
var jsonPath = Path.Combine(_imageDirectory, "tips.json");
await File.WriteAllTextAsync(jsonPath, JsonConvert.SerializeObject(_tips));

await _loggingService.LogAction($"[{ServiceName}] Added tip from {message.Author.Username} with keywords {string.Join(", ", keywordList)}.", ExtendedLogSeverity.Info);

// Send a confirmation message
if (message.Channel is SocketTextChannel textChannel)
{
var builder = new EmbedBuilder()
.WithTitle("Tip Added")
.WithDescription($"Your tip has been added with the keywords `{string.Join(", ", keywordList)}`.")
.WithColor(Color.Green);

// TODO: (James) Attach the images if they exist?

await textChannel.SendMessageAsync(embed: builder.Build());
}
}

public List<Tip> GetTips(string keyword)
{
var regex = new Regex(keyword, RegexOptions.IgnoreCase);
return _tips.Where(kvp => kvp.Key.Split(',').Any(k => regex.IsMatch(k)))
.SelectMany(kvp => kvp.Value)
.Distinct()
.ToList();
}
}
Loading