The configuration setting is called `Hide*` instead of `Show*`, because it's backward compatible with existing configuration settings. This is useful for when you setup an email forwarding inbox and you're always sending to it through the same email address. In that case, you don't need to see the email address in each Matrix message. In the future, another similar `bool` setting (`hide-subject`) will land, which controls whether the email's subject is shown in the final message or not. That setting can make use of most of the same setup (all of `handleBooleanConfigurationKey`).
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
package bot
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
"gitlab.com/etke.cc/go/logger"
|
|
"gitlab.com/etke.cc/linkpearl"
|
|
"gitlab.com/etke.cc/postmoogle/utils"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/format"
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
// Bot represents matrix bot
|
|
type Bot struct {
|
|
noowner bool
|
|
federation bool
|
|
prefix string
|
|
domain string
|
|
rooms map[string]id.RoomID
|
|
roomsmu *sync.Mutex
|
|
log *logger.Logger
|
|
lp *linkpearl.Linkpearl
|
|
}
|
|
|
|
// New creates a new matrix bot
|
|
func New(lp *linkpearl.Linkpearl, log *logger.Logger, prefix, domain string, noowner, federation bool) *Bot {
|
|
return &Bot{
|
|
noowner: noowner,
|
|
federation: federation,
|
|
roomsmu: &sync.Mutex{},
|
|
prefix: prefix,
|
|
domain: domain,
|
|
log: log,
|
|
lp: lp,
|
|
}
|
|
}
|
|
|
|
// 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...)
|
|
|
|
if sentry.HasHubOnContext(ctx) {
|
|
sentry.GetHubFromContext(ctx).CaptureException(fmt.Errorf(message, args...))
|
|
} else {
|
|
sentry.CaptureException(fmt.Errorf(message, args...))
|
|
}
|
|
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...),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Notice sends a notice message to the matrix room
|
|
func (b *Bot) Notice(ctx context.Context, roomID id.RoomID, message string, args ...interface{}) {
|
|
b.lp.Send(roomID, &event.MessageEventContent{
|
|
MsgType: event.MsgNotice,
|
|
Body: fmt.Sprintf(message, args...),
|
|
})
|
|
}
|
|
|
|
// Start performs matrix /sync
|
|
func (b *Bot) Start() error {
|
|
if err := b.migrate(); err != nil {
|
|
return err
|
|
}
|
|
ctx := sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone())
|
|
if err := b.syncRooms(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.initSync()
|
|
b.log.Info("Postmoogle has been started")
|
|
return b.lp.Start()
|
|
}
|
|
|
|
// Send email to matrix room
|
|
func (b *Bot) Send(ctx context.Context, from, to, subject, body string, files []*utils.File) error {
|
|
roomID, ok := b.GetMapping(ctx, utils.Mailbox(to))
|
|
if !ok {
|
|
return errors.New("room not found")
|
|
}
|
|
|
|
settings, err := b.getSettings(ctx, roomID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var text strings.Builder
|
|
if !settings.HideSenderAddress {
|
|
text.WriteString("From: ")
|
|
text.WriteString(from)
|
|
text.WriteString("\n\n")
|
|
}
|
|
text.WriteString("# ")
|
|
text.WriteString(subject)
|
|
text.WriteString("\n\n")
|
|
text.WriteString(format.HTMLToMarkdown(body))
|
|
|
|
content := format.RenderMarkdown(text.String(), true, true)
|
|
_, err = b.lp.Send(roomID, content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range files {
|
|
req := file.Convert()
|
|
resp, err := b.lp.GetClient().UploadMedia(req)
|
|
if err != nil {
|
|
b.Error(ctx, roomID, "cannot upload file %s: %v", req.FileName, err)
|
|
continue
|
|
}
|
|
_, err = b.lp.Send(roomID, &event.MessageEventContent{
|
|
MsgType: event.MsgFile,
|
|
Body: req.FileName,
|
|
URL: resp.ContentURI.CUString(),
|
|
})
|
|
if err != nil {
|
|
b.Error(ctx, roomID, "cannot send uploaded file %s: %v", req.FileName, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetMappings returns mapping of mailbox = room
|
|
func (b *Bot) GetMapping(ctx context.Context, mailbox string) (id.RoomID, bool) {
|
|
if len(b.rooms) == 0 {
|
|
err := b.syncRooms(ctx)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
roomID, ok := b.rooms[mailbox]
|
|
return roomID, ok
|
|
}
|
|
|
|
// Stop the bot
|
|
func (b *Bot) Stop() {
|
|
err := b.lp.GetClient().SetPresence(event.PresenceOffline)
|
|
if err != nil {
|
|
b.log.Error("cannot set presence = offline: %v", err)
|
|
}
|
|
b.lp.GetClient().StopSync()
|
|
}
|