package mail import ( "fmt" "log" "strings" "github.com/emersion/go-imap/v2" client "github.com/emersion/go-imap/v2/imapclient" "github.com/logrusorgru/aurora/v4" "github.com/spf13/viper" "git.amok.space/yevhen/resource-scraper/helper/sugar" ) type EmailService struct { User string pass string Err error client *client.Client Mailboxes []*imap.SelectData Messages []*client.FetchMessageBuffer } func (s *EmailService) CheckErr(msg string, err error) bool { if err != nil { if sugar.IsDev() { sugar.LogError(err.Error()) } s.Err = err return true } return false } func (s *EmailService) success(msg string) { if sugar.IsDev() { sugar.LogSuccess(msg) } } func (s *EmailService) Warn(msg string) { if sugar.IsDev() { sugar.LogWarning(msg) } } func (s *EmailService) Connect() { box := strings.Split(s.User, "@") mail := viper.GetStringMapString("mail." + box[1]) conn, err := client.DialTLS(mail["dial-tls"], nil) if s.CheckErr("DialTLS", err) { return } s.client = conn s.pass = mail[s.User] s.success("connected to " + mail["dial-tls"]) //defer s.Logout() } func (s *EmailService) Login() { s.Connect() err := s.client.Login(s.User, s.pass).Wait() if s.CheckErr("Login", err) { return } s.success(s.User + " logged") } func (s *EmailService) ListMailboxes(mailboxes []string) { selectOptions := &imap.SelectOptions{ReadOnly: true} for _, mailbox := range mailboxes { mbox, err := s.client.Select(mailbox, selectOptions).Wait() if s.CheckErr("Listing mailbox", err) { return } s.Mailboxes = append(s.Mailboxes, mbox) } if len(s.Mailboxes) == 0 { s.Warn(sugar.SqueezeLine("mailboxes " + strings.Join(mailboxes, ", ") + " not found")) } } func (s *EmailService) ListMessages(mailboxes []string, criteria *imap.SearchCriteria) { for _, mailbox := range mailboxes { mbox, err := s.client.Select(mailbox, nil).Wait() if s.CheckErr("Listing mailbox", err) { continue } seqs, err := s.client.UIDSearch(criteria, nil).Wait() if s.CheckErr("UIDSearch", err) { return } seqSet := imap.SeqSet{} seqSet.AddRange(1, mbox.NumMessages) if len(seqs.AllUIDs()) == 0 { s.Warn("no messages found in mailbox: " + mailbox) continue } s.success(fmt.Sprintf("Search complete, found %d messages: %+v", len(seqs.AllUIDs()), mbox.UIDNext)) fetchOptions := &imap.FetchOptions{ Envelope: true, Flags: true, BodyStructure: &imap.FetchItemBodyStructure{Extended: true}, BodySection: []*imap.FetchItemBodySection{{Peek: true, Part: []int{2}}}, } messages, err := s.client.Fetch(seqSet, fetchOptions).Collect() if s.CheckErr("Fetch", err) { continue } s.Messages = messages } } func (s *EmailService) LogOut() { err := s.client.Logout().Wait() if !s.CheckErr("failed to logout", err) { s.success("logged out successfully") } } func (s *EmailService) CreateMailbox(mailboxName string) { s.client.Create(mailboxName, nil) } func (s *EmailService) MailboxesList() { listCmd := s.client.List("", "%", &imap.ListOptions{ ReturnStatus: &imap.StatusOptions{ NumMessages: true, NumUnseen: true, }, }) for { mbox := listCmd.Next() if mbox == nil { break } fmt.Printf("%+v\n", mbox) } if err := listCmd.Close(); err != nil { log.Fatalf("LIST command failed: %v", err) } } func (s *EmailService) MoveMessageToMailbox(msg *client.FetchMessageBuffer, status string) bool { movable := imap.SeqSet{} movable.AddNum(msg.SeqNum) mailbox := viper.GetStringMapString("stb.move-processed-to-mailbox") wait, err := s.client.Move(movable, mailbox[status]).Wait() if s.CheckErr("Moving to archive", err) { return false } fmt.Printf("Message %s moved, srcUIDs: %s, dstUIDs: %s\n", aurora.White(msg.Envelope.MessageID), aurora.Yellow(wait.SourceUIDs), aurora.Yellow(wait.DestUIDs)) return true }