diff --git a/README.md b/README.md
index 42f6c68..1a74193 100644
--- a/README.md
+++ b/README.md
@@ -25,15 +25,14 @@ An Email to Matrix bridge
env vars
-### mandatory
-
* **POSTMOOGLE_HOMESERVER** - homeserver url, eg: `https://matrix.example.com`
* **POSTMOOGLE_LOGIN** - user login/localpart, eg: `moogle`
* **POSTMOOGLE_PASSWORD** - user password
* **POSTMOOGLE_DOMAIN** - SMTP domain to listen for new emails
* **POSTMOOGLE_PORT** - SMTP port to listen for new emails
-### optional
+
+other optional config parameters
* **POSTMOOGLE_NOOWNER** - allow change room settings by any room partisipant
* **POSTMOOGLE_FEDERATION** - allow usage of Postmoogle by users from others homeservers
@@ -48,6 +47,40 @@ env vars
You can find default values in [config/defaults.go](config/defaults.go)
+
+
+## Usage
+
+### How to start
+
+1. Invite the bot into a room you want to use as mailbox
+2. Read the bot's introduction
+3. Set mailbox using `!pm mailbox NAME` where `NAME` is part of email (e.g. `NAME@example.com`)
+4. Done. Mailbox owner and other options will be set automatically when you configure mailbox.
+If you want to change them - check available options in the help message (`!pm help`)
+
+
+Full list of available commands
+
+* **!pm help** - Show help message
+* **!pm stop** - Disable bridge for the room and clear all configuration
+
+---
+
+* **!pm mailbox** - Get or set mailbox of the room
+* **!pm owner** - Get or set owner of the room
+
+---
+
+* **!pm nosender** - Get or set `nosender` of the room (`true` - hide email sender; `false` - show email sender)
+* **!pm nosubject** - Get or set `nosubject` of the room (`true` - hide email subject; `false` - show email subject)
+* **!pm nohtml** - Get or set `nohtml` of the room (`true` - ignore HTML in email; `false` - parse HTML in emails)
+* **!pm nothreads** - Get or set `nothreads` of the room (`true` - ignore email threads; `false` - convert email threads into matrix threads)
+* **!pm nofiles** - Get or set `nofiles` of the room (`true` - ignore email attachments; `false` - upload email attachments)
+
+
+
+
## Where to get
[docker registry](https://gitlab.com/etke.cc/postmoogle/container_registry), [etke.cc](https://etke.cc)
diff --git a/bot/bot.go b/bot/bot.go
index db3a7e3..832e2e2 100644
--- a/bot/bot.go
+++ b/bot/bot.go
@@ -24,20 +24,22 @@ type Bot struct {
rooms sync.Map
log *logger.Logger
lp *linkpearl.Linkpearl
+ mu map[id.RoomID]*sync.Mutex
handledMembershipEvents sync.Map
}
// New creates a new matrix bot
func New(lp *linkpearl.Linkpearl, log *logger.Logger, prefix, domain string, noowner, federation bool, allowedUsers []*regexp.Regexp) *Bot {
return &Bot{
- noowner: noowner,
- federation: federation,
- prefix: prefix,
- domain: domain,
+ noowner: noowner,
+ federation: federation,
+ prefix: prefix,
+ domain: domain,
allowedUsers: allowedUsers,
- rooms: sync.Map{},
- log: log,
- lp: lp,
+ rooms: sync.Map{},
+ log: log,
+ lp: lp,
+ mu: map[id.RoomID]*sync.Mutex{},
}
}
@@ -79,20 +81,6 @@ func (b *Bot) Start(statusMsg string) error {
return b.lp.Start(statusMsg)
}
-// GetMappings returns mapping of mailbox = room
-func (b *Bot) GetMapping(mailbox string) (id.RoomID, bool) {
- v, ok := b.rooms.Load(mailbox)
- if !ok {
- return "", ok
- }
- roomID, ok := v.(id.RoomID)
- if !ok {
- return "", ok
- }
-
- return roomID, ok
-}
-
// Stop the bot
func (b *Bot) Stop() {
err := b.lp.GetClient().SetPresence(event.PresenceOffline)
diff --git a/bot/command.go b/bot/command.go
index 9a67a97..2a6c9b2 100644
--- a/bot/command.go
+++ b/bot/command.go
@@ -256,11 +256,16 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
return
}
+ old := cfg.Get(name)
cfg.Set(name, value)
+
if name == optionMailbox {
- value = fmt.Sprintf("%s@%s", value, b.domain)
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)
diff --git a/bot/email.go b/bot/email.go
index e4849af..cf8b10a 100644
--- a/bot/email.go
+++ b/bot/email.go
@@ -43,12 +43,28 @@ func email2content(email *utils.Email, cfg settings, threadID id.EventID) *event
return &content
}
+// GetMapping returns mapping of mailbox = room
+func (b *Bot) GetMapping(mailbox string) (id.RoomID, bool) {
+ v, ok := b.rooms.Load(mailbox)
+ if !ok {
+ return "", ok
+ }
+ roomID, ok := v.(id.RoomID)
+ if !ok {
+ return "", ok
+ }
+
+ return roomID, ok
+}
+
// Send email to matrix room
func (b *Bot) Send(ctx context.Context, email *utils.Email) error {
roomID, ok := b.GetMapping(utils.Mailbox(email.To))
if !ok {
return errors.New("room not found")
}
+ b.lock(roomID)
+ defer b.unlock(roomID)
cfg, err := b.getSettings(roomID)
if err != nil {
@@ -66,7 +82,7 @@ func (b *Bot) Send(ctx context.Context, email *utils.Email) error {
content := email2content(email, cfg, threadID)
eventID, serr := b.lp.Send(roomID, content)
if serr != nil {
- return serr
+ return utils.UnwrapError(serr)
}
if threadID == "" && !cfg.NoThreads() {
diff --git a/bot/mutext.go b/bot/mutext.go
new file mode 100644
index 0000000..8bc091d
--- /dev/null
+++ b/bot/mutext.go
@@ -0,0 +1,26 @@
+package bot
+
+import (
+ "sync"
+
+ "maunium.net/go/mautrix/id"
+)
+
+func (b *Bot) lock(roomID id.RoomID) {
+ _, ok := b.mu[roomID]
+ if !ok {
+ b.mu[roomID] = &sync.Mutex{}
+ }
+
+ b.mu[roomID].Lock()
+}
+
+func (b *Bot) unlock(roomID id.RoomID) {
+ _, ok := b.mu[roomID]
+ if !ok {
+ return
+ }
+
+ b.mu[roomID].Unlock()
+ delete(b.mu, roomID)
+}
diff --git a/bot/settings.go b/bot/settings.go
index a7a103c..8c97d50 100644
--- a/bot/settings.go
+++ b/bot/settings.go
@@ -117,9 +117,9 @@ func (b *Bot) getSettings(roomID id.RoomID) (settings, error) {
}
}
- return config, err
+ return config, utils.UnwrapError(err)
}
func (b *Bot) setSettings(roomID id.RoomID, cfg settings) error {
- return b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg)
+ return utils.UnwrapError(b.lp.GetClient().SetRoomAccountData(roomID, settingskey, cfg))
}
diff --git a/e2e/send-stress b/e2e/send-stress
new file mode 100755
index 0000000..e4b5da9
--- /dev/null
+++ b/e2e/send-stress
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+for i in {0..10..1}; do
+ echo "#${i}..."
+ ssmtp test@localhost < $1
+done
+
+echo "done"
diff --git a/utils/matrix.go b/utils/matrix.go
index faa2a54..0115996 100644
--- a/utils/matrix.go
+++ b/utils/matrix.go
@@ -1,6 +1,7 @@
package utils
import (
+ "maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@@ -24,3 +25,29 @@ func RelatesTo(noThreads bool, parentID id.EventID) *event.RelatesTo {
EventID: parentID,
}
}
+
+// UnwrapError tries to unwrap a error into something meaningful, like mautrix.HTTPError or mautrix.RespError
+func UnwrapError(err error) error {
+ switch err.(type) {
+ case nil:
+ return nil
+ case mautrix.HTTPError:
+ return unwrapHTTPError(err)
+ default:
+ return err
+ }
+}
+
+func unwrapHTTPError(err error) error {
+ httperr, ok := err.(mautrix.HTTPError)
+ if !ok {
+ return err
+ }
+
+ uwerr := httperr.Unwrap()
+ if uwerr != nil {
+ return uwerr
+ }
+
+ return httperr
+}