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) {