From a62dc0df4f70dde821dc5479f31428b6bfd74fc0 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Mon, 29 Aug 2022 09:10:31 +0300 Subject: [PATCH 1/9] Add POSTMOOGLE_ADMINS --- README.md | 1 + bot/bot.go | 29 +++++++++++++++++++---------- cmd/cmd.go | 2 +- config/config.go | 32 ++++++++++++++++++++++++-------- config/types.go | 2 ++ 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 1a74193..c0c28d7 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ env vars * **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 is 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 You can find default values in [config/defaults.go](config/defaults.go) diff --git a/bot/bot.go b/bot/bot.go index d731b7e..3c8a662 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -21,6 +21,7 @@ type Bot struct { prefix string domain string allowedUsers []*regexp.Regexp + allowedAdmins []*regexp.Regexp rooms sync.Map log *logger.Logger lp *linkpearl.Linkpearl @@ -29,17 +30,25 @@ type Bot struct { } // New creates a new matrix bot -func New(lp *linkpearl.Linkpearl, log *logger.Logger, prefix, domain string, noowner, federation bool, allowedUsers []*regexp.Regexp) *Bot { +func New( + lp *linkpearl.Linkpearl, + log *logger.Logger, + prefix, domain string, + noowner, federation bool, + allowedUsers []*regexp.Regexp, + allowedAdmins []*regexp.Regexp, +) *Bot { return &Bot{ - noowner: noowner, - federation: federation, - prefix: prefix, - domain: domain, - allowedUsers: allowedUsers, - rooms: sync.Map{}, - log: log, - lp: lp, - mu: map[id.RoomID]*sync.Mutex{}, + noowner: noowner, + federation: federation, + prefix: prefix, + domain: domain, + allowedUsers: allowedUsers, + allowedAdmins: allowedAdmins, + rooms: sync.Map{}, + log: log, + lp: lp, + mu: map[id.RoomID]*sync.Mutex{}, } } diff --git a/cmd/cmd.go b/cmd/cmd.go index 4ccf225..dc7d455 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -87,7 +87,7 @@ func initBot(cfg *config.Config) { // nolint // Fatal = panic, not os.Exit() log.Fatal("cannot initialize matrix bot: %v", err) } - mxb = bot.New(lp, mxlog, cfg.Prefix, cfg.Domain, cfg.NoOwner, cfg.Federation, cfg.Users) + mxb = bot.New(lp, mxlog, cfg.Prefix, cfg.Domain, cfg.NoOwner, cfg.Federation, cfg.Users, cfg.Admins) log.Debug("bot has been created") } diff --git a/config/config.go b/config/config.go index 6c4804f..fd21239 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "regexp" "gitlab.com/etke.cc/go/env" @@ -14,14 +15,14 @@ const prefix = "postmoogle" func New() (*Config, error) { env.SetPrefix(prefix) - mxidPatterns := env.Slice("users") - regexPatterns, err := utils.WildcardMXIDsToRegexes(mxidPatterns) + userPatterns, err := getUserRegexPatterns("users") if err != nil { - return nil, fmt.Errorf( - "failed to convert wildcard user patterns (`%s`) to regular expression: %s", - mxidPatterns, - err, - ) + return nil, err + } + + adminPatterns, err := getUserRegexPatterns("admins") + if err != nil { + return nil, err } cfg := &Config{ @@ -36,7 +37,8 @@ func New() (*Config, error) { Federation: env.Bool("federation"), MaxSize: env.Int("maxsize", defaultConfig.MaxSize), StatusMsg: env.String("statusmsg", defaultConfig.StatusMsg), - Users: *regexPatterns, + Users: *userPatterns, + Admins: *adminPatterns, Sentry: Sentry{ DSN: env.String("sentry.dsn", defaultConfig.Sentry.DSN), }, @@ -49,3 +51,17 @@ func New() (*Config, error) { return cfg, nil } + +func getUserRegexPatterns(key string) (*[]*regexp.Regexp, error) { + mxidPatterns := env.Slice(key) + regexPatterns, err := utils.WildcardMXIDsToRegexes(mxidPatterns) + if err != nil { + return nil, fmt.Errorf( + "failed to convert wildcard %s patterns (`%s`) to regular expression: %s", + key, + mxidPatterns, + err, + ) + } + return regexPatterns, nil +} diff --git a/config/types.go b/config/types.go index 4920919..ba99036 100644 --- a/config/types.go +++ b/config/types.go @@ -30,6 +30,8 @@ type Config struct { StatusMsg string // Users holds list of allowed users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = * Users []*regexp.Regexp + // Admins holds list of admin users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = * + Admins []*regexp.Regexp // DB config DB DB From a0576549625232fda33bbeea95be0aa4ee3880eb Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Mon, 29 Aug 2022 10:25:17 +0300 Subject: [PATCH 2/9] Put command access checks on the command level Checking using `settings.Allowed` is odd. Not all commands are related to setting configuration settings. Admin commands are coming in the future, for which this is certainly not the case. We now do access checks early on (during command processing), so command handlers can be clean of access checks. If we're inside of a command handler, the user is privileged to run it. --- bot/access.go | 37 ++++++++++ bot/bot.go | 7 +- bot/command.go | 185 ++++++++++++++++++++++++++---------------------- bot/settings.go | 18 ----- bot/sync.go | 2 +- 5 files changed, 146 insertions(+), 103 deletions(-) create mode 100644 bot/access.go diff --git a/bot/access.go b/bot/access.go new file mode 100644 index 0000000..bbf48a3 --- /dev/null +++ b/bot/access.go @@ -0,0 +1,37 @@ +package bot + +import ( + "fmt" + + "maunium.net/go/mautrix/id" + + "gitlab.com/etke.cc/postmoogle/utils" +) + +type accessCheckerFunc func(id.UserID, id.RoomID) (bool, error) + +func (b *Bot) allowAnyone(actorID id.UserID, targetRoomID id.RoomID) (bool, error) { + return true, nil +} + +func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) (bool, error) { + if !utils.Match(actorID.String(), b.allowedUsers) { + return false, nil + } + + if b.noowner { + return true, nil + } + + cfg, err := b.getSettings(targetRoomID) + if err != nil { + return false, fmt.Errorf("failed to retrieve settings: %v", err) + } + + owner := cfg.Owner() + if owner == "" { + return true, nil + } + + return owner == actorID.String(), nil +} diff --git a/bot/bot.go b/bot/bot.go index 3c8a662..bcb7cb1 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -22,6 +22,7 @@ type Bot struct { domain string allowedUsers []*regexp.Regexp allowedAdmins []*regexp.Regexp + commands commandList rooms sync.Map log *logger.Logger lp *linkpearl.Linkpearl @@ -38,7 +39,7 @@ func New( allowedUsers []*regexp.Regexp, allowedAdmins []*regexp.Regexp, ) *Bot { - return &Bot{ + b := &Bot{ noowner: noowner, federation: federation, prefix: prefix, @@ -50,6 +51,10 @@ func New( lp: lp, mu: map[id.RoomID]*sync.Mutex{}, } + + b.commands = b.buildCommandList() + + return b } // Error message to the log and matrix room diff --git a/bot/command.go b/bot/command.go index 2fb754c..58a3cbe 100644 --- a/bot/command.go +++ b/bot/command.go @@ -11,11 +11,18 @@ import ( "gitlab.com/etke.cc/postmoogle/utils" ) +const ( + commandHelp = "help" + commandStop = "stop" + commandMailboxes = "mailboxes" +) + type ( command struct { - key string - description string - sanitizer func(string) string + key string + description string + sanitizer func(string) string + accessChecker accessCheckerFunc } commandList []command ) @@ -29,73 +36,85 @@ func (c commandList) get(key string) *command { return nil } -var commands = commandList{ - // special commands - { - key: "help", - description: "Show this help message", - }, - { - key: "stop", - description: "Disable bridge for the room and clear all configuration", - }, - {}, // delimiter - // options commands - { - key: optionMailbox, - description: "Get or set mailbox of the room", - sanitizer: utils.Mailbox, - }, - { - key: optionOwner, - description: "Get or set owner of the room", - sanitizer: func(s string) string { return s }, - }, - {}, // delimiter - { - key: optionNoSender, - description: fmt.Sprintf( - "Get or set `%s` of the room (`true` - hide email sender; `false` - show email sender)", - optionNoSender, - ), - sanitizer: utils.SanitizeBoolString, - }, - { - key: optionNoSubject, - description: fmt.Sprintf( - "Get or set `%s` of the room (`true` - hide email subject; `false` - show email subject)", - optionNoSubject, - ), - sanitizer: utils.SanitizeBoolString, - }, - { - key: optionNoHTML, - description: fmt.Sprintf( - "Get or set `%s` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)", - optionNoHTML, - ), - sanitizer: utils.SanitizeBoolString, - }, - { - key: optionNoThreads, - description: fmt.Sprintf( - "Get or set `%s` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)", - optionNoThreads, - ), - sanitizer: utils.SanitizeBoolString, - }, - { - key: optionNoFiles, - description: fmt.Sprintf( - "Get or set `%s` of the room (`true` - ignore email attachments; `false` - upload email attachments)", - optionNoFiles, - ), - sanitizer: utils.SanitizeBoolString, - }, +func (b *Bot) buildCommandList() commandList { + return commandList{ + // special commands + { + key: commandHelp, + description: "Show this help message", + accessChecker: b.allowAnyone, + }, + { + key: commandStop, + description: "Disable bridge for the room and clear all configuration", + accessChecker: b.allowOwner, + }, + {}, // delimiter + // options commands + { + key: optionMailbox, + description: "Get or set mailbox of the room", + sanitizer: utils.Mailbox, + accessChecker: b.allowOwner, + }, + { + key: optionOwner, + description: "Get or set owner of the room", + sanitizer: func(s string) string { return s }, + accessChecker: b.allowOwner, + }, + {}, // delimiter + { + key: optionNoSender, + description: fmt.Sprintf( + "Get or set `%s` of the room (`true` - hide email sender; `false` - show email sender)", + optionNoSender, + ), + sanitizer: utils.SanitizeBoolString, + accessChecker: b.allowOwner, + }, + { + key: optionNoSubject, + description: fmt.Sprintf( + "Get or set `%s` of the room (`true` - hide email subject; `false` - show email subject)", + optionNoSubject, + ), + sanitizer: utils.SanitizeBoolString, + accessChecker: b.allowOwner, + }, + { + key: optionNoHTML, + description: fmt.Sprintf( + "Get or set `%s` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)", + optionNoHTML, + ), + sanitizer: utils.SanitizeBoolString, + accessChecker: b.allowOwner, + }, + { + key: optionNoThreads, + description: fmt.Sprintf( + "Get or set `%s` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)", + optionNoThreads, + ), + sanitizer: utils.SanitizeBoolString, + accessChecker: b.allowOwner, + }, + { + key: optionNoFiles, + description: fmt.Sprintf( + "Get or set `%s` of the room (`true` - ignore email attachments; `false` - upload email attachments)", + optionNoFiles, + ), + sanitizer: utils.SanitizeBoolString, + accessChecker: b.allowOwner, + }, + } } func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice []string) { - if cmd := commands.get(commandSlice[0]); cmd == nil { + cmd := b.commands.get(commandSlice[0]) + if cmd == nil { return } @@ -104,11 +123,21 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice return } + allowed, err := cmd.accessChecker(evt.Sender, evt.RoomID) + if err != nil { + b.Error(ctx, evt.RoomID, err.Error()) + return + } + if !allowed { + b.Notice(ctx, evt.RoomID, "not allowed to do that, kupo") + return + } + switch commandSlice[0] { - case "help": + case commandHelp: b.sendHelp(ctx, evt.RoomID) - case "stop": - b.runStop(ctx, true) + case commandStop: + b.runStop(ctx) default: b.handleOption(ctx, commandSlice) } @@ -157,7 +186,7 @@ func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { var msg strings.Builder msg.WriteString("The following commands are supported:\n\n") - for _, cmd := range commands { + for _, cmd := range b.commands { if cmd.key == "" { msg.WriteString("\n---\n") continue @@ -192,7 +221,7 @@ func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { b.Notice(ctx, roomID, msg.String()) } -func (b *Bot) runStop(ctx context.Context, checkAllowed bool) { +func (b *Bot) runStop(ctx context.Context) { evt := eventFromContext(ctx) cfg, err := b.getSettings(evt.RoomID) if err != nil { @@ -200,11 +229,6 @@ func (b *Bot) runStop(ctx context.Context, checkAllowed bool) { return } - if checkAllowed && !b.Allowed(evt.Sender, cfg) { - b.Notice(ctx, evt.RoomID, "you don't have permission to do that") - return - } - mailbox := cfg.Get(optionMailbox) if mailbox == "" { b.Notice(ctx, evt.RoomID, "that room is not configured yet") @@ -252,7 +276,7 @@ func (b *Bot) getOption(ctx context.Context, name string) { } func (b *Bot) setOption(ctx context.Context, name, value string) { - cmd := commands.get(name) + cmd := b.commands.get(name) if cmd != nil { value = cmd.sanitizer(value) } @@ -272,11 +296,6 @@ func (b *Bot) setOption(ctx context.Context, name, value string) { return } - if !b.Allowed(evt.Sender, cfg) { - b.Notice(ctx, evt.RoomID, "you don't have permission to do that, kupo") - return - } - old := cfg.Get(name) cfg.Set(name, value) diff --git a/bot/settings.go b/bot/settings.go index 0f93d37..4efd21f 100644 --- a/bot/settings.go +++ b/bot/settings.go @@ -98,21 +98,3 @@ func (b *Bot) getSettings(roomID id.RoomID) (settings, error) { func (b *Bot) setSettings(roomID id.RoomID, cfg settings) error { return utils.UnwrapError(b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg)) } - -// Allowed checks if change is allowed -func (b *Bot) Allowed(userID id.UserID, cfg settings) bool { - if !utils.Match(userID.String(), b.allowedUsers) { - return false - } - - if b.noowner { - return true - } - - owner := cfg.Owner() - if owner == "" { - return true - } - - return owner == userID.String() -} diff --git a/bot/sync.go b/bot/sync.go index 7b1deba..f23eeb3 100644 --- a/bot/sync.go +++ b/bot/sync.go @@ -94,7 +94,7 @@ func (b *Bot) onLeave(ctx context.Context) { count := len(members) if count == 1 && members[0] == b.lp.GetClient().UserID { b.log.Info("no more users left in the %s room", evt.RoomID) - b.runStop(ctx, false) + b.runStop(ctx) _, err := b.lp.GetClient().LeaveRoom(evt.RoomID) if err != nil { b.Error(ctx, evt.RoomID, "cannot leave empty room: %v", err) From 79775c0c13c1dc0e0510ff55766964c7dbc6ccad Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Mon, 29 Aug 2022 10:28:19 +0300 Subject: [PATCH 3/9] Add basic `mailboxes` command This can be improved in the future, to show some additional information about each mailbox like: - "how many users are in that room" - "which users are in that room" - "who is the owner of the mailbox" This can all be done later though. --- bot/access.go | 4 ++++ bot/admin_command.go | 39 +++++++++++++++++++++++++++++++++++++++ bot/command.go | 8 ++++++++ 3 files changed, 51 insertions(+) create mode 100644 bot/admin_command.go diff --git a/bot/access.go b/bot/access.go index bbf48a3..f54f66d 100644 --- a/bot/access.go +++ b/bot/access.go @@ -35,3 +35,7 @@ func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) (bool, error return owner == actorID.String(), nil } + +func (b *Bot) allowAdmin(actorID id.UserID, targetRoomID id.RoomID) (bool, error) { + return utils.Match(actorID.String(), b.allowedAdmins), nil +} diff --git a/bot/admin_command.go b/bot/admin_command.go new file mode 100644 index 0000000..eff4bf4 --- /dev/null +++ b/bot/admin_command.go @@ -0,0 +1,39 @@ +package bot + +import ( + "context" + "fmt" + "strings" + + "maunium.net/go/mautrix/id" +) + +func (b *Bot) sendMailboxes(ctx context.Context) { + evt := eventFromContext(ctx) + + mailboxes := map[string]id.RoomID{} + + b.rooms.Range(func(mailbox any, roomID any) bool { + mailboxes[mailbox.(string)] = roomID.(id.RoomID) + return true + }) + + if len(mailboxes) == 0 { + b.Notice(ctx, evt.RoomID, "No mailboxes are managed by the bot so far, kupo!") + return + } + + var msg strings.Builder + msg.WriteString("The following mailboxes are managed by the bot:\n") + for mailbox, roomID := range mailboxes { + email := fmt.Sprintf("%s@%s", mailbox, b.domain) + msg.WriteString("* `") + msg.WriteString(email) + msg.WriteString("` - `") + msg.WriteString(roomID.String()) + msg.WriteString("`") + msg.WriteString("\n") + } + + b.Notice(ctx, evt.RoomID, msg.String()) +} diff --git a/bot/command.go b/bot/command.go index 58a3cbe..4fc12aa 100644 --- a/bot/command.go +++ b/bot/command.go @@ -109,6 +109,12 @@ func (b *Bot) buildCommandList() commandList { sanitizer: utils.SanitizeBoolString, accessChecker: b.allowOwner, }, + {}, // delimiter + { + key: commandMailboxes, + description: "Show the list of all mailboxes", + accessChecker: b.allowAdmin, + }, } } @@ -138,6 +144,8 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice b.sendHelp(ctx, evt.RoomID) case commandStop: b.runStop(ctx) + case commandMailboxes: + b.sendMailboxes(ctx) default: b.handleOption(ctx, commandSlice) } From 66232516951ade093f3f67ae1f38768b5588d66f Mon Sep 17 00:00:00 2001 From: Aine Date: Mon, 29 Aug 2022 12:30:43 +0300 Subject: [PATCH 4/9] refactored --- bot/access.go | 25 ++++---- bot/admin_command.go | 39 ------------- bot/bot.go | 18 +++--- bot/command.go | 132 ++++++++++++++++++++++++++++--------------- config/config.go | 6 +- utils/user.go | 4 +- utils/user_test.go | 2 +- 7 files changed, 114 insertions(+), 112 deletions(-) delete mode 100644 bot/admin_command.go diff --git a/bot/access.go b/bot/access.go index f54f66d..18208c3 100644 --- a/bot/access.go +++ b/bot/access.go @@ -1,41 +1,40 @@ package bot import ( - "fmt" + "context" "maunium.net/go/mautrix/id" "gitlab.com/etke.cc/postmoogle/utils" ) -type accessCheckerFunc func(id.UserID, id.RoomID) (bool, error) - -func (b *Bot) allowAnyone(actorID id.UserID, targetRoomID id.RoomID) (bool, error) { - return true, nil +func (b *Bot) allowAnyone(actorID id.UserID, targetRoomID id.RoomID) bool { + return true } -func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) (bool, error) { +func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) bool { if !utils.Match(actorID.String(), b.allowedUsers) { - return false, nil + return false } if b.noowner { - return true, nil + return true } cfg, err := b.getSettings(targetRoomID) if err != nil { - return false, fmt.Errorf("failed to retrieve settings: %v", err) + b.Error(context.Background(), targetRoomID, "failed to retrieve settings: %v", err) + return false } owner := cfg.Owner() if owner == "" { - return true, nil + return true } - return owner == actorID.String(), nil + return owner == actorID.String() } -func (b *Bot) allowAdmin(actorID id.UserID, targetRoomID id.RoomID) (bool, error) { - return utils.Match(actorID.String(), b.allowedAdmins), nil +func (b *Bot) allowAdmin(actorID id.UserID, targetRoomID id.RoomID) bool { + return utils.Match(actorID.String(), b.allowedAdmins) } diff --git a/bot/admin_command.go b/bot/admin_command.go deleted file mode 100644 index eff4bf4..0000000 --- a/bot/admin_command.go +++ /dev/null @@ -1,39 +0,0 @@ -package bot - -import ( - "context" - "fmt" - "strings" - - "maunium.net/go/mautrix/id" -) - -func (b *Bot) sendMailboxes(ctx context.Context) { - evt := eventFromContext(ctx) - - mailboxes := map[string]id.RoomID{} - - b.rooms.Range(func(mailbox any, roomID any) bool { - mailboxes[mailbox.(string)] = roomID.(id.RoomID) - return true - }) - - if len(mailboxes) == 0 { - b.Notice(ctx, evt.RoomID, "No mailboxes are managed by the bot so far, kupo!") - return - } - - var msg strings.Builder - msg.WriteString("The following mailboxes are managed by the bot:\n") - for mailbox, roomID := range mailboxes { - email := fmt.Sprintf("%s@%s", mailbox, b.domain) - msg.WriteString("* `") - msg.WriteString(email) - msg.WriteString("` - `") - msg.WriteString(roomID.String()) - msg.WriteString("`") - msg.WriteString("\n") - } - - b.Notice(ctx, evt.RoomID, msg.String()) -} diff --git a/bot/bot.go b/bot/bot.go index bcb7cb1..7c6128d 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -60,19 +60,21 @@ func New( // Error message to the log and matrix room func (b *Bot) Error(ctx context.Context, roomID id.RoomID, message string, args ...interface{}) { b.log.Error(message, args...) + err := fmt.Errorf(message, args...) - sentry.GetHubFromContext(ctx).CaptureException(fmt.Errorf(message, args...)) + sentry.GetHubFromContext(ctx).CaptureException(err) if roomID != "" { - // nolint // if something goes wrong here nobody can help... - b.lp.Send(roomID, &event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: "ERROR: " + fmt.Sprintf(message, args...), - }) + b.SendError(ctx, roomID, message) } } -// Notice sends a notice message to the matrix room -func (b *Bot) Notice(ctx context.Context, roomID id.RoomID, message string) { +// SendError sends an error message to the matrix room +func (b *Bot) SendError(ctx context.Context, roomID id.RoomID, message string) { + b.SendNotice(ctx, roomID, "ERROR: "+message) +} + +// SendNotice sends a notice message to the matrix room +func (b *Bot) SendNotice(ctx context.Context, roomID id.RoomID, message string) { content := format.RenderMarkdown(message, true, true) content.MsgType = event.MsgNotice _, err := b.lp.Send(roomID, &content) diff --git a/bot/command.go b/bot/command.go index 4fc12aa..efcd7c2 100644 --- a/bot/command.go +++ b/bot/command.go @@ -19,10 +19,10 @@ const ( type ( command struct { - key string - description string - sanitizer func(string) string - accessChecker accessCheckerFunc + key string + description string + sanitizer func(string) string + allowed func(id.UserID, id.RoomID) bool } commandList []command ) @@ -40,28 +40,28 @@ func (b *Bot) buildCommandList() commandList { return commandList{ // special commands { - key: commandHelp, - description: "Show this help message", - accessChecker: b.allowAnyone, + key: commandHelp, + description: "Show this help message", + allowed: b.allowAnyone, }, { - key: commandStop, - description: "Disable bridge for the room and clear all configuration", - accessChecker: b.allowOwner, + key: commandStop, + description: "Disable bridge for the room and clear all configuration", + allowed: b.allowOwner, }, {}, // delimiter // options commands { - key: optionMailbox, - description: "Get or set mailbox of the room", - sanitizer: utils.Mailbox, - accessChecker: b.allowOwner, + key: optionMailbox, + description: "Get or set mailbox of the room", + sanitizer: utils.Mailbox, + allowed: b.allowOwner, }, { - key: optionOwner, - description: "Get or set owner of the room", - sanitizer: func(s string) string { return s }, - accessChecker: b.allowOwner, + key: optionOwner, + description: "Get or set owner of the room", + sanitizer: func(s string) string { return s }, + allowed: b.allowOwner, }, {}, // delimiter { @@ -70,8 +70,8 @@ func (b *Bot) buildCommandList() commandList { "Get or set `%s` of the room (`true` - hide email sender; `false` - show email sender)", optionNoSender, ), - sanitizer: utils.SanitizeBoolString, - accessChecker: b.allowOwner, + sanitizer: utils.SanitizeBoolString, + allowed: b.allowOwner, }, { key: optionNoSubject, @@ -79,8 +79,8 @@ func (b *Bot) buildCommandList() commandList { "Get or set `%s` of the room (`true` - hide email subject; `false` - show email subject)", optionNoSubject, ), - sanitizer: utils.SanitizeBoolString, - accessChecker: b.allowOwner, + sanitizer: utils.SanitizeBoolString, + allowed: b.allowOwner, }, { key: optionNoHTML, @@ -88,8 +88,8 @@ func (b *Bot) buildCommandList() commandList { "Get or set `%s` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)", optionNoHTML, ), - sanitizer: utils.SanitizeBoolString, - accessChecker: b.allowOwner, + sanitizer: utils.SanitizeBoolString, + allowed: b.allowOwner, }, { key: optionNoThreads, @@ -97,8 +97,8 @@ func (b *Bot) buildCommandList() commandList { "Get or set `%s` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)", optionNoThreads, ), - sanitizer: utils.SanitizeBoolString, - accessChecker: b.allowOwner, + sanitizer: utils.SanitizeBoolString, + allowed: b.allowOwner, }, { key: optionNoFiles, @@ -106,14 +106,14 @@ func (b *Bot) buildCommandList() commandList { "Get or set `%s` of the room (`true` - ignore email attachments; `false` - upload email attachments)", optionNoFiles, ), - sanitizer: utils.SanitizeBoolString, - accessChecker: b.allowOwner, + sanitizer: utils.SanitizeBoolString, + allowed: b.allowOwner, }, {}, // delimiter { - key: commandMailboxes, - description: "Show the list of all mailboxes", - accessChecker: b.allowAdmin, + key: commandMailboxes, + description: "Show the list of all mailboxes", + allowed: b.allowAdmin, }, } } @@ -129,13 +129,8 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice return } - allowed, err := cmd.accessChecker(evt.Sender, evt.RoomID) - if err != nil { - b.Error(ctx, evt.RoomID, err.Error()) - return - } - if !allowed { - b.Notice(ctx, evt.RoomID, "not allowed to do that, kupo") + if !cmd.allowed(evt.Sender, evt.RoomID) { + b.SendNotice(ctx, evt.RoomID, "not allowed to do that, kupo") return } @@ -181,7 +176,7 @@ func (b *Bot) sendIntroduction(ctx context.Context, roomID id.RoomID) { msg.WriteString(b.domain) msg.WriteString("` and have them appear in this room.") - b.Notice(ctx, roomID, msg.String()) + b.SendNotice(ctx, roomID, msg.String()) } func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { @@ -226,7 +221,52 @@ func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { msg.WriteString("\n") } - b.Notice(ctx, roomID, msg.String()) + b.SendNotice(ctx, roomID, msg.String()) +} + +func (b *Bot) sendMailboxes(ctx context.Context) { + evt := eventFromContext(ctx) + mailboxes := map[string]id.RoomID{} + b.rooms.Range(func(key any, value any) bool { + if key == nil { + return true + } + if value == nil { + return true + } + + mailbox, ok := key.(string) + if !ok { + return true + } + roomID, ok := value.(id.RoomID) + if !ok { + return true + } + + mailboxes[mailbox] = roomID + return true + }) + + if len(mailboxes) == 0 { + b.SendNotice(ctx, evt.RoomID, "No mailboxes are managed by the bot so far, kupo!") + return + } + + var msg strings.Builder + msg.WriteString("The following mailboxes are managed by the bot:\n") + for mailbox, roomID := range mailboxes { + msg.WriteString("* `") + msg.WriteString(mailbox) + msg.WriteString("@") + msg.WriteString(b.domain) + msg.WriteString("` - `") + msg.WriteString(roomID.String()) + msg.WriteString("`") + msg.WriteString("\n") + } + + b.SendNotice(ctx, evt.RoomID, msg.String()) } func (b *Bot) runStop(ctx context.Context) { @@ -239,7 +279,7 @@ func (b *Bot) runStop(ctx context.Context) { mailbox := cfg.Get(optionMailbox) if mailbox == "" { - b.Notice(ctx, evt.RoomID, "that room is not configured yet") + b.SendNotice(ctx, evt.RoomID, "that room is not configured yet") return } @@ -251,7 +291,7 @@ func (b *Bot) runStop(ctx context.Context) { return } - b.Notice(ctx, evt.RoomID, "mailbox has been disabled") + b.SendNotice(ctx, evt.RoomID, "mailbox has been disabled") } func (b *Bot) handleOption(ctx context.Context, cmd []string) { @@ -272,7 +312,7 @@ func (b *Bot) getOption(ctx context.Context, name string) { value := cfg.Get(name) if value == "" { - b.Notice(ctx, evt.RoomID, fmt.Sprintf("`%s` is not set, kupo.", name)) + b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` is not set, kupo.", name)) return } @@ -280,7 +320,7 @@ func (b *Bot) getOption(ctx context.Context, name string) { value = value + "@" + b.domain } - b.Notice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room is `%s`", name, value)) + b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room is `%s`", name, value)) } func (b *Bot) setOption(ctx context.Context, name, value string) { @@ -293,7 +333,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) { if name == optionMailbox { existingID, ok := b.GetMapping(value) if ok && existingID != "" && existingID != evt.RoomID { - b.Notice(ctx, evt.RoomID, fmt.Sprintf("Mailbox `%s@%s` already taken, kupo", value, b.domain)) + b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Mailbox `%s@%s` already taken, kupo", value, b.domain)) return } } @@ -326,5 +366,5 @@ func (b *Bot) setOption(ctx context.Context, name, value string) { value = value + "@" + b.domain } - b.Notice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room set to `%s`", name, value)) + b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room set to `%s`", name, value)) } diff --git a/config/config.go b/config/config.go index fd21239..c9e297f 100644 --- a/config/config.go +++ b/config/config.go @@ -37,8 +37,8 @@ func New() (*Config, error) { Federation: env.Bool("federation"), MaxSize: env.Int("maxsize", defaultConfig.MaxSize), StatusMsg: env.String("statusmsg", defaultConfig.StatusMsg), - Users: *userPatterns, - Admins: *adminPatterns, + Users: userPatterns, + Admins: adminPatterns, Sentry: Sentry{ DSN: env.String("sentry.dsn", defaultConfig.Sentry.DSN), }, @@ -52,7 +52,7 @@ func New() (*Config, error) { return cfg, nil } -func getUserRegexPatterns(key string) (*[]*regexp.Regexp, error) { +func getUserRegexPatterns(key string) ([]*regexp.Regexp, error) { mxidPatterns := env.Slice(key) regexPatterns, err := utils.WildcardMXIDsToRegexes(mxidPatterns) if err != nil { diff --git a/utils/user.go b/utils/user.go index 0e8c59e..130d91a 100644 --- a/utils/user.go +++ b/utils/user.go @@ -7,7 +7,7 @@ import ( ) // WildcardMXIDsToRegexes converts a list of wildcard patterns to a list of regular expressions -func WildcardMXIDsToRegexes(wildCardPatterns []string) (*[]*regexp.Regexp, error) { +func WildcardMXIDsToRegexes(wildCardPatterns []string) ([]*regexp.Regexp, error) { regexPatterns := make([]*regexp.Regexp, len(wildCardPatterns)) for idx, wildCardPattern := range wildCardPatterns { @@ -18,7 +18,7 @@ func WildcardMXIDsToRegexes(wildCardPatterns []string) (*[]*regexp.Regexp, error regexPatterns[idx] = regex } - return ®exPatterns, nil + return regexPatterns, nil } // Match tells if the given user id is allowed to use the bot, according to the given whitelist diff --git a/utils/user_test.go b/utils/user_test.go index 5068204..f59cbdb 100644 --- a/utils/user_test.go +++ b/utils/user_test.go @@ -202,7 +202,7 @@ func TestMatch(t *testing.T) { t.Error(err) } - actualResult := Match(testData.checkedValue, *allowedUserRegexes) + actualResult := Match(testData.checkedValue, allowedUserRegexes) if actualResult == testData.expectedResult { return From e59f5d5502976ac26bd744319e1ccc6eb24727ad Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Mon, 29 Aug 2022 14:10:52 +0300 Subject: [PATCH 5/9] Make Match() with empty list not return a positive result Now that we use Match() in allowAdmin() as well, it's awkward to have it return `true` when called with an empty admin list. No admins defined was taken to mean "everyone is an admin". We can either have a `len(users) == 0` check in `allowAdmin` which rejects the request, or we can change `Match()` so that it doesn't return positive responses when called with an empty list. Doing the latter sounds better. It's more natural that matching against an empty list will yield "no match". --- bot/access.go | 6 ++++-- utils/user.go | 5 ----- utils/user_test.go | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/bot/access.go b/bot/access.go index 18208c3..297cf1c 100644 --- a/bot/access.go +++ b/bot/access.go @@ -13,8 +13,10 @@ func (b *Bot) allowAnyone(actorID id.UserID, targetRoomID id.RoomID) bool { } func (b *Bot) allowOwner(actorID id.UserID, targetRoomID id.RoomID) bool { - if !utils.Match(actorID.String(), b.allowedUsers) { - return false + if len(b.allowedUsers) != 0 { + if !utils.Match(actorID.String(), b.allowedUsers) { + return false + } } if b.noowner { diff --git a/utils/user.go b/utils/user.go index 130d91a..3b93c49 100644 --- a/utils/user.go +++ b/utils/user.go @@ -23,11 +23,6 @@ func WildcardMXIDsToRegexes(wildCardPatterns []string) ([]*regexp.Regexp, error) // Match tells if the given user id is allowed to use the bot, according to the given whitelist func Match(userID string, allowed []*regexp.Regexp) bool { - // No whitelisted users means everyone is whitelisted - if len(allowed) == 0 { - return true - } - for _, regex := range allowed { if regex.MatchString(userID) { return true diff --git a/utils/user_test.go b/utils/user_test.go index f59cbdb..28e87c9 100644 --- a/utils/user_test.go +++ b/utils/user_test.go @@ -127,10 +127,10 @@ func TestMatch(t *testing.T) { tests := []testDataDefinition{ { - name: "Empty allowed users allows anyone", + name: "Empty allowed users allows no one", checkedValue: "@someone:example.com", allowedUsers: []string{}, - expectedResult: true, + expectedResult: false, }, { name: "Direct full mxid match is allowed", From d20d4aa5bf4ec347a6bdca93cd74f2b44bb954ff Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Mon, 29 Aug 2022 14:20:20 +0300 Subject: [PATCH 6/9] Move `mailboxes` admin command to a separate file --- bot/admin_command.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ bot/command.go | 45 ------------------------------------- 2 files changed, 53 insertions(+), 45 deletions(-) create mode 100644 bot/admin_command.go diff --git a/bot/admin_command.go b/bot/admin_command.go new file mode 100644 index 0000000..8513d92 --- /dev/null +++ b/bot/admin_command.go @@ -0,0 +1,53 @@ +package bot + +import ( + "context" + "strings" + + "maunium.net/go/mautrix/id" +) + +func (b *Bot) sendMailboxes(ctx context.Context) { + evt := eventFromContext(ctx) + mailboxes := map[string]id.RoomID{} + b.rooms.Range(func(key any, value any) bool { + if key == nil { + return true + } + if value == nil { + return true + } + + mailbox, ok := key.(string) + if !ok { + return true + } + roomID, ok := value.(id.RoomID) + if !ok { + return true + } + + mailboxes[mailbox] = roomID + return true + }) + + if len(mailboxes) == 0 { + b.SendNotice(ctx, evt.RoomID, "No mailboxes are managed by the bot so far, kupo!") + return + } + + var msg strings.Builder + msg.WriteString("The following mailboxes are managed by the bot:\n") + for mailbox, roomID := range mailboxes { + msg.WriteString("* `") + msg.WriteString(mailbox) + msg.WriteString("@") + msg.WriteString(b.domain) + msg.WriteString("` - `") + msg.WriteString(roomID.String()) + msg.WriteString("`") + msg.WriteString("\n") + } + + b.SendNotice(ctx, evt.RoomID, msg.String()) +} diff --git a/bot/command.go b/bot/command.go index efcd7c2..79b34a4 100644 --- a/bot/command.go +++ b/bot/command.go @@ -224,51 +224,6 @@ func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { b.SendNotice(ctx, roomID, msg.String()) } -func (b *Bot) sendMailboxes(ctx context.Context) { - evt := eventFromContext(ctx) - mailboxes := map[string]id.RoomID{} - b.rooms.Range(func(key any, value any) bool { - if key == nil { - return true - } - if value == nil { - return true - } - - mailbox, ok := key.(string) - if !ok { - return true - } - roomID, ok := value.(id.RoomID) - if !ok { - return true - } - - mailboxes[mailbox] = roomID - return true - }) - - if len(mailboxes) == 0 { - b.SendNotice(ctx, evt.RoomID, "No mailboxes are managed by the bot so far, kupo!") - return - } - - var msg strings.Builder - msg.WriteString("The following mailboxes are managed by the bot:\n") - for mailbox, roomID := range mailboxes { - msg.WriteString("* `") - msg.WriteString(mailbox) - msg.WriteString("@") - msg.WriteString(b.domain) - msg.WriteString("` - `") - msg.WriteString(roomID.String()) - msg.WriteString("`") - msg.WriteString("\n") - } - - b.SendNotice(ctx, evt.RoomID, msg.String()) -} - func (b *Bot) runStop(ctx context.Context) { evt := eventFromContext(ctx) cfg, err := b.getSettings(evt.RoomID) From bc30f59e964f6fadc527c5ac1dd0752fdd5ff17c Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Mon, 29 Aug 2022 14:25:14 +0300 Subject: [PATCH 7/9] Move owner commands to their own file --- bot/command.go | 100 ---------------------------------------- bot/owner_command.go | 106 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 100 deletions(-) create mode 100644 bot/owner_command.go diff --git a/bot/command.go b/bot/command.go index 79b34a4..b6931bc 100644 --- a/bot/command.go +++ b/bot/command.go @@ -223,103 +223,3 @@ func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { b.SendNotice(ctx, roomID, msg.String()) } - -func (b *Bot) runStop(ctx context.Context) { - evt := eventFromContext(ctx) - cfg, err := b.getSettings(evt.RoomID) - if err != nil { - b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) - return - } - - mailbox := cfg.Get(optionMailbox) - if mailbox == "" { - b.SendNotice(ctx, evt.RoomID, "that room is not configured yet") - return - } - - b.rooms.Delete(mailbox) - - err = b.setSettings(evt.RoomID, settings{}) - if err != nil { - b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) - return - } - - b.SendNotice(ctx, evt.RoomID, "mailbox has been disabled") -} - -func (b *Bot) handleOption(ctx context.Context, cmd []string) { - if len(cmd) == 1 { - b.getOption(ctx, cmd[0]) - return - } - b.setOption(ctx, cmd[0], cmd[1]) -} - -func (b *Bot) getOption(ctx context.Context, name string) { - evt := eventFromContext(ctx) - cfg, err := b.getSettings(evt.RoomID) - if err != nil { - b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) - return - } - - value := cfg.Get(name) - if value == "" { - b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` is not set, kupo.", name)) - return - } - - if name == optionMailbox { - value = value + "@" + b.domain - } - - b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room is `%s`", name, value)) -} - -func (b *Bot) setOption(ctx context.Context, name, value string) { - cmd := b.commands.get(name) - if cmd != nil { - value = cmd.sanitizer(value) - } - - evt := eventFromContext(ctx) - if name == optionMailbox { - 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)) - return - } - } - - cfg, err := b.getSettings(evt.RoomID) - if err != nil { - b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) - return - } - - old := cfg.Get(name) - cfg.Set(name, value) - - if name == optionMailbox { - cfg.Set(optionOwner, evt.Sender.String()) - if old != "" { - b.rooms.Delete(old) - } - b.rooms.Store(value, evt.RoomID) - value = fmt.Sprintf("%s@%s", value, b.domain) - } - - err = b.setSettings(evt.RoomID, cfg) - if err != nil { - b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) - return - } - - if name == optionMailbox { - value = value + "@" + b.domain - } - - b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room set to `%s`", name, value)) -} diff --git a/bot/owner_command.go b/bot/owner_command.go new file mode 100644 index 0000000..84f26cd --- /dev/null +++ b/bot/owner_command.go @@ -0,0 +1,106 @@ +package bot + +import ( + "context" + "fmt" +) + +func (b *Bot) runStop(ctx context.Context) { + evt := eventFromContext(ctx) + cfg, err := b.getSettings(evt.RoomID) + if err != nil { + b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) + return + } + + mailbox := cfg.Get(optionMailbox) + if mailbox == "" { + b.SendNotice(ctx, evt.RoomID, "that room is not configured yet") + return + } + + b.rooms.Delete(mailbox) + + err = b.setSettings(evt.RoomID, settings{}) + if err != nil { + b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) + return + } + + b.SendNotice(ctx, evt.RoomID, "mailbox has been disabled") +} + +func (b *Bot) handleOption(ctx context.Context, cmd []string) { + if len(cmd) == 1 { + b.getOption(ctx, cmd[0]) + return + } + b.setOption(ctx, cmd[0], cmd[1]) +} + +func (b *Bot) getOption(ctx context.Context, name string) { + evt := eventFromContext(ctx) + cfg, err := b.getSettings(evt.RoomID) + if err != nil { + b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) + return + } + + value := cfg.Get(name) + if value == "" { + b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` is not set, kupo.", name)) + return + } + + if name == optionMailbox { + value = value + "@" + b.domain + } + + b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room is `%s`", name, value)) +} + +func (b *Bot) setOption(ctx context.Context, name, value string) { + cmd := b.commands.get(name) + if cmd != nil { + value = cmd.sanitizer(value) + } + + evt := eventFromContext(ctx) + if name == optionMailbox { + 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)) + return + } + } + + cfg, err := b.getSettings(evt.RoomID) + if err != nil { + b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) + return + } + + old := cfg.Get(name) + cfg.Set(name, value) + + if name == optionMailbox { + cfg.Set(optionOwner, evt.Sender.String()) + if old != "" { + b.rooms.Delete(old) + } + b.rooms.Store(value, evt.RoomID) + value = fmt.Sprintf("%s@%s", value, b.domain) + } + + err = b.setSettings(evt.RoomID, cfg) + if err != nil { + b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) + return + } + + if name == optionMailbox { + value = value + "@" + b.domain + } + + b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room set to `%s`", name, value)) +} From 1a87929567b765c6660885d98fe4fb29da478a4b Mon Sep 17 00:00:00 2001 From: Aine Date: Mon, 29 Aug 2022 15:19:07 +0300 Subject: [PATCH 8/9] rename files; show commands by access level --- bot/command.go | 15 +++++++++------ bot/{admin_command.go => command_admin.go} | 0 bot/{owner_command.go => command_owner.go} | 0 bot/sync.go | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) rename bot/{admin_command.go => command_admin.go} (100%) rename bot/{owner_command.go => command_owner.go} (100%) diff --git a/bot/command.go b/bot/command.go index b6931bc..606436e 100644 --- a/bot/command.go +++ b/bot/command.go @@ -49,7 +49,7 @@ func (b *Bot) buildCommandList() commandList { description: "Disable bridge for the room and clear all configuration", allowed: b.allowOwner, }, - {}, // delimiter + {allowed: b.allowOwner}, // delimiter // options commands { key: optionMailbox, @@ -63,7 +63,7 @@ func (b *Bot) buildCommandList() commandList { sanitizer: func(s string) string { return s }, allowed: b.allowOwner, }, - {}, // delimiter + {allowed: b.allowOwner}, // delimiter { key: optionNoSender, description: fmt.Sprintf( @@ -109,7 +109,7 @@ func (b *Bot) buildCommandList() commandList { sanitizer: utils.SanitizeBoolString, allowed: b.allowOwner, }, - {}, // delimiter + {allowed: b.allowAdmin}, // delimiter { key: commandMailboxes, description: "Show the list of all mailboxes", @@ -136,7 +136,7 @@ func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, commandSlice switch commandSlice[0] { case commandHelp: - b.sendHelp(ctx, evt.RoomID) + b.sendHelp(ctx) case commandStop: b.runStop(ctx) case commandMailboxes: @@ -179,7 +179,7 @@ func (b *Bot) sendIntroduction(ctx context.Context, roomID id.RoomID) { b.SendNotice(ctx, roomID, msg.String()) } -func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { +func (b *Bot) sendHelp(ctx context.Context) { evt := eventFromContext(ctx) cfg, serr := b.getSettings(evt.RoomID) @@ -190,6 +190,9 @@ func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { var msg strings.Builder msg.WriteString("The following commands are supported:\n\n") for _, cmd := range b.commands { + if !cmd.allowed(evt.Sender, evt.RoomID) { + continue + } if cmd.key == "" { msg.WriteString("\n---\n") continue @@ -221,5 +224,5 @@ func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) { msg.WriteString("\n") } - b.SendNotice(ctx, roomID, msg.String()) + b.SendNotice(ctx, evt.RoomID, msg.String()) } diff --git a/bot/admin_command.go b/bot/command_admin.go similarity index 100% rename from bot/admin_command.go rename to bot/command_admin.go diff --git a/bot/owner_command.go b/bot/command_owner.go similarity index 100% rename from bot/owner_command.go rename to bot/command_owner.go diff --git a/bot/sync.go b/bot/sync.go index f23eeb3..b46a7d7 100644 --- a/bot/sync.go +++ b/bot/sync.go @@ -80,7 +80,7 @@ func (b *Bot) onBotJoin(ctx context.Context) { } b.sendIntroduction(ctx, evt.RoomID) - b.sendHelp(ctx, evt.RoomID) + b.sendHelp(ctx) } func (b *Bot) onLeave(ctx context.Context) { From 5a5a649cba7a0e452a069de64d44a4a15ad6172e Mon Sep 17 00:00:00 2001 From: Aine Date: Mon, 29 Aug 2022 15:28:37 +0300 Subject: [PATCH 9/9] add cache --- bot/bot.go | 3 +++ bot/settings.go | 8 ++++++++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 14 insertions(+) diff --git a/bot/bot.go b/bot/bot.go index 7c6128d..c36373d 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -6,6 +6,7 @@ import ( "regexp" "sync" + "git.sr.ht/~xn/cache/v2" "github.com/getsentry/sentry-go" "gitlab.com/etke.cc/go/logger" "gitlab.com/etke.cc/linkpearl" @@ -24,6 +25,7 @@ type Bot struct { allowedAdmins []*regexp.Regexp commands commandList rooms sync.Map + cfg cache.Cache[settings] log *logger.Logger lp *linkpearl.Linkpearl mu map[id.RoomID]*sync.Mutex @@ -47,6 +49,7 @@ func New( allowedUsers: allowedUsers, allowedAdmins: allowedAdmins, rooms: sync.Map{}, + cfg: cache.NewLRU[settings](1000), log: log, lp: lp, mu: map[id.RoomID]*sync.Mutex{}, diff --git a/bot/settings.go b/bot/settings.go index 4efd21f..74886c3 100644 --- a/bot/settings.go +++ b/bot/settings.go @@ -81,6 +81,11 @@ func (b *Bot) migrateSettings(roomID id.RoomID) { } 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 { @@ -90,11 +95,14 @@ func (b *Bot) getSettings(roomID id.RoomID) (settings, error) { // 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/go.mod b/go.mod index f170745..a69b4b8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gitlab.com/etke.cc/postmoogle go 1.18 require ( + git.sr.ht/~xn/cache/v2 v2.0.0 github.com/emersion/go-smtp v0.15.0 github.com/getsentry/sentry-go v0.13.0 github.com/jhillyerd/enmime v0.10.0 diff --git a/go.sum b/go.sum index 316dd09..aeb359c 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +git.sr.ht/~xn/cache/v2 v2.0.0 h1:aYzwGDyVIzjCl2yqcxZjprnu++Q3BmUQeK2agqvcQt8= +git.sr.ht/~xn/cache/v2 v2.0.0/go.mod h1:HIPSMiDudQ483tRDup586e0YZdwMySIZFWXMPwYMuV8= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=