diff --git a/Slackord/AppShell.xaml b/Slackord/AppShell.xaml index dc46c29..02e0f8a 100644 --- a/Slackord/AppShell.xaml +++ b/Slackord/AppShell.xaml @@ -5,7 +5,7 @@ xmlns:local="clr-namespace:MenuApp" Shell.FlyoutBehavior="Disabled"> - diff --git a/Slackord/Classes/DiscordBot.cs b/Slackord/Classes/DiscordBot.cs index 5a92cc4..52104d6 100644 --- a/Slackord/Classes/DiscordBot.cs +++ b/Slackord/Classes/DiscordBot.cs @@ -4,49 +4,56 @@ using Discord.Rest; using Discord.WebSocket; using MenuApp; +using System; namespace Slackord.Classes { class DiscordBot { - public DiscordSocketClient _discordClient; + public DiscordSocketClient DiscordClient { get; set; } public IServiceProvider _services; public async Task MainAsync(string discordToken) { - Editor debugWindow = new(); - MainThread.BeginInvokeOnMainThread(() => + if (DiscordClient is not null) { - MainPage.WriteToDebugWindow("Starting Slackord Bot..." + "\n"); - }); - _discordClient = new DiscordSocketClient(); + throw new InvalidOperationException("DiscordClient is already initialized."); + } + //Editor debugWindow = new(); + MainPage.WriteToDebugWindow("Starting Slackord Bot..." + "\n"); + + //DiscordClient = new DiscordSocketClient(); DiscordSocketConfig _config = new(); { _config.GatewayIntents = GatewayIntents.DirectMessages | GatewayIntents.GuildMessages | GatewayIntents.Guilds; } - _discordClient = new(_config); + DiscordClient = new(_config); _services = new ServiceCollection() - .AddSingleton(_discordClient) + .AddSingleton(DiscordClient) .BuildServiceProvider(); - _discordClient.Log += DiscordClient_Log; - await _discordClient.LoginAsync(TokenType.Bot, discordToken.Trim()); - await _discordClient.StartAsync(); - await _discordClient.SetActivityAsync(new Game("for the Slackord command!", ActivityType.Watching)); - _discordClient.Ready += ClientReady; - _discordClient.LoggedOut += OnClientDisconnect; - _discordClient.SlashCommandExecuted += SlashCommandHandler; - await Task.Delay(-1); + DiscordClient.Log += DiscordClient_Log; + await DiscordClient.LoginAsync(TokenType.Bot, discordToken.Trim()); + await DiscordClient.StartAsync(); + await DiscordClient.SetActivityAsync(new Game("for the Slackord command!", ActivityType.Watching)); + DiscordClient.Ready += ClientReady; + DiscordClient.LoggedOut += OnClientDisconnect; + DiscordClient.SlashCommandExecuted += SlashCommandHandler; + //await Task.Delay(-1); } private async Task SlashCommandHandler(SocketSlashCommand command) { - if (command.Data.Name.Equals("slackord")) + if (command.Data.Name.Equals("slackord") && + DiscordClient.Guilds.FirstOrDefault() is { } guild) { - var guildID = _discordClient.Guilds.FirstOrDefault().Id; - await MainPage.Current.Dispatcher.DispatchAsync(async () => - { - await PostMessagesToDiscord(guildID, command); - }); + var guildID = guild.Id; + //TODO: Do we actually need to dispatch to the UI thread here? + //await MainPage.Current.Dispatcher.DispatchAsync(async () => + //{ + // await PostMessagesToDiscord(guildID, command); + //}); + await PostMessagesToDiscord(guildID, command); + } } @@ -61,7 +68,7 @@ private async Task ClientReady() MainPage.BotConnectionButtonInstance.BackgroundColor = new Microsoft.Maui.Graphics.Color(0, 255, 0); }); - foreach (var guild in _discordClient.Guilds) + foreach (var guild in DiscordClient.Guilds) { var guildCommand = new SlashCommandBuilder(); guildCommand.WithName("slackord"); @@ -101,7 +108,7 @@ private Task DiscordClient_Log(LogMessage arg) public async Task DisconectClient() { await MainPage.ChangeBotConnectionButton("Disconnecting", new Microsoft.Maui.Graphics.Color(255, 204, 0), new Microsoft.Maui.Graphics.Color(0, 0, 0)); - await _discordClient.StopAsync(); + await DiscordClient.StopAsync(); await MainPage.ToggleBotTokenEnable(true, new Microsoft.Maui.Graphics.Color(255, 69, 0)); await MainPage.ChangeBotConnectionButton("Disconnected", new Microsoft.Maui.Graphics.Color(255, 0, 0), new Microsoft.Maui.Graphics.Color(255, 255, 255)); await Task.CompletedTask; @@ -131,7 +138,7 @@ public async Task PostMessagesToDiscord(ulong guildID, SocketInteraction interac await interaction.DeferAsync(); - SocketGuild guild = _discordClient.GetGuild(guildID); + SocketGuild guild = DiscordClient.GetGuild(guildID); string categoryName = "Slackord Import"; var slackordCategory = await guild.CreateCategoryChannelAsync(categoryName); ulong slackordCategoryId = slackordCategory.Id; @@ -154,7 +161,7 @@ public async Task PostMessagesToDiscord(ulong guildID, SocketInteraction interac try { - await _discordClient.SetActivityAsync(new Game("messages...", ActivityType.Streaming)); + await DiscordClient.SetActivityAsync(new Game("messages...", ActivityType.Streaming)); int messageCount = 0; if (channels.TryGetValue(channelName, out var messages)) @@ -209,7 +216,7 @@ public async Task PostMessagesToDiscord(ulong guildID, SocketInteraction interac if (sendAsThread) { - if (_discordClient.GetChannel(createdChannelId) is SocketTextChannel textChannel) + if (DiscordClient.GetChannel(createdChannelId) is SocketTextChannel textChannel) { await textChannel.SendMessageAsync(messageToSend).ConfigureAwait(false); var latestMessages = await textChannel.GetMessagesAsync(1).FlattenAsync(); @@ -228,12 +235,16 @@ public async Task PostMessagesToDiscord(ulong guildID, SocketInteraction interac { MainPage.WriteToDebugWindow("Caught a Slackdump thread reply exception where a JSON entry had thread_ts and wasn't actually a thread start or reply before it excepted. Sending as a normal message..."); }); - await _discordClient.GetGuild(guildID).GetTextChannel(createdChannelId).SendMessageAsync(messageToSend).ConfigureAwait(false); + await DiscordClient.GetGuild(guildID).GetTextChannel(createdChannelId).SendMessageAsync(messageToSend).ConfigureAwait(false); } } else if (sendAsNormalMessage) { - await _discordClient.GetGuild(guildID).GetTextChannel(createdChannelId).SendMessageAsync(messageToSend).ConfigureAwait(false); + if (DiscordClient.GetGuild(guildID).GetTextChannel(createdChannelId) is { } channel) + { + await channel.SendMessageAsync(messageToSend).ConfigureAwait(false); + } + //TODO: else? Do we care? } progress += 1; @@ -290,7 +301,7 @@ public async Task PostMessagesToDiscord(ulong guildID, SocketInteraction interac } } } - await _discordClient.SetActivityAsync(new Game("for the Slackord command...", ActivityType.Listening)); + await DiscordClient.SetActivityAsync(new Game("for the Slackord command...", ActivityType.Listening)); } catch (Exception ex) { @@ -298,8 +309,8 @@ public async Task PostMessagesToDiscord(ulong guildID, SocketInteraction interac { MainPage.WriteToDebugWindow($"\n{ex.Message}\n"); }); - Page page = new(); - await page.DisplayAlert("Error", ex.Message, "OK"); + //Page page = new(); + //await page.DisplayAlert("Error", ex.Message, "OK"); } } @@ -309,8 +320,7 @@ public async Task PostMessagesToDiscord(ulong guildID, SocketInteraction interac }); await interaction.FollowupAsync("All messages sent to Discord successfully!", ephemeral: true); - await _discordClient.SetActivityAsync(new Game("to some cool music!", ActivityType.Listening)); - await Task.CompletedTask; + await DiscordClient.SetActivityAsync(new Game("to some cool music!", ActivityType.Listening)); } } } diff --git a/Slackord/Classes/ImportJson.cs b/Slackord/Classes/ImportJson.cs index 0c446ef..bbd395b 100644 --- a/Slackord/Classes/ImportJson.cs +++ b/Slackord/Classes/ImportJson.cs @@ -4,7 +4,7 @@ namespace Slackord.Classes { - class ImportJson + static class ImportJson { public static readonly Dictionary> Channels = new(); @@ -21,40 +21,31 @@ public static async Task ImportJsonFolder(CancellationToken cancellationToken) } selectedFolder = result.Folder.Path; - subDirectories = Directory.GetDirectories(selectedFolder).ToList(); + //subDirectories = Directory.GetDirectories(selectedFolder).ToList(); - int folderCount = subDirectories.Count; + //int folderCount = subDirectories.Count; int fileCount = 0; - foreach (var subDir in subDirectories) - { - var folderName = Path.GetFileName(subDir); - var files = Directory.EnumerateFiles(subDir, "*.*", SearchOption.TopDirectoryOnly) - .Where(s => s.EndsWith(".JSON", StringComparison.OrdinalIgnoreCase)); - - fileCount += files.Count(); - } + //foreach (var subDir in subDirectories) + //{ + // var folderName = Path.GetFileName(subDir); + // var files = Directory.EnumerateFiles(subDir, "*.*", SearchOption.TopDirectoryOnly) + // .Where(s => s.EndsWith(".JSON", StringComparison.OrdinalIgnoreCase)); - await MainPage.Current.DisplayAlert("Information", $"Found {fileCount} JSON files in {folderCount} folders.", "OK"); + // fileCount += files.Count(); + //} + List fileList = new(); - foreach (var subDir in subDirectories) + foreach (var file in Directory.EnumerateFiles(selectedFolder, "*.json", SearchOption.AllDirectories)) { - var folderName = Path.GetFileName(subDir); - var files = Directory.EnumerateFiles(subDir, "*.*", SearchOption.TopDirectoryOnly) - .Where(s => s.EndsWith(".JSON", StringComparison.OrdinalIgnoreCase)); - - // Create a list to store the files for the channel - List fileList = new(); + fileCount++; + var folderName = Path.GetFileName(Path.GetDirectoryName(file)); - foreach (var file in files) + string fileName = Path.GetFileNameWithoutExtension(file); + if (DateTime.TryParseExact(fileName, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var fileDate)) { - string fileName = Path.GetFileNameWithoutExtension(file); - if (DateTime.TryParseExact(fileName, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var fileDate)) - { - fileList.Add(file); - } + fileList.Add(file); } - if (fileList.Count > 0) { // Add the channel and its file list to the channels dictionary @@ -65,10 +56,48 @@ public static async Task ImportJsonFolder(CancellationToken cancellationToken) await parser.ParseJsonFiles(fileList, folderName, Channels); } } + int folderCount = Channels.Keys.Count; + await MainThread.InvokeOnMainThreadAsync(async () => + { + await MainPage.Current.DisplayAlert("Information", $"Found {fileCount} JSON files in {folderCount} folders.", "OK"); + }); + MainPage.PushDebugText(); + + //foreach (var subDir in subDirectories) + //{ + // var folderName = Path.GetFileName(subDir); + // var files = Directory.EnumerateFiles(subDir, "*.*", SearchOption.TopDirectoryOnly) + // .Where(s => s.EndsWith(".JSON", StringComparison.OrdinalIgnoreCase)); + // + // // Create a list to store the files for the channel + // List fileList = new(); + // + // foreach (var file in files) + // { + // string fileName = Path.GetFileNameWithoutExtension(file); + // if (DateTime.TryParseExact(fileName, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var fileDate)) + // { + // fileList.Add(file); + // } + // } + // + // if (fileList.Count > 0) + // { + // // Add the channel and its file list to the channels dictionary + // Channels[folderName] = fileList; + // + // // Parse JSON files for the channel + // var parser = new Parser(); + // await parser.ParseJsonFiles(fileList, folderName, Channels); + // } + //} } catch (Exception ex) { + //writeLog($"\n\n{ex.Message}\n\n"); MainPage.WriteToDebugWindow($"\n\n{ex.Message}\n\n"); + //TODO + //MainPage.DisplayAlert Page page = new(); await page.DisplayAlert("Error", ex.Message, "OK"); } diff --git a/Slackord/Classes/Parser.cs b/Slackord/Classes/Parser.cs index 9de1951..6e31056 100644 --- a/Slackord/Classes/Parser.cs +++ b/Slackord/Classes/Parser.cs @@ -1,23 +1,24 @@ using MenuApp; using Newtonsoft.Json.Linq; +using System.Globalization; namespace Slackord.Classes { class Parser { - public bool _isFileParsed; + //public bool _isFileParsed; public static readonly List isThreadMessages = new(); public static readonly List isThreadStart = new(); public static int TotalMessageCount; - public async Task ParseJsonFiles(List files, string channelName, Dictionary> channels) + public async Task ParseJsonFiles(IEnumerable files, string channelName, Dictionary> channels) { - string character = "⬓"; + const char character = '⬓'; string parsingLine = $"Begin parsing JSON data for {channelName}..."; int lineLength = parsingLine.Length / 2 + 1; int boxCharacters = lineLength + 6; - string boxOutput = new(character[0], boxCharacters); + string boxOutput = new(character, boxCharacters); string leadingSpaces = new(' ', (boxCharacters - lineLength - 2) / 2); MainPage.WriteToDebugWindow($"{boxOutput}\n⬓ {leadingSpaces}{parsingLine}{leadingSpaces} ⬓\n{boxOutput}\n"); @@ -37,25 +38,9 @@ public async Task ParseJsonFiles(List files, string channelName, Diction { var rawTimeDate = pair["ts"]; double oldDateTime = (double)rawTimeDate; - string convertDateTime = Helpers.ConvertFromUnixTimestampToHumanReadableTime(oldDateTime).ToString("g"); - string newDateTime = convertDateTime.ToString(); - - // JSON message thread handling. - if (pair.ContainsKey("reply_count") && pair.ContainsKey("thread_ts")) - { - isThreadStart.Add(true); - isThreadMessages.Add(false); - } - else if (pair.ContainsKey("thread_ts") && !pair.ContainsKey("reply_count")) - { - isThreadStart.Add(false); - isThreadMessages.Add(true); - } - else - { - isThreadStart.Add(false); - isThreadMessages.Add(false); - } + string convertDateTime = Helpers.ConvertFromUnixTimestampToHumanReadableTime(oldDateTime).ToString("g", CultureInfo.CurrentUICulture); + string newDateTime = convertDateTime; + ParseThreads(pair); // JSON message parsing. if (pair.ContainsKey("text") && !pair.ContainsKey("bot_profile")) @@ -121,9 +106,10 @@ The following parse is over 2000 characters. Discord does not allow messages ove if (pair.ContainsKey("bot_profile")) { + //TODO: Clean up try/catch blocks. try { - currentMessageParsing = pair["bot_profile"]["name"].ToString() + ": " + pair["text"] + "\n"; + currentMessageParsing = pair["bot_profile"]?["name"]?.ToString() + ": " + pair["text"] + "\n"; parsedMessages.Add(currentMessageParsing); TotalMessageCount += 1; } @@ -137,6 +123,7 @@ The following parse is over 2000 characters. Discord does not allow messages ove } catch (NullReferenceException) { + //TODO: Include details on which message was ignored. currentMessageParsing = "A bot message was ignored. Please submit an issue on Github for this."; } } @@ -146,24 +133,44 @@ The following parse is over 2000 characters. Discord does not allow messages ove } channels[channelName] = parsedMessages; - character = "⬓"; parsingLine = $"Parsing for [{channelName}]>[{currentFile}] complete!"; lineLength = parsingLine.Length / 2 + 1; boxCharacters = lineLength + 6; - boxOutput = new(character[0], boxCharacters); + boxOutput = new(character, boxCharacters); leadingSpaces = new(' ', (boxCharacters - lineLength - 2) / 2); - MainPage.WriteToDebugWindow($"{boxOutput}\n⬓ {leadingSpaces}{parsingLine}{leadingSpaces} ⬓\n{boxOutput}\n\n"); + MainPage.WriteToDebugWindow($"{boxOutput}\n{character} {leadingSpaces}{parsingLine}{leadingSpaces} {character}\n{boxOutput}\n\n"); - _isFileParsed = true; + //_isFileParsed = true; } catch (Exception ex) { MainPage.WriteToDebugWindow($"\n\n{ex.Message}\n\n"); - Page page = new(); - await page.DisplayAlert("Error", ex.Message, "OK"); + //TODO MainPage.DisplayAlert + //Page page = new(); + //await page.DisplayAlert("Error", ex.Message, "OK"); + } + } + + private static void ParseThreads(JObject pair) + { + + // JSON message thread handling. + if (pair.ContainsKey("reply_count") && pair.ContainsKey("thread_ts")) + { + isThreadStart.Add(true); + isThreadMessages.Add(false); + } + else if (pair.ContainsKey("thread_ts") && !pair.ContainsKey("reply_count")) + { + isThreadStart.Add(false); + isThreadMessages.Add(true); + } + else + { + isThreadStart.Add(false); + isThreadMessages.Add(false); } - await Task.CompletedTask; } } } diff --git a/Slackord/MainPage.xaml b/Slackord/MainPage.xaml index b20a403..8528764 100644 --- a/Slackord/MainPage.xaml +++ b/Slackord/MainPage.xaml @@ -27,12 +27,12 @@ -