feat: implement Threads (#25)

* main: refactor MESSAGe_CREATE event to separate function

* main: add thread channel as child to guild text channel

* main: write thread channel messages to messages TextView on select

* *: refactor to use for range loops

* main: write discord.Message.ReferencedMessage if message type is ThreadStarterMessage

* fix(util): write Message.Content if it is not an empty string

* refactor(util): remove WriteMessages

* refactor(main): defer-close websocket connection

* refactor(main): update api.UserAgent to latest

* refactor: use named return values

* fix: increase guilds TreeView fixedSize

* docs(README): update preview
This commit is contained in:
rigormorrtiss 2021-08-15 10:01:02 +04:00 • committed by GitHub
parent 0ee43018c1
commit 1a2be50e14
12 changed files with 129 additions and 103 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 192 KiB

View file

@ -56,13 +56,13 @@ func main() {
messageInputField = ui.NewMessageInputField(onMessageInputFieldInputCapture, config.Theme)
mainFlex = ui.NewMainFlex(guildsTreeView, messagesTextView, messageInputField)
token := util.GetItem(kr, "token")
if token != "" {
if t := util.GetItem(kr, "token"); t != "" {
app.
SetRoot(mainFlex, true).
SetFocus(guildsTreeView)
discordSession = newSession("", "", token)
discordSession = newSession("", "", t)
defer discordSession.Close()
} else {
loginForm = ui.NewLoginForm(onLoginFormLoginButtonSelected)
app.SetRoot(loginForm, true)
@ -88,8 +88,8 @@ func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey {
return e
}
func onMessageInputFieldInputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey {
switch e.Key() {
case tcell.KeyEnter:
t := strings.TrimSpace(messageInputField.GetText())
if t == "" {
@ -104,69 +104,68 @@ func onMessageInputFieldInputCapture(event *tcell.EventKey) *tcell.EventKey {
messageInputField.SetText(text)
}
return event
return e
}
func newSession(email string, password string, token string) *session.Session {
func newSession(email string, password string, token string) (s *session.Session) {
api.UserAgent = "" +
"Mozilla/5.0 (X11; Linux x86_64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/91.0.4472.164 Safari/537.36"
"Chrome/92.0.4515.131 Safari/537.36"
gateway.DefaultIdentity.Browser = "Chrome"
gateway.DefaultIdentity.OS = "Linux"
gateway.DefaultIdentity.Device = ""
var sess *session.Session
var err error
if email != "" && password != "" {
sess, err = session.Login(email, password, "")
s, err = session.Login(email, password, "")
} else if token != "" {
sess, err = session.New(token)
s, err = session.New(token)
}
if err != nil {
panic(err)
}
sess.AddHandler(onSessionReady)
sess.AddHandler(func(m *gateway.MessageCreateEvent) {
if currentChannel.ID == m.ChannelID {
util.WriteMessage(messagesTextView, clientID, m.Message)
}
})
if err = sess.Open(context.Background()); err != nil {
s.AddHandler(onSessionReady)
s.AddHandler(onSessionMessageCreate)
if err = s.Open(context.Background()); err != nil {
panic(err)
}
return sess
return
}
func onSessionMessageCreate(m *gateway.MessageCreateEvent) {
if currentChannel.ID == m.ChannelID {
util.WriteMessage(messagesTextView, clientID, m.Message)
}
}
func onSessionReady(r *gateway.ReadyEvent) {
clientID = r.User.ID
for i := range r.Guilds {
g := r.Guilds[i]
gNode := tview.NewTreeNode(g.Name).
for _, g := range r.Guilds {
gn := tview.NewTreeNode(g.Name).
SetReference(g).
Collapse()
guildsTreeView.GetRoot().AddChild(gNode)
guildsTreeView.GetRoot().AddChild(gn)
sort.Slice(g.Channels, func(i, j int) bool {
return g.Channels[i].Position < g.Channels[j].Position
})
for i := range g.Channels {
c := g.Channels[i]
for _, c := range g.Channels {
switch c.Type {
case discord.GuildCategory:
cNode := tview.NewTreeNode(c.Name).
SetReference(c)
gNode.AddChild(cNode)
gn.AddChild(cNode)
case discord.GuildText, discord.GuildNews:
if c.ParentID == 0 || c.ParentID == discord.NullChannelID {
cNode := tview.NewTreeNode("[::d]#" + c.Name + "[-:-:-]").
SetReference(c)
gNode.AddChild(cNode)
gn.AddChild(cNode)
}
}
}
@ -174,37 +173,62 @@ func onSessionReady(r *gateway.ReadyEvent) {
}
func onGuildsTreeViewSelected(n *tview.TreeNode) {
switch ref := n.GetReference().(type) {
switch r := n.GetReference().(type) {
case gateway.GuildCreateEvent:
currentGuild = ref
currentGuild = r
n.SetExpanded(!n.IsExpanded())
case discord.Channel:
switch ref.Type {
switch r.Type {
case discord.GuildCategory:
if len(n.GetChildren()) == 0 {
for i := range currentGuild.Channels {
c := currentGuild.Channels[i]
if (c.Type == discord.GuildText || c.Type == discord.GuildNews) && c.ParentID == ref.ID {
cNode := tview.NewTreeNode("[::d]#" + c.Name + "[-:-:-]").
for _, c := range currentGuild.Channels {
if (c.Type == discord.GuildText || c.Type == discord.GuildNews) && c.ParentID == r.ID {
cn := tview.NewTreeNode("[::d]#" + c.Name + "[-:-:-]").
SetReference(c)
n.AddChild(cNode)
n.AddChild(cn)
}
}
} else {
n.SetExpanded(!n.IsExpanded())
}
case discord.GuildText, discord.GuildNews:
currentChannel = ref
if len(n.GetChildren()) == 0 {
currentChannel = r
app.SetFocus(messageInputField)
messagesTextView.Clear()
messagesTextView.SetTitle(r.Name)
go func() {
for _, t := range currentGuild.Threads {
if t.ParentID == currentChannel.ID {
cn := tview.NewTreeNode("[::d]🗨 " + t.Name + "[::-]").
SetReference(t)
n.AddChild(cn)
}
}
}()
go func() {
msgs, _ := discordSession.Messages(r.ID, config.GetMessagesLimit)
for _, m := range msgs {
util.WriteMessage(messagesTextView, clientID, m)
}
}()
} else {
n.SetExpanded(!n.IsExpanded())
}
case discord.GuildNewsThread, discord.GuildPrivateThread, discord.GuildPublicThread:
currentChannel = r
app.SetFocus(messageInputField)
messagesTextView.Clear()
messagesTextView.SetTitle(ref.Name)
messagesTextView.SetTitle(r.Name)
go func() {
messages, _ := discordSession.Messages(ref.ID, config.GetMessagesLimit)
for i := len(messages) - 1; i >= 0; i-- {
util.WriteMessage(messagesTextView, clientID, messages[i])
msgs, _ := discordSession.Messages(r.ID, config.GetMessagesLimit)
for _, m := range msgs {
util.WriteMessage(messagesTextView, clientID, m)
}
}()
}
@ -223,6 +247,7 @@ func onLoginFormLoginButtonSelected() {
SetFocus(guildsTreeView)
discordSession = newSession(email, password, "")
defer discordSession.Close()
go util.SetItem(kr, "token", discordSession.Token)
}

View file

@ -5,8 +5,8 @@ import (
"github.com/rivo/tview"
)
func NewApp(onAppInputCapture func(*tcell.EventKey) *tcell.EventKey) *tview.Application {
app := tview.NewApplication().
func NewApp(onAppInputCapture func(*tcell.EventKey) *tcell.EventKey) (app *tview.Application) {
app = tview.NewApplication().
EnableMouse(true).
SetInputCapture(onAppInputCapture)

View file

@ -6,10 +6,9 @@ import (
"github.com/rivo/tview"
)
func NewGuildsDropDown(onGuildsDropDownSelected func(string, int), theme *util.Theme) *tview.DropDown {
guildsDropDown := tview.NewDropDown()
guildsDropDown.
func NewGuildsDropDown(onGuildsDropDownSelected func(string, int), theme *util.Theme) (d *tview.DropDown) {
d = tview.NewDropDown()
d.
SetLabel("Guild: ").
SetSelectedFunc(onGuildsDropDownSelected).
SetFieldBackgroundColor(tcell.GetColor(theme.DropDownBackground)).
@ -17,5 +16,5 @@ func NewGuildsDropDown(onGuildsDropDownSelected func(string, int), theme *util.T
SetBorder(true).
SetBorderPadding(0, 0, 1, 1)
return guildsDropDown
return
}

View file

@ -4,13 +4,13 @@ import (
"github.com/rivo/tview"
)
func NewMainFlex(treeV *tview.TreeView, textV *tview.TextView, i *tview.InputField) *tview.Flex {
func NewMainFlex(treeV *tview.TreeView, textV *tview.TextView, i *tview.InputField) (mainFlex *tview.Flex) {
rightFlex := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(textV, 0, 1, false).
AddItem(i, 3, 1, false)
mainFlex := tview.NewFlex().
AddItem(treeV, 25, 1, false).
mainFlex = tview.NewFlex().
AddItem(treeV, 30, 1, false).
AddItem(rightFlex, 0, 1, false)
return mainFlex

View file

@ -5,9 +5,8 @@ import (
"github.com/rivo/tview"
)
func NewLoginForm(onLoginFormLoginButtonSelected func()) *tview.Form {
f := tview.NewForm()
func NewLoginForm(onLoginFormLoginButtonSelected func()) (f *tview.Form) {
f = tview.NewForm()
f.
AddInputField("Email", "", 0, nil, nil).
AddPasswordField("Password", "", 0, 0, nil).

View file

@ -6,8 +6,8 @@ import (
"github.com/rivo/tview"
)
func NewMessageInputField(onMessageInputFieldInputCapture func(*tcell.EventKey) *tcell.EventKey, theme *util.Theme) *tview.InputField {
i := tview.NewInputField()
func NewMessageInputField(onMessageInputFieldInputCapture func(*tcell.EventKey) *tcell.EventKey, theme *util.Theme) (i *tview.InputField) {
i = tview.NewInputField()
i.
SetPlaceholder("Message...").
SetPlaceholderTextColor(tcell.ColorWhite).
@ -17,5 +17,5 @@ func NewMessageInputField(onMessageInputFieldInputCapture func(*tcell.EventKey)
SetBorderPadding(0, 0, 1, 1).
SetInputCapture(onMessageInputFieldInputCapture)
return i
return
}

View file

@ -6,10 +6,9 @@ import (
"github.com/rivo/tview"
)
func NewMessagesTextView(app *tview.Application, theme *util.Theme) *tview.TextView {
messagesTextView := tview.NewTextView()
messagesTextView.
func NewMessagesTextView(app *tview.Application, theme *util.Theme) (textV *tview.TextView) {
textV = tview.NewTextView()
textV.
SetDynamicColors(true).
SetWordWrap(true).
ScrollToEnd().
@ -20,5 +19,5 @@ func NewMessagesTextView(app *tview.Application, theme *util.Theme) *tview.TextV
SetBorder(true).
SetBorderPadding(0, 0, 1, 1)
return messagesTextView
return
}

View file

@ -6,19 +6,18 @@ import (
"github.com/rivo/tview"
)
func NewGuildsTreeView(onGuildsTreeViewSelected func(*tview.TreeNode), theme *util.Theme) *tview.TreeView {
guildsTreeView := tview.NewTreeView()
guildsTreeNode := tview.NewTreeNode("")
guildsTreeView.
func NewGuildsTreeView(onGuildsTreeViewSelected func(*tview.TreeNode), theme *util.Theme) (treeV *tview.TreeView) {
treeV = tview.NewTreeView()
treeN := tview.NewTreeNode("")
treeV.
SetTopLevel(1).
SetRoot(guildsTreeNode).
SetCurrentNode(guildsTreeNode).
SetRoot(treeN).
SetCurrentNode(treeN).
SetSelectedFunc(onGuildsTreeViewSelected).
SetBackgroundColor(tcell.GetColor(theme.TreeViewBackground)).
SetTitle("Guilds").
SetBorder(true).
SetBorderPadding(0, 0, 1, 1)
return guildsTreeView
return
}

View file

@ -23,23 +23,23 @@ func NewConfig() *Config {
panic(err)
}
var config Config = Config{
var c Config = Config{
GetMessagesLimit: 50,
Theme: &Theme{},
}
configPath := userHomeDir + "/.config/discordo/config.json"
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return &config
return &c
}
data, err := os.ReadFile(configPath)
d, err := os.ReadFile(configPath)
if err != nil {
panic(err)
}
if err = json.Unmarshal(data, &config); err != nil {
if err = json.Unmarshal(d, &c); err != nil {
panic(err)
}
return &config
return &c
}

View file

@ -9,31 +9,35 @@ import (
)
func WriteMessage(v *tview.TextView, clientID discord.UserID, m discord.Message) {
m.Content = parseMessageMentions(m.Content, m.Mentions, clientID)
switch m.Type {
case discord.DefaultMessage, discord.InlinedReplyMessage:
var b strings.Builder
// $ ╭ AUTHOR_USERNAME (BOT) MESSAGE_CONTENT*linebreak*
writeReferencedMessage(&b, clientID, m.ReferencedMessage)
// $ AUTHOR_USERNAME (BOT)*spacee*
writeAuthor(&b, clientID, m.Author)
// $ MESSAGE_CONTENT
if m.Content != "" {
m.Content = parseMessageMentions(m.Content, m.Mentions, clientID)
b.WriteString(m.Content)
}
// $ *space*(edited)
if m.EditedTimestamp.IsValid() {
b.WriteString(" [::d](edited)[::-]")
}
// $ *linebreak*EMBED
writeEmbeds(&b, m.Embeds)
// $ *linebreak*ATTACHMENT_URL
writeAttachments(&b, m.Attachments)
var b strings.Builder
// $ ╭ AUTHOR_USERNAME (BOT) MESSAGE_CONTENT*linebreak*
writeReferencedMessage(&b, clientID, m.ReferencedMessage)
// $ AUTHOR_USERNAME (BOT)*spacee*
writeAuthor(&b, clientID, m.Author)
// $ MESSAGE_CONTENT
b.WriteString(m.Content)
// $ *space*(edited)
if m.EditedTimestamp.IsValid() {
b.WriteString(" [::d](edited)[::-]")
fmt.Fprintln(v, b.String())
case discord.ThreadStarterMessage:
WriteMessage(v, clientID, *m.ReferencedMessage)
}
// $ *linebreak*EMBED
writeEmbeds(&b, m.Embeds)
// $ *linebreak*ATTACHMENT_URL
writeAttachments(&b, m.Attachments)
fmt.Fprintln(v, b.String())
}
func parseMessageMentions(content string, mentions []discord.GuildUser, clientID discord.UserID) string {
for i := range mentions {
mUser := mentions[i]
for _, mUser := range mentions {
var color string
if mUser.ID == clientID {
color = "[#000000:#FEE75C]"
@ -61,8 +65,7 @@ func writeEmbeds(b *strings.Builder, embeds []discord.Embed) {
}
func writeAttachments(b *strings.Builder, attachments []discord.Attachment) {
for i := range attachments {
a := attachments[i]
for _, a := range attachments {
b.WriteString("\n[" + a.Filename + "]: ")
b.WriteString(a.URL)
}
@ -100,7 +103,9 @@ func writeReferencedMessage(b *strings.Builder, clientID discord.UserID, rm *dis
b.WriteString("[#EB459E]BOT[-] ")
}
rm.Content = parseMessageMentions(rm.Content, rm.Mentions, clientID)
b.WriteString(rm.Content + "[::-]\n")
if rm.Content != "" {
rm.Content = parseMessageMentions(rm.Content, rm.Mentions, clientID)
b.WriteString(rm.Content + "[::-]\n")
}
}
}

View file

@ -4,22 +4,22 @@ import (
"github.com/99designs/keyring"
)
func OpenKeyringBackend() keyring.Keyring {
func OpenKeyringBackend() (kr keyring.Keyring) {
kr, err := keyring.Open(keyring.Config{})
if err != nil {
panic(err)
}
return kr
return
}
func GetItem(kr keyring.Keyring, k string) string {
item, err := kr.Get(k)
i, err := kr.Get(k)
if err != nil {
return ""
}
return string(item.Data)
return string(i.Data)
}
func SetItem(kr keyring.Keyring, k string, d string) {