2021-12-15 01:37:03 +00:00
using System.Diagnostics ;
using System.Text ;
2021-12-18 02:35:21 +00:00
using System.Text.RegularExpressions ;
2021-12-15 01:37:03 +00:00
using Discord ;
using Discord.WebSocket ;
using Microsoft.Extensions.Configuration ;
using RestSharp ;
using Terminal.Gui ;
2021-12-16 22:58:32 +00:00
using Terminal.Gui.Trees ;
2021-12-15 01:37:03 +00:00
using Attribute = Terminal . Gui . Attribute ;
using Color = Terminal . Gui . Color ;
namespace chord
{
public class Program
{
private ListView chatBoxList ;
private List < string > currentChannelMessages ;
private ulong currentSelectedChannel ;
private ulong currentSelectedGuild ;
private SortedDictionary < ulong , List < ulong > > guilds ;
private Settings settings ;
private Window window ;
public static void Main ( )
{
new Program ( ) . Start ( ) ;
}
private async void Start ( )
{
2021-12-18 02:35:21 +00:00
try {
2021-12-15 01:37:03 +00:00
#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 < Settings > ( ) ;
var client = new DiscordSocketClient ( new DiscordSocketConfig
{ AlwaysDownloadUsers = true , GatewayIntents = GatewayIntents . All } ) ;
await client . LoginAsync ( TokenType . Bot , settings . Token ) ;
await client . StartAsync ( ) ;
Application . Init ( ) ;
window = new Window ( "chord" )
{
X = 0 ,
Y = 1 ,
Width = Dim . Fill ( ) ,
Height = Dim . Fill ( ) ,
ColorScheme =
{
Normal = new Attribute ( Color . White , Color . Black )
}
} ;
client . Ready + = ( ) = >
{
guilds = new SortedDictionary < ulong , List < ulong > > ( ) ;
foreach ( var guild in client . Guilds )
{
guilds . Add ( guild . Id , new List < ulong > ( ) ) ;
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 menuBar = buildMenu ( ) ;
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 ) ;
2021-12-16 22:58:32 +00:00
var channelListTree = new TreeView
2021-12-15 01:37:03 +00:00
{
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
} ;
2021-12-17 01:44:44 +00:00
var userListTree = new TreeView
2021-12-15 01:37:03 +00:00
{
X = 0 ,
Y = 0 ,
Width = Dim . Fill ( ) ,
Height = Dim . Fill ( )
} ;
serverListList . OpenSelectedItem + = args = >
{
2021-12-18 02:35:21 +00:00
channelListTree . ClearObjects ( ) ;
2021-12-15 01:37:03 +00:00
currentSelectedGuild = guilds . Keys . ToList ( ) [ args . Item ] ;
var channelNames = guilds . GetValueOrDefault ( currentSelectedGuild ) !
. Select ( channel = > client . GetGuild ( currentSelectedGuild ) . GetTextChannel ( channel ) . Name ) . ToList ( ) ;
2021-12-16 22:58:32 +00:00
var categoryDict = new SortedDictionary < ulong , TreeNode > ( ) ;
foreach ( var categories in client . GetGuild ( currentSelectedGuild ) . CategoryChannels )
{
2021-12-18 02:35:21 +00:00
var categoryName = Regex . Replace ( categories . Name , @"[^\u0000-\u007F]+" , string . Empty ) ;
var node = new TreeNode ( categoryName ) ;
2021-12-16 22:58:32 +00:00
foreach ( var channel in categories . Channels )
{
if ( channel . GetType ( ) . Name = = "SocketTextChannel" )
{
2021-12-18 02:35:21 +00:00
var channelName = Regex . Replace ( categories . Name , @"[^\u0000-\u007F]+" , string . Empty ) ;
var channelNode = new TreeNode ( channelName ) ;
2021-12-16 22:58:32 +00:00
channelNode . Tag = new { Id = channel . Id } ;
node . Children . Add ( channelNode ) ;
}
}
categoryDict . Add ( categories . Id , node ) ;
}
foreach ( var category in categoryDict )
{
channelListTree . AddObject ( category . Value ) ;
}
2021-12-15 01:37:03 +00:00
} ;
2021-12-16 22:58:32 +00:00
channelListTree . SelectionChanged + = async ( arg1 , arg2 ) = >
2021-12-15 01:37:03 +00:00
{
2021-12-18 02:35:21 +00:00
userListTree . ClearObjects ( ) ;
2021-12-16 22:58:32 +00:00
try {
currentSelectedChannel = ( ulong ) arg2 . NewValue . Tag . GetType ( ) . GetProperty ( "Id" ) . GetValue ( arg2 . NewValue . Tag ) ;
} catch {
return ;
}
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
try {
var restSharpClient = new RestClient ( "https://discord.com/api/v9" ) ;
var restSharpReq = new RestRequest ( $"channels/{currentSelectedChannel}/messages" ) ;
restSharpClient . AddDefaultHeader ( "Authorization" , settings . Token ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
var response = await restSharpClient . GetAsync < List < GetMessagesResponse > > ( restSharpReq ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
var messages = new List < string > ( ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
foreach ( var msg in response )
{
var msgNewLines = msg . content . Split ( "\n" ) . ToList ( ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
var firstMsg = msgNewLines [ 0 ] ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
msgNewLines . RemoveAt ( 0 ) ;
msgNewLines . Reverse ( ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
messages . AddRange ( msgNewLines . Select ( message = > $"{message}" ) ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
messages . Add ( $"{msg.author.username}#{msg.author.discriminator} | {firstMsg}" ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
if ( msg . embeds . Count ! = 0 )
messages . Add ( $"{msg.author.username}#{msg.author.discriminator} | [Unable to display embed]" ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
if ( msg . attachments . Count = = 0 ) continue ;
messages . AddRange ( msg . attachments
. Select ( attachment = > $"{msg.author.username}#{msg.author.discriminator} | {attachment.url}" )
. Select ( dummy = > dummy ) ) ;
}
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
messages . Reverse ( ) ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
await chatBoxList . SetSourceAsync ( messages ) ;
currentChannelMessages = messages ;
2021-12-15 01:37:03 +00:00
2021-12-18 02:39:16 +00:00
chatBoxList . ScrollDown ( currentChannelMessages . Count - chatBoxList . Bounds . Height ) ;
chatBoxList . SelectedItem = currentChannelMessages . Count - 1 ;
2021-12-17 01:44:44 +00:00
2021-12-18 02:39:16 +00:00
var rolesDict = new SortedDictionary < int , TreeNode > ( ) ;
2021-12-17 01:44:44 +00:00
2021-12-18 02:39:16 +00:00
foreach ( var roles in client . GetGuild ( currentSelectedGuild ) . Roles )
2021-12-17 01:44:44 +00:00
{
2021-12-18 02:39:16 +00:00
var roleName = Regex . Replace ( roles . Name , @"[^\u0000-\u007F]+" , string . Empty ) ;
var node = new TreeNode ( roleName ) ;
2021-12-17 01:44:44 +00:00
2021-12-18 02:39:16 +00:00
foreach ( var users in roles . Members )
{
node . Children . Add ( new TreeNode ( $"{users.Username}#{users.Discriminator} ({users.Nickname})" ) ) ;
}
2021-12-17 01:44:44 +00:00
2021-12-18 02:39:16 +00:00
rolesDict . Add ( roles . Position , node ) ;
}
foreach ( var role in rolesDict )
{
userListTree . AddObject ( role . Value ) ;
}
} catch {
await chatBoxList . SetSourceAsync ( new List < string > ( ) ) ;
2021-12-17 01:44:44 +00:00
}
2021-12-15 01:37:03 +00:00
} ;
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 < List < object > > ( restSharpReq ) ;
messageBoxText . Text = "" ;
} ;
chatBoxList . OpenSelectedItem + = args = >
{
if ( currentChannelMessages [ args . Item ] . Split ( " | " ) [ 1 ] . Contains ( "http" ) )
2021-12-15 02:04:22 +00:00
for ( var i = 0 ; i < currentChannelMessages [ args . Item ] . Split ( " | " ) [ 1 ] . Split ( " " ) . Count ( ) ; i + + )
2021-12-15 01:37:03 +00:00
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 ( ) ;
}
} ;
2021-12-16 19:23:54 +00:00
Application . Top . KeyPress + = async args = >
{
switch ( ShortcutHelper . GetModifiersKey ( args . KeyEvent ) ) {
case Key . CtrlMask | Key . S :
serverListList . SetFocus ( ) ;
break ;
case Key . CtrlMask | Key . C :
2021-12-16 22:58:32 +00:00
channelListTree . SetFocus ( ) ;
2021-12-16 19:23:54 +00:00
break ;
case Key . CtrlMask | Key . AltMask | Key . C :
chatBoxList . SetFocus ( ) ;
break ;
case Key . CtrlMask | Key . U :
2021-12-17 01:44:44 +00:00
userListTree . SetFocus ( ) ;
2021-12-16 19:23:54 +00:00
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 < List < object > > ( restSharpReq ) ;
messageBoxText . Text = "" ;
break ;
}
} ;
2021-12-15 01:37:03 +00:00
Application . Top . Add ( window , menuBar ) ;
window . Add ( serverList , channelList , messageBox , chatBox , userList ) ;
serverList . Add ( serverListList ) ;
2021-12-16 22:58:32 +00:00
channelList . Add ( channelListTree ) ;
2021-12-15 01:37:03 +00:00
chatBox . Add ( chatBoxList ) ;
messageBox . Add ( messageBoxText , messageBoxSend ) ;
2021-12-17 01:44:44 +00:00
userList . Add ( userListTree ) ;
2021-12-15 01:37:03 +00:00
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 < List < GetMessagesResponse > > ( 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 = > true ) ;
Application . Run ( ) ;
Application . Shutdown ( ) ;
2021-12-18 02:35:21 +00:00
} catch ( System . Exception err ) {
Console . WriteLine ( err ) ;
}
2021-12-15 01:37:03 +00:00
}
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 [ ]
{
2021-12-16 20:56:44 +00:00
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" ) ;
} )
2021-12-15 01:37:03 +00:00
} )
} ) ;
}
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 class GetMessagesResponse
{
public string content { get ; set ; }
public MessageAuthor author { get ; set ; }
public List < object > embeds { get ; set ; }
public List < MessageAttachments > attachments { get ; set ; }
}
public class MessageAuthor
{
public string username { get ; set ; }
public string discriminator { get ; set ; }
}
public class MessageAttachments
{
public string url { get ; set ; }
}