Use yaml for configuration instead of Lua (#215)

This commit is contained in:
ayntgl 2022-09-09 18:17:18 +05:30 • committed by GitHub
parent cb1a830888
commit 40e2e7e6a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 150 additions and 542 deletions

View file

@ -73,7 +73,7 @@ sudo mv ./discordo /usr/local/bin
### Configuration
A default configuration file is created on first start-up at `$HOME/.config/discordo/config.lua` on Unix, `$HOME/Library/Application Support/discordo/config.lua` on Darwin, and `%AppData%/discordo/config.lua` on Windows. You can configure the default configuration directory path using the `config` command-line flag (eg: `discordo --config $HOME/my-custom-dir).
A default configuration file is created on first start-up at `$HOME/.config/discordo/config.yml` on Unix, `$HOME/Library/Application Support/discordo/config.yml` on Darwin, and `%AppData%/discordo/config.yml` on Windows. You can configure the default configuration directory path using the `config` command-line flag (eg: `discordo --config $HOME/my-custom-dir).
## Disclaimer

View file

@ -2,78 +2,114 @@ package config
import (
_ "embed"
"io"
"os"
"path/filepath"
"time"
lua "github.com/yuin/gopher-lua"
"gopkg.in/yaml.v3"
)
const Name = "discordo"
//go:embed config.lua
var LuaConfig []byte
type MessagesPanelKeysConfig struct {
OpenActionsList string `yaml:"open_actions_list"`
// Config initializes a new Lua state, loads a configuration file, and defines essential micellaneous fields.
type Config struct {
// Path is the path of the configuration file. Its value is the configuration directory until Load() is called.
Path string
State *lua.LState
SelectPreviousMessage string `yaml:"select_previous_message"`
SelectNextMessage string `yaml:"select_next_message"`
SelectFirstMessage string `yaml:"select_first_message"`
SelectLastMessage string `yaml:"select_last_message"`
}
func New(path string) *Config {
type MessageInputKeysConfig struct {
OpenExternalEditor string `yaml:"open_external_editor"`
PasteClipboard string `yaml:"paste_clipboard"`
}
type KeysConfig struct {
MessagesPanel MessagesPanelKeysConfig `yaml:"messages_panel"`
MessageInput MessageInputKeysConfig `yaml:"message_input"`
}
type ThemeConfig struct {
Background string `yaml:"background"`
Border string `yaml:"border"`
Title string `yaml:"title"`
}
type Config struct {
// Whether the mouse is usable or not.
Mouse bool `yaml:"mouse"`
// The maximum number of messages to fetch and display on the messages panel. Its value must not be lesser than 1 and greater than 100.
MessagesLimit uint `yaml:"messages_limit"`
// Whether to display the timestamps of the messages beside the displayed message or not.
Timestamps bool `yaml:"timestamps"`
// The timezone of the timestamps. Learn more: https://pkg.go.dev/time#LoadLocation
Timezone string `yaml:"timezone"`
// A textual representation of the time value formatted according to the layout defined by its value. Learn more: https://pkg.go.dev/time#Layout
TimeFormat string `yaml:"time_format"`
// Keybindings
Keys KeysConfig `yaml:"keys"`
// Theme
Theme ThemeConfig `yaml:"theme"`
}
func New() *Config {
return &Config{
Path: path,
State: lua.NewState(),
Mouse: true,
MessagesLimit: 50,
Timestamps: false,
Timezone: "Local",
TimeFormat: time.Kitchen,
Keys: KeysConfig{
MessagesPanel: MessagesPanelKeysConfig{
OpenActionsList: "Rune[a]",
SelectPreviousMessage: "Up",
SelectNextMessage: "Down",
SelectFirstMessage: "Home",
SelectLastMessage: "End",
},
},
Theme: ThemeConfig{
Background: "default",
Border: "white",
Title: "white",
},
}
}
func (c *Config) Load() error {
// Create directories that do not exist and are mentioned in the path recursively.
err := os.MkdirAll(c.Path, os.ModePerm)
configPath, err := os.UserConfigDir()
if err != nil {
return err
}
c.Path = filepath.Join(c.Path, "config.lua")
// Open the existing configuration file with read-only flag.
f, err := os.Open(c.Path)
// If the configuration file does not exist, create a new configuration file with the read-write flag.
if os.IsNotExist(err) {
f, err = os.Create(c.Path)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(LuaConfig)
if err != nil {
return err
}
return f.Sync()
configPath = filepath.Join(configPath, Name)
// Create directories that do not exist and are mentioned in the path recursively.
err = os.MkdirAll(configPath, os.ModePerm)
if err != nil {
return err
}
configPath = filepath.Join(configPath, "config.yml")
// Open the existing configuration file with read-only flag.
f, err := os.OpenFile(configPath, os.O_CREATE|os.O_RDWR, os.ModePerm)
if err != nil {
return err
}
defer f.Close()
b, err := io.ReadAll(f)
fi, err := f.Stat()
if err != nil {
return err
}
LuaConfig = b
return nil
}
// If the configuration file is empty (the size of the file is zero; a new configuration file was created), write the default configuration to the file.
if fi.Size() == 0 {
return yaml.NewEncoder(f).Encode(c)
}
func (c *Config) KeyLua(s *lua.LState) int {
keyTable := s.NewTable()
keyTable.RawSetString("name", s.Get(1))
keyTable.RawSetString("description", s.Get(2))
keyTable.RawSetString("action", s.Get(3))
s.Push(keyTable) // Push the result
return 1 // Number of results
return yaml.NewDecoder(f).Decode(&c)
}

View file

@ -1,107 +0,0 @@
local string = require "string"
-- Whether the mouse is usable or not.
mouse = true
-- The maximum number of messages to fetch and display on the messages panel.
-- Its value must not be lesser than 1 and greater than 100.
messagesLimit = 50
-- Whether to display the timestamp of the message beside the displayed message or not.
timestamps = false
-- The timezone of the timestamps.
-- Learn more: https://pkg.go.dev/time#LoadLocation
timezone = "Local"
-- A textual representation of the time value formatted according to the layout defined by its value.
-- Learn more: https://pkg.go.dev/time#Layout
timeFormat = "3:04PM"
browser = "Chrome"
browserVersion = "104.0.5112.102"
oss = "Linux"
-- Identify properties are connection properties that are dispatched in the IDENTIFY gateway event to trigger the initial handshake with the gateway.
-- Learn more: https://discord.com/developers/docs/topics/gateway#identify
identifyProperties = {
userAgent = string.format(
"Mozilla/5.0 (X11; %s x86_64) AppleWebKit/537.36 (KHTML, like Gecko) %s/%s Safari/537.36",
oss,
browser,
browserVersion
),
browser = browser,
browserVersion = browserVersion,
os = oss
}
-- Keybindings
keys = {
-- application = {
-- key(
-- "Ctrl+R",
-- "Refresh the screen.",
-- function(core, event)
-- core.Application:Sync()
-- return nil
-- end
-- )
-- },
messagesPanel = {
key(
"Rune[a]",
"Open the message actions list widget.",
function(core, event)
return openMessageActionsList()
end
),
key(
"Up",
"Select the previous message.",
function(core, event)
return selectPreviousMessage()
end
),
key(
"Down",
"Select the next message.",
function(core, event)
return selectNextMessage()
end
),
key(
"Home",
"Select the first message.",
function(core, event)
return selectFirstMessage()
end
),
key(
"End",
"Select the last message.",
function(core, event)
return selectLastMessage()
end
)
},
messageInput = {
key(
"Ctrl+E",
"Open the external editor.",
function()
return openExternalEditor()
end
),
key(
"Ctrl+V",
"Paste the clipboard content.",
function()
return pasteClipboardContent()
end
)
}
}
-- Theme
theme = {background = "default", border = "white", title = "white"}

3
go.mod
View file

@ -8,9 +8,8 @@ require (
github.com/gdamore/tcell/v2 v2.5.3
github.com/rivo/tview v0.0.0-20220906194528-4664d8bf22d9
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64
github.com/zalando/go-keyring v0.2.1
layeh.com/gopher-luar v1.0.10
gopkg.in/yaml.v3 v3.0.1
)
require (

10
go.sum
View file

@ -2,9 +2,6 @@ github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVK
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
@ -46,14 +43,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc=
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -73,11 +66,10 @@ golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
layeh.com/gopher-luar v1.0.10 h1:55b0mpBhN9XSshEd2Nz6WsbYXctyBT35azk4POQNSXo=
layeh.com/gopher-luar v1.0.10/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=

26
main.go
View file

@ -3,8 +3,6 @@ package main
import (
"flag"
"log"
"os"
"path/filepath"
"github.com/ayntgl/discordo/config"
"github.com/ayntgl/discordo/ui"
@ -12,38 +10,24 @@ import (
"github.com/zalando/go-keyring"
)
const (
name = "discordo"
)
var (
token string
configPath string
)
var token string
func init() {
flag.StringVar(&token, "token", "", "The client authentication token.")
// If the token is provided via a command-line flag, store it in the default keyring.
if token != "" {
go keyring.Set(name, "token", token)
go keyring.Set(config.Name, "token", token)
}
if token == "" {
token, _ = keyring.Get(name, "token")
token, _ = keyring.Get(config.Name, "token")
}
configDirPath, err := os.UserConfigDir()
if err != nil {
log.Fatal(err)
}
flag.StringVar(&configPath, "config", filepath.Join(configDirPath, name), "The path to the configuration directory.")
}
func main() {
flag.Parse()
cfg := config.New(configPath)
cfg := config.New()
err := cfg.Load()
if err != nil {
log.Fatal(err)
@ -80,7 +64,7 @@ func main() {
log.Fatal(err)
}
go keyring.Set(name, "token", tkn)
go keyring.Set(config.Name, "token", tkn)
c.DrawMainFlex()
c.Application.SetRoot(c.MainFlex, true)

View file

@ -6,7 +6,6 @@ import (
"time"
"github.com/diamondburned/arikawa/v3/discord"
lua "github.com/yuin/gopher-lua"
)
func buildMessage(c *Core, m discord.Message) []byte {
@ -23,19 +22,14 @@ func buildMessage(c *Core, m discord.Message) []byte {
// Build the message associated with crosspost, channel follow add, pin, or a reply.
buildReferencedMessage(&b, m.ReferencedMessage, c.State.Ready().User.ID)
timestamps := c.Config.State.GetGlobal("timestamps")
if lua.LVAsBool(timestamps) {
timezone := c.Config.State.GetGlobal("timezone")
loc, err := time.LoadLocation(lua.LVAsString(timezone))
if c.Config.Timestamps {
loc, err := time.LoadLocation(c.Config.Timezone)
if err != nil {
return nil
}
timeFormat := c.Config.State.GetGlobal("timeFormat")
b.WriteString("[::d]")
b.WriteString(m.Timestamp.Time().In(loc).Format(lua.LVAsString(timeFormat)))
b.WriteString(m.Timestamp.Time().In(loc).Format(c.Config.TimeFormat))
b.WriteString("[::-]")
b.WriteByte(' ')
}

View file

@ -5,7 +5,6 @@ import (
"github.com/diamondburned/arikawa/v3/discord"
"github.com/rivo/tview"
lua "github.com/yuin/gopher-lua"
)
type ChannelsTree struct {
@ -64,9 +63,8 @@ func (ct *ChannelsTree) onSelected(node *tview.TreeNode) {
ct.core.MessagesPanel.SetTitle(title)
go func() {
messagesLimit := ct.core.Config.State.GetGlobal("messagesLimit")
// The returned slice will be sorted from latest to oldest.
ms, err := ct.core.State.Messages(c.ID, uint(lua.LVAsNumber(messagesLimit)))
ms, err := ct.core.State.Messages(c.ID, ct.core.Config.MessagesLimit)
if err != nil {
return
}

View file

@ -1,15 +1,9 @@
package ui
import (
"strings"
"github.com/ayntgl/discordo/config"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
)
type focused int
@ -47,7 +41,14 @@ func NewCore(cfg *config.Config) *Core {
Config: cfg,
}
tview.Styles.PrimitiveBackgroundColor = tcell.GetColor(cfg.Theme.Background)
tview.Styles.BorderColor = tcell.GetColor(cfg.Theme.Border)
tview.Styles.TitleColor = tcell.GetColor(cfg.Theme.Title)
c.Application.EnableMouse(c.Config.Mouse)
c.Application.SetInputCapture(c.onInputCapture)
c.Application.SetBeforeDrawFunc(c.beforeDraw)
c.GuildsTree = NewGuildsTree(c)
c.ChannelsTree = NewChannelsTree(c)
c.MessagesPanel = NewMessagesPanel(c)
@ -56,65 +57,10 @@ func NewCore(cfg *config.Config) *Core {
}
func (c *Core) Run(token string) error {
c.register()
err := c.Config.State.DoString(string(config.LuaConfig))
if err != nil {
return err
}
themeTable, ok := c.Config.State.GetGlobal("theme").(*lua.LTable)
if !ok {
themeTable = c.Config.State.NewTable()
}
backgroundColor := tcell.GetColor(lua.LVAsString(themeTable.RawGetString("background")))
borderColor := tcell.GetColor(lua.LVAsString(themeTable.RawGetString("border")))
titleColor := tcell.GetColor(lua.LVAsString(themeTable.RawGetString("title")))
c.GuildsTree.SetBackgroundColor(backgroundColor)
c.GuildsTree.SetBorderColor(borderColor)
c.GuildsTree.SetTitleColor(titleColor)
c.ChannelsTree.SetBackgroundColor(backgroundColor)
c.ChannelsTree.SetBorderColor(borderColor)
c.ChannelsTree.SetTitleColor(titleColor)
c.MessagesPanel.SetBackgroundColor(backgroundColor)
c.MessagesPanel.SetBorderColor(borderColor)
c.MessagesPanel.SetTitleColor(titleColor)
c.MessageInput.SetBackgroundColor(backgroundColor)
c.MessageInput.SetBorderColor(borderColor)
c.MessageInput.SetTitleColor(titleColor)
c.MessageInput.SetPlaceholderStyle(tcell.StyleDefault.Background(backgroundColor))
c.Application.SetBeforeDrawFunc(func(s tcell.Screen) bool {
if backgroundColor == 0 {
s.Clear()
}
return false
})
c.Application.EnableMouse(lua.LVAsBool(c.Config.State.GetGlobal("mouse")))
c.State = NewState(token, c)
return c.State.Run()
}
func (c *Core) register() {
c.Config.State.SetGlobal("key", c.Config.State.NewFunction(c.Config.KeyLua))
// Messages panel
c.Config.State.SetGlobal("openMessageActionsList", c.Config.State.NewFunction(c.MessagesPanel.openMessageActionsListLua))
c.Config.State.SetGlobal("selectPreviousMessage", c.Config.State.NewFunction(c.MessagesPanel.selectPreviousMessageLua))
c.Config.State.SetGlobal("selectNextMessage", c.Config.State.NewFunction(c.MessagesPanel.selectNextMessageLua))
c.Config.State.SetGlobal("selectFirstMessage", c.Config.State.NewFunction(c.MessagesPanel.selectFirstMessageLua))
c.Config.State.SetGlobal("selectLastMessage", c.Config.State.NewFunction(c.MessagesPanel.selectLastMessageLua))
// Message input
c.Config.State.SetGlobal("openExternalEditor", c.Config.State.NewFunction(c.MessageInput.openExternalEditorLua))
c.Config.State.SetGlobal("pasteClipboardContent", c.Config.State.NewFunction(c.MessageInput.pasteClipboardContentLua))
}
func (c *Core) DrawMainFlex() {
leftFlex := tview.NewFlex().
SetDirection(tview.FlexRow).
@ -129,52 +75,21 @@ func (c *Core) DrawMainFlex() {
AddItem(rightFlex, 0, 4, false)
}
func (c *Core) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
func (c *Core) beforeDraw(screen tcell.Screen) bool {
if c.Config.Theme.Background == "default" {
screen.Clear()
}
return false
}
func (c *Core) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
// If the main flex is nil, that is, it is not initialized yet, then the login form is currently focused.
if c.MainFlex == nil {
return e
return event
}
keysTable, ok := c.Config.State.GetGlobal("keys").(*lua.LTable)
if !ok {
keysTable = c.Config.State.NewTable()
}
applicationTable, ok := keysTable.RawGetString("application").(*lua.LTable)
if !ok {
applicationTable = c.Config.State.NewTable()
}
var fn lua.LValue
applicationTable.ForEach(func(k, v lua.LValue) {
keyTable := v.(*lua.LTable)
if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
fn = keyTable.RawGetString("action")
}
})
if fn != nil {
c.Config.State.CallByParam(lua.P{
Fn: fn,
NRet: 1,
Protect: true,
}, luar.New(c.Config.State, c), luar.New(c.Config.State, e))
// Returned value
ret, ok := c.Config.State.Get(-1).(*lua.LUserData)
if !ok {
return e
}
// Remove returned value
c.Config.State.Pop(1)
ev, ok := ret.Value.(*tcell.EventKey)
if ok {
return ev
}
}
// Default
switch e.Key() {
switch event.Key() {
case tcell.KeyEsc:
c.focused = 0
case tcell.KeyBacktab:
@ -197,7 +112,7 @@ func (c *Core) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
c.setFocus()
}
return e
return event
}
func (c *Core) setFocus() {
@ -215,101 +130,3 @@ func (c *Core) setFocus() {
c.Application.SetFocus(p)
}
func (c *Core) onStateReady(r *gateway.ReadyEvent) {
rootNode := c.GuildsTree.GetRoot()
for _, gf := range r.UserSettings.GuildFolders {
if gf.ID == 0 {
for _, gID := range gf.GuildIDs {
g, err := c.State.Cabinet.Guild(gID)
if err != nil {
return
}
guildNode := tview.NewTreeNode(g.Name)
guildNode.SetReference(g.ID)
rootNode.AddChild(guildNode)
}
} else {
var b strings.Builder
if gf.Color != discord.NullColor {
b.WriteByte('[')
b.WriteString(gf.Color.String())
b.WriteByte(']')
} else {
b.WriteString("[#ED4245]")
}
if gf.Name != "" {
b.WriteString(gf.Name)
} else {
b.WriteString("Folder")
}
b.WriteString("[-]")
folderNode := tview.NewTreeNode(b.String())
rootNode.AddChild(folderNode)
for _, gID := range gf.GuildIDs {
g, err := c.State.Cabinet.Guild(gID)
if err != nil {
return
}
guildNode := tview.NewTreeNode(g.Name)
guildNode.SetReference(g.ID)
folderNode.AddChild(guildNode)
}
}
}
c.GuildsTree.SetCurrentNode(rootNode)
c.Application.SetFocus(c.GuildsTree)
}
func (c *Core) onStateGuildCreate(g *gateway.GuildCreateEvent) {
guildNode := tview.NewTreeNode(g.Name)
guildNode.SetReference(g.ID)
rootNode := c.GuildsTree.GetRoot()
rootNode.AddChild(guildNode)
c.GuildsTree.SetCurrentNode(rootNode)
c.Application.SetFocus(c.GuildsTree)
c.Application.Draw()
}
func (c *Core) onStateGuildDelete(g *gateway.GuildDeleteEvent) {
rootNode := c.GuildsTree.GetRoot()
var parentNode *tview.TreeNode
rootNode.Walk(func(node, _ *tview.TreeNode) bool {
if node.GetReference() == g.ID {
parentNode = node
return false
}
return true
})
if parentNode != nil {
rootNode.RemoveChild(parentNode)
}
c.Application.Draw()
}
func (c *Core) onStateMessageCreate(m *gateway.MessageCreateEvent) {
if c.ChannelsTree.SelectedChannel != nil && c.ChannelsTree.SelectedChannel.ID == m.ChannelID {
_, err := c.MessagesPanel.Write(buildMessage(c, m.Message))
if err != nil {
return
}
if len(c.MessagesPanel.GetHighlights()) == 0 {
c.MessagesPanel.ScrollToEnd()
}
}
}

View file

@ -12,8 +12,6 @@ import (
"github.com/diamondburned/arikawa/v3/utils/json/option"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
)
type MessageInput struct {
@ -30,7 +28,7 @@ func NewMessageInput(c *Core) *MessageInput {
mi.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
mi.SetPlaceholder("Message...")
mi.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor))
mi.SetInputCapture(mi.onInputCapture)
mi.SetInputCapture(mi.inputCapture)
mi.SetTitleAlign(tview.AlignLeft)
mi.SetBorder(true)
@ -39,50 +37,14 @@ func NewMessageInput(c *Core) *MessageInput {
return mi
}
func (mi *MessageInput) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
keysTable, ok := mi.core.Config.State.GetGlobal("keys").(*lua.LTable)
if !ok {
keysTable = mi.core.Config.State.NewTable()
}
messageInputTable, ok := keysTable.RawGetString("messageInput").(*lua.LTable)
if !ok {
messageInputTable = mi.core.Config.State.NewTable()
}
var fn lua.LValue
messageInputTable.ForEach(func(k, v lua.LValue) {
keyTable := v.(*lua.LTable)
if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
fn = keyTable.RawGetString("action")
}
})
if fn != nil {
mi.core.Config.State.CallByParam(lua.P{
Fn: fn,
NRet: 1,
Protect: true,
}, luar.New(mi.core.Config.State, mi.core), luar.New(mi.core.Config.State, e))
// Returned value
ret, ok := mi.core.Config.State.Get(-1).(*lua.LUserData)
if !ok {
return e
}
// Remove returned value
mi.core.Config.State.Pop(1)
ev, ok := ret.Value.(*tcell.EventKey)
if ok {
return ev
}
}
// Defaults
switch e.Name() {
func (mi *MessageInput) inputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Name() {
case "Enter":
return mi.sendMessage()
case mi.core.Config.Keys.MessageInput.OpenExternalEditor:
return mi.openExternalEditor()
case mi.core.Config.Keys.MessageInput.PasteClipboard:
return mi.pasteClipboard()
case "Esc":
mi.
SetText("").
@ -92,7 +54,7 @@ func (mi *MessageInput) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
return nil
}
return e
return event
}
func (mi *MessageInput) sendMessage() *tcell.EventKey {
@ -105,8 +67,7 @@ func (mi *MessageInput) sendMessage() *tcell.EventKey {
return nil
}
messagesLimit := mi.core.Config.State.GetGlobal("messagesLimit")
ms, err := mi.core.State.Messages(mi.core.ChannelsTree.SelectedChannel.ID, uint(lua.LVAsNumber(messagesLimit)))
ms, err := mi.core.State.Messages(mi.core.ChannelsTree.SelectedChannel.ID, mi.core.Config.MessagesLimit)
if err != nil {
return nil
}
@ -143,22 +104,22 @@ func (mi *MessageInput) sendMessage() *tcell.EventKey {
return nil
}
func (mi *MessageInput) pasteClipboardContentLua(s *lua.LState) int {
func (mi *MessageInput) pasteClipboard() *tcell.EventKey {
text, _ := clipboard.ReadAll()
text = mi.GetText() + text
mi.SetText(text)
return returnNilLua(s)
return nil
}
func (mi *MessageInput) openExternalEditorLua(s *lua.LState) int {
func (mi *MessageInput) openExternalEditor() *tcell.EventKey {
e := os.Getenv("EDITOR")
if e == "" {
return returnNilLua(s)
return nil
}
f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
if err != nil {
return returnNilLua(s)
return nil
}
defer os.Remove(f.Name())
@ -175,9 +136,9 @@ func (mi *MessageInput) openExternalEditorLua(s *lua.LState) int {
b, err := io.ReadAll(f)
if err != nil {
return returnNilLua(s)
return nil
}
mi.SetText(string(b))
return returnNilLua(s)
return nil
}

View file

@ -4,8 +4,6 @@ import (
"github.com/diamondburned/arikawa/v3/discord"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
)
type MessagesPanel struct {
@ -51,47 +49,19 @@ func (mp *MessagesPanel) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
return nil
}
keysTable, ok := mp.core.Config.State.GetGlobal("keys").(*lua.LTable)
if !ok {
keysTable = mp.core.Config.State.NewTable()
}
messagesPanel, ok := keysTable.RawGetString("messagesPanel").(*lua.LTable)
if !ok {
messagesPanel = mp.core.Config.State.NewTable()
}
var fn lua.LValue
messagesPanel.ForEach(func(k, v lua.LValue) {
keyTable := v.(*lua.LTable)
if e.Name() == lua.LVAsString(keyTable.RawGetString("name")) {
fn = keyTable.RawGetString("action")
}
})
if fn != nil {
mp.core.Config.State.CallByParam(lua.P{
Fn: fn,
NRet: 1,
Protect: true,
}, luar.New(mp.core.Config.State, mp.core), luar.New(mp.core.Config.State, e))
// Returned value
ret, ok := mp.core.Config.State.Get(-1).(*lua.LUserData)
if !ok {
return e
}
// Remove returned value
mp.core.Config.State.Pop(1)
ev, ok := ret.Value.(*tcell.EventKey)
if ok {
return ev
}
}
// Defaults
switch e.Name() {
case mp.core.Config.Keys.MessagesPanel.OpenActionsList:
return mp.openMessageActionsList(ms)
case mp.core.Config.Keys.MessagesPanel.SelectPreviousMessage:
return mp.selectPreviousMessage(ms)
case mp.core.Config.Keys.MessagesPanel.SelectNextMessage:
return mp.selectNextMessage(ms)
case mp.core.Config.Keys.MessagesPanel.SelectFirstMessage:
return mp.selectFirstMessage(ms)
case mp.core.Config.Keys.MessagesPanel.SelectLastMessage:
return mp.selectLastMessage(ms)
case "Esc":
mp.SelectedMessage = -1
mp.core.Application.SetFocus(mp.core.MainFlex)
@ -105,13 +75,7 @@ func (mp *MessagesPanel) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
return e
}
func (mp *MessagesPanel) selectPreviousMessageLua(s *lua.LState) int {
// Messages should return messages ordered from latest to earliest.
ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
if err != nil || len(ms) == 0 {
return returnNilLua(s)
}
func (mp *MessagesPanel) selectPreviousMessage(ms []discord.Message) *tcell.EventKey {
// If there are no highlighted regions, select the latest (last) message in the messages panel.
if len(mp.GetHighlights()) == 0 {
mp.SelectedMessage = 0
@ -126,16 +90,10 @@ func (mp *MessagesPanel) selectPreviousMessageLua(s *lua.LState) int {
mp.Highlight(ms[mp.SelectedMessage].ID.String())
mp.ScrollToHighlight()
return returnNilLua(s)
return nil
}
func (mp *MessagesPanel) selectNextMessageLua(s *lua.LState) int {
// Messages should return messages ordered from latest to earliest.
ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
if err != nil || len(ms) == 0 {
return returnNilLua(s)
}
func (mp *MessagesPanel) selectNextMessage(ms []discord.Message) *tcell.EventKey {
// If there are no highlighted regions, select the latest (last) message in the messages panel.
if len(mp.GetHighlights()) == 0 {
mp.SelectedMessage = 0
@ -151,60 +109,42 @@ func (mp *MessagesPanel) selectNextMessageLua(s *lua.LState) int {
mp.
Highlight(ms[mp.SelectedMessage].ID.String()).
ScrollToHighlight()
return returnNilLua(s)
return nil
}
func (mp *MessagesPanel) selectFirstMessageLua(s *lua.LState) int {
// Messages should return messages ordered from latest to earliest.
ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
if err != nil || len(ms) == 0 {
return returnNilLua(s)
}
func (mp *MessagesPanel) selectFirstMessage(ms []discord.Message) *tcell.EventKey {
mp.SelectedMessage = len(ms) - 1
mp.
Highlight(ms[mp.SelectedMessage].ID.String()).
ScrollToHighlight()
return returnNilLua(s)
return nil
}
func (mp *MessagesPanel) selectLastMessageLua(s *lua.LState) int {
// Messages should return messages ordered from latest to earliest.
ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
if err != nil || len(ms) == 0 {
return returnNilLua(s)
}
func (mp *MessagesPanel) selectLastMessage(ms []discord.Message) *tcell.EventKey {
mp.SelectedMessage = 0
mp.
Highlight(ms[mp.SelectedMessage].ID.String()).
ScrollToHighlight()
return returnNilLua(s)
return nil
}
func (mp *MessagesPanel) openMessageActionsListLua(s *lua.LState) int {
// Messages should return messages ordered from latest to earliest.
ms, err := mp.core.State.Cabinet.Messages(mp.core.ChannelsTree.SelectedChannel.ID)
if err != nil || len(ms) == 0 {
return returnNilLua(s)
}
func (mp *MessagesPanel) openMessageActionsList(ms []discord.Message) *tcell.EventKey {
hs := mp.GetHighlights()
if len(hs) == 0 {
return returnNilLua(s)
return nil
}
mID, err := discord.ParseSnowflake(hs[0])
if err != nil {
return returnNilLua(s)
return nil
}
_, m := findMessageByID(ms, discord.MessageID(mID))
if m == nil {
return returnNilLua(s)
return nil
}
actionsList := NewMessageActionsList(mp.core, m)
mp.core.Application.SetRoot(actionsList, true)
return returnNilLua(s)
return nil
}

View file

@ -5,7 +5,6 @@ import (
"strings"
"github.com/diamondburned/arikawa/v3/discord"
lua "github.com/yuin/gopher-lua"
)
var (
@ -66,8 +65,3 @@ func hasPermission(s *State, cID discord.ChannelID, p discord.Permissions) bool
return perm&p == p
}
func returnNilLua(s *lua.LState) int {
s.Push(lua.LNil) // Push the result
return 1 // Number of results
}