config.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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. Config struct {
  58. AutoFocus bool `toml:"auto_focus"`
  59. Mouse bool `toml:"mouse"`
  60. Editor string `toml:"editor"`
  61. Status discord.Status `toml:"status"`
  62. HideBlockedUsers bool `toml:"hide_blocked_users"`
  63. ShowAttachmentLinks bool `toml:"show_attachment_links"`
  64. // Use 0 to disable
  65. AutocompleteLimit uint8 `toml:"autocomplete_limit"`
  66. MessagesLimit uint8 `toml:"messages_limit"`
  67. Markdown MarkdownConfig `toml:"markdown"`
  68. Picker PickerConfig `toml:"picker"`
  69. Timestamps Timestamps `toml:"timestamps"`
  70. DateSeparator DateSeparator `toml:"date_separator"`
  71. Notifications Notifications `toml:"notifications"`
  72. TypingIndicator TypingIndicator `toml:"typing_indicator"`
  73. Icons Icons `toml:"icons"`
  74. Keybinds Keybinds `toml:"keybinds"`
  75. Theme Theme `toml:"theme"`
  76. }
  77. )
  78. //go:embed config.toml
  79. var defaultCfg []byte
  80. func DefaultPath() string {
  81. path, err := os.UserConfigDir()
  82. if err != nil {
  83. slog.Info(
  84. "user config dir cannot be determined; falling back to the current dir",
  85. "err", err,
  86. )
  87. path = "."
  88. }
  89. return filepath.Join(path, consts.Name, fileName)
  90. }
  91. // Load reads the configuration file and parses it.
  92. func Load(path string) (*Config, error) {
  93. var cfg Config
  94. if err := toml.Unmarshal(defaultCfg, &cfg); err != nil {
  95. return nil, fmt.Errorf("failed to unmarshal default config: %w", err)
  96. }
  97. file, err := os.Open(path)
  98. if os.IsNotExist(err) {
  99. slog.Info(
  100. "config file does not exist, falling back to the default config",
  101. "path",
  102. path,
  103. "err",
  104. err,
  105. )
  106. } else {
  107. if err != nil {
  108. return nil, fmt.Errorf("failed to open config file: %w", err)
  109. }
  110. defer file.Close()
  111. if _, err := toml.NewDecoder(file).Decode(&cfg); err != nil {
  112. return nil, fmt.Errorf("failed to decode config: %w", err)
  113. }
  114. }
  115. applyDefaults(&cfg)
  116. return &cfg, nil
  117. }
  118. func applyDefaults(cfg *Config) {
  119. if cfg.Editor == "default" {
  120. cfg.Editor = os.Getenv("EDITOR")
  121. }
  122. if cfg.Status == "default" {
  123. cfg.Status = discord.UnknownStatus
  124. }
  125. if cfg.DateSeparator.Format == "" {
  126. cfg.DateSeparator.Format = "January 2, 2006"
  127. }
  128. if cfg.DateSeparator.Character == "" {
  129. cfg.DateSeparator.Character = "─"
  130. return
  131. }
  132. r, _ := utf8.DecodeRuneInString(cfg.DateSeparator.Character)
  133. if r == utf8.RuneError {
  134. cfg.DateSeparator.Character = "─"
  135. return
  136. }
  137. cfg.DateSeparator.Character = string(r)
  138. }