diff --git a/.github/preview.png b/.github/preview.png index a892cd1..df708c0 100644 Binary files a/.github/preview.png and b/.github/preview.png differ diff --git a/README.md b/README.md index cffcf06..f51f28b 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ By default, Discordo utilizes OS-specific keyring to store credentials such as c ### Default Keybindings -- `Alt` + `1`: Sets the focus on the guilds dropdown. +- `Alt` + `1`: Sets the focus on the guilds list. - `Alt` + `2`: Sets the focus on the channels treeview. - `Alt` + `3`: Sets the focus on the messages textview. - `Alt` + `4`: Sets the focus on the message inputfield. diff --git a/discordo.go b/discordo.go index 839a96a..e079f09 100644 --- a/discordo.go +++ b/discordo.go @@ -19,7 +19,8 @@ import ( var ( app *tview.Application loginForm *tview.Form - guildsTreeView *tview.TreeView + guildsList *tview.List + channelsTreeView *tview.TreeView messagesTextView *tview.TextView messageInputField *tview.InputField mainFlex *tview.Flex @@ -27,8 +28,9 @@ var ( conf *util.Config discordSession *session.Session clientID discord.UserID - currentGuild gateway.GuildCreateEvent - currentChannel discord.Channel + guilds []gateway.GuildCreateEvent + guild gateway.GuildCreateEvent + channel discord.Channel ) func main() { @@ -59,10 +61,11 @@ func main() { } app = ui.NewApp(onAppInputCapture) - guildsTreeView = ui.NewGuildsTreeView(onGuildsTreeViewSelected) + guildsList = ui.NewGuildsList(onGuildsListSelected, conf.Theme) + channelsTreeView = ui.NewChannelsTreeView(onChannelsTreeViewSelected) messagesTextView = ui.NewMessagesTextView(app) messageInputField = ui.NewMessageInputField(onMessageInputFieldInputCapture, conf.Theme) - mainFlex = ui.NewMainFlex(guildsTreeView, messagesTextView, messageInputField) + mainFlex = ui.NewMainFlex(guildsList, channelsTreeView, messagesTextView, messageInputField) token := os.Getenv("DISCORDO_TOKEN") if t := util.GetPassword("token"); t != "" { @@ -72,7 +75,7 @@ func main() { if token != "" { app. SetRoot(mainFlex, true). - SetFocus(guildsTreeView) + SetFocus(guildsList) discordSession = newSession(token) } else { @@ -88,13 +91,13 @@ func main() { func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey { switch e.Name() { case "Alt+Rune[1]": - app.SetFocus(guildsTreeView) + app.SetFocus(guildsList) case "Alt+Rune[2]": - app.SetFocus(messagesTextView) + app.SetFocus(channelsTreeView) case "Alt+Rune[3]": - if messageInputField != nil { - app.SetFocus(messageInputField) - } + app.SetFocus(messagesTextView) + case "Alt+Rune[4]": + app.SetFocus(messageInputField) } return e @@ -108,7 +111,7 @@ func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey { return nil } - discordSession.SendMessage(currentChannel.ID, t) + discordSession.SendMessage(channel.ID, t) messageInputField.SetText("") case tcell.KeyCtrlV: text, _ := clipboard.ReadAll() @@ -143,110 +146,98 @@ func newSession(token string) (s *session.Session) { return } +func onSessionReady(r *gateway.ReadyEvent) { + clientID = r.User.ID + guilds = r.Guilds + for _, g := range r.Guilds { + guildsList.AddItem(g.Name, "", 0, nil) + } +} + func onSessionMessageCreate(m *gateway.MessageCreateEvent) { - if currentChannel.ID == m.ChannelID { + if channel.ID == m.ChannelID { util.WriteMessage(messagesTextView, clientID, m.Message) } } -func onSessionReady(r *gateway.ReadyEvent) { - clientID = r.User.ID +func onGuildsListSelected(i int, _ string, _ string, _ rune) { + app.SetFocus(channelsTreeView) + messagesTextView.SetTitle("") + messagesTextView.Clear() - for _, g := range r.Guilds { - gn := tview.NewTreeNode(g.Name). - SetReference(g). - Collapse() - guildsTreeView.GetRoot().AddChild(gn) + n := channelsTreeView.GetRoot() + n.ClearChildren() - sort.Slice(g.Channels, func(i, j int) bool { - return g.Channels[i].Position < g.Channels[j].Position - }) + guild = guilds[i] + sort.SliceStable(guild.Channels, func(i, j int) bool { + return guild.Channels[i].Position < guild.Channels[j].Position + }) - for _, c := range g.Channels { - switch c.Type { - case discord.GuildCategory: - cn := tview.NewTreeNode(c.Name). + for _, c := range guild.Channels { + switch c.Type { + case discord.GuildCategory: + cn := tview.NewTreeNode(c.Name). + SetReference(c) + n.AddChild(cn) + case discord.GuildText, discord.GuildNews: + if c.ParentID == 0 || c.ParentID == discord.NullChannelID { + cn := tview.NewTreeNode("[::d]#" + c.Name + "[::-]"). SetReference(c) - gn.AddChild(cn) - case discord.GuildText, discord.GuildNews: - if c.ParentID == 0 || c.ParentID == discord.NullChannelID { - cn := tview.NewTreeNode("[::d]#" + c.Name + "[::-]"). - SetReference(c) - gn.AddChild(cn) - } - case discord.GuildStageVoice, discord.GuildVoice: - if c.ParentID == 0 || c.ParentID == discord.NullChannelID { - cn := tview.NewTreeNode("[::d]🔊" + c.Name + "[::-]"). - SetReference(c) - gn.AddChild(cn) - } + n.AddChild(cn) + } + case discord.GuildStageVoice, discord.GuildVoice: + if c.ParentID == 0 || c.ParentID == discord.NullChannelID { + cn := tview.NewTreeNode("[::d]🔊" + c.Name + "[::-]"). + SetReference(c) + n.AddChild(cn) } } } } -func onGuildsTreeViewSelected(n *tview.TreeNode) { - switch r := n.GetReference().(type) { - case gateway.GuildCreateEvent: - currentGuild = r - n.SetExpanded(!n.IsExpanded()) - case discord.Channel: - switch r.Type { - case discord.GuildCategory: - if len(n.GetChildren()) == 0 { - for _, c := range currentGuild.Channels { - switch c.Type { - case discord.GuildText, discord.GuildNews: - if c.ParentID == r.ID { - cn := tview.NewTreeNode("[::d]#" + c.Name + "[::-]"). - SetReference(c) - n.AddChild(cn) - } - case discord.GuildStageVoice, discord.GuildVoice: - if c.ParentID == r.ID { - cn := tview.NewTreeNode("[::d]🔊" + c.Name + "[::-]"). - SetReference(c) - n.AddChild(cn) - } +func onChannelsTreeViewSelected(n *tview.TreeNode) { + r := n.GetReference().(discord.Channel) + switch r.Type { + case discord.GuildCategory: + if len(n.GetChildren()) == 0 { + for _, c := range guild.Channels { + switch c.Type { + case discord.GuildText, discord.GuildNews: + if c.ParentID == r.ID { + cn := tview.NewTreeNode("[::d]#" + c.Name + "[::-]"). + SetReference(c) + n.AddChild(cn) } - } - } else { - n.SetExpanded(!n.IsExpanded()) - } - case discord.GuildText, discord.GuildNews: - if len(n.GetChildren()) == 0 { - currentChannel = r - app.SetFocus(messageInputField) - messagesTextView.Clear() - - title := "#" + r.Name - if r.Topic != "" { - title += " - " + r.Topic - } - messagesTextView.SetTitle(title) - - for _, t := range currentGuild.Threads { - if t.ParentID == currentChannel.ID { - cn := tview.NewTreeNode("[::d]🗨 " + t.Name + "[::-]"). - SetReference(t) + case discord.GuildStageVoice, discord.GuildVoice: + if c.ParentID == r.ID { + cn := tview.NewTreeNode("[::d]🔊" + c.Name + "[::-]"). + SetReference(c) n.AddChild(cn) } } - - go func() { - msgs, _ := discordSession.Messages(r.ID, conf.GetMessagesLimit) - for i := len(msgs) - 1; i >= 0; i-- { - util.WriteMessage(messagesTextView, clientID, msgs[i]) - } - }() - } else { - n.SetExpanded(!n.IsExpanded()) } - case discord.GuildNewsThread, discord.GuildPrivateThread, discord.GuildPublicThread: - currentChannel = r + } else { + n.SetExpanded(!n.IsExpanded()) + } + case discord.GuildText, discord.GuildNews: + if len(n.GetChildren()) == 0 { + channel = r app.SetFocus(messageInputField) messagesTextView.Clear() - messagesTextView.SetTitle(r.Name) + + title := "#" + r.Name + if r.Topic != "" { + title += " - " + r.Topic + } + messagesTextView.SetTitle(title) + + for _, t := range guild.Threads { + if t.ParentID == channel.ID { + cn := tview.NewTreeNode("[::d]🗨 " + t.Name + "[::-]"). + SetReference(t) + n.AddChild(cn) + } + } go func() { msgs, _ := discordSession.Messages(r.ID, conf.GetMessagesLimit) @@ -254,10 +245,24 @@ func onGuildsTreeViewSelected(n *tview.TreeNode) { util.WriteMessage(messagesTextView, clientID, msgs[i]) } }() - case discord.GuildStageVoice, discord.GuildVoice: - messagesTextView.Clear() - messagesTextView.SetTitle(r.Name) + } else { + n.SetExpanded(!n.IsExpanded()) } + case discord.GuildNewsThread, discord.GuildPrivateThread, discord.GuildPublicThread: + channel = r + app.SetFocus(messageInputField) + messagesTextView.Clear() + messagesTextView.SetTitle(r.Name) + + go func() { + msgs, _ := discordSession.Messages(r.ID, conf.GetMessagesLimit) + for i := len(msgs) - 1; i >= 0; i-- { + util.WriteMessage(messagesTextView, clientID, msgs[i]) + } + }() + case discord.GuildStageVoice, discord.GuildVoice: + messagesTextView.Clear() + messagesTextView.SetTitle(r.Name) } } @@ -278,8 +283,8 @@ func onLoginFormLoginButtonSelected() { if l.Token != "" && !l.MFA { app. - SetRoot(mainFlex, true). - SetFocus(guildsTreeView) + SetRoot(guildsList, true). + SetFocus(channelsTreeView) discordSession = newSession(l.Token) go util.SetPassword("token", l.Token) @@ -297,7 +302,7 @@ func onLoginFormLoginButtonSelected() { app. SetRoot(mainFlex, true). - SetFocus(guildsTreeView) + SetFocus(guildsList) discordSession = newSession(l.Token) go util.SetPassword("token", l.Token) diff --git a/ui/flex.go b/ui/flex.go index 32e1e38..9fd960f 100644 --- a/ui/flex.go +++ b/ui/flex.go @@ -4,14 +4,18 @@ import ( "github.com/rivo/tview" ) -func NewMainFlex(treeV *tview.TreeView, textV *tview.TextView, i *tview.InputField) (mf *tview.Flex) { +func NewMainFlex(l *tview.List, treeV *tview.TreeView, textV *tview.TextView, i *tview.InputField) (mf *tview.Flex) { + lf := tview.NewFlex(). + SetDirection(tview.FlexRow). + AddItem(l, 0, 1, false). + AddItem(treeV, 0, 3, false) rf := tview.NewFlex(). SetDirection(tview.FlexRow). AddItem(textV, 0, 1, false). AddItem(i, 3, 1, false) mf = tview.NewFlex(). - AddItem(treeV, 25, 1, false). - AddItem(rf, 0, 1, false) + AddItem(lf, 0, 1, false). + AddItem(rf, 0, 5, false) return mf } diff --git a/ui/lists.go b/ui/lists.go new file mode 100644 index 0000000..2dd8fe5 --- /dev/null +++ b/ui/lists.go @@ -0,0 +1,19 @@ +package ui + +import ( + "github.com/rigormorrtiss/discordo/util" + "github.com/rivo/tview" +) + +func NewGuildsList(onGuildsListSelected func(int, string, string, rune), t *util.Theme) (l *tview.List) { + l = tview.NewList() + l. + SetSelectedFunc(onGuildsListSelected). + ShowSecondaryText(false). + SetBorder(true). + SetBorderPadding(0, 0, 1, 0). + SetTitle("Guilds"). + SetTitleAlign(tview.AlignLeft) + + return +} diff --git a/ui/treeviews.go b/ui/treeviews.go index 9c6485a..effcea6 100644 --- a/ui/treeviews.go +++ b/ui/treeviews.go @@ -4,15 +4,15 @@ import ( "github.com/rivo/tview" ) -func NewGuildsTreeView(onGuildsTreeViewSelected func(*tview.TreeNode)) (treeV *tview.TreeView) { +func NewChannelsTreeView(onChannelsTreeViewSelected func(*tview.TreeNode)) (treeV *tview.TreeView) { treeV = tview.NewTreeView() treeN := tview.NewTreeNode("") treeV. SetTopLevel(1). SetRoot(treeN). SetCurrentNode(treeN). - SetSelectedFunc(onGuildsTreeViewSelected). - SetTitle("Guilds"). + SetSelectedFunc(onChannelsTreeViewSelected). + SetTitle("Channels"). SetBorder(true). SetBorderPadding(0, 0, 1, 0). SetTitleAlign(tview.AlignLeft)