discordo/internal/ui/chat/msg.go
2026-10-12 12:53:49 -04:00

101 lines
2.6 KiB
Go

package chat
import (
"context"
"errors"
"log/slog"
"time"
"github.com/ayn2op/arikawa/v3/discord"
"github.com/ayn2op/arikawa/v3/gateway"
"github.com/ayn2op/arikawa/v3/utils/ws"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/discordo/internal/ui/chat/composer"
"github.com/ayn2op/discordo/internal/ui/chat/guildstree"
"github.com/ayn2op/ningen/v3"
"github.com/ayn2op/tview"
)
// closeCodeAuthenticationFailed is the close code of the gateway for a token it rejects.
const closeCodeAuthenticationFailed = 4004
func openState(state *ningen.State) tview.Cmd {
return func() tview.Msg {
if err := state.Open(context.Background()); err != nil {
slog.Error("failed to open chat state", "err", err)
// A rejected token never works again, so say so over the login.
if event, ok := errors.AsType[*ws.CloseEvent](err); ok && event.Code == closeCodeAuthenticationFailed {
return tview.Sequence(logout(), func() tview.Msg { return ui.ErrorDialog("open chat state", err) })()
}
}
return nil
}
}
func closeState(state *ningen.State) tview.Cmd {
if state == nil {
return nil
}
return func() tview.Msg {
if err := state.Close(); err != nil {
slog.Error("failed to close the session", "err", err)
}
return nil
}
}
func listen(events <-chan gateway.Event) tview.Cmd {
return func() tview.Msg {
return <-events
}
}
type LogoutMsg struct{}
func logout() tview.Cmd {
return func() tview.Msg {
return LogoutMsg{}
}
}
type QuitMsg struct{}
func (m *Model) setSelectedChannel(channel *discord.Channel) tview.Cmd {
m.selectedChannel = channel
m.composer.SetChannel(channel)
cmd := m.membersTree.SetChannel(channel)
if !m.canFocus(m.focused) {
m.focused = paneMessagesList
}
return cmd
}
func (m *Model) navigateToChannel(channelID discord.ChannelID) tview.Cmd {
m.closePicker()
var cmd tview.Cmd
m.guildsTree, cmd = m.guildsTree.Update(guildstree.NavigateMsg{ChannelID: channelID})
return cmd
}
// paneMsg is what a pane's widget made of a mouse message within it. A left mouse button press also focuses the pane.
type paneMsg struct {
pane pane
msg tview.Msg
focus bool
}
// typingExpiredMsg ends a user's typing indicator unless they typed again and extended it.
type typingExpiredMsg struct {
userID discord.UserID
}
// addTyper shows userID as typing and returns a command that ends it after the typing duration.
func (m Model) addTyper(userID discord.UserID) tview.Cmd {
m.typers[userID] = time.Now().Add(composer.TypingDuration)
return func() tview.Msg {
time.Sleep(composer.TypingDuration)
return typingExpiredMsg{userID}
}
}