config.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package config
  2. import (
  3. _ "embed"
  4. "fmt"
  5. "log/slog"
  6. "os"
  7. "path/filepath"
  8. "unicode/utf8"
  9. "github.com/BurntSushi/toml"
  10. "github.com/ayn2op/discordo/internal/consts"
  11. "github.com/diamondburned/arikawa/v3/discord"
  12. )
  13. const fileName = "config.toml"
  14. type (
  15. Timestamps struct {
  16. Enabled bool `toml:"enabled"`
  17. Format string `toml:"format"`
  18. }
  19. DateSeparator struct {
  20. Enabled bool `toml:"enabled"`
  21. Format string `toml:"format"`
  22. Character string `toml:"character"`
  23. }
  24. Notifications struct {
  25. Enabled bool `toml:"enabled"`
  26. Duration int `toml:"duration"`
  27. Sound Sound `toml:"sound"`
  28. }
  29. Sound struct {
  30. Enabled bool `toml:"enabled"`
  31. OnlyOnPing bool `toml:"only_on_ping"`
  32. }
  33. TypingIndicator struct {
  34. Send bool `toml:"send"`
  35. Receive bool `toml:"receive"`
  36. }
  37. Icons struct {
  38. GuildCategory string `toml:"guild_category"`
  39. GuildText string `toml:"guild_text"`
  40. GuildVoice string `toml:"guild_voice"`
  41. GuildStageVoice string `toml:"guild_stage_voice"`
  42. GuildAnnouncementThread string `toml:"guild_announcement_thread"`
  43. GuildPublicThread string `toml:"guild_public_thread"`
  44. GuildPrivateThread string `toml:"guild_private_thread"`
  45. GuildAnnouncement string `toml:"guild_announcement"`
  46. GuildForum string `toml:"guild_forum"`
  47. GuildStore string `toml:"guild_store"`
  48. }
  49. PickerConfig struct {
  50. Width int `toml:"width"`
  51. Height int `toml:"height"`
  52. }
  53. MarkdownConfig struct {
  54. Enabled bool `toml:"enabled"`
  55. Theme string `toml:"theme"`
  56. }
  57. HelpConfig struct {
  58. CompactModifiers bool `toml:"compact_modifiers"`
  59. Padding [2]int `toml:"padding"`
  60. Separator string `toml:"separator"`
  61. }
  62. SidebarMarkersConfig struct {
  63. Expanded string `toml:"expanded"`
  64. Collapsed string `toml:"collapsed"`
  65. Leaf string `toml:"leaf"`
  66. }
  67. SidebarConfig struct {
  68. Markers SidebarMarkersConfig `toml:"markers"`
  69. }
  70. Config struct {
  71. AutoFocus bool `toml:"auto_focus"`
  72. Mouse bool `toml:"mouse"`
  73. Editor string `toml:"editor"`
  74. Status discord.Status `toml:"status"`
  75. HideBlockedUsers bool `toml:"hide_blocked_users"`
  76. ShowAttachmentLinks bool `toml:"show_attachment_links"`
  77. // Use 0 to disable
  78. AutocompleteLimit uint8 `toml:"autocomplete_limit"`
  79. MessagesLimit uint8 `toml:"messages_limit"`
  80. Markdown MarkdownConfig `toml:"markdown"`
  81. Help HelpConfig `toml:"help"`
  82. Picker PickerConfig `toml:"picker"`
  83. Timestamps Timestamps `toml:"timestamps"`
  84. DateSeparator DateSeparator `toml:"date_separator"`
  85. Notifications Notifications `toml:"notifications"`
  86. TypingIndicator TypingIndicator `toml:"typing_indicator"`
  87. Sidebar SidebarConfig `toml:"sidebar"`
  88. Icons Icons `toml:"icons"`
  89. Keybinds Keybinds `toml:"keybinds"`
  90. Theme Theme `toml:"theme"`
  91. }
  92. )
  93. //go:embed config.toml
  94. var defaultCfg []byte
  95. func DefaultPath() string {
  96. path, err := os.UserConfigDir()
  97. if err != nil {
  98. slog.Info(
  99. "user config dir cannot be determined; falling back to the current dir",
  100. "err", err,
  101. )
  102. path = "."
  103. }
  104. return filepath.Join(path, consts.Name, fileName)
  105. }
  106. // Load reads the configuration file and parses it.
  107. func Load(path string) (*Config, error) {
  108. cfg := Config{
  109. Keybinds: defaultKeybinds(),
  110. }
  111. if err := toml.Unmarshal(defaultCfg, &cfg); err != nil {
  112. return nil, fmt.Errorf("failed to unmarshal default config: %w", err)
  113. }
  114. file, err := os.Open(path)
  115. if os.IsNotExist(err) {
  116. slog.Info(
  117. "config file does not exist, falling back to the default config",
  118. "path",
  119. path,
  120. "err",
  121. err,
  122. )
  123. } else {
  124. if err != nil {
  125. return nil, fmt.Errorf("failed to open config file: %w", err)
  126. }
  127. defer file.Close()
  128. if _, err := toml.NewDecoder(file).Decode(&cfg); err != nil {
  129. return nil, fmt.Errorf("failed to decode config: %w", err)
  130. }
  131. }
  132. applyDefaults(&cfg)
  133. return &cfg, nil
  134. }
  135. func applyDefaults(cfg *Config) {
  136. if cfg.Editor == "default" {
  137. cfg.Editor = os.Getenv("EDITOR")
  138. }
  139. if cfg.Status == "default" {
  140. cfg.Status = discord.UnknownStatus
  141. }
  142. if cfg.DateSeparator.Format == "" {
  143. cfg.DateSeparator.Format = "January 2, 2006"
  144. }
  145. if cfg.DateSeparator.Character == "" {
  146. cfg.DateSeparator.Character = "─"
  147. return
  148. }
  149. r, _ := utf8.DecodeRuneInString(cfg.DateSeparator.Character)
  150. if r == utf8.RuneError {
  151. cfg.DateSeparator.Character = "─"
  152. return
  153. }
  154. cfg.DateSeparator.Character = string(r)
  155. }