Merge branch 'botconfig' into 'main'

manage users in runtime

Closes #16

See merge request etke.cc/postmoogle!24
This commit is contained in:
Aine
2022-08-30 16:37:13 +00:00
12 changed files with 354 additions and 216 deletions

View File

@@ -44,8 +44,8 @@ env vars
* **POSTMOOGLE_DB_DSN** - database connection string * **POSTMOOGLE_DB_DSN** - database connection string
* **POSTMOOGLE_DB_DIALECT** - database dialect (postgres, sqlite3) * **POSTMOOGLE_DB_DIALECT** - database dialect (postgres, sqlite3)
* **POSTMOOGLE_MAXSIZE** - max email size (including attachments) in megabytes * **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_ADMINS** - a space-separated list of admin users. See `POSTMOOGLE_USERS` for syntax examples
* <s>**POSTMOOGLE_USERS**</s> - deprecated and ignored, use `!pm users` instead
You can find default values in [config/defaults.go](config/defaults.go) 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 mailboxes** - Show the list of all mailboxes
* **!pm users** - Get or set allowed users patterns
* **!pm delete** &lt;mailbox&gt; - Delete specific mailbox * **!pm delete** &lt;mailbox&gt; - Delete specific mailbox
</details> </details>

View File

@@ -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 { if err != nil {
b.Error(context.Background(), targetRoomID, "failed to retrieve settings: %v", err) b.Error(context.Background(), targetRoomID, "failed to retrieve settings: %v", err)
return false return false

View File

@@ -23,7 +23,8 @@ type Bot struct {
allowedAdmins []*regexp.Regexp allowedAdmins []*regexp.Regexp
commands commandList commands commandList
rooms sync.Map rooms sync.Map
cfg cache.Cache[settings] botcfg cache.Cache[botSettings]
cfg cache.Cache[roomSettings]
log *logger.Logger log *logger.Logger
lp *linkpearl.Linkpearl lp *linkpearl.Linkpearl
mu map[id.RoomID]*sync.Mutex mu map[id.RoomID]*sync.Mutex
@@ -36,34 +37,35 @@ func New(
log *logger.Logger, log *logger.Logger,
prefix string, prefix string,
domain string, domain string,
users []string, envUsers []string,
admins []string, admins []string,
) (*Bot, error) { ) (*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 { if err != nil {
return nil, err return nil, err
} }
var allowedUsers []*regexp.Regexp allowedUsers, uerr := parseMXIDpatterns(users, "")
allowedUsers, uerr := parseMXIDpatterns(users, "@*:"+homeserver)
if uerr != nil { if uerr != nil {
return nil, uerr return nil, uerr
} }
b.allowedUsers = allowedUsers
allowedAdmins, aerr := parseMXIDpatterns(admins, "") allowedAdmins, aerr := parseMXIDpatterns(admins, "")
if aerr != nil { if aerr != nil {
return nil, aerr 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() b.commands = b.buildCommandList()
return b, nil return b, nil

View File

@@ -14,6 +14,7 @@ import (
const ( const (
commandHelp = "help" commandHelp = "help"
commandStop = "stop" commandStop = "stop"
commandUsers = botOptionUsers
commandDelete = "delete" commandDelete = "delete"
commandMailboxes = "mailboxes" commandMailboxes = "mailboxes"
) )
@@ -53,64 +54,69 @@ func (b *Bot) buildCommandList() commandList {
{allowed: b.allowOwner}, // delimiter {allowed: b.allowOwner}, // delimiter
// options commands // options commands
{ {
key: optionMailbox, key: roomOptionMailbox,
description: "Get or set mailbox of the room", description: "Get or set mailbox of the room",
sanitizer: utils.Mailbox, sanitizer: utils.Mailbox,
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{ {
key: optionOwner, key: roomOptionOwner,
description: "Get or set owner of the room", description: "Get or set owner of the room",
sanitizer: func(s string) string { return s }, sanitizer: func(s string) string { return s },
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{allowed: b.allowOwner}, // delimiter {allowed: b.allowOwner}, // delimiter
{ {
key: optionNoSender, key: roomOptionNoSender,
description: fmt.Sprintf( description: fmt.Sprintf(
"Get or set `%s` of the room (`true` - hide email sender; `false` - show email sender)", "Get or set `%s` of the room (`true` - hide email sender; `false` - show email sender)",
optionNoSender, roomOptionNoSender,
), ),
sanitizer: utils.SanitizeBoolString, sanitizer: utils.SanitizeBoolString,
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{ {
key: optionNoSubject, key: roomOptionNoSubject,
description: fmt.Sprintf( description: fmt.Sprintf(
"Get or set `%s` of the room (`true` - hide email subject; `false` - show email subject)", "Get or set `%s` of the room (`true` - hide email subject; `false` - show email subject)",
optionNoSubject, roomOptionNoSubject,
), ),
sanitizer: utils.SanitizeBoolString, sanitizer: utils.SanitizeBoolString,
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{ {
key: optionNoHTML, key: roomOptionNoHTML,
description: fmt.Sprintf( description: fmt.Sprintf(
"Get or set `%s` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)", "Get or set `%s` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)",
optionNoHTML, roomOptionNoHTML,
), ),
sanitizer: utils.SanitizeBoolString, sanitizer: utils.SanitizeBoolString,
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{ {
key: optionNoThreads, key: roomOptionNoThreads,
description: fmt.Sprintf( description: fmt.Sprintf(
"Get or set `%s` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)", "Get or set `%s` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)",
optionNoThreads, roomOptionNoThreads,
), ),
sanitizer: utils.SanitizeBoolString, sanitizer: utils.SanitizeBoolString,
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{ {
key: optionNoFiles, key: roomOptionNoFiles,
description: fmt.Sprintf( description: fmt.Sprintf(
"Get or set `%s` of the room (`true` - ignore email attachments; `false` - upload email attachments)", "Get or set `%s` of the room (`true` - ignore email attachments; `false` - upload email attachments)",
optionNoFiles, roomOptionNoFiles,
), ),
sanitizer: utils.SanitizeBoolString, sanitizer: utils.SanitizeBoolString,
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{allowed: b.allowAdmin}, // delimiter {allowed: b.allowAdmin}, // delimiter
{
key: botOptionUsers,
description: "Get or set allowed users",
allowed: b.allowAdmin,
},
{ {
key: commandMailboxes, key: commandMailboxes,
description: "Show the list of all mailboxes", 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) b.sendHelp(ctx)
case commandStop: case commandStop:
b.runStop(ctx) b.runStop(ctx)
case commandUsers:
b.runUsers(ctx, commandSlice)
case commandDelete: case commandDelete:
b.runDelete(ctx, commandSlice) b.runDelete(ctx, commandSlice)
case commandMailboxes: 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("To get started, assign an email address to this room by sending a `")
msg.WriteString(b.prefix) msg.WriteString(b.prefix)
msg.WriteString(" ") msg.WriteString(" ")
msg.WriteString(optionMailbox) msg.WriteString(roomOptionMailbox)
msg.WriteString("` command.\n") msg.WriteString("` command.\n")
msg.WriteString("You will then be able to send emails to `SOME_INBOX@") 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) { func (b *Bot) sendHelp(ctx context.Context) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
cfg, serr := b.getSettings(evt.RoomID) cfg, serr := b.getRoomSettings(evt.RoomID)
if serr != nil { if serr != nil {
b.log.Error("cannot retrieve settings: %v", serr) b.log.Error("cannot retrieve settings: %v", serr)
} }
@@ -213,7 +221,7 @@ func (b *Bot) sendHelp(ctx context.Context) {
case true: case true:
msg.WriteString("(currently `") msg.WriteString("(currently `")
msg.WriteString(value) msg.WriteString(value)
if cmd.key == optionMailbox { if cmd.key == roomOptionMailbox {
msg.WriteString("@") msg.WriteString("@")
msg.WriteString(b.domain) msg.WriteString(b.domain)
} }

View File

@@ -13,7 +13,7 @@ import (
func (b *Bot) sendMailboxes(ctx context.Context) { func (b *Bot) sendMailboxes(ctx context.Context) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
mailboxes := map[string]settings{} mailboxes := map[string]roomSettings{}
slice := []string{} slice := []string{}
b.rooms.Range(func(key any, value any) bool { b.rooms.Range(func(key any, value any) bool {
if key == nil { if key == nil {
@@ -31,7 +31,7 @@ func (b *Bot) sendMailboxes(ctx context.Context) {
if !ok { if !ok {
return true return true
} }
config, err := b.getSettings(roomID) config, err := b.getRoomSettings(roomID)
if err != nil { if err != nil {
b.log.Error("cannot retrieve settings: %v", err) 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) roomID := v.(id.RoomID)
b.rooms.Delete(mailbox) b.rooms.Delete(mailbox)
err := b.setSettings(roomID, settings{}) err := b.setRoomSettings(roomID, roomSettings{})
if err != nil { if err != nil {
b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) b.Error(ctx, evt.RoomID, "cannot update settings: %v", err)
return return
@@ -87,3 +87,46 @@ func (b *Bot) runDelete(ctx context.Context, commandSlice []string) {
b.SendNotice(ctx, evt.RoomID, "mailbox has been deleted") 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")
}

View File

@@ -7,13 +7,13 @@ import (
func (b *Bot) runStop(ctx context.Context) { func (b *Bot) runStop(ctx context.Context) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
cfg, err := b.getSettings(evt.RoomID) cfg, err := b.getRoomSettings(evt.RoomID)
if err != nil { if err != nil {
b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err)
return return
} }
mailbox := cfg.Get(optionMailbox) mailbox := cfg.Get(roomOptionMailbox)
if mailbox == "" { if mailbox == "" {
b.SendNotice(ctx, evt.RoomID, "that room is not configured yet") b.SendNotice(ctx, evt.RoomID, "that room is not configured yet")
return return
@@ -21,7 +21,7 @@ func (b *Bot) runStop(ctx context.Context) {
b.rooms.Delete(mailbox) b.rooms.Delete(mailbox)
err = b.setSettings(evt.RoomID, settings{}) err = b.setRoomSettings(evt.RoomID, roomSettings{})
if err != nil { if err != nil {
b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) b.Error(ctx, evt.RoomID, "cannot update settings: %v", err)
return return
@@ -40,7 +40,7 @@ func (b *Bot) handleOption(ctx context.Context, cmd []string) {
func (b *Bot) getOption(ctx context.Context, name string) { func (b *Bot) getOption(ctx context.Context, name string) {
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
cfg, err := b.getSettings(evt.RoomID) cfg, err := b.getRoomSettings(evt.RoomID)
if err != nil { if err != nil {
b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err)
return return
@@ -52,7 +52,7 @@ func (b *Bot) getOption(ctx context.Context, name string) {
return return
} }
if name == optionMailbox { if name == roomOptionMailbox {
value = value + "@" + b.domain value = value + "@" + b.domain
} }
@@ -66,7 +66,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
} }
evt := eventFromContext(ctx) evt := eventFromContext(ctx)
if name == optionMailbox { if name == roomOptionMailbox {
existingID, ok := b.GetMapping(value) existingID, ok := b.GetMapping(value)
if ok && existingID != "" && existingID != evt.RoomID { if ok && existingID != "" && existingID != evt.RoomID {
b.SendNotice(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))
@@ -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 { if err != nil {
b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err) b.Error(ctx, evt.RoomID, "failed to retrieve settings: %v", err)
return return
@@ -83,8 +83,8 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
old := cfg.Get(name) old := cfg.Get(name)
cfg.Set(name, value) cfg.Set(name, value)
if name == optionMailbox { if name == roomOptionMailbox {
cfg.Set(optionOwner, evt.Sender.String()) cfg.Set(roomOptionOwner, evt.Sender.String())
if old != "" { if old != "" {
b.rooms.Delete(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) value = fmt.Sprintf("%s@%s", value, b.domain)
} }
err = b.setSettings(evt.RoomID, cfg) err = b.setRoomSettings(evt.RoomID, cfg)
if err != nil { if err != nil {
b.Error(ctx, evt.RoomID, "cannot update settings: %v", err) b.Error(ctx, evt.RoomID, "cannot update settings: %v", err)
return return
} }
if name == optionMailbox { if name == roomOptionMailbox {
value = value + "@" + b.domain value = value + "@" + b.domain
} }

View File

@@ -1,34 +1,5 @@
package bot 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{} var migrations = []string{}
func (b *Bot) migrate() error { func (b *Bot) migrate() error {
@@ -67,7 +38,7 @@ func (b *Bot) syncRooms() error {
} }
for _, roomID := range resp.JoinedRooms { for _, roomID := range resp.JoinedRooms {
b.migrateSettings(roomID) b.migrateSettings(roomID)
cfg, serr := b.getSettings(roomID) cfg, serr := b.getRoomSettings(roomID)
if serr != nil { if serr != nil {
b.log.Warn("cannot get %s settings: %v", roomID, err) b.log.Warn("cannot get %s settings: %v", roomID, err)
continue continue
@@ -80,31 +51,3 @@ func (b *Bot) syncRooms() error {
return nil 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)
}
}
}

View File

@@ -12,7 +12,16 @@ import (
"gitlab.com/etke.cc/postmoogle/utils" "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 var text strings.Builder
if !cfg.NoSender() { if !cfg.NoSender() {
text.WriteString("From: ") text.WriteString("From: ")
@@ -66,7 +75,7 @@ func (b *Bot) Send(ctx context.Context, email *utils.Email) error {
b.lock(roomID) b.lock(roomID)
defer b.unlock(roomID) defer b.unlock(roomID)
cfg, err := b.getSettings(roomID) cfg, err := b.getRoomSettings(roomID)
if err != nil { if err != nil {
b.Error(ctx, roomID, "cannot get settings: %v", err) 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)
}
}
}

View File

@@ -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))
}

91
bot/settings_bot.go Normal file
View File

@@ -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))
}

121
bot/settings_room.go Normal file
View File

@@ -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))
}

View File

@@ -22,7 +22,7 @@ type Config struct {
MaxSize int MaxSize int
// StatusMsg of the bot // StatusMsg of the bot
StatusMsg string 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 Users []string
// Admins holds list of admin users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = no admins // Admins holds list of admin users (wildcards supported), e.g.: @*:example.com, @bot.*:example.com, @admin:*. Empty = no admins
Admins []string Admins []string