Sanitizing options on Get() ensures that when someone asks for a given option which may not be defined (`nosubject`, `nosender`), we'll return a valid value (`'true'` or `'false'`) and not `''` (empty string, undefined). This way, users do not need to wonder if "nosender is not set" is handled like "true" or "false" or in some 3rd way. They also don't need to think about "how to unset this setting, now that I've set it to something". Options will appear to have a default sanitized value no matter if they've explicitly been set or not. The `NoSender()` and `NoSubject()` getters are just there for convenience, so that we won't need to do casting in other places.
168 lines
3.8 KiB
Go
168 lines
3.8 KiB
Go
package bot
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
"gitlab.com/etke.cc/postmoogle/utils"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
const settingskey = "cc.etke.postmoogle.settings"
|
|
|
|
var migrations = []string{}
|
|
|
|
// settings of a room
|
|
type settings map[string]string
|
|
|
|
// settingsOld of a room
|
|
type settingsOld struct {
|
|
Mailbox string
|
|
Owner id.UserID
|
|
NoSender bool
|
|
}
|
|
|
|
// Allowed checks if change is allowed
|
|
func (s settings) Allowed(noowner bool, userID id.UserID) bool {
|
|
if noowner {
|
|
return true
|
|
}
|
|
|
|
owner := s.Get("owner")
|
|
if owner == "" {
|
|
return true
|
|
}
|
|
|
|
return owner == userID.String()
|
|
}
|
|
|
|
// Get option
|
|
func (s settings) Get(key string) string {
|
|
rawValue := s[strings.ToLower(strings.TrimSpace(key))]
|
|
|
|
sanitizer, exists := sanitizers[key]
|
|
if exists {
|
|
return sanitizer(rawValue)
|
|
}
|
|
return rawValue
|
|
}
|
|
|
|
func (s settings) NoSender() bool {
|
|
return utils.Bool(s.Get("nosender"))
|
|
}
|
|
|
|
func (s settings) NoSubject() bool {
|
|
return utils.Bool(s.Get("nosubject"))
|
|
}
|
|
|
|
// Set option
|
|
func (s settings) Set(key, value string) {
|
|
s[strings.ToLower(strings.TrimSpace(key))] = value
|
|
}
|
|
|
|
func (b *Bot) migrate() error {
|
|
b.log.Debug("migrating database...")
|
|
tx, beginErr := b.lp.GetDB().Begin()
|
|
if beginErr != nil {
|
|
b.log.Error("cannot begin transaction: %v", beginErr)
|
|
return beginErr
|
|
}
|
|
|
|
for _, query := range migrations {
|
|
_, execErr := tx.Exec(query)
|
|
if execErr != nil {
|
|
b.log.Error("cannot apply migration: %v", execErr)
|
|
// nolint // we already have the execErr to return
|
|
tx.Rollback()
|
|
return execErr
|
|
}
|
|
}
|
|
|
|
commitErr := tx.Commit()
|
|
if commitErr != nil {
|
|
b.log.Error("cannot commit transaction: %v", commitErr)
|
|
// nolint // we already have the commitErr to return
|
|
tx.Rollback()
|
|
return commitErr
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Bot) syncRooms(ctx context.Context) error {
|
|
b.roomsmu.Lock()
|
|
defer b.roomsmu.Unlock()
|
|
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("syncRooms"))
|
|
defer span.Finish()
|
|
|
|
resp, err := b.lp.GetClient().JoinedRooms()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
b.rooms = make(map[string]id.RoomID, len(resp.JoinedRooms))
|
|
for _, roomID := range resp.JoinedRooms {
|
|
b.migrateSettings(span.Context(), roomID)
|
|
cfg, serr := b.getSettings(span.Context(), roomID)
|
|
if serr != nil {
|
|
b.log.Warn("cannot get %s settings: %v", roomID, err)
|
|
continue
|
|
}
|
|
mailbox := cfg.Get("mailbox")
|
|
if mailbox != "" {
|
|
b.rooms[mailbox] = roomID
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO: remove after migration
|
|
func (b *Bot) migrateSettings(ctx context.Context, 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("mailbox", config.Mailbox)
|
|
cfg.Set("owner", config.Owner.String())
|
|
cfg.Set("nosender", strconv.FormatBool(config.NoSender))
|
|
|
|
err = b.setSettings(ctx, roomID, cfg)
|
|
if err != nil {
|
|
b.log.Error("cannot migrate settings: %v", err)
|
|
}
|
|
}
|
|
|
|
func (b *Bot) getSettings(ctx context.Context, roomID id.RoomID) (settings, error) {
|
|
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("getSettings"))
|
|
defer span.Finish()
|
|
|
|
var 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
|
|
}
|
|
}
|
|
|
|
return config, err
|
|
}
|
|
|
|
func (b *Bot) setSettings(ctx context.Context, roomID id.RoomID, cfg settings) error {
|
|
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("setSettings"))
|
|
defer span.Finish()
|
|
|
|
return b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg)
|
|
}
|