asgard.git

commit ef1cd44ec46f1f69a7062e05125e4e57ff8cf2eb

Author: Adam Evyčędo <git@apiote.xyz>

rewrite hermodr to gott v2

 gersemi.go | 10 -
 hermodr.go | 225 +++++++++++++++++++++-----------------------------
 imap.go | 4 
 imap_message.go | 12 ++
 mimir.go | 6 


diff --git a/gersemi.go b/gersemi.go
index 301d5fa4e9a5bc0832a280334c94ca89b6f4d71e..bf6b12dc858aa474b54e78f24b150fed5a796358 100644
--- a/gersemi.go
+++ b/gersemi.go
@@ -12,7 +12,6 @@ 	"strings"
 	"time"
 
 	"apiote.xyz/p/gott/v2"
-	"github.com/emersion/go-imap/client"
 	_ "github.com/emersion/go-message/charset"
 )
 
@@ -62,7 +61,6 @@ }
 
 type GersemiImapMessage struct {
 	ImapMessage
-	cli               *client.Client
 	hc                *http.Client
 	withdrawalRegexes []*regexp.Regexp
 	depositRegexes    []*regexp.Regexp
@@ -140,12 +138,12 @@ 				ImapMessage: ImapMessage{
 					msg:      msg,
 					sect:     m.section(),
 					mimetype: m.config().Gersemi.MessageMime,
+					cli:      am.client(),
 				},
 				config:            m.config(),
 				withdrawalRegexes: m.withdrawalRegexes,
 				depositRegexes:    m.depositRegexes,
 				hc:                m.hc,
-				cli:               m.cli,
 			},
 		}.
 			Bind(readMessageBody).
@@ -157,7 +155,7 @@ 			Bind(marshalBody).
 			Bind(createRequest).
 			Bind(doRequest).
 			Bind(handleHttpError).
-			SafeTee(moveGersemiMessage).
+			Tee(moveGersemiMessage).
 			Recover(ignoreGersemiInvalidMessage).
 			Recover(recoverMalformedMessage).
 			Recover(recoverErroredMessages)
@@ -296,9 +294,9 @@ 	}
 	return m, nil
 }
 
-func moveGersemiMessage(am AbstractImapMessage) { // todo collect messages out of loop and move all
+func moveGersemiMessage(am AbstractImapMessage) error { // TODO collect messages out of loop and move all
 	m := am.(*GersemiImapMessage)
-	moveMsg(m.cli, m.msg, m.config.Gersemi.DoneFolder)
+	return moveMsg(m.client(), m.msg, m.config.Gersemi.DoneFolder)
 }
 
 func ignoreGersemiInvalidMessage(s AbstractImapMessage, e error) (AbstractImapMessage, error) {




diff --git a/hermodr.go b/hermodr.go
index e21e23942ab8a97d2f1c53d34b24686094e91ee2..7f73a9c7b9ca4b4f2527e9d16fe241c62be315cb 100644
--- a/hermodr.go
+++ b/hermodr.go
@@ -1,17 +1,14 @@
 package main
 
 import (
-	"fmt"
 	"io"
 	"net/mail"
-	"os"
 	"strings"
 
+	"apiote.xyz/p/gott/v2"
 	"github.com/ProtonMail/gopenpgp/v2/helper"
 	"github.com/emersion/go-imap"
-	"github.com/emersion/go-imap/client"
 	"github.com/emersion/go-smtp"
-	"notabug.org/apiote/gott"
 )
 
 type EmptyMessageError struct{}
@@ -20,129 +17,100 @@ func (EmptyMessageError) Error() string {
 	return "Server didn't return message body"
 }
 
-type Result struct {
-	Config Config
-
-	Client    *client.Client
-	Mailbox   *imap.MailboxStatus
-	Literal   imap.Literal
-	Message   *mail.Message
-	Body      string
-	PlainText string
-	Armour    string
+type HermodrMailbox struct {
+	Mailbox
 }
 
-func connectHermodr(args ...interface{}) (interface{}, error) {
-	result := args[0].(Result)
-	c, err := client.Dial(result.Config.Hermodr.ImapAddress)
-	result.Client = c
-	return result, err
-}
-
-func loginHermodr(args ...interface{}) error {
-	result := args[0].(Result)
-	err := result.Client.Login(result.Config.Hermodr.ImapUsername, result.Config.Hermodr.ImapPassword)
-	return err
+type HermodrImapMessage struct {
+	ImapMessage
+	config  Config
+	literal imap.Literal
+	mailMsg *mail.Message
+	body    string
+	plain   string
+	armour  string
 }
 
-func selectHermodrInbox(args ...interface{}) (interface{}, error) {
-	result := args[0].(Result)
-	mbox, err := result.Client.Select(result.Config.Hermodr.ImapFolderInbox, false)
-	result.Mailbox = mbox
-	return result, err
-}
-
-func redirectMessages(args ...interface{}) (interface{}, error) {
-	result := args[0].(Result)
-
-	for result.Mailbox.Messages > 0 {
-		r, err := gott.NewResult(result).
-			Bind(getMessage).
-			Bind(readMessage).
-			Bind(readHermodrBody).
+func redirectMessages(am AbstractMailbox) error {
+	m := am.(*HermodrMailbox)
+	for msg := range m.messages() {
+		r := gott.R[AbstractImapMessage]{
+			S: &HermodrImapMessage{
+				ImapMessage: ImapMessage{
+					msg:  msg,
+					sect: m.section(),
+					cli:  am.client(),
+				},
+				config: m.conf,
+			},
+		}.
+			Bind(getBodySection).
+			Bind(readLiteralMessage).
+			Bind(readLiteralBody).
 			Map(composePlaintextBody).
 			Bind(encrypt).
 			Tee(send).
 			Tee(markRead).
-			Tee(moveMessage).
-			Bind(selectHermodrInbox).
-			Finish()
-		if err != nil {
-			return r, err
+			Tee(moveMessage)
+
+		if r.E != nil {
+			return r.E
 		}
-		result = r.(Result)
 	}
-	return result, nil
+	return nil
 }
-func getMessage(args ...interface{}) (interface{}, error) {
-	result := args[0].(Result)
 
-	seqset := new(imap.SeqSet)
-	seqset.AddNum(1)
-
-	section := &imap.BodySectionName{}
-	items := []imap.FetchItem{section.FetchItem()}
-
-	messages := make(chan *imap.Message, 1)
-	done := make(chan error, 1)
-	go func() {
-		done <- result.Client.Fetch(seqset, items, messages)
-	}()
-
-	msg := <-messages
-	r := msg.GetBody(section)
+func getBodySection(m AbstractImapMessage) (AbstractImapMessage, error) {
+	hm := m.(*HermodrImapMessage)
+	r := hm.message().GetBody(m.section())
 	if r == nil {
-		return result, EmptyMessageError{}
+		return hm, EmptyMessageError{}
 	}
-	result.Literal = r
-
-	err := <-done
-	return result, err
+	hm.literal = r
+	return hm, nil
 }
 
-func readMessage(args ...interface{}) (interface{}, error) {
-	result := args[0].(Result)
-	m, err := mail.ReadMessage(result.Literal)
-	result.Message = m
-	return result, err
+func readLiteralMessage(m AbstractImapMessage) (AbstractImapMessage, error) {
+	hm := m.(*HermodrImapMessage)
+	msg, err := mail.ReadMessage(hm.literal)
+	hm.mailMsg = msg
+	return hm, err
 }
 
-func readHermodrBody(args ...interface{}) (interface{}, error) {
-	result := args[0].(Result)
-	body, err := io.ReadAll(result.Message.Body)
-	result.Body = string(body)
-	return result, err
+func readLiteralBody(m AbstractImapMessage) (AbstractImapMessage, error) {
+	hm := m.(*HermodrImapMessage)
+	body, err := io.ReadAll(hm.mailMsg.Body)
+	hm.body = string(body)
+	return hm, err
 }
 
-func composePlaintextBody(args ...interface{}) interface{} {
-	result := args[0].(Result)
-
-	header := result.Message.Header
+func composePlaintextBody(m AbstractImapMessage) AbstractImapMessage {
+	hm := m.(*HermodrImapMessage)
+	header := hm.mailMsg.Header
 	plainText := "Content-Type: " + header.Get("Content-Type") + "; protected-headers=\"v1\"\r\n"
 	plainText += "From: " + header.Get("From") + "\r\n"
 	plainText += "Message-ID: " + header.Get("Message-ID") + "\r\n"
 	plainText += "Subject: " + header.Get("Subject") + "\r\n"
 	plainText += "\r\n"
-	plainText += string(result.Body)
-	result.PlainText = plainText
-	return result
+	plainText += string(hm.body)
+	hm.plain = plainText
+	return hm
 }
 
-func encrypt(args ...interface{}) (interface{}, error) {
-	result := args[0].(Result)
-	armour, err := helper.EncryptMessageArmored(result.Config.Hermodr.PublicKey, result.PlainText)
-	result.Armour = armour
-	return result, err
+func encrypt(m AbstractImapMessage) (AbstractImapMessage, error) {
+	hm := m.(*HermodrImapMessage)
+	armour, err := helper.EncryptMessageArmored(hm.config.Hermodr.PublicKey, hm.plain)
+	hm.armour = armour
+	return hm, err
 }
 
-func send(args ...interface{}) error {
-	result := args[0].(Result)
-
-	from := result.Message.Header.Get("From")
-	date := result.Message.Header.Get("Date")
-	messageID := result.Message.Header.Get("Message-ID")
-	to := []string{result.Config.Hermodr.Recipient}
-	msg := strings.NewReader("To: " + result.Config.Hermodr.Recipient + "\r\n" +
+func send(m AbstractImapMessage) error {
+	hm := m.(*HermodrImapMessage)
+	from := hm.mailMsg.Header.Get("From")
+	date := hm.mailMsg.Header.Get("Date")
+	messageID := hm.mailMsg.Header.Get("Message-ID")
+	to := []string{hm.config.Hermodr.Recipient}
+	msg := strings.NewReader("To: " + hm.config.Hermodr.Recipient + "\r\n" +
 		"From: " + from + "\r\n" +
 		"Date: " + date + "\r\n" +
 		"Message-ID: " + messageID + "\r\n" +
@@ -162,50 +130,51 @@ 		"Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n" +
 		"Content-Description: OpenPGP encrypted message\r\n" +
 		"Content-Disposition: inline; filename=\"encrypted.asc\"\r\n" +
 		"\r\n" +
-		result.Armour +
+		hm.armour +
 		"\r\n" +
 		"\r\n" +
 		"-----------------------997d365ae018229dc62ea2ff6b617cac--\r\n")
-	err := smtp.SendMail(result.Config.Hermodr.SmtpServer, nil, result.Config.Hermodr.SmtpUsername, to, msg)
+	err := smtp.SendMail(hm.config.Hermodr.SmtpServer, nil, hm.config.Hermodr.SmtpUsername, to, msg)
 	return err
 }
 
-func markRead(args ...interface{}) error {
-	result := args[0].(Result)
-
+func markRead(m AbstractImapMessage) error {
 	seqset := new(imap.SeqSet)
-	seqset.AddNum(1)
+	seqset.AddNum(1) // TODO collect seqset
 	item := imap.FormatFlagsOp(imap.AddFlags, true)
 	flags := []interface{}{imap.SeenFlag}
-	err := result.Client.Store(seqset, item, flags, nil)
+	err := m.client().Store(seqset, item, flags, nil) // TODO store outside of loop
 	return err
 }
 
-func moveMessage(args ...interface{}) error {
-	result := args[0].(Result)
-
-	seqset := new(imap.SeqSet)
-	seqset.AddNum(1)
-	err := result.Client.Copy(seqset, result.Config.Hermodr.ImapFolderRedirected)
-	return err
+func moveMessage(m AbstractImapMessage) error { // TODO collect messages out of loop and move all
+	hm := m.(*HermodrImapMessage)
+	return moveMsg(m.client(), hm.msg, hm.config.Hermodr.ImapFolderRedirected)
 }
 
-func hermodr(config Config) {
-	r, err := gott.NewResult(Result{Config: config}).
-		SetLevelLog(gott.Debug).
-		Bind(connectHermodr).
-		Tee(loginHermodr).
-		Bind(selectHermodrInbox).
-		Bind(redirectMessages).
-		Finish()
-
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error: %v", err)
-		return
-	}
-	// Don't forget to logout
-	if r.(Result).Client != nil {
-		r.(Result).Client.Logout()
+func hermodr(config Config) error {
+	mailbox := &HermodrMailbox{
+		Mailbox: Mailbox{
+			mboxName: config.Hermodr.ImapFolderInbox,
+			imapAdr:  config.Hermodr.ImapAddress,
+			imapUser: config.Hermodr.ImapUsername,
+			imapPass: config.Hermodr.ImapPassword,
+			conf:     config,
+		},
 	}
 
+	mailbox.setupChannels()
+	r := gott.R[AbstractMailbox]{
+		S: mailbox,
+	}.
+		Bind(connect).
+		Tee(login).
+		Bind(selectInbox).
+		Tee(checkEmptyBox).
+		Map(fetchMessages).
+		Tee(redirectMessages).
+		Recover(ignoreEmptyBox).
+		Recover(disconnect)
+
+	return r.E
 }




diff --git a/imap.go b/imap.go
index afffcc783039033275145d73c4e1ca86b3b3a8de..a27fcfe46eaafb38697aff7bfe98076611311463 100644
--- a/imap.go
+++ b/imap.go
@@ -14,12 +14,12 @@ 	"github.com/emersion/go-sasl"
 	"github.com/emersion/go-smtp"
 )
 
-func moveMsg(c *client.Client, msg *imap.Message, dest string) {
+func moveMsg(c *client.Client, msg *imap.Message, dest string) error {
 	log.Printf("moving %v : %s from %s to %s\n", msg.Envelope.Date, msg.Envelope.Subject, msg.Envelope.From[0].Address(), dest)
 	moveClient := move.NewClient(c)
 	seqSet := new(imap.SeqSet)
 	seqSet.AddNum(msg.Uid)
-	moveClient.UidMoveWithFallback(seqSet, dest)
+	return moveClient.UidMoveWithFallback(seqSet, dest)
 }
 
 func moveMultiple(c *client.Client, seqSet *imap.SeqSet, dest string) error {




diff --git a/imap_message.go b/imap_message.go
index 009faf5732bab9c22f6e5dbb7f5532e1afa0a885..e8422ed2bb4cdae9b1e4da1910a28db7d332319e 100644
--- a/imap_message.go
+++ b/imap_message.go
@@ -8,6 +8,7 @@ 	"io"
 	"log"
 
 	"github.com/emersion/go-imap"
+	"github.com/emersion/go-imap/client"
 	"github.com/emersion/go-message"
 )
 
@@ -22,12 +23,15 @@ 	mimeMessage() *message.Entity
 	setMessageReader(io.Reader)
 	messageReader() io.Reader
 	setMessageBody([]byte)
+	setClient(*client.Client)
+	client() *client.Client
 }
 
 type ImapMessage struct {
 	sect     *imap.BodySectionName
 	msg      *imap.Message
 	mimetype string
+	cli      *client.Client
 
 	msgBytes []byte
 	mimeMsg  *message.Entity
@@ -77,6 +81,14 @@ }
 
 func (m ImapMessage) messageBody() []byte {
 	return m.bdy
+}
+
+func (m ImapMessage) setClient(c *client.Client) {
+	m.cli = c
+}
+
+func (m ImapMessage) client() *client.Client {
+	return m.cli
 }
 
 func readMessageBody(m AbstractImapMessage) (AbstractImapMessage, error) {




diff --git a/mimir.go b/mimir.go
index 57f7b776a5c99d7cdc5e560c2453ee7b479527b5..7c5bb051e45645c23da647bd493e2d9eb3cc3c65 100644
--- a/mimir.go
+++ b/mimir.go
@@ -42,7 +42,6 @@ 	"strings"
 
 	"apiote.xyz/p/gott/v2"
 	"github.com/emersion/go-imap"
-	"github.com/emersion/go-imap/client"
 	_ "github.com/emersion/go-message/charset"
 	"github.com/emersion/go-msgauth/dkim"
 )
@@ -85,7 +84,6 @@ 	dkimStatus     bool
 	db             *sql.DB
 	config         Config
 	recipients     []string
-	client         *client.Client
 	mboxName       string
 }
 
@@ -147,12 +145,12 @@ 				ImapMessage: ImapMessage{
 					msg:      msg,
 					sect:     m.section(),
 					mimetype: "text/plain",
+					cli:      m.cli,
 				},
 				categoryRegexp: m.categoryRegexp,
 				categories:     m.categories,
 				db:             m.db,
 				config:         m.config(),
-				client:         m.cli,
 				mboxName:       m.mboxName,
 			},
 		}.
@@ -273,7 +271,7 @@ }
 
 func removeMimirMessage(am AbstractImapMessage) error {
 	m := am.(*MimirImapMessage)
-	return removeMessage(m.client, m.message().Uid, m.mboxName)
+	return removeMessage(m.client(), m.message().Uid, m.mboxName)
 }
 
 func mimir_serve(db *sql.DB) func(w http.ResponseWriter, r *http.Request) {