From f14b0034b5a86461a1fedb1b64a07397f28640e0 Mon Sep 17 00:00:00 2001 From: Daryl Ronningen Date: Wed, 22 Dec 2021 23:56:13 -0700 Subject: [PATCH] feat: added loading screen --- Program.cs | 20 ++- TODO | 2 +- ] | 482 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 501 insertions(+), 3 deletions(-) create mode 100644 ] diff --git a/Program.cs b/Program.cs index 76c86b6..7ee89f2 100644 --- a/Program.cs +++ b/Program.cs @@ -71,6 +71,18 @@ namespace chord } }; + var menuBar = buildMenu(); + + var loadingLabel = new Label("Loading...") + { + X = 0, + Y = 0, + Width = Dim.Fill(), + Height = Dim.Fill(), + TextAlignment = TextAlignment.Centered, + VerticalTextAlignment = VerticalTextAlignment.Middle, + }; + client.Ready += () => { guilds = new SortedDictionary>(); @@ -91,7 +103,6 @@ namespace chord } } - var menuBar = buildMenu(); var serverList = buildServerList(); var channelList = buildChannelList(serverList); var chatBox = buildChatBox(serverList); @@ -328,7 +339,7 @@ namespace chord } }; - Application.Top.Add(window, menuBar); + window.RemoveAll(); window.Add(serverList, channelList, messageBox, chatBox, userList); serverList.Add(serverListList); channelList.Add(channelListTree); @@ -364,10 +375,15 @@ namespace chord return true; }); + Application.Top.Add(window, menuBar); + window.Add(loadingLabel); + Application.Run(); Application.Shutdown(); } catch (System.Exception err) { Console.WriteLine(err); + + Application.Shutdown(); } } diff --git a/TODO b/TODO index cb488d4..55c96d6 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,6 @@ - Convert message attachments to links (DONE) - Sort Channels by catagories (DONE) - DMs - - Loading Screen + - Loading Screen (DONE) - Hotkeys (Partially Done. Still some weird stuff that happens that idfk why happens) - User roles sorting (DONE) diff --git a/] b/] new file mode 100644 index 0000000..6b5149a --- /dev/null +++ b/] @@ -0,0 +1,482 @@ +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using Discord; +using Discord.WebSocket; +using DiscordRPC; +using Microsoft.Extensions.Configuration; +using RestSharp; +using Terminal.Gui; +using Terminal.Gui.Trees; +using Attribute = Terminal.Gui.Attribute; +using Button = Terminal.Gui.Button; +using Color = Terminal.Gui.Color; + +namespace chord +{ + public class Program + { + private ListView chatBoxList; + private List currentChannelMessages; + private ulong currentSelectedChannel; + private ulong currentSelectedGuild; + private SortedDictionary> guilds; + private DiscordRpcClient rpcClient; + private Settings settings; + private Window window; + + public static void Main() + { + new Program().Start(); + } + + private async void Start() + { + try { +#if DEBUG + var config = new ConfigurationBuilder() + .AddJsonFile("config.json") + .Build(); +#else + var config = new ConfigurationBuilder() + .AddJsonFile($"{Environment.GetEnvironmentVariable("HOME")}/.config/chord/config.json") + .Build(); +#endif + + settings = config.Get(); + + var client = new DiscordSocketClient(new DiscordSocketConfig + { AlwaysDownloadUsers = true, GatewayIntents = GatewayIntents.All }); + + await client.LoginAsync(TokenType.Bot, settings.Token); + await client.StartAsync(); + + if (settings.EnableRichPresence) { + rpcClient = new DiscordRpcClient("923436807297859625"); + rpcClient.Initialize(); + rpcClient.SetPresence(new RichPresence()); + } + + Application.Init(); + + window = new Window("chord") + { + X = 0, + Y = 1, + Width = Dim.Fill(), + Height = Dim.Fill(), + ColorScheme = + { + Normal = new Attribute(Color.White, Color.Black) + } + }; + + var menuBar = buildMenu(); + + var loadingLabel = new Label("Loading...") + { + X = 0, + Y = 0, + Width = Dim.Percent(50), + Height = Dim.Height(50), + }; + + client.Ready += () => + { + guilds = new SortedDictionary>(); + + foreach (var guild in client.Guilds) + { + guilds.Add(guild.Id, new List()); + + foreach (var channel in guild.TextChannels) + { + var findGuild = guilds.GetValueOrDefault(guild.Id); + + if (channel.Users.ToList().Find(user => user.Id == client.CurrentUser.Id) == null) continue; + if (findGuild == null) continue; + findGuild.Add(channel.Id); + + findGuild.Sort(); + } + } + + var serverList = buildServerList(); + var channelList = buildChannelList(serverList); + var chatBox = buildChatBox(serverList); + var messageBox = buildMessageBox(serverList, chatBox); + var userList = buildUserList(chatBox); + + var serverListList = new ListView + { + X = 0, + Y = 0, + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + var guildNames = guilds.Select(guild => client.GetGuild(guild.Key).Name).ToList(); + + serverListList.SetSource(guildNames); + + var channelListTree = new TreeView + { + X = 0, + Y = 0, + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + chatBoxList = new ListView + { + X = 0, + Y = 0, + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + var messageBoxText = new TextView + { + X = 0, + Y = 0, + Width = Dim.Percent(95), + Height = Dim.Fill() + }; + + var messageBoxSend = new Button("Send", true) + { + X = Pos.Right(messageBoxText) - 2, + Y = 0 + }; + + var userListTree = new TreeView + { + X = 0, + Y = 0, + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + serverListList.OpenSelectedItem += args => + { + channelListTree.ClearObjects(); + + currentSelectedGuild = guilds.Keys.ToList()[args.Item]; + + var channelNames = guilds.GetValueOrDefault(currentSelectedGuild)! + .Select(channel => client.GetGuild(currentSelectedGuild).GetTextChannel(channel).Name).ToList(); + + var categoryDict = new SortedDictionary(); + + foreach (var categories in client.GetGuild(currentSelectedGuild).CategoryChannels) + { + var categoryName = Regex.Replace(categories.Name, @"[^\u0000-\u007F]+", string.Empty); + var node = new TreeNode(categoryName); + + foreach(var channel in categories.Channels) + { + if (channel.GetType().Name == "SocketTextChannel") + { + var channelName = Regex.Replace(channel.Name, @"[^\u0000-\u007F]+", string.Empty); + var channelNode = new TreeNode(channelName); + channelNode.Tag = new { Id = channel.Id }; + + node.Children.Add(channelNode); + } + } + + categoryDict.Add(categories.Id, node); + } + + foreach (var category in categoryDict) + { + channelListTree.AddObject(category.Value); + } + + if (settings.EnableRichPresence) { + rpcClient.UpdateDetails($"Chatting in {client.GetGuild(currentSelectedGuild).Name}"); + } + }; + + channelListTree.SelectionChanged += async (arg1, arg2) => + { + userListTree.ClearObjects(); + + try { + currentSelectedChannel = (ulong)arg2.NewValue.Tag.GetType().GetProperty("Id").GetValue(arg2.NewValue.Tag); + } catch { + return; + } + + try { + var restSharpClient = new RestClient("https://discord.com/api/v9"); + var restSharpReq = new RestRequest($"channels/{currentSelectedChannel}/messages"); + restSharpClient.AddDefaultHeader("Authorization", settings.Token); + + var response = await restSharpClient.GetAsync>(restSharpReq); + + var messages = new List(); + + foreach (var msg in response) + { + var msgNewLines = msg.content.Split("\n").ToList(); + + var firstMsg = msgNewLines[0]; + + msgNewLines.RemoveAt(0); + msgNewLines.Reverse(); + + messages.AddRange(msgNewLines.Select(message => $"{message}")); + + messages.Add($"{msg.author.username}#{msg.author.discriminator} | {firstMsg}"); + + if (msg.embeds.Count != 0) + messages.Add($"{msg.author.username}#{msg.author.discriminator} | [Unable to display embed]"); + + if (msg.attachments.Count == 0) continue; + messages.AddRange(msg.attachments + .Select(attachment => $"{msg.author.username}#{msg.author.discriminator} | {attachment.url}") + .Select(dummy => dummy)); + } + + messages.Reverse(); + + await chatBoxList.SetSourceAsync(messages); + currentChannelMessages = messages; + + chatBoxList.ScrollDown(currentChannelMessages.Count - chatBoxList.Bounds.Height); + chatBoxList.SelectedItem = currentChannelMessages.Count - 1; + + var rolesDict = new SortedDictionary(); + + foreach (var roles in client.GetGuild(currentSelectedGuild).Roles) + { + var roleName = Regex.Replace(roles.Name, @"[^\u0000-\u007F]+", string.Empty); + var node = new TreeNode(roleName); + + foreach (var users in roles.Members) + { + node.Children.Add(new TreeNode($"{users.Username}#{users.Discriminator} ({users.Nickname})")); + } + + rolesDict.Add(roles.Position, node); + } + + foreach (var role in rolesDict) + { + userListTree.AddObject(role.Value); + } + + if (settings.EnableRichPresence) { + rpcClient.UpdateState($"In channel {client.GetGuild(currentSelectedGuild).GetTextChannel(currentSelectedChannel).Name}"); + rpcClient.UpdateStartTime(); + } + } catch { + await chatBoxList.SetSourceAsync(new List()); + } + }; + + messageBoxSend.Clicked += async () => + { + var restSharpClient = new RestClient("https://discord.com/api/v9"); + var restSharpReq = new RestRequest($"channels/{currentSelectedChannel}/messages"); + restSharpClient.AddDefaultHeader("Authorization", settings.Token); + restSharpReq.AddJsonBody(new { content = Encoding.UTF8.GetString(messageBoxText.Text.ToByteArray()) }); + + await restSharpClient.PostAsync>(restSharpReq); + + messageBoxText.Text = ""; + }; + + chatBoxList.OpenSelectedItem += args => + { + if (currentChannelMessages[args.Item].Split(" | ")[1].Contains("http")) + for (var i = 0; i < currentChannelMessages[args.Item].Split(" | ")[1].Split(" ").Count(); i++) + if (currentChannelMessages[args.Item].Split(" | ")[1].Split(" ")[i].Contains("http")) + { + var procStartInfo = new ProcessStartInfo("/bin/sh", + $"-c \"xdg-open {currentChannelMessages[args.Item].Split(" | ")[1].Split(" ")[i]}\"") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false + }; + + var proc = new Process(); + proc.StartInfo = procStartInfo; + proc.Start(); + } + }; + + Application.Top.KeyPress += async args => + { + switch (ShortcutHelper.GetModifiersKey(args.KeyEvent)) { + case Key.CtrlMask | Key.S: + serverListList.SetFocus(); + break; + case Key.CtrlMask | Key.C: + channelListTree.SetFocus(); + break; + case Key.CtrlMask | Key.AltMask | Key.C: + chatBoxList.SetFocus(); + break; + case Key.CtrlMask | Key.U: + userListTree.SetFocus(); + break; + case Key.CtrlMask | Key.Enter: + var restSharpClient = new RestClient("https://discord.com/api/v9"); + var restSharpReq = new RestRequest($"channels/{currentSelectedChannel}/messages"); + restSharpClient.AddDefaultHeader("Authorization", settings.Token); + restSharpReq.AddJsonBody(new { content = Encoding.UTF8.GetString(messageBoxText.Text.ToByteArray()) }); + + await restSharpClient.PostAsync>(restSharpReq); + + messageBoxText.Text = ""; + break; + } + }; + + window.RemoveAll(); + window.Add(serverList, channelList, messageBox, chatBox, userList); + serverList.Add(serverListList); + channelList.Add(channelListTree); + chatBox.Add(chatBoxList); + messageBox.Add(messageBoxText, messageBoxSend); + userList.Add(userListTree); + + return Task.CompletedTask; + }; + + client.MessageReceived += async msg => + { + if (msg.Channel.Id != currentSelectedChannel) return; + + var restSharpClient = new RestClient("https://discord.com/api/v9"); + var restSharpReq = new RestRequest($"channels/{currentSelectedChannel}/messages"); + restSharpReq.AddQueryParameter("limit", "1"); + restSharpClient.AddDefaultHeader("Authorization", settings.Token); + + var response = await restSharpClient.GetAsync>(restSharpReq); + + currentChannelMessages.Add( + $"{response.First().author.username}#{response.First().author.discriminator} | {response.First().content}"); + await chatBoxList.SetSourceAsync(currentChannelMessages); + + chatBoxList.ScrollDown(currentChannelMessages.Count - chatBoxList.Bounds.Height); + chatBoxList.SelectedItem = currentChannelMessages.Count - 1; + }; + + Application.MainLoop.AddTimeout(TimeSpan.FromMilliseconds(100), caller => { + if (settings.EnableRichPresence) + rpcClient.Invoke(); + return true; + }); + + Application.Top.Add(window, menuBar); + + Application.Run(); + Application.Shutdown(); + } catch (System.Exception err) { + Console.WriteLine(err); + } + } + + private Window buildChatBox(Window serverList) + { + return new Window("Chat Box") + { + X = Pos.Right(serverList), + Y = 0, + Width = Dim.Percent(60), + Height = Dim.Percent(90) + }; + } + + private Window buildChannelList(Window serverList) + { + return new Window("Channel List") + { + X = 0, + Y = Pos.Bottom(serverList), + Width = Dim.Percent(30), + Height = Dim.Percent(50) + }; + } + + private MenuBar buildMenu() + { + return new MenuBar(new[] + { + new MenuBarItem("File", new[] + { + new MenuItem("_Quit", "Quit the Application", () => { Application.Top.Running = false; }) + }), + new MenuBarItem("Help", new[] + { + new MenuItem("_License", "See the Application License", () => { + MessageBox.Query("License", "chord Copyright (C) 2021 Daryl Ronningen\nThis program comes with ABSOLUTELY NO WARRANTY; for details see section `15` of the GPL license\nThis is free software, and you are welcome to redistribute it under certain conditions; for details see section `2` of the GPL license", "Close"); + }) + }) + }); + } + + private Window buildMessageBox(Window serverList, Window chatBox) + { + return new Window("Message Box") + { + X = Pos.Right(serverList), + Y = Pos.Bottom(chatBox), + Width = Dim.Percent(60), + Height = Dim.Percent(10) + }; + } + + private Window buildUserList(Window chatBox) + { + return new Window("User List") + { + X = Pos.Right(chatBox), + Y = 0, + Width = Dim.Percent(10), + Height = Dim.Fill() + }; + } + + private Window buildServerList() + { + return new Window("Server List") + { + X = 0, + Y = 0, + Width = Dim.Percent(30), + Height = Dim.Percent(50) + }; + } + } +} + +public class Settings +{ + public string Token { get; set; } + public bool EnableRichPresence { get; set; } +} + +public class GetMessagesResponse +{ + public string content { get; set; } + public MessageAuthor author { get; set; } + public List embeds { get; set; } + public List attachments { get; set; } +} + +public class MessageAuthor +{ + public string username { get; set; } + public string discriminator { get; set; } +} + +public class MessageAttachments +{ + public string url { get; set; } +}