Quellcode durchsuchen

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
rigormorrtiss vor 4 Jahren
Ursprung
Commit
1a2be50e14
12 geänderte Dateien mit 130 neuen und 104 gelöschten Zeilen
  1. BIN
      assets/preview.png
  2. 66 41
      discordo.go
  3. 2 2
      ui/app.go
  4. 4 5
      ui/dropdowns.go
  5. 3 3
      ui/flex.go
  6. 2 3
      ui/forms.go
  7. 3 3
      ui/inputfields.go
  8. 4 5
      ui/textviews.go
  9. 7 8
      ui/treeviews.go
  10. 5 5
      util/config.go
  11. 30 25
      util/discord.go
  12. 4 4
      util/keyring.go

BIN
assets/preview.png


+ 66 - 41
discordo.go

@@ -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)
 }

+ 2 - 2
ui/app.go

@@ -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)
 

+ 4 - 5
ui/dropdowns.go

@@ -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
 }

+ 3 - 3
ui/flex.go

@@ -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

+ 2 - 3
ui/forms.go

@@ -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).

+ 3 - 3
ui/inputfields.go

@@ -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
 }

+ 4 - 5
ui/textviews.go

@@ -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
 }

+ 7 - 8
ui/treeviews.go

@@ -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
 }

+ 5 - 5
util/config.go

@@ -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
 }

+ 30 - 25
util/discord.go

@@ -9,31 +9,35 @@ import (
 )
 
 func WriteMessage(v *tview.TextView, clientID discord.UserID, m discord.Message) {
-	m.Content = parseMessageMentions(m.Content, m.Mentions, clientID)
-
-	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)[::-]")
+	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)
+
+		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")
+		}
 	}
 }

+ 4 - 4
util/keyring.go

@@ -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) {