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.
323 lines
7.4 KiB
Go
323 lines
7.4 KiB
Go
package bot
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
|
|
"gitlab.com/etke.cc/postmoogle/utils"
|
|
)
|
|
|
|
const (
|
|
commandHelp = "help"
|
|
commandStop = "stop"
|
|
commandMailboxes = "mailboxes"
|
|
)
|
|
|
|
type (
|
|
command struct {
|
|
key string
|
|
description string
|
|
sanitizer func(string) string
|
|
accessChecker accessCheckerFunc
|
|
}
|
|
commandList []command
|
|
)
|
|
|
|
func (c commandList) get(key string) *command {
|
|
for _, cmd := range c {
|
|
if cmd.key == key {
|
|
return &cmd
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
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) {
|
|
cmd := b.commands.get(commandSlice[0])
|
|
if cmd == nil {
|
|
return
|
|
}
|
|
|
|
// ignore requests over federation if disabled
|
|
if !b.federation && evt.Sender.Homeserver() != b.lp.GetClient().UserID.Homeserver() {
|
|
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 commandHelp:
|
|
b.sendHelp(ctx, evt.RoomID)
|
|
case commandStop:
|
|
b.runStop(ctx)
|
|
default:
|
|
b.handleOption(ctx, commandSlice)
|
|
}
|
|
}
|
|
|
|
func (b *Bot) parseCommand(message string) []string {
|
|
if message == "" {
|
|
return nil
|
|
}
|
|
|
|
index := strings.LastIndex(message, b.prefix)
|
|
if index == -1 {
|
|
return nil
|
|
}
|
|
|
|
message = strings.ToLower(strings.TrimSpace(strings.Replace(message, b.prefix, "", 1)))
|
|
return strings.Split(message, " ")
|
|
}
|
|
|
|
func (b *Bot) sendIntroduction(ctx context.Context, roomID id.RoomID) {
|
|
var msg strings.Builder
|
|
msg.WriteString("Hello, kupo!\n\n")
|
|
|
|
msg.WriteString("This is Postmoogle - a bot that bridges Email to Matrix.\n\n")
|
|
|
|
msg.WriteString("To get started, assign an email address to this room by sending a `")
|
|
msg.WriteString(b.prefix)
|
|
msg.WriteString(" ")
|
|
msg.WriteString(optionMailbox)
|
|
msg.WriteString("` command.\n")
|
|
|
|
msg.WriteString("You will then be able to send emails to `SOME_INBOX@")
|
|
msg.WriteString(b.domain)
|
|
msg.WriteString("` and have them appear in this room.")
|
|
|
|
b.Notice(ctx, roomID, msg.String())
|
|
}
|
|
|
|
func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) {
|
|
evt := eventFromContext(ctx)
|
|
|
|
cfg, serr := b.getSettings(evt.RoomID)
|
|
if serr != nil {
|
|
b.log.Error("cannot retrieve settings: %v", serr)
|
|
}
|
|
|
|
var msg strings.Builder
|
|
msg.WriteString("The following commands are supported:\n\n")
|
|
for _, cmd := range b.commands {
|
|
if cmd.key == "" {
|
|
msg.WriteString("\n---\n")
|
|
continue
|
|
}
|
|
msg.WriteString("* **`")
|
|
msg.WriteString(b.prefix)
|
|
msg.WriteString(" ")
|
|
msg.WriteString(cmd.key)
|
|
msg.WriteString("`**")
|
|
value := cfg.Get(cmd.key)
|
|
if cmd.sanitizer != nil {
|
|
switch value != "" {
|
|
case false:
|
|
msg.WriteString("(currently not set)")
|
|
case true:
|
|
msg.WriteString("(currently `")
|
|
msg.WriteString(value)
|
|
if cmd.key == optionMailbox {
|
|
msg.WriteString("@")
|
|
msg.WriteString(b.domain)
|
|
}
|
|
msg.WriteString("`)")
|
|
}
|
|
}
|
|
|
|
msg.WriteString(" - ")
|
|
|
|
msg.WriteString(cmd.description)
|
|
msg.WriteString("\n")
|
|
}
|
|
|
|
b.Notice(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.Notice(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.Notice(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.Notice(ctx, evt.RoomID, fmt.Sprintf("`%s` is not set, kupo.", name))
|
|
return
|
|
}
|
|
|
|
if name == optionMailbox {
|
|
value = value + "@" + b.domain
|
|
}
|
|
|
|
b.Notice(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.Notice(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.Notice(ctx, evt.RoomID, fmt.Sprintf("`%s` of this room set to `%s`", name, value))
|
|
}
|