diff --git a/README.md b/README.md index 33fa228..4dd842e 100644 --- a/README.md +++ b/README.md @@ -55,46 +55,6 @@ go build . 2. Enter your email and password and click on the "Login" button to continue. -## Keymaps - -### Global - -- `Ctrl+G`: Focus Guilds Tree -- `Ctrl+T`: Focus Messages Text -- `Ctrl+P`: Focus Message Input -- `Ctrl+B`: Toggle Guilds Tree (sidebar) -- `Esc`: Reset message selection or close the channel selection popup. -- `Ctrl+C`: Quit the application. -- `Ctrl+D`: Log out and remove the authentication token from keyring (requires re-login upon restart). - -#### Navigation - -- `k`: Select Previous (any context except input) -- `j`: Select Next (any context except input) -- `g`: Select First (any context except input) -- `G`: Select Last (any context except input) - -### Guilds Tree - -- `Enter`: Select the currently highlighted text-based channel or expand a guild or channel. - -### Message Text - -- `s`: Select the message reference (reply) of the selected channel. -- `p`: Select the pinned message. -- `r`: Reply to the selected message. -- `R`: Reply (with mention) to the selected message. -- `d`: Delete the selected message. -- `y`: Yank (copy) the selected message's content. -- `o`: Open the selected message's attachments in the default browser application. - -### Message Input - -- `Alt+Enter`: Insert a new line to the current text. -- `Enter`: Send the message. -- `Ctrl+E`: Open message input in your default `$EDITOR`. -- `Esc`: Remove existing text or cancel reply. - ## Configuration The configuration file allows you to configure and customize the behavior, keybindings, and theme of the application. @@ -103,7 +63,7 @@ The configuration file allows you to configure and customize the behavior, keybi - Darwin: `$HOME/Library/Application Support/discordo/config.toml` - Windows: `%AppData%/discordo/config.toml` -[The default configuration can be found here](./internal/config/config.go). +[The default configuration can be found here](./internal/config/config.toml). ## FAQ diff --git a/cmd/root.go b/cmd/root.go index f13a49f..508b812 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,6 +20,11 @@ var ( func Run() error { logLevel := flag.String("log-level", "info", "log level") + logFormat := flag.String("log-format", "text", "log format") + token := flag.String("token", "", "authentication token") + configPath := flag.String("config", config.DefaultPath(), "path to the configuration file") + flag.Parse() + var level slog.Level switch *logLevel { case "debug": @@ -33,7 +38,6 @@ func Run() error { level = slog.LevelError } - logFormat := flag.String("log-format", "text", "log format") var format logger.Format switch *logFormat { case "text": @@ -46,7 +50,6 @@ func Run() error { return err } - token := flag.String("token", "", "authentication token") tok := *token if tok == "" { var err error @@ -56,7 +59,7 @@ func Run() error { } } - cfg, err := config.Load() + cfg, err := config.Load(*configPath) if err != nil { return err } diff --git a/internal/config/border.go b/internal/config/border.go index 50fc497..031215e 100644 --- a/internal/config/border.go +++ b/internal/config/border.go @@ -19,6 +19,8 @@ func (p *BorderPreset) UnmarshalTOML(v any) error { *p = borderPresetThick() case "round": *p = borderPresetRound() + case "light": + *p = borderPresetLight() case "hidden": *p = BorderPreset{ Horizontal: ' ', @@ -65,3 +67,14 @@ func borderPresetRound() BorderPreset { BottomRight: tview.BoxDrawingsLightArcUpAndLeft, } } + +func borderPresetLight() BorderPreset { + return BorderPreset{ + Horizontal: tview.BoxDrawingsLightHorizontal, + Vertical: tview.BoxDrawingsLightVertical, + TopLeft: tview.BoxDrawingsLightDownAndRight, + TopRight: tview.BoxDrawingsLightDownAndLeft, + BottomLeft: tview.BoxDrawingsLightUpAndRight, + BottomRight: tview.BoxDrawingsLightUpAndLeft, + } +} diff --git a/internal/config/config.go b/internal/config/config.go index b6e91b3..acffae8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,10 +1,10 @@ package config import ( + _ "embed" "log/slog" "os" "path/filepath" - "time" "github.com/BurntSushi/toml" "github.com/ayn2op/discordo/internal/consts" @@ -56,57 +56,31 @@ type ( } ) -func defaultConfig() *Config { - return &Config{ - Mouse: true, - Editor: "default", +//go:embed config.toml +var defaultCfg []byte - HideBlockedUsers: true, - ShowAttachmentLinks: true, - MessagesLimit: 50, - - MarkdownEnabled: true, - - Timestamps: Timestamps{ - Enabled: true, - Format: time.Kitchen, - }, - - Identify: Identify{ - Status: discord.OnlineStatus, - Browser: consts.Browser, - BrowserVersion: consts.BrowserVersion, - UserAgent: consts.UserAgent, - }, - - Notifications: Notifications{ - Enabled: true, - Duration: 500, - Sound: Sound{ - Enabled: true, - OnlyOnPing: true, - }, - }, - - Keys: defaultKeys(), - Theme: defaultTheme(), - } -} - -// Reads the configuration file and parses it. -func Load() (*Config, error) { +func DefaultPath() string { path, err := os.UserConfigDir() if err != nil { slog.Info("user configuration directory path cannot be determined; falling back to the current directory path") path = "." } - path = filepath.Join(path, consts.Name, fileName) + return filepath.Join(path, consts.Name, fileName) +} + +// Reads the configuration file and parses it. +func Load(path string) (*Config, error) { f, err := os.Open(path) - cfg := defaultConfig() + var cfg *Config + if err := toml.Unmarshal(defaultCfg, &cfg); err != nil { + return nil, err + } + if os.IsNotExist(err) { slog.Info("the configuration file does not exist, falling back to the default configuration", "path", path, "err", err) + handleDefaults(cfg) return cfg, nil } @@ -119,5 +93,18 @@ func Load() (*Config, error) { return nil, err } + handleDefaults(cfg) return cfg, nil } + +func handleDefaults(cfg *Config) { + if cfg.Identify.Browser == "default" { + cfg.Identify.Browser = consts.Browser + } + if cfg.Identify.BrowserVersion == "default" { + cfg.Identify.BrowserVersion = consts.BrowserVersion + } + if cfg.Identify.UserAgent == "default" { + cfg.Identify.UserAgent = consts.UserAgent + } +} diff --git a/internal/config/config.toml b/internal/config/config.toml new file mode 100644 index 0000000..75bcfd4 --- /dev/null +++ b/internal/config/config.toml @@ -0,0 +1,128 @@ +# Enable mouse controls +mouse = true + +# "default" means use $EDITOR. +editor = "default" + +hide_blocked_users = true +show_attachment_links = true +messages_limit = 50 +markdown_enabled = true + +# Timestamps uses Go timestamp format +# See: https://gosamples.dev/date-time-format-cheatsheet +[timestamps] +enabled = true +format = "3:04PM" + +[notifications] +enabled = true +duration = 500 +[notifications.sound] +enabled = true +only_on_ping = true + +# How Discord sees us. +# status: "online", "idle", "dnd" (Do Not Disturb), "" (invisible), +# note: does not seem to work +[identify] +status = "online" +browser = "default" +browser_version = "default" +user_agent = "default" + +# Global shortcuts +# Esc: Reset message selection or close the channel selection popup. +[keys] +focus_guilds_tree = "Ctrl+G" +focus_messages_text = "Ctrl+T" +focus_message_input = "Ctrl+P" +# Hide/show the guilds tree +toggle_guilds_tree = "Ctrl+B" +quit = "Ctrl+C" +# Log out and remove the authentication token from keyring. +# Requires re-login upon restart. +logout = "Ctrl+D" + +# Only while focusing on the guilds tree +[keys.guilds_tree] +select_previous = "Rune[k]" +select_next = "Rune[j]" +select_first = "Rune[g]" +select_last = "Rune[G]" +# Select the currently highlighted text-based channel or expand a guild or channel. +select_current = "Enter" +yank_id = "Rune[i]" +collapse_parent_node = "Rune[-]" +move_to_parent_node = "Rune[p]" + +# Only while focusing on sent messages +[keys.messages_text] +select_previous = "Rune[k]" +select_next = "Rune[j]" +select_first = "Rune[g]" +select_last = "Rune[G]" +# Select the message reference (reply) of the selected channel. +select_reply = "Rune[s]" +# Reply to the selected message. +reply = "Rune[r]" +# Reply (with mention) to the selected message. +reply_mention = "Rune[R]" +cancel = "Esc" +delete = "Rune[d]" +# Open the selected message's attachments or hyperlinks in the message +# using the default browser application. +open = "Rune[o]" +# Yank (copy) the selected message's content/url/id. +yank_content = "Rune[y]" +yank_url = "Rune[u]" +yank_id = "Rune[i]" + +# Only while typing a message +# Alt+Enter: Insert a new line to the current text. +[keys.message_input] +# Send the message. +send = "Enter" +# Open message input in your editor. +editor = "Ctrl+E" +# Remove existing text or cancel reply. +cancel = "Esc" + +# Applies to all +[theme] +background_color = "default" + +[theme.title] +# Title color of non-focused widgets +color = "default" +# Title color of the focused widget +active_color = "green" +align = "left" + +[theme.border] +enabled = true +# [top, bottom, left, right] +padding = [0, 0, 1, 1] +color = "default" +active_color = "green" +preset = "round" + +[theme.guilds_tree] +auto_expand_folders = true +# Give tree-like shape +graphics = true +private_channel_color = "white" +guild_color = "white" +channel_color = "white" + +[theme.messages_text] +# Set to false to show messages with usernames instead of nicknames +show_user_nicks = true +show_user_colors = true +reply_indicator = ">" +forwarded_indicator = "<" +author_color = "aqua" +content_color = "white" +emoji_color = "green" +link_color = "blue" +attachment_color = "yellow" diff --git a/internal/config/keys.go b/internal/config/keys.go index 313450a..1ffabcb 100644 --- a/internal/config/keys.go +++ b/internal/config/keys.go @@ -52,55 +52,3 @@ type ( Cancel string `toml:"cancel"` } ) - -func defaultKeys() Keys { - return Keys{ - FocusGuildsTree: "Ctrl+G", - FocusMessagesText: "Ctrl+T", - FocusMessageInput: "Ctrl+P", - ToggleGuildsTree: "Ctrl+B", - - Logout: "Ctrl+D", - Quit: "Ctrl+C", - - GuildsTree: GuildsTreeKeys{ - NavigationKeys: NavigationKeys{ - SelectPrevious: "Rune[k]", - SelectNext: "Rune[j]", - SelectFirst: "Rune[g]", - SelectLast: "Rune[G]", - }, - SelectCurrent: "Enter", - YankID: "Rune[i]", - CollapseParentNode: "Rune[-]", - MoveToParentNode: "Rune[p]", - }, - - MessagesText: MessagesTextKeys{ - NavigationKeys: NavigationKeys{ - SelectPrevious: "Rune[k]", - SelectNext: "Rune[j]", - SelectFirst: "Rune[g]", - SelectLast: "Rune[G]", - }, - SelectReply: "Rune[s]", - - Reply: "Rune[r]", - ReplyMention: "Rune[R]", - - Cancel: "Esc", - Delete: "Rune[d]", - Open: "Rune[o]", - - YankContent: "Rune[y]", - YankURL: "Rune[u]", - YankID: "Rune[i]", - }, - - MessageInput: MessageInputKeys{ - Send: "Enter", - Editor: "Ctrl+E", - Cancel: "Esc", - }, - } -} diff --git a/internal/config/theme.go b/internal/config/theme.go index 477b59a..0c6f073 100644 --- a/internal/config/theme.go +++ b/internal/config/theme.go @@ -68,45 +68,3 @@ type ( AttachmentColor string `toml:"attachment_color"` } ) - -func defaultTheme() Theme { - return Theme{ - BackgroundColor: "default", - - Border: BorderTheme{ - Enabled: true, - Padding: [...]int{0, 0, 1, 1}, - - Color: "default", - ActiveColor: "green", - Preset: borderPresetRound(), - }, - - Title: TitleTheme{ - Color: "default", - ActiveColor: "green", - Align: tview.AlignLeft, - }, - - GuildsTree: GuildsTreeTheme{ - AutoExpandFolders: true, - ChannelColor: tview.Styles.PrimaryTextColor.String(), - Graphics: true, - GuildColor: tview.Styles.PrimaryTextColor.String(), - PrivateChannelColor: tview.Styles.PrimaryTextColor.String(), - }, - MessagesText: MessagesTextTheme{ - ShowNicknames: true, - ShowUsernameColors: true, - - ReplyIndicator: ">", - ForwardedIndicator: "<", - - AuthorColor: "aqua", - ContentColor: tview.Styles.PrimaryTextColor.String(), - EmojiColor: "green", - LinkColor: "blue", - AttachmentColor: "yellow", - }, - } -}