Merge branch 'botconfig' into 'main'
manage users in runtime Closes #16 See merge request etke.cc/postmoogle!24
This commit is contained in:
@@ -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** <mailbox> - Delete specific mailbox
|
* **!pm delete** <mailbox> - Delete specific mailbox
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
34
bot/bot.go
34
bot/bot.go
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
bot/data.go
59
bot/data.go
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
41
bot/email.go
41
bot/email.go
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
108
bot/settings.go
108
bot/settings.go
@@ -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
91
bot/settings_bot.go
Normal 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
121
bot/settings_room.go
Normal 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))
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user