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:
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 |
107
discordo.go
107
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue