discordo/discordo.go
2021-08-18 21:02:21 +04:00

308 lines
7.4 KiB
Go

package main
import (
"os"
"sort"
"strings"
"github.com/atotto/clipboard"
"github.com/diamondburned/arikawa/v3/api"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/arikawa/v3/session"
"github.com/gdamore/tcell/v2"
"github.com/rigormorrtiss/discordo/ui"
"github.com/rigormorrtiss/discordo/util"
"github.com/rivo/tview"
)
var (
app *tview.Application
loginForm *tview.Form
guildsTreeView *tview.TreeView
messagesTextView *tview.TextView
messageInputField *tview.InputField
mainFlex *tview.Flex
conf *util.Config
discordSession *session.Session
clientID discord.UserID
currentGuild gateway.GuildCreateEvent
currentChannel discord.Channel
)
func main() {
conf = util.NewConfig()
tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(conf.Theme.Background)
if !conf.Theme.Borders {
tview.Borders = struct {
Horizontal rune
Vertical rune
TopLeft rune
TopRight rune
BottomLeft rune
BottomRight rune
LeftT rune
RightT rune
TopT rune
BottomT rune
Cross rune
HorizontalFocus rune
VerticalFocus rune
TopLeftFocus rune
TopRightFocus rune
BottomLeftFocus rune
BottomRightFocus rune
}{}
}
app = ui.NewApp(onAppInputCapture)
guildsTreeView = ui.NewGuildsTreeView(onGuildsTreeViewSelected)
messagesTextView = ui.NewMessagesTextView(app)
messageInputField = ui.NewMessageInputField(onMessageInputFieldInputCapture, conf.Theme)
mainFlex = ui.NewMainFlex(guildsTreeView, messagesTextView, messageInputField)
token := os.Getenv("DISCORDO_TOKEN")
if t := util.GetPassword("token"); t != "" {
token = t
}
if token != "" {
app.
SetRoot(mainFlex, true).
SetFocus(guildsTreeView)
discordSession = newSession(token)
} else {
loginForm = ui.NewLoginForm(onLoginFormLoginButtonSelected)
app.SetRoot(loginForm, true)
}
if err := app.Run(); err != nil {
panic(err)
}
}
func onAppInputCapture(e *tcell.EventKey) *tcell.EventKey {
switch e.Name() {
case "Alt+Rune[1]":
app.SetFocus(guildsTreeView)
case "Alt+Rune[2]":
app.SetFocus(messagesTextView)
case "Alt+Rune[3]":
if messageInputField != nil {
app.SetFocus(messageInputField)
}
}
return e
}
func onMessageInputFieldInputCapture(e *tcell.EventKey) *tcell.EventKey {
switch e.Key() {
case tcell.KeyEnter:
t := strings.TrimSpace(messageInputField.GetText())
if t == "" {
return nil
}
discordSession.SendMessage(currentChannel.ID, t)
messageInputField.SetText("")
case tcell.KeyCtrlV:
text, _ := clipboard.ReadAll()
text = messageInputField.GetText() + text
messageInputField.SetText(text)
}
return e
}
func newSession(token string) (s *session.Session) {
api.UserAgent = "" +
"Mozilla/5.0 (X11; Linux x86_64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/92.0.4515.131 Safari/537.36"
gateway.DefaultIdentity.Browser = "Chrome"
gateway.DefaultIdentity.OS = "Linux"
gateway.DefaultIdentity.Device = ""
var err error
s, err = session.New(token)
if err != nil {
panic(err)
}
s.AddHandler(onSessionReady)
s.AddHandler(onSessionMessageCreate)
if err = s.Open(s.Context()); err != nil {
panic(err)
}
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 _, g := range r.Guilds {
gn := tview.NewTreeNode(g.Name).
SetReference(g).
Collapse()
guildsTreeView.GetRoot().AddChild(gn)
sort.Slice(g.Channels, func(i, j int) bool {
return g.Channels[i].Position < g.Channels[j].Position
})
for _, c := range g.Channels {
switch c.Type {
case discord.GuildCategory:
cn := tview.NewTreeNode(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)
}
}
}
}
}
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)
}
}
}
} 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)
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
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)
}
}
}
func onLoginFormLoginButtonSelected() {
email := loginForm.GetFormItem(0).(*tview.InputField).GetText()
password := loginForm.GetFormItem(1).(*tview.InputField).GetText()
if email == "" || password == "" {
return
}
// Make a scratch HTTP client without a token
client := api.NewClient("")
// Try to login without TOTP
l, err := client.Login(email, password)
if err != nil {
panic(err)
}
if l.Token != "" && !l.MFA {
app.
SetRoot(mainFlex, true).
SetFocus(guildsTreeView)
discordSession = newSession(l.Token)
go util.SetPassword("token", l.Token)
} else if l.MFA {
loginForm = ui.NewMfaLoginForm(func() {
code := loginForm.GetFormItem(0).(*tview.InputField).GetText()
if code == "" {
return
}
l, err := client.TOTP(code, l.Ticket)
if err != nil {
panic(err)
}
app.
SetRoot(mainFlex, true).
SetFocus(guildsTreeView)
discordSession = newSession(l.Token)
go util.SetPassword("token", l.Token)
})
app.SetRoot(loginForm, true)
}
}