Author: Adam <git@apiote.xyz>
reformat gersemi
gersemi.go | 352 +++++++++++++++++++++----------------------------- hermodr.go | 18 +- imap.go | 13 - imap_errors.go | 20 ++ imap_mailbox.go | 146 +++++++++++++++++++++ imap_message.go | 126 ++++++++++++++++++ mimir.go | 45 ------
diff --git a/gersemi.go b/gersemi.go index fd278c8d0e47466c3fe5c3950fd279e46c2001b3..d3a1723a19135dc949d71e06d391a2caa11ba046 100644 --- a/gersemi.go +++ b/gersemi.go @@ -5,7 +5,6 @@ "bytes" "encoding/json" "errors" "fmt" - "io" "log" "net/http" "regexp" @@ -13,9 +12,7 @@ "strings" "time" "apiote.xyz/p/gott/v2" - "github.com/emersion/go-imap" "github.com/emersion/go-imap/client" - "github.com/emersion/go-message" _ "github.com/emersion/go-message/charset" ) @@ -56,129 +53,103 @@ type GersemiRequestBody struct { Transactions []Transaction `json:"transactions"` } -type gersemiStruct struct { - c *client.Client - config Config - hc *http.Client +type GersemiMailbox struct { + Mailbox + hc *http.Client + withdrawalRegexes []*regexp.Regexp + depositRegexes []*regexp.Regexp +} - mbox *imap.MailboxStatus - section *imap.BodySectionName - messages chan *imap.Message - done chan error +type GersemiImapMessage struct { + ImapMessage + cli *client.Client + hc *http.Client withdrawalRegexes []*regexp.Regexp depositRegexes []*regexp.Regexp - - message *imap.Message - messageBytes []byte - mimeMessage *message.Entity - plainTextBodyReader io.Reader - plainTextBody []byte - src, dst string - title string - amount string - day, month, year string - requestBody GersemiRequestBody - requestBodyBytes []byte - request *http.Request - response *http.Response + config Config + src, dst string + title string + amount string + day, month, year string + requestBody GersemiRequestBody + requestBodyBytes []byte + request *http.Request + response *http.Response } func gersemi(config Config) error { - c, err := client.DialTLS(config.Gersemi.ImapAddress, nil) - if err != nil { - log.Fatalln(err) - } - log.Println("Connected") - defer c.Close() - defer c.Logout() - if err := c.Login(config.Gersemi.ImapUsername, config.Gersemi.ImapPassword); err != nil { - log.Fatalln(err) - } - log.Println("Logged in") - timeout, _ := time.ParseDuration("60s") - r := gott.R[gersemiStruct]{ - S: gersemiStruct{ - c: c, - config: config, - hc: &http.Client{ - Timeout: timeout, - }, + mailbox := &GersemiMailbox{ + Mailbox: Mailbox{ + imapAdr: config.Gersemi.ImapAddress, + imapUser: config.Gersemi.ImapUsername, + imapPass: config.Gersemi.ImapPassword, + conf: config, }, + hc: &http.Client{ + Timeout: timeout, + }, + } + mailbox.setupChannels() + + r := gott.R[AbstractMailbox]{ + S: mailbox, }. Bind(prepareRegexes). - Bind(selectGersemiInbox). - Tee(checkGersemiEmptyBox). - Map(fetchGersemiMessages). + Bind(connect). + Tee(login). + Bind(selectInbox). + Tee(checkEmptyBox). + Map(fetchMessages). Bind(createTransactions). - Recover(ignoreGersemiEmptyBox) + Recover(ignoreEmptyBox). + Recover(disconnect) return r.E } -func prepareRegexes(s gersemiStruct) (gersemiStruct, error) { - for i, wr := range s.config.Gersemi.WithdrawalRegexes { +func prepareRegexes(am AbstractMailbox) (AbstractMailbox, error) { + m := am.(*GersemiMailbox) + for i, wr := range m.config().Gersemi.WithdrawalRegexes { re, err := regexp.Compile(wr) if err != nil { - return s, fmt.Errorf("while compiling withdrawal regex %d: %w", i, err) + return m, fmt.Errorf("while compiling withdrawal regex %d: %w", i, err) } - s.withdrawalRegexes = append(s.withdrawalRegexes, re) + m.withdrawalRegexes = append(m.withdrawalRegexes, re) } - for i, dr := range s.config.Gersemi.DepositRegexes { + for i, dr := range m.config().Gersemi.DepositRegexes { re, err := regexp.Compile(dr) if err != nil { - return s, fmt.Errorf("while compiling deposit regex %d: %w", i, err) + return m, fmt.Errorf("while compiling deposit regex %d: %w", i, err) } - s.depositRegexes = append(s.depositRegexes, re) + m.depositRegexes = append(m.depositRegexes, re) } - return s, nil -} - -func selectGersemiInbox(s gersemiStruct) (gersemiStruct, error) { - mbox, err := s.c.Select("INBOX", false) - s.mbox = mbox - return s, err -} - -func checkGersemiEmptyBox(s gersemiStruct) error { - return checkImapEmptyBox(s.mbox) -} - -func fetchGersemiMessages(s gersemiStruct) gersemiStruct { // fixme same as in mimir - from := uint32(1) - to := s.mbox.Messages - seqset := new(imap.SeqSet) - seqset.AddRange(from, to) - - s.section = &imap.BodySectionName{} - items := []imap.FetchItem{imap.FetchEnvelope, s.section.FetchItem(), imap.FetchUid} - s.messages = make(chan *imap.Message, 10) - s.done = make(chan error, 1) - go func() { - s.done <- s.c.Fetch(seqset, items, s.messages) - }() - return s -} - -func ignoreGersemiEmptyBox(s gersemiStruct, e error) (gersemiStruct, error) { - var emptyBoxError EmptyBoxError - if errors.As(e, &emptyBoxError) { - log.Println("Mailbox is empty") - return s, nil - } - return s, e + return m, nil } -func createTransactions(s gersemiStruct) (gersemiStruct, error) { - for msg := range s.messages { - s.message = msg - r := gott.R[gersemiStruct]{S: s}. - Bind(readGersemiMsgBody). - Bind(parseGersemiMimeMessage). - Bind(getGersemiBody). - Bind(readGersemiBody). +func createTransactions(am AbstractMailbox) (AbstractMailbox, error) { + m := am.(*GersemiMailbox) + for msg := range m.messages() { + r := gott.R[AbstractImapMessage]{ + S: &GersemiImapMessage{ + ImapMessage: ImapMessage{ + msg: msg, + sect: m.section(), + mimetype: m.config().Gersemi.MessageMime, + }, + config: m.config(), + withdrawalRegexes: m.withdrawalRegexes, + depositRegexes: m.depositRegexes, + hc: m.hc, + cli: m.cli, + }, + }. + Bind(readMessageBody). + Bind(parseMimeMessage). + Bind(getBody). + Bind(readBody). Bind(createTransaction). Bind(marshalBody). Bind(createRequest). @@ -189,156 +160,146 @@ Recover(ignoreGersemiInvalidMessage). Recover(recoverGersemiMalformedMessage). Recover(recoverGersemiErroredMessages) if r.E != nil { - return s, r.E - } - } - return s, nil -} - -func readGersemiMsgBody(s gersemiStruct) (gersemiStruct, error) { - r := s.message.GetBody(s.section) - if r == nil { - return s, MalformedMessageError{ - Cause: errors.New("no body in message"), - MessageID: s.message.Envelope.MessageId, + return m, r.E } } - messageBytes, err := io.ReadAll(r) - s.messageBytes = messageBytes - return s, err -} - -func parseGersemiMimeMessage(s gersemiStruct) (gersemiStruct, error) { - r := bytes.NewReader(s.messageBytes) - m, err := message.Read(r) - s.mimeMessage = m - return s, err -} - -func getGersemiBody(s gersemiStruct) (gersemiStruct, error) { - body, err := getNextPart(s.mimeMessage, s.message.Envelope.MessageId, s.config.Gersemi.MessageMime) - s.plainTextBodyReader = body - return s, err + return m, nil } -func readGersemiBody(s gersemiStruct) (gersemiStruct, error) { - body, err := io.ReadAll(s.plainTextBodyReader) - s.plainTextBody = body - return s, err -} - -func matchRegex(s gersemiStruct, match []string, groupNames []string) gersemiStruct { +func matchRegex(m *GersemiImapMessage, match []string, groupNames []string) *GersemiImapMessage { for groupIdx, group := range match { names := groupNames[groupIdx] for _, name := range strings.Split(names, "_") { switch name { case "TITLE": - s.title = regexp.MustCompile(" +").ReplaceAllString(group, " ") + m.title = regexp.MustCompile(" +").ReplaceAllString(group, " ") case "SRC": - s.src = group + m.src = group case "DST": - s.dst = regexp.MustCompile(" +").ReplaceAllString(group, " ") + m.dst = regexp.MustCompile(" +").ReplaceAllString(group, " ") case "AMOUNTC": - s.amount = group - s.amount = strings.Replace(s.amount, ",", ".", -1) + m.amount = group + m.amount = strings.Replace(m.amount, ",", ".", -1) case "AMOUNT": - s.amount = group + m.amount = group case "DAY": - s.day = group + m.day = group case "MONTH": - s.month = group + m.month = group case "YEAR": - s.year = group + m.year = group } } } - return s + return m } -func createTransaction(s gersemiStruct) (gersemiStruct, error) { - s.src = s.config.Gersemi.DefaultSource - for _, wr := range s.withdrawalRegexes { - groupNames := wr.SubexpNames() - matches := wr.FindAllStringSubmatch(string(s.plainTextBody), -1) +func createWithdrawal(m *GersemiImapMessage) *GersemiImapMessage { + for _, regex := range m.withdrawalRegexes { + groupNames := regex.SubexpNames() + matches := regex.FindAllStringSubmatch(string(m.messageBody()), -1) if matches == nil { continue } match := matches[0] - s = matchRegex(s, match, groupNames) + m = matchRegex(m, match, groupNames) transaction := Withdrawal{ TransactionData: TransactionData{ Type: "withdrawal", - Date: s.year + "-" + s.month + "-" + s.day + "T06:00:00+00:00", - Amount: s.amount, - Description: s.title, + Date: m.year + "-" + m.month + "-" + m.day + "T06:00:00+00:00", + Amount: m.amount, + Description: m.title, }, - SourceID: s.config.Gersemi.Accounts[s.src], - DestinationName: s.dst, + SourceID: m.config.Gersemi.Accounts[m.src], + DestinationName: m.dst, } body := GersemiRequestBody{ Transactions: []Transaction{transaction}, } - s.requestBody = body - return s, nil + m.requestBody = body + return m } - for _, dr := range s.depositRegexes { - groupNames := dr.SubexpNames() - matches := dr.FindAllStringSubmatch(string(s.plainTextBody), -1) + return nil +} + +func createDeposit(m *GersemiImapMessage) *GersemiImapMessage { + for _, regex := range m.depositRegexes { + groupNames := regex.SubexpNames() + matches := regex.FindAllStringSubmatch(string(m.messageBody()), -1) if matches == nil { continue } match := matches[0] - s = matchRegex(s, match, groupNames) + m = matchRegex(m, match, groupNames) transaction := Deposit{ TransactionData: TransactionData{ Type: "deposit", - Date: s.year + "-" + s.month + "-" + s.day + "T06:00:00+00:00", - Amount: s.amount, - Description: s.title, + Date: m.year + "-" + m.month + "-" + m.day + "T06:00:00+00:00", + Amount: m.amount, + Description: m.title, }, - SourceName: s.src, - DestinationID: s.config.Gersemi.Accounts[s.dst], + SourceName: m.src, + DestinationID: m.config.Gersemi.Accounts[m.dst], } body := GersemiRequestBody{ Transactions: []Transaction{transaction}, } - s.requestBody = body - return s, nil + m.requestBody = body + return m } + return nil +} - return s, InvalidMessageError{} +func createTransaction(am AbstractImapMessage) (AbstractImapMessage, error) { + m := am.(*GersemiImapMessage) + m.src = m.config.Gersemi.DefaultSource + result := createWithdrawal(m) + if result != nil { + return result, nil + } + result = createDeposit(m) + if result != nil { + return result, nil + } + + return m, InvalidMessageError{} } -func marshalBody(s gersemiStruct) (_ gersemiStruct, err error) { - s.requestBodyBytes, err = json.Marshal(s.requestBody) - return s, err +func marshalBody(am AbstractImapMessage) (_ AbstractImapMessage, err error) { + m := am.(*GersemiImapMessage) + m.requestBodyBytes, err = json.Marshal(m.requestBody) + return m, err } -func createRequest(s gersemiStruct) (_ gersemiStruct, err error) { - s.request, err = http.NewRequest("POST", s.config.Gersemi.Firefly+"/api/v1/transactions", bytes.NewReader(s.requestBodyBytes)) - s.request.Header.Add("Authorization", "Bearer "+s.config.Gersemi.FireflyToken) - s.request.Header.Add("Accept", "application/vnd.api+json") - s.request.Header.Add("Content-Type", "application/json") - return s, err +func createRequest(am AbstractImapMessage) (_ AbstractImapMessage, err error) { + m := am.(*GersemiImapMessage) + m.request, err = http.NewRequest("POST", m.config.Gersemi.Firefly+"/api/v1/transactions", bytes.NewReader(m.requestBodyBytes)) + m.request.Header.Add("Authorization", "Bearer "+m.config.Gersemi.FireflyToken) + m.request.Header.Add("Accept", "application/vnd.api+json") + m.request.Header.Add("Content-Type", "application/json") + return m, err } -func doRequest(s gersemiStruct) (_ gersemiStruct, err error) { - s.response, err = s.hc.Do(s.request) - return s, err +func doRequest(am AbstractImapMessage) (_ AbstractImapMessage, err error) { + m := am.(*GersemiImapMessage) + m.response, err = m.hc.Do(m.request) + return m, err } -func handleHttpError(s gersemiStruct) (gersemiStruct, error) { - if s.response.StatusCode != 200 { - return s, fmt.Errorf(s.response.Status) +func handleHttpError(am AbstractImapMessage) (AbstractImapMessage, error) { + m := am.(*GersemiImapMessage) + if m.response.StatusCode != 200 { + return m, fmt.Errorf(m.response.Status) } - return s, nil + return m, nil } -func moveGersemiMessage(s gersemiStruct) { - moveMsg(s.c, s.message, s.config.Gersemi.DoneFolder) +func moveGersemiMessage(am AbstractImapMessage) { // todo collect messages out of loop and move all + m := am.(*GersemiImapMessage) + moveMsg(m.cli, m.msg, m.config.Gersemi.DoneFolder) } -func ignoreGersemiInvalidMessage(s gersemiStruct, e error) (gersemiStruct, error) { +func ignoreGersemiInvalidMessage(s AbstractImapMessage, e error) (AbstractImapMessage, error) { var invalidMessageErr InvalidMessageError if errors.As(e, &invalidMessageErr) { log.Println(e.Error()) @@ -346,18 +307,3 @@ return s, nil } return s, e } - -func recoverGersemiMalformedMessage(s gersemiStruct, err error) (gersemiStruct, error) { - var malformedMessageError MalformedMessageError - if errors.As(err, &malformedMessageError) { - err = nil - log.Println(malformedMessageError.Error()) - log.Println(string(s.messageBytes)) - } - return s, err -} - -func recoverGersemiErroredMessages(s gersemiStruct, err error) (gersemiStruct, error) { - log.Printf("message %s errored: %v\n", s.message.Envelope.MessageId, err) - return s, nil -} diff --git a/hermodr.go b/hermodr.go index 2b070345719cf5c7da2e8cd44b6bd8199eab0ec1..e21e23942ab8a97d2f1c53d34b24686094e91ee2 100644 --- a/hermodr.go +++ b/hermodr.go @@ -32,20 +32,20 @@ PlainText string Armour string } -func connect(args ...interface{}) (interface{}, error) { +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 login(args ...interface{}) error { +func loginHermodr(args ...interface{}) error { result := args[0].(Result) err := result.Client.Login(result.Config.Hermodr.ImapUsername, result.Config.Hermodr.ImapPassword) return err } -func selectInbox(args ...interface{}) (interface{}, error) { +func selectHermodrInbox(args ...interface{}) (interface{}, error) { result := args[0].(Result) mbox, err := result.Client.Select(result.Config.Hermodr.ImapFolderInbox, false) result.Mailbox = mbox @@ -59,13 +59,13 @@ for result.Mailbox.Messages > 0 { r, err := gott.NewResult(result). Bind(getMessage). Bind(readMessage). - Bind(readBody). + Bind(readHermodrBody). Map(composePlaintextBody). Bind(encrypt). Tee(send). Tee(markRead). Tee(moveMessage). - Bind(selectInbox). + Bind(selectHermodrInbox). Finish() if err != nil { return r, err @@ -107,7 +107,7 @@ result.Message = m return result, err } -func readBody(args ...interface{}) (interface{}, error) { +func readHermodrBody(args ...interface{}) (interface{}, error) { result := args[0].(Result) body, err := io.ReadAll(result.Message.Body) result.Body = string(body) @@ -193,9 +193,9 @@ func hermodr(config Config) { r, err := gott.NewResult(Result{Config: config}). SetLevelLog(gott.Debug). - Bind(connect). - Tee(login). - Bind(selectInbox). + Bind(connectHermodr). + Tee(loginHermodr). + Bind(selectHermodrInbox). Bind(redirectMessages). Finish() diff --git a/imap.go b/imap.go index 728babdccd0f8f449b5f86ae3fdf38edbddd3008..9e5bf0c3fff00194f30018b290b2fd086ad6a81c 100644 --- a/imap.go +++ b/imap.go @@ -14,12 +14,6 @@ "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" ) -type EmptyBoxError struct{} - -func (EmptyBoxError) Error() string { - return "" -} - func moveMsg(c *client.Client, msg *imap.Message, dest string) { 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) @@ -149,10 +143,3 @@ item := imap.FormatFlagsOp(imap.AddFlags, true) flags := []interface{}{imap.DeletedFlag} return c.UidStore(seqset, item, flags, nil) } - -func checkImapEmptyBox(mbox *imap.MailboxStatus) error { - if mbox.Messages == 0 { - return EmptyBoxError{} - } - return nil -} diff --git a/imap_errors.go b/imap_errors.go new file mode 100644 index 0000000000000000000000000000000000000000..628900e0d3e9e16033beea0152d3104775d0a2ad --- /dev/null +++ b/imap_errors.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" +) + +type EmptyBoxError struct{} + +func (EmptyBoxError) Error() string { + return "" +} + +type MalformedMessageError struct { + MessageID string + Cause error +} + +func (e MalformedMessageError) Error() string { + return fmt.Sprintf("Malformed message %s: %s", e.MessageID, e.Cause.Error()) +} diff --git a/imap_mailbox.go b/imap_mailbox.go new file mode 100644 index 0000000000000000000000000000000000000000..50d3a471530675998722344c441f294ad434c7ca --- /dev/null +++ b/imap_mailbox.go @@ -0,0 +1,146 @@ +package main + +import ( + "errors" + "log" + + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/client" +) + +type AbstractMailbox interface { + imapAddress() string + imapUsername() string + imapPassword() string + setClient(*client.Client) + client() *client.Client + config() Config + setMailbox(*imap.MailboxStatus) + mailbox() *imap.MailboxStatus + setSection(*imap.BodySectionName) + section() *imap.BodySectionName + messages() chan *imap.Message + done() chan error + + setupChannels() +} + +type Mailbox struct { + imapAdr string + imapUser string + imapPass string + conf Config + + cli *client.Client + mbox *imap.MailboxStatus + sect *imap.BodySectionName + msgs chan *imap.Message + dn chan error +} + +func (m Mailbox) imapAddress() string { + return m.imapAdr +} + +func (m Mailbox) imapUsername() string { + return m.imapUser +} + +func (m Mailbox) imapPassword() string { + return m.imapPass +} + +func (m *Mailbox) setClient(c *client.Client) { + m.cli = c +} + +func (m Mailbox) client() *client.Client { + return m.cli +} + +func (m Mailbox) config() Config { + return m.conf +} + +func (m *Mailbox) setMailbox(mbox *imap.MailboxStatus) { + m.mbox = mbox +} + +func (m Mailbox) mailbox() *imap.MailboxStatus { + return m.mbox +} + +func (m *Mailbox) setSection(sect *imap.BodySectionName) { + m.sect = sect +} + +func (m Mailbox) section() *imap.BodySectionName { + return m.sect +} + +func (m Mailbox) messages() chan *imap.Message { + return m.msgs +} + +func (m Mailbox) done() chan error { + return m.dn +} + +func (m *Mailbox) setupChannels() { + m.msgs = make(chan *imap.Message, 10) + m.dn = make(chan error, 1) +} + +func connect(m AbstractMailbox) (AbstractMailbox, error) { + c, err := client.DialTLS(m.imapAddress(), nil) + m.setClient(c) + return m, err +} + +func login(m AbstractMailbox) error { + return m.client().Login(m.imapUsername(), m.imapPassword()) +} + +func selectInbox(m AbstractMailbox) (AbstractMailbox, error) { + mbox, err := m.client().Select("INBOX", false) // todo configure inbox name + m.setMailbox(mbox) + return m, err +} + +func checkEmptyBox(m AbstractMailbox) error { + if m.mailbox().Messages == 0 { + return EmptyBoxError{} + } + return nil +} + +func fetchMessages(m AbstractMailbox) AbstractMailbox { + from := uint32(1) + to := m.mailbox().Messages + seqset := new(imap.SeqSet) + seqset.AddRange(from, to) + + m.setSection(&imap.BodySectionName{}) + items := []imap.FetchItem{imap.FetchEnvelope, m.section().FetchItem(), imap.FetchUid} + go func() { + m.done() <- m.client().Fetch(seqset, items, m.messages()) + }() + return m +} + +func ignoreEmptyBox(m AbstractMailbox, e error) (AbstractMailbox, error) { + var emptyBoxError EmptyBoxError + if errors.As(e, &emptyBoxError) { + log.Println("Mailbox is empty") + return m, nil + } + return m, e +} + +func disconnect(m AbstractMailbox, e error) (AbstractMailbox, error) { + if m.client() != nil { + m.client().Logout() + m.client().Close() + } + return m, e +} diff --git a/imap_message.go b/imap_message.go new file mode 100644 index 0000000000000000000000000000000000000000..3a9c5a4eed2cd864f071ff1cf5679cfa441bdf95 --- /dev/null +++ b/imap_message.go @@ -0,0 +1,126 @@ +package main + +import ( + "bytes" + "errors" + "io" + "log" + + "github.com/emersion/go-imap" + "github.com/emersion/go-message" +) + +type AbstractImapMessage interface { + section() *imap.BodySectionName + message() *imap.Message + mimeType() string + setMessageBytes([]byte) + messageBytes() []byte + setMimeMessage(*message.Entity) + mimeMessage() *message.Entity + setMessageReader(io.Reader) + messageReader() io.Reader + setMessageBody([]byte) +} + +type ImapMessage struct { + sect *imap.BodySectionName + msg *imap.Message + mimetype string + + msgBytes []byte + mimeMsg *message.Entity + bdyRdr io.Reader + bdy []byte +} + +func (m ImapMessage) section() *imap.BodySectionName { + return m.sect +} + +func (m ImapMessage) message() *imap.Message { + return m.msg +} + +func (m ImapMessage) mimeType() string { + return m.mimetype +} + +func (m *ImapMessage) setMessageBytes(b []byte) { + m.msgBytes = b +} + +func (m ImapMessage) messageBytes() []byte { + return m.msgBytes +} + +func (m *ImapMessage) setMimeMessage(msg *message.Entity) { + m.mimeMsg = msg +} + +func (m ImapMessage) mimeMessage() *message.Entity { + return m.mimeMsg +} + +func (m *ImapMessage) setMessageReader(r io.Reader) { + m.bdyRdr = r +} + +func (m ImapMessage) messageReader() io.Reader { + return m.bdyRdr +} + +func (m *ImapMessage) setMessageBody(b []byte) { + m.bdy = b +} + +func (m ImapMessage) messageBody() []byte { + return m.bdy +} + +func readMessageBody(m AbstractImapMessage) (AbstractImapMessage, error) { + r := m.message().GetBody(m.section()) + if r == nil { + return m, MalformedMessageError{ + Cause: errors.New("no body in message"), + MessageID: m.message().Envelope.MessageId, + } + } + messageBytes, err := io.ReadAll(r) + m.setMessageBytes(messageBytes) + return m, err +} + +func parseMimeMessage(m AbstractImapMessage) (AbstractImapMessage, error) { + r := bytes.NewReader(m.messageBytes()) + message, err := message.Read(r) + m.setMimeMessage(message) + return m, err +} + +func getBody(m AbstractImapMessage) (AbstractImapMessage, error) { + reader, err := getNextPart(m.mimeMessage(), m.message().Envelope.MessageId, m.mimeType()) + m.setMessageReader(reader) + return m, err +} + +func readBody(m AbstractImapMessage) (AbstractImapMessage, error) { + body, err := io.ReadAll(m.messageReader()) + m.setMessageBody(body) + return m, err +} + +func recoverGersemiMalformedMessage(m AbstractImapMessage, err error) (AbstractImapMessage, error) { + var malformedMessageError MalformedMessageError + if errors.As(err, &malformedMessageError) { + err = nil + log.Println(malformedMessageError.Error()) + log.Println(string(m.messageBytes())) + } + return m, err +} + +func recoverGersemiErroredMessages(m AbstractImapMessage, err error) (AbstractImapMessage, error) { + log.Printf("message %s errored: %v\n", m.message().Envelope.MessageId, err) + return m, nil +} diff --git a/mimir.go b/mimir.go index 652b8e972578fa55b7edac0a8c3696e2ae89b65c..8c0bd0cd9fc6f14d1ab548dc9abbffe7878de933 100644 --- a/mimir.go +++ b/mimir.go @@ -49,15 +49,6 @@ _ "github.com/emersion/go-message/charset" "github.com/emersion/go-msgauth/dkim" ) -type MalformedMessageError struct { - MessageID string - Cause error -} - -func (e MalformedMessageError) Error() string { - return fmt.Sprintf("Malformed message %s: %s", e.MessageID, e.Cause.Error()) -} - type UnknownCategoryError struct { MessageID string Category string @@ -121,32 +112,12 @@ s.categoryRegexp = r return s, err } -func checkEmptyBox(s mimirStruct) error { - return checkImapEmptyBox(s.mbox) -} - func selectMimirInbox(s mimirStruct) (mimirStruct, error) { mbox, err := s.c.Select(s.mboxName, true) s.mbox = mbox return s, err } -func fetchMessages(s mimirStruct) mimirStruct { - from := uint32(1) - to := s.mbox.Messages - seqset := new(imap.SeqSet) - seqset.AddRange(from, to) - - s.section = &imap.BodySectionName{} - items := []imap.FetchItem{imap.FetchEnvelope, s.section.FetchItem(), imap.FetchUid} - s.messages = make(chan *imap.Message, 10) - s.done = make(chan error, 1) - go func() { - s.done <- s.c.Fetch(seqset, items, s.messages) - }() - return s -} - func getMessageCategory(s mimirStruct) (mimirStruct, error) { categoryInAddress := "" recipients := append(s.message.Envelope.To, s.message.Envelope.Cc...) @@ -196,13 +167,6 @@ } } s.dkimStatus = dkimStatus return s, nil -} - -func parseMimeMessage(s mimirStruct) (mimirStruct, error) { - r := bytes.NewReader(s.messageBytes) - m, err := message.Read(r) - s.mimeMessage = m - return s, err } func getNextPart(message *message.Entity, ID string, mimetype string) (io.Reader, error) { @@ -343,15 +307,6 @@ } func expunge(s mimirStruct) error { return s.c.Expunge(nil) -} - -func ignoreEmptyBox(s mimirStruct, e error) (mimirStruct, error) { - var emptyBoxError EmptyBoxError - if errors.As(e, &emptyBoxError) { - log.Println("Mailbox is empty") - return s, nil - } - return s, e } func archiveInbox(db *sql.DB, mboxName string, c *client.Client, config Config) error {