wip
This commit is contained in:
71
bot/bot.go
Normal file
71
bot/bot.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"gitlab.com/etke.cc/go/logger"
|
||||
"gitlab.com/etke.cc/linkpearl"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// Bot represents matrix bot
|
||||
type Bot struct {
|
||||
prefix string
|
||||
domain string
|
||||
log *logger.Logger
|
||||
lp *linkpearl.Linkpearl
|
||||
}
|
||||
|
||||
// New creates a new matrix bot
|
||||
func New(lp *linkpearl.Linkpearl, log *logger.Logger, prefix, domain string) *Bot {
|
||||
return &Bot{
|
||||
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...),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Start performs matrix /sync
|
||||
func (b *Bot) Start() error {
|
||||
if err := b.migrate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.lp.GetClient().SetPresence(event.PresenceOnline); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.initSync()
|
||||
b.log.Info("Postmoogle has been started")
|
||||
return b.lp.GetClient().Sync()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
60
bot/command.go
Normal file
60
bot/command.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/format"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
var commands = map[string]string{
|
||||
"mailbox": "Get or set mailbox of that room",
|
||||
"help": "Get help",
|
||||
}
|
||||
|
||||
func (b *Bot) handleCommand(ctx context.Context, evt *event.Event, command []string) {
|
||||
if _, ok := commands[command[0]]; !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch command[0] {
|
||||
case "help":
|
||||
b.sendHelp(ctx, evt.RoomID)
|
||||
case "mailbox":
|
||||
b.handleMailbox(ctx, evt, command)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bot) parseCommand(message string) []string {
|
||||
if message == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
message = strings.Replace(message, b.prefix+" ", "", 1)
|
||||
return strings.Split(message, " ")
|
||||
}
|
||||
|
||||
func (b *Bot) sendHelp(ctx context.Context, roomID id.RoomID) {
|
||||
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("sendHelp"))
|
||||
defer span.Finish()
|
||||
|
||||
var msg strings.Builder
|
||||
msg.WriteString("the following commands are supported:\n\n")
|
||||
for name, desc := range commands {
|
||||
msg.WriteString("* **")
|
||||
msg.WriteString(name)
|
||||
msg.WriteString("** - ")
|
||||
msg.WriteString(desc)
|
||||
msg.WriteString("\n")
|
||||
}
|
||||
|
||||
content := format.RenderMarkdown(msg.String(), true, true)
|
||||
content.MsgType = event.MsgNotice
|
||||
_, err := b.lp.Send(roomID, content)
|
||||
if err != nil {
|
||||
b.Error(span.Context(), roomID, "cannot send message: %v", err)
|
||||
}
|
||||
}
|
||||
125
bot/data.go
Normal file
125
bot/data.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
const settingskey = "cc.etke.postmoogle.settings"
|
||||
|
||||
var migrations = []string{
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
room_id VARCHAR(255),
|
||||
mailbox VARCHAR(255)
|
||||
)
|
||||
`,
|
||||
}
|
||||
|
||||
// settings of a room
|
||||
type settings struct {
|
||||
Mailbox string
|
||||
}
|
||||
|
||||
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) getSettings(ctx context.Context, roomID id.RoomID) (*settings, error) {
|
||||
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("getSettings"))
|
||||
defer span.Finish()
|
||||
|
||||
var config settings
|
||||
query := "SELECT * FROM settings WHERE room_id = "
|
||||
switch b.lp.GetStore().GetDialect() {
|
||||
case "postgres":
|
||||
query += "$1"
|
||||
case "sqlite3":
|
||||
query += "?"
|
||||
}
|
||||
row := b.lp.GetDB().QueryRow(query, roomID)
|
||||
err := row.Scan(&config.Mailbox)
|
||||
if err == nil {
|
||||
return &config, nil
|
||||
}
|
||||
b.log.Error("cannot find settings in database: %v", err)
|
||||
|
||||
err = b.lp.GetClient().GetRoomAccountData(roomID, settingskey, &config)
|
||||
|
||||
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()
|
||||
|
||||
tx, err := b.lp.GetDB().Begin()
|
||||
if err == nil {
|
||||
var insert string
|
||||
switch b.lp.GetStore().GetDialect() {
|
||||
case "postgres":
|
||||
insert = "INSERT INTO settings VALUES ($1) ON CONFLICT (room_id) DO UPDATE SET mailbox = $1"
|
||||
case "sqlite3":
|
||||
insert = "INSERT INTO settings VALUES (?) ON CONFLICT (room_id) DO UPDATE SET mailbox = ?"
|
||||
}
|
||||
_, err = tx.Exec(insert, cfg.Mailbox)
|
||||
if err != nil {
|
||||
b.log.Error("cannot insert settigs: %v", err)
|
||||
// nolint // no need to check error here
|
||||
tx.Rollback()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
b.log.Error("cannot commit transaction: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg)
|
||||
}
|
||||
|
||||
// FindRoomID by mailbox
|
||||
func (b *Bot) FindRoomID(mailbox string) (id.RoomID, error) {
|
||||
query := "SELECT room_id FROM settings WHERE mailbox = "
|
||||
switch b.lp.GetStore().GetDialect() {
|
||||
case "postgres":
|
||||
query += "$1"
|
||||
case "sqlite3":
|
||||
query += "?"
|
||||
}
|
||||
|
||||
var roomID string
|
||||
row := b.lp.GetDB().QueryRow(query, mailbox)
|
||||
err := row.Scan(&roomID)
|
||||
|
||||
return id.RoomID(roomID), err
|
||||
}
|
||||
66
bot/mailbox.go
Normal file
66
bot/mailbox.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/format"
|
||||
)
|
||||
|
||||
func (b *Bot) handleMailbox(ctx context.Context, evt *event.Event, command []string) {
|
||||
if len(command) == 1 {
|
||||
b.getMailbox(ctx, evt)
|
||||
return
|
||||
}
|
||||
b.setMailbox(ctx, evt, command[1])
|
||||
}
|
||||
|
||||
func (b *Bot) getMailbox(ctx context.Context, evt *event.Event) {
|
||||
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("getMailbox"))
|
||||
defer span.Finish()
|
||||
|
||||
cfg, err := b.getSettings(span.Context(), evt.RoomID)
|
||||
if err != nil || cfg == nil {
|
||||
b.Error(span.Context(), evt.RoomID, "cannot get settings: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.Mailbox == "" {
|
||||
b.Error(span.Context(), evt.RoomID, "mailbox name is not set")
|
||||
return
|
||||
}
|
||||
|
||||
content := format.RenderMarkdown("Mailbox of this room is **"+cfg.Mailbox+"@"+b.domain+"**", true, true)
|
||||
content.MsgType = event.MsgNotice
|
||||
_, err = b.lp.Send(evt.RoomID, content)
|
||||
if err != nil {
|
||||
b.Error(span.Context(), evt.RoomID, "cannot send message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bot) setMailbox(ctx context.Context, evt *event.Event, mailbox string) {
|
||||
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("setMailbox"))
|
||||
defer span.Finish()
|
||||
|
||||
cfg, err := b.getSettings(span.Context(), evt.RoomID)
|
||||
if err != nil {
|
||||
b.log.Warn("cannot get settings: %v", err)
|
||||
}
|
||||
if cfg == nil {
|
||||
cfg = &settings{}
|
||||
}
|
||||
cfg.Mailbox = mailbox
|
||||
err = b.setSettings(span.Context(), evt.RoomID, cfg)
|
||||
if err != nil {
|
||||
b.Error(span.Context(), evt.RoomID, "cannot update settings: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content := format.RenderMarkdown("Mailbox of this room set to **"+cfg.Mailbox+"@"+b.domain+"**", true, true)
|
||||
content.MsgType = event.MsgNotice
|
||||
_, err = b.lp.Send(evt.RoomID, content)
|
||||
if err != nil {
|
||||
b.Error(span.Context(), evt.RoomID, "cannot send message: %v", err)
|
||||
}
|
||||
}
|
||||
23
bot/message.go
Normal file
23
bot/message.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"maunium.net/go/mautrix/event"
|
||||
)
|
||||
|
||||
func (b *Bot) handle(ctx context.Context, evt *event.Event) {
|
||||
content := evt.Content.AsMessage()
|
||||
if content == nil {
|
||||
b.Error(ctx, evt.RoomID, "cannot read message")
|
||||
return
|
||||
}
|
||||
message := strings.TrimSpace(content.Body)
|
||||
command := b.parseCommand(message)
|
||||
if command == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.handleCommand(ctx, evt, command)
|
||||
}
|
||||
72
bot/sync.go
Normal file
72
bot/sync.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
)
|
||||
|
||||
func (b *Bot) initSync() {
|
||||
b.lp.OnEventType(
|
||||
event.EventMessage,
|
||||
func(_ mautrix.EventSource, evt *event.Event) {
|
||||
go b.onMessage(evt)
|
||||
})
|
||||
b.lp.OnEventType(
|
||||
event.EventEncrypted,
|
||||
func(_ mautrix.EventSource, evt *event.Event) {
|
||||
go b.onEncryptedMessage(evt)
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Bot) onMessage(evt *event.Event) {
|
||||
// ignore own messages
|
||||
if evt.Sender == b.lp.GetClient().UserID {
|
||||
return
|
||||
}
|
||||
|
||||
hub := sentry.CurrentHub().Clone()
|
||||
hub.ConfigureScope(func(scope *sentry.Scope) {
|
||||
scope.SetUser(sentry.User{ID: evt.Sender.String()})
|
||||
scope.SetContext("event", map[string]string{
|
||||
"id": evt.ID.String(),
|
||||
"room": evt.RoomID.String(),
|
||||
"sender": evt.Sender.String(),
|
||||
})
|
||||
})
|
||||
ctx := sentry.SetHubOnContext(context.Background(), hub)
|
||||
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("onMessage"))
|
||||
defer span.Finish()
|
||||
|
||||
b.handle(span.Context(), evt)
|
||||
}
|
||||
|
||||
func (b *Bot) onEncryptedMessage(evt *event.Event) {
|
||||
// ignore own messages
|
||||
if evt.Sender == b.lp.GetClient().UserID {
|
||||
return
|
||||
}
|
||||
|
||||
hub := sentry.CurrentHub().Clone()
|
||||
hub.ConfigureScope(func(scope *sentry.Scope) {
|
||||
scope.SetUser(sentry.User{ID: evt.Sender.String()})
|
||||
scope.SetContext("event", map[string]string{
|
||||
"id": evt.ID.String(),
|
||||
"room": evt.RoomID.String(),
|
||||
"sender": evt.Sender.String(),
|
||||
})
|
||||
})
|
||||
ctx := sentry.SetHubOnContext(context.Background(), hub)
|
||||
span := sentry.StartSpan(ctx, "http.server", sentry.TransactionName("onMessage"))
|
||||
defer span.Finish()
|
||||
|
||||
decrypted, err := b.lp.GetMachine().DecryptMegolmEvent(evt)
|
||||
if err != nil {
|
||||
b.Error(span.Context(), evt.RoomID, "cannot decrypt a message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
b.handle(span.Context(), decrypted)
|
||||
}
|
||||
Reference in New Issue
Block a user