diff --git a/README.md b/README.md index b5913e7..631e933 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ env vars * **POSTMOOGLE_DB_DSN** - database connection string * **POSTMOOGLE_DB_DIALECT** - database dialect (postgres, sqlite3) * **POSTMOOGLE_MAXSIZE** - max email size (including attachments) in megabytes -* **POSTMOOGLE_USERS** - a space-separated list of whitelisted users allowed to use the bridge. If not defined, everyone on the homeserver are allowed. Example rule: `@someone:example.com @another:example.com @bot.*:example.com @*:another.com` * **POSTMOOGLE_ADMINS** - a space-separated list of admin users. See `POSTMOOGLE_USERS` for syntax examples +* **POSTMOOGLE_USERS** - deprecated and ignored, use `!pm users` instead You can find default values in [config/defaults.go](config/defaults.go) @@ -83,6 +83,7 @@ If you want to change them - check available options in the help message (`!pm h --- * **!pm mailboxes** - Show the list of all mailboxes +* **!pm users** - Get or set allowed users patterns * **!pm delete** <mailbox> - Delete specific mailbox diff --git a/bot/access.go b/bot/access.go index 31cd5cc..bcb99fc 100644 --- a/bot/access.go +++ b/bot/access.go @@ -28,7 +28,7 @@ func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) bool { } } - cfg, err := b.getSettings(targetRoomID) + cfg, err := b.getRoomSettings(targetRoomID) if err != nil { b.Error(context.Background(), targetRoomID, "failed to retrieve settings: %v", err) return false diff --git a/bot/bot.go b/bot/bot.go index de25ddb..9365e5e 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -23,7 +23,8 @@ type Bot struct { allowedAdmins []*regexp.Regexp commands commandList rooms sync.Map - cfg cache.Cache[settings] + botcfg cache.Cache[botSettings] + cfg cache.Cache[roomSettings] log *logger.Logger lp *linkpearl.Linkpearl mu map[id.RoomID]*sync.Mutex @@ -36,34 +37,35 @@ func New( log *logger.Logger, prefix string, domain string, - users []string, + envUsers []string, admins []string, ) (*Bot, error) { - _, homeserver, err := lp.GetClient().UserID.Parse() + b := &Bot{ + prefix: prefix, + domain: domain, + rooms: sync.Map{}, + botcfg: cache.NewLRU[botSettings](1), + cfg: cache.NewLRU[roomSettings](1000), + log: log, + lp: lp, + mu: map[id.RoomID]*sync.Mutex{}, + } + users, err := b.initBotUsers(envUsers) if err != nil { return nil, err } - var allowedUsers []*regexp.Regexp - allowedUsers, uerr := parseMXIDpatterns(users, "@*:"+homeserver) + allowedUsers, uerr := parseMXIDpatterns(users, "") if uerr != nil { return nil, uerr } + b.allowedUsers = allowedUsers + allowedAdmins, aerr := parseMXIDpatterns(admins, "") if aerr != nil { return nil, aerr } + b.allowedAdmins = allowedAdmins - b := &Bot{ - prefix: prefix, - domain: domain, - allowedUsers: allowedUsers, - allowedAdmins: allowedAdmins, - rooms: sync.Map{}, - cfg: cache.NewLRU[settings](1000), - log: log, - lp: lp, - mu: map[id.RoomID]*sync.Mutex{}, - } b.commands = b.buildCommandList() return b, nil diff --git a/bot/command.go b/bot/command.go index 966662f..4db54ce 100644 --- a/bot/command.go +++ b/bot/command.go @@ -14,6 +14,7 @@ import ( const ( commandHelp = "help" commandStop = "stop" + commandUsers = botOptionUsers commandDelete = "delete" commandMailboxes = "mailboxes" ) @@ -53,64 +54,69 @@ func (b *Bot) buildCommandList() commandList { {allowed: b.allowOwner}, // delimiter // options commands { - key: optionMailbox, + key: roomOptionMailbox, description: "Get or set mailbox of the room", sanitizer: utils.Mailbox, allowed: b.allowOwner, }, { - key: optionOwner, + key: roomOptionOwner, description: "Get or set owner of the room", sanitizer: func(s string) string { return s }, allowed: b.allowOwner, }, {allowed: b.allowOwner}, // delimiter { - key: optionNoSender, + key: roomOptionNoSender, description: fmt.Sprintf( "Get or set `%s` of the room (`true` - hide email sender; `false` - show email sender)", - optionNoSender, + roomOptionNoSender, ), sanitizer: utils.SanitizeBoolString, allowed: b.allowOwner, }, { - key: optionNoSubject, + key: roomOptionNoSubject, description: fmt.Sprintf( "Get or set `%s` of the room (`true` - hide email subject; `false` - show email subject)", - optionNoSubject, + roomOptionNoSubject, ), sanitizer: utils.SanitizeBoolString, allowed: b.allowOwner, }, { - key: optionNoHTML, + key: roomOptionNoHTML, description: fmt.Sprintf( "Get or set `%s` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)", - optionNoHTML, + roomOptionNoHTML, ), sanitizer: utils.SanitizeBoolString, allowed: b.allowOwner, }, { - key: optionNoThreads, + key: roomOptionNoThreads, description: fmt.Sprintf( "Get or set `%s` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)", - optionNoThreads, + roomOptionNoThreads, ), sanitizer: utils.SanitizeBoolString, allowed: b.allowOwner, }, { - key: optionNoFiles, + key: roomOptionNoFiles, description: fmt.Sprintf( "Get or set `%s` of the room (`true` - ignore email attachments; `false` - upload email attachments)", - optionNoFiles, + roomOptionNoFiles, ), sanitizer: utils.SanitizeBoolString, allowed: b.allowOwner, }, {allowed: b.allowAdmin}, // delimiter + { + key: botOptionUsers, + description: "Get or set allowed users", + allowed: b.allowAdmin, + }, { key: commandMailboxes, description: "Show the list of all mailboxes", @@ -140,6 +146,8 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice b.sendHelp(ctx) case commandStop: b.runStop(ctx) + case commandUsers: + b.runUsers(ctx, commandSlice) case commandDelete: b.runDelete(ctx, commandSlice) case commandMailboxes: @@ -172,7 +180,7 @@ func (b *Bot) sendIntroduction(ctx context.Context, roomID id.RoomID) { msg.WriteString("To get started, assign an email address to this room by sending a `") msg.WriteString(b.prefix) msg.WriteString(" ") - msg.WriteString(optionMailbox) + msg.WriteString(roomOptionMailbox) msg.WriteString("` command.\n") msg.WriteString("You will then be able to send emails to `SOME_INBOX@") @@ -185,7 +193,7 @@ func (b *Bot) sendIntroduction(ctx context.Context, roomID id.RoomID) { func (b *Bot) sendHelp(ctx context.Context) { evt := eventFromContext(ctx) - cfg, serr := b.getSettings(evt.RoomID) + cfg, serr := b.getRoomSettings(evt.RoomID) if serr != nil { b.log.Error("cannot retrieve settings: %v", serr) } @@ -213,7 +221,7 @@ func (b *Bot) sendHelp(ctx context.Context) { case true: msg.WriteString("(currently `") msg.WriteString(value) - if cmd.key == optionMailbox { + if cmd.key == roomOptionMailbox { msg.WriteString("@") msg.WriteString(b.domain) } diff --git a/bot/command_admin.go b/bot/command_admin.go index b51b1de..978d8eb 100644 --- a/bot/command_admin.go +++ b/bot/command_admin.go @@ -13,7 +13,7 @@ import ( func (b *Bot) sendMailboxes(ctx context.Context) { evt := eventFromContext(ctx) - mailboxes := map[string]settings{} + mailboxes := map[string]roomSettings{} slice := []string{} b.rooms.Range(func(key any, value any) bool { if key == nil { @@ -31,7 +31,7 @@ func (b *Bot) sendMailboxes(ctx context.Context) { if !ok { return true } - config, err := b.getSettings(roomID) + config, err := b.getRoomSettings(roomID) if err != nil { b.log.Error("cannot retrieve settings: %v", err) } @@ -79,7 +79,7 @@ func (b *Bot) runDelete(ctx context.Context, commandSlice []string) { roomID := v.(id.RoomID) b.rooms.Delete(mailbox) - err := b.setSettings(roomID, settings{}) + err := b.setRoomSettings(roomID, roomSettings{}) if err != nil { b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) return @@ -87,3 +87,46 @@ func (b *Bot) runDelete(ctx context.Context, commandSlice []string) { b.SendNotice(ctx, evt.RoomID, "mailbox has been deleted") } + +func (b *Bot) runUsers(ctx context.Context, commandSlice []string) { + evt := eventFromContext(ctx) + cfg := b.getBotSettings() + if len(commandSlice) < 2 { + var msg strings.Builder + users := cfg.Users() + if len(users) > 0 { + msg.WriteString("Currently: `") + msg.WriteString(strings.Join(users, " ")) + msg.WriteString("`\n\n") + } + msg.WriteString("Usage: `") + msg.WriteString(b.prefix) + msg.WriteString(" users PATTERN1 PATTERN2 PATTERN3...`") + msg.WriteString("where each pattern is like `@someone:example.com`, ") + msg.WriteString("`@bot.*:example.com`, `@*:another.com`, or `@*:*`\n") + + b.SendNotice(ctx, evt.RoomID, msg.String()) + return + } + + _, homeserver, err := b.lp.GetClient().UserID.Parse() + if err != nil { + b.SendError(ctx, evt.RoomID, fmt.Sprintf("invalid userID: %v", err)) + } + + patterns := commandSlice[1:] + allowedUsers, err := parseMXIDpatterns(patterns, "@*:"+homeserver) + if err != nil { + b.SendError(ctx, evt.RoomID, fmt.Sprintf("invalid patterns: %v", err)) + return + } + + cfg.Set(botOptionUsers, strings.Join(patterns, " ")) + + err = b.setBotSettings(cfg) + if err != nil { + b.Error(ctx, evt.RoomID, "cannot set bot config: %v", err) + } + b.allowedUsers = allowedUsers + b.SendNotice(ctx, evt.RoomID, "allowed users updated") +} diff --git a/bot/command_owner.go b/bot/command_owner.go index 84f26cd..becd5fe 100644 --- a/bot/command_owner.go +++ b/bot/command_owner.go @@ -7,13 +7,13 @@ import ( func (b *Bot) runStop(ctx context.Context) { evt := eventFromContext(ctx) - cfg, err := b.getSettings(evt.RoomID) + cfg, err := b.getRoomSettings(evt.RoomID) if err != nil { b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) return } - mailbox := cfg.Get(optionMailbox) + mailbox := cfg.Get(roomOptionMailbox) if mailbox == "" { b.SendNotice(ctx, evt.RoomID, "that room is not configured yet") return @@ -21,7 +21,7 @@ func (b *Bot) runStop(ctx context.Context) { b.rooms.Delete(mailbox) - err = b.setSettings(evt.RoomID, settings{}) + err = b.setRoomSettings(evt.RoomID, roomSettings{}) if err != nil { b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) return @@ -40,7 +40,7 @@ func (b *Bot) handleOption(ctx context.Context, cmd []string) { func (b *Bot) getOption(ctx context.Context, name string) { evt := eventFromContext(ctx) - cfg, err := b.getSettings(evt.RoomID) + cfg, err := b.getRoomSettings(evt.RoomID) if err != nil { b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) return @@ -52,7 +52,7 @@ func (b *Bot) getOption(ctx context.Context, name string) { return } - if name == optionMailbox { + if name == roomOptionMailbox { value = value + "@" + b.domain } @@ -66,7 +66,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) { } evt := eventFromContext(ctx) - if name == optionMailbox { + if name == roomOptionMailbox { existingID, ok := b.GetMapping(value) if ok && existingID != "" && existingID != evt.RoomID { b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Mailbox `%s@%s` already taken, kupo", value, b.domain)) @@ -74,7 +74,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) { } } - cfg, err := b.getSettings(evt.RoomID) + cfg, err := b.getRoomSettings(evt.RoomID) if err != nil { b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) return @@ -83,8 +83,8 @@ func (b *Bot) setOption(ctx context.Context, name, value string) { old := cfg.Get(name) cfg.Set(name, value) - if name == optionMailbox { - cfg.Set(optionOwner, evt.Sender.String()) + if name == roomOptionMailbox { + cfg.Set(roomOptionOwner, evt.Sender.String()) if old != "" { b.rooms.Delete(old) } @@ -92,13 +92,13 @@ func (b *Bot) setOption(ctx context.Context, name, value string) { value = fmt.Sprintf("%s@%s", value, b.domain) } - err = b.setSettings(evt.RoomID, cfg) + err = b.setRoomSettings(evt.RoomID, cfg) if err != nil { b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) return } - if name == optionMailbox { + if name == roomOptionMailbox { value = value + "@" + b.domain } diff --git a/bot/data.go b/bot/data.go index 31b8902..160cc72 100644 --- a/bot/data.go +++ b/bot/data.go @@ -1,34 +1,5 @@ package bot -import ( - "strings" - - "maunium.net/go/mautrix/id" -) - -// account data keys -const ( - messagekey = "cc.etke.postmoogle.message" - settingskey = "cc.etke.postmoogle.settings" -) - -// event keys -const ( - eventMessageIDkey = "cc.etke.postmoogle.messageID" - eventInReplyToKey = "cc.etke.postmoogle.inReplyTo" -) - -// option keys -const ( - optionOwner = "owner" - optionMailbox = "mailbox" - optionNoSender = "nosender" - optionNoSubject = "nosubject" - optionNoHTML = "nohtml" - optionNoThreads = "nothreads" - optionNoFiles = "nofiles" -) - var migrations = []string{} func (b *Bot) migrate() error { @@ -67,7 +38,7 @@ func (b *Bot) syncRooms() error { } for _, roomID := range resp.JoinedRooms { b.migrateSettings(roomID) - cfg, serr := b.getSettings(roomID) + cfg, serr := b.getRoomSettings(roomID) if serr != nil { b.log.Warn("cannot get %s settings: %v", roomID, err) continue @@ -80,31 +51,3 @@ func (b *Bot) syncRooms() error { return nil } - -func (b *Bot) getThreadID(roomID id.RoomID, messageID string) id.EventID { - key := messagekey + "." + messageID - data := map[string]id.EventID{} - err := b.lp.GetClient().GetRoomAccountData(roomID, key, &data) - if err != nil { - if !strings.Contains(err.Error(), "M_NOT_FOUND") { - b.log.Error("cannot retrieve account data %s: %v", key, err) - return "" - } - } - - return data["eventID"] -} - -func (b *Bot) setThreadID(roomID id.RoomID, messageID string, eventID id.EventID) { - key := messagekey + "." + messageID - data := map[string]id.EventID{ - "eventID": eventID, - } - - err := b.lp.GetClient().SetRoomAccountData(roomID, key, data) - if err != nil { - if !strings.Contains(err.Error(), "M_NOT_FOUND") { - b.log.Error("cannot save account data %s: %v", key, err) - } - } -} diff --git a/bot/email.go b/bot/email.go index cf8b10a..2981136 100644 --- a/bot/email.go +++ b/bot/email.go @@ -12,7 +12,16 @@ import ( "gitlab.com/etke.cc/postmoogle/utils" ) -func email2content(email *utils.Email, cfg settings, threadID id.EventID) *event.Content { +// account data key +const acMessagePrefix = "cc.etke.postmoogle.message" + +// event keys +const ( + eventMessageIDkey = "cc.etke.postmoogle.messageID" + eventInReplyToKey = "cc.etke.postmoogle.inReplyTo" +) + +func email2content(email *utils.Email, cfg roomSettings, threadID id.EventID) *event.Content { var text strings.Builder if !cfg.NoSender() { text.WriteString("From: ") @@ -66,7 +75,7 @@ func (b *Bot) Send(ctx context.Context, email *utils.Email) error { b.lock(roomID) defer b.unlock(roomID) - cfg, err := b.getSettings(roomID) + cfg, err := b.getRoomSettings(roomID) if err != nil { b.Error(ctx, roomID, "cannot get settings: %v", err) } @@ -115,3 +124,31 @@ func (b *Bot) sendFiles(ctx context.Context, roomID id.RoomID, files []*utils.Fi } } } + +func (b *Bot) getThreadID(roomID id.RoomID, messageID string) id.EventID { + key := acMessagePrefix + "." + messageID + data := map[string]id.EventID{} + err := b.lp.GetClient().GetRoomAccountData(roomID, key, &data) + if err != nil { + if !strings.Contains(err.Error(), "M_NOT_FOUND") { + b.log.Error("cannot retrieve account data %s: %v", key, err) + return "" + } + } + + return data["eventID"] +} + +func (b *Bot) setThreadID(roomID id.RoomID, messageID string, eventID id.EventID) { + key := acMessagePrefix + "." + messageID + data := map[string]id.EventID{ + "eventID": eventID, + } + + err := b.lp.GetClient().SetRoomAccountData(roomID, key, data) + if err != nil { + if !strings.Contains(err.Error(), "M_NOT_FOUND") { + b.log.Error("cannot save account data %s: %v", key, err) + } + } +} diff --git a/bot/settings.go b/bot/settings.go deleted file mode 100644 index 74886c3..0000000 --- a/bot/settings.go +++ /dev/null @@ -1,108 +0,0 @@ -package bot - -import ( - "strconv" - "strings" - - "maunium.net/go/mautrix/id" - - "gitlab.com/etke.cc/postmoogle/utils" -) - -// settings of a room -type settings map[string]string - -// settingsOld of a room -type settingsOld struct { - Mailbox string - Owner id.UserID - NoSender bool -} - -// Get option -func (s settings) Get(key string) string { - return s[strings.ToLower(strings.TrimSpace(key))] -} - -func (s settings) Mailbox() string { - return s.Get(optionMailbox) -} - -func (s settings) Owner() string { - return s.Get(optionOwner) -} - -func (s settings) NoSender() bool { - return utils.Bool(s.Get(optionNoSender)) -} - -func (s settings) NoSubject() bool { - return utils.Bool(s.Get(optionNoSubject)) -} - -func (s settings) NoHTML() bool { - return utils.Bool(s.Get(optionNoHTML)) -} - -func (s settings) NoThreads() bool { - return utils.Bool(s.Get(optionNoThreads)) -} - -func (s settings) NoFiles() bool { - return utils.Bool(s.Get(optionNoFiles)) -} - -// Set option -func (s settings) Set(key, value string) { - s[strings.ToLower(strings.TrimSpace(key))] = value -} - -// TODO: remove after migration -func (b *Bot) migrateSettings(roomID id.RoomID) { - var config settingsOld - err := b.lp.GetClient().GetRoomAccountData(roomID, settingskey, &config) - if err != nil { - // any error = no need to migrate - return - } - - if config.Mailbox == "" { - return - } - cfg := settings{} - cfg.Set(optionMailbox, config.Mailbox) - cfg.Set(optionOwner, config.Owner.String()) - cfg.Set(optionNoSender, strconv.FormatBool(config.NoSender)) - - err = b.setSettings(roomID, cfg) - if err != nil { - b.log.Error("cannot migrate settings: %v", err) - } -} - -func (b *Bot) getSettings(roomID id.RoomID) (settings, error) { - cfg := b.cfg.Get(roomID.String()) - if cfg != nil { - return cfg, nil - } - - config := settings{} - err := b.lp.GetClient().GetRoomAccountData(roomID, settingskey, &config) - if err != nil { - if strings.Contains(err.Error(), "M_NOT_FOUND") { - // Suppress `M_NOT_FOUND (HTTP 404): Room account data not found` errors. - // Until some settings are explicitly set, we don't store any. - // In such cases, just return a default (empty) settings object. - err = nil - } - } else { - b.cfg.Set(roomID.String(), config) - } - - return config, utils.UnwrapError(err) -} - -func (b *Bot) setSettings(roomID id.RoomID, cfg settings) error { - b.cfg.Set(roomID.String(), cfg) - return utils.UnwrapError(b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg)) -} diff --git a/bot/settings_bot.go b/bot/settings_bot.go new file mode 100644 index 0000000..72ee168 --- /dev/null +++ b/bot/settings_bot.go @@ -0,0 +1,91 @@ +package bot + +import ( + "strings" + + "gitlab.com/etke.cc/postmoogle/utils" +) + +// account data key +const acBotSettingsKey = "cc.etke.postmoogle.config" + +// bot options keys +const ( + botOptionUsers = "users" +) + +type botSettings map[string]string + +// Get option +func (s botSettings) Get(key string) string { + return s[strings.ToLower(strings.TrimSpace(key))] +} + +// Set option +func (s botSettings) Set(key, value string) { + s[strings.ToLower(strings.TrimSpace(key))] = value +} + +// Users option +func (s botSettings) Users() []string { + value := s.Get(botOptionUsers) + if value == "" { + return []string{} + } + + if strings.Contains(value, " ") { + return strings.Split(value, " ") + } + + return []string{value} +} + +func (b *Bot) initBotUsers(envUsers []string) ([]string, error) { + config := b.getBotSettings() + cfgUsers := config.Users() + if len(cfgUsers) > 0 { + // already migrated + return cfgUsers, nil + } + if len(envUsers) == 0 { + _, homeserver, err := b.lp.GetClient().UserID.Parse() + if err != nil { + return nil, err + } + config.Set(botOptionUsers, "@*:"+homeserver) + } else { + // Initialize from environment variable + // TODO: remove this migration later and always initialize to `"@*:"+homeserver` + config.Set(botOptionUsers, strings.Join(envUsers, " ")) + } + + return config.Users(), b.setBotSettings(config) +} + +func (b *Bot) getBotSettings() botSettings { + cfg := b.botcfg.Get(acBotSettingsKey) + if cfg != nil { + return cfg + } + + config := botSettings{} + err := b.lp.GetClient().GetAccountData(acBotSettingsKey, &config) + if err != nil { + if strings.Contains(err.Error(), "M_NOT_FOUND") { + err = nil + } else { + b.log.Error("cannot get bot settings: %v", utils.UnwrapError(err)) + } + } + + if err == nil { + b.botcfg.Set(acBotSettingsKey, config) + } + + return config +} + +func (b *Bot) setBotSettings(cfg botSettings) error { + b.botcfg.Set(acBotSettingsKey, cfg) + return utils.UnwrapError(b.lp.GetClient().SetAccountData(acBotSettingsKey, cfg)) +} diff --git a/bot/settings_room.go b/bot/settings_room.go new file mode 100644 index 0000000..ce0bfd6 --- /dev/null +++ b/bot/settings_room.go @@ -0,0 +1,121 @@ +package bot + +import ( + "strconv" + "strings" + + "maunium.net/go/mautrix/id" + + "gitlab.com/etke.cc/postmoogle/utils" +) + +// account data key +const acRoomSettingsKey = "cc.etke.postmoogle.settings" + +// option keys +const ( + roomOptionOwner = "owner" + roomOptionMailbox = "mailbox" + roomOptionNoSender = "nosender" + roomOptionNoSubject = "nosubject" + roomOptionNoHTML = "nohtml" + roomOptionNoThreads = "nothreads" + roomOptionNoFiles = "nofiles" +) + +type roomSettings map[string]string + +// settingsOld of a room +type settingsOld struct { + Mailbox string + Owner id.UserID + NoSender bool +} + +// Get option +func (s roomSettings) Get(key string) string { + return s[strings.ToLower(strings.TrimSpace(key))] +} + +// Set option +func (s roomSettings) Set(key, value string) { + s[strings.ToLower(strings.TrimSpace(key))] = value +} + +func (s roomSettings) Mailbox() string { + return s.Get(roomOptionMailbox) +} + +func (s roomSettings) Owner() string { + return s.Get(roomOptionOwner) +} + +func (s roomSettings) NoSender() bool { + return utils.Bool(s.Get(roomOptionNoSender)) +} + +func (s roomSettings) NoSubject() bool { + return utils.Bool(s.Get(roomOptionNoSubject)) +} + +func (s roomSettings) NoHTML() bool { + return utils.Bool(s.Get(roomOptionNoHTML)) +} + +func (s roomSettings) NoThreads() bool { + return utils.Bool(s.Get(roomOptionNoThreads)) +} + +func (s roomSettings) NoFiles() bool { + return utils.Bool(s.Get(roomOptionNoFiles)) +} + +// TODO: remove after migration +func (b *Bot) migrateSettings(roomID id.RoomID) { + var config settingsOld + err := b.lp.GetClient().GetRoomAccountData(roomID, acRoomSettingsKey, &config) + if err != nil { + // any error = no need to migrate + return + } + + if config.Mailbox == "" { + return + } + cfg := roomSettings{} + cfg.Set(roomOptionMailbox, config.Mailbox) + cfg.Set(roomOptionOwner, config.Owner.String()) + cfg.Set(roomOptionNoSender, strconv.FormatBool(config.NoSender)) + + err = b.setRoomSettings(roomID, cfg) + if err != nil { + b.log.Error("cannot migrate settings: %v", err) + } +} + +func (b *Bot) getRoomSettings(roomID id.RoomID) (roomSettings, error) { + cfg := b.cfg.Get(roomID.String()) + if cfg != nil { + return cfg, nil + } + + config := roomSettings{} + err := b.lp.GetClient().GetRoomAccountData(roomID, acRoomSettingsKey, &config) + if err != nil { + if strings.Contains(err.Error(), "M_NOT_FOUND") { + // Suppress `M_NOT_FOUND (HTTP 404): Room account data not found` errors. + // Until some settings are explicitly set, we don't store any. + // In such cases, just return a default (empty) settings object. + err = nil + } + } else { + b.cfg.Set(roomID.String(), config) + } + + return config, utils.UnwrapError(err) +} + +func (b *Bot) setRoomSettings(roomID id.RoomID, cfg roomSettings) error { + b.cfg.Set(roomID.String(), cfg) + return utils.UnwrapError(b.lp.GetClient().SetRoomAccountData(roomID, acRoomSettingsKey, cfg)) +} diff --git a/config/types.go b/config/types.go index 531ebfb..605d8a5 100644 --- a/config/types.go +++ b/config/types.go @@ -22,7 +22,7 @@ type Config struct { MaxSize int // StatusMsg of the bot StatusMsg string - // Users holds list of allowed users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = homeserver only + // Users DEPRECATED holds list of allowed users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = homeserver only Users []string // Admins holds list of admin users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = no admins Admins []string