func getThreads(c *imap.Client) ([]Thread, error) { set, err := imap.NewSeqSet("1:*") cmd, err := imap.Wait(c.Fetch(set, "X-GM-THRID", "UID")) if err != nil { fmt.Println(err) return nil, ErrBadConnection } var result []Thread seen := make(map[string]int) for _, rsp := range cmd.Data { thrid := imap.AsString(rsp.MessageInfo().Attrs["X-GM-THRID"]) uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"]) if i, ok := seen[thrid]; ok { result[i] = append(result[i], uid) } else { result = append(result, Thread{uid}) seen[thrid] = len(result) - 1 } } return result, nil }
// getEmails will fetch the full bodies of all emails listed in the given command. func getEmails(client *imap.Client, cmd *imap.Command) ([]map[string]interface{}, error) { var emails []map[string]interface{} seq := new(imap.SeqSet) for _, rsp := range cmd.Data { for _, uid := range rsp.SearchResults() { seq.AddNum(uid) } } if seq.Empty() { return emails, nil } fCmd, err := imap.Wait(client.UIDFetch(seq, "INTERNALDATE", "BODY[]", "UID", "RFC822.HEADER")) if err != nil { return emails, err } var email map[string]interface{} for _, msgData := range fCmd.Data { msgFields := msgData.MessageInfo().Attrs email, err = newEmailMessage(msgFields) if err != nil { return emails, err } emails = append(emails, email) // mark message as read fSeq := new(imap.SeqSet) fSeq.AddNum(imap.AsNumber(msgFields["UID"])) _, err = imap.Wait(client.UIDStore(fSeq, "+FLAGS", "\\SEEN")) if err != nil { return emails, err } } return emails, nil }
func getEmails(client *imap.Client, cmd *imap.Command, markAsRead, delete bool, responses chan Response) { seq := &imap.SeqSet{} msgCount := 0 for _, rsp := range cmd.Data { for _, uid := range rsp.SearchResults() { msgCount++ seq.AddNum(uid) } } // nothing to request?! why you even callin me, foolio? if seq.Empty() { return } fCmd, err := imap.Wait(client.UIDFetch(seq, "INTERNALDATE", "BODY[]", "UID", "RFC822.HEADER")) if err != nil { responses <- Response{Err: fmt.Errorf("unable to perform uid fetch: %s", err)} return } var email Email for _, msgData := range fCmd.Data { msgFields := msgData.MessageInfo().Attrs // make sure is a legit response before we attempt to parse it // deal with unsolicited FETCH responses containing only flags // I'm lookin' at YOU, Gmail! // http://mailman13.u.washington.edu/pipermail/imap-protocol/2014-October/002355.html // http://stackoverflow.com/questions/26262472/gmail-imap-is-sometimes-returning-bad-results-for-fetch if _, ok := msgFields["RFC822.HEADER"]; !ok { continue } email, err = newEmail(msgFields) if err != nil { responses <- Response{Err: fmt.Errorf("unable to parse email: %s", err)} return } responses <- Response{Email: email} if !markAsRead { err = removeSeen(client, imap.AsNumber(msgFields["UID"])) if err != nil { responses <- Response{Err: fmt.Errorf("unable to remove seen flag: %s", err)} return } } if delete { err = deleteEmail(client, imap.AsNumber(msgFields["UID"])) if err != nil { responses <- Response{Err: fmt.Errorf("unable to delete email: %s", err)} return } } } return }
func fetch(c *imap.Client, user string, thread Thread) ([]*ParsedMail, error) { var set imap.SeqSet for _, uid := range thread { set.AddNum(uid) } cmd, err := imap.Wait(c.UIDFetch(&set, "BODY[]", "X-GM-MSGID", "X-GM-THRID")) if err != nil { return nil, ErrBadConnection } parsed := make([]*ParsedMail, len(cmd.Data)) for i, rsp := range cmd.Data { p, err := parseMail(imap.AsBytes(rsp.MessageInfo().Attrs["BODY[]"]), user) if err != nil { return nil, err } link, err := gmailLink(imap.AsString(rsp.MessageInfo().Attrs["X-GM-MSGID"])) if err != nil { return nil, err } p.GmailLink = link p.Thrid = imap.AsString(rsp.MessageInfo().Attrs["X-GM-THRID"]) parsed[i] = p } return parsed, nil }
// newIMAPClient will initiate a new IMAP connection with the given creds. func newIMAPClient(info MailboxInfo) (*imap.Client, error) { var client *imap.Client var err error if info.TLS { client, err = imap.DialTLS(info.Host, new(tls.Config)) if err != nil { return client, err } } else { client, err = imap.Dial(info.Host) if err != nil { return client, err } } _, err = client.Login(info.User, info.Pwd) if err != nil { return client, err } _, err = imap.Wait(client.Select(info.Folder, info.ReadOnly)) if err != nil { return client, err } return client, nil }
// findUnreadEmails will run a find the UIDs of any unread emails in the // mailbox. func findUnreadEmails(conn *imap.Client) (*imap.Command, error) { // get headers and UID for UnSeen message in src inbox... cmd, err := imap.Wait(conn.UIDSearch("UNSEEN")) if err != nil { return &imap.Command{}, err } return cmd, nil }
func alterEmail(client *imap.Client, UID uint32, flag string, plus bool) error { flg := "-FLAGS" if plus { flg = "+FLAGS" } fSeq := &imap.SeqSet{} fSeq.AddNum(UID) _, err := imap.Wait(client.UIDStore(fSeq, flg, flag)) if err != nil { return err } return nil }
func archive(c *imap.Client, thrid string) error { cmd, err := imap.Wait(c.UIDSearch("X-GM-THRID", thrid)) if err != nil { log.Println(err) return ErrBadConnection } if len(cmd.Data) == 0 { return nil } var set imap.SeqSet set.AddNum(cmd.Data[0].SearchResults()...) _, err = imap.Wait(c.UIDStore(&set, "+FLAGS.SILENT", `(\Deleted)`)) return err }
// findEmails will run a find the UIDs of any emails that match the search.: func findEmails(client *imap.Client, search string, since *time.Time) (*imap.Command, error) { var specs []imap.Field if len(search) > 0 { specs = append(specs, search) } if since != nil { sinceStr := since.Format(dateFormat) specs = append(specs, "SINCE", sinceStr) } // get headers and UID for UnSeen message in src inbox... cmd, err := imap.Wait(client.UIDSearch(specs...)) if err != nil { return &imap.Command{}, fmt.Errorf("uid search failed: %s", err) } return cmd, nil }
func notifyNewEmail(c *imap.Client, mId uint32) { set, _ := imap.NewSeqSet("") set.AddNum(mId) cmd := ReportOK(c.Fetch(set, "FLAGS", "RFC822.HEADER")) for _, rsp := range cmd.Data { header := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822.HEADER"]) if msg, _ := mail.ReadMessage(bytes.NewReader(header)); msg != nil { subj := msg.Header.Get("Subject") from := msg.Header.Get("From") formattedFrom := strings.Split(from, " ")[0] exec.Command( "terminal-notifier", "-message", subj, "-title", "Mail From: "+formattedFrom+"", "-sender", "com.apple.Mail", ).Output() } } }
// fetchMessage send UID FETCH command to retrieve message body func fetchMessage(client *imap.Client, messageUID uint32) ([]byte, error) { seq := new(imap.SeqSet) seq.AddNum(messageUID) cmd, err := imap.Wait(client.UIDFetch(seq, "INTERNALDATE", "BODY[]", "UID", "RFC822.HEADER")) if err != nil { logger.Errorf("Unable to fetch message (%d): %s", messageUID, err.Error()) return nil, nil } if len(cmd.Data) == 0 { logger.WithField("uid", messageUID).Info("Unable to fetch message from src: NO DATA") return nil, errors.New("message not found") } msgFields := cmd.Data[0].MessageInfo().Attrs body := imap.AsBytes(msgFields["BODY[]"]) cmd.Data = nil return body, nil }
func Sensitive(c *imap.Client, action string) imap.LogMask { mask := c.SetLogMask(imap.LogConn) hide := imap.LogCmd | imap.LogRaw if mask&hide != 0 { c.Logln(imap.LogConn, "Raw logging disabled during", action) } c.SetLogMask(mask &^ hide) return mask }
// loadMessages will search and fetch message body func loadMessages(client *imap.Client, config *Config) error { cmd, err := imap.Wait(client.UIDSearch("FROM", client.Quote(config.From), "UNSEEN")) if err != nil { return err } seq := new(imap.SeqSet) if len(cmd.Data) > 0 { seq.AddNum(cmd.Data[0].SearchResults()...) } if seq.Empty() { return nil } logger.Infof("Fetched UIDs %v", seq) cmd, err = imap.Wait(client.UIDFetch(seq, "FLAGS", "INTERNALDATE", "RFC822.SIZE", "BODY[]")) if err != nil { return err } for _, rsp := range cmd.Data { body, err := fetchMessage(client, rsp.MessageInfo().UID) if err != nil { logger.Fatal(err) return err } err = writeFileFromBody(bytes.NewReader(body), config) if err != nil { logger.Fatal(err) return err } } if !seq.Empty() { _, err = imap.Wait(client.UIDStore(seq, "+FLAGS.SILENT", imap.NewFlagSet(`\Seen`))) } cmd.Data = nil return err }
func kickOffWatcher(c *imap.Client) { cmd, _ := c.Idle() for cmd.InProgress() { c.Data = nil c.Recv(-1) for _, rsp := range c.Data { if rsp.Label == "EXISTS" { newEmailCount := rsp.Fields[0].(uint32) if newEmailCount > curMsgCount { c.IdleTerm() notifyNewEmail(c, newEmailCount) kickOffWatcher(c) } curMsgCount = newEmailCount } } } }
// updateTray is called whenever a client detects that the number of unseen // messages *may* have changed. It will search the selected folder for unseen // messages, count them and store the result. Then it will use the notify // channel to let our main process update the status icon. func UpdateTray(c *imap.Client, notify chan bool, name string) { // Send > Search since Search adds CHARSET UTF-8 which might not be supported cmd, err := c.Send("SEARCH", "UNSEEN") if err != nil { fmt.Printf("%s failed to look for new messages\n", name) fmt.Println(" ", err) return } if _, ok := Unseen[name]; !ok { Unseen[name] = []uint32{} } unseenMessages := []uint32{} for cmd.InProgress() { // Wait for the next response (no timeout) err = c.Recv(-1) // Process command data for _, rsp := range cmd.Data { result := rsp.SearchResults() unseenMessages = append(unseenMessages, result...) } // Reset for next run cmd.Data = nil c.Data = nil } // Check command completion status if rsp, err := cmd.Result(imap.OK); err != nil { if err == imap.ErrAborted { fmt.Println("fetch command aborted") } else { fmt.Println("fetch error:", rsp.Info) } return } fmt.Printf("%d unseen\n", len(unseenMessages)) // Find messages that the user hasn't been notified of // TODO: Make this optional/configurable newUnseen, _ := imap.NewSeqSet("") numNewUnseen := 0 for _, uid := range unseenMessages { seen := false for _, olduid := range Unseen[name] { if olduid == uid { seen = true break } } if !seen { newUnseen.AddNum(uid) numNewUnseen++ } } // If we do have new unseen messages, fetch and display them if numNewUnseen > 0 { messages := make([]string, numNewUnseen) i := 0 // Fetch headers... cmd, _ = c.Fetch(newUnseen, "RFC822.HEADER") for cmd.InProgress() { c.Recv(-1) for _, rsp := range cmd.Data { header := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822.HEADER"]) if msg, _ := mail.ReadMessage(bytes.NewReader(header)); msg != nil { subject := msg.Header.Get("Subject") messages[i], err = new(mime.WordDecoder).DecodeHeader(subject) if err != nil { messages[i] = subject } i++ } } cmd.Data = nil c.Data = nil } // Print them in reverse order to get newest first notification := "" for ; i > 0; i-- { notification += "> " + messages[i-1] + "\n" } notification = strings.TrimRight(notification, "\n") fmt.Println(notification) // And send them with notify-send! title := fmt.Sprintf("%s has new mail (%d unseen)", name, len(unseenMessages)) sh := exec.Command("/usr/bin/notify-send", "-i", "notification-message-email", "-c", "email", title, notification) err := sh.Start() if err != nil { fmt.Println("Failed to notify user...") fmt.Println(err) } go func() { // collect exit code to avoid zombies sh.Wait() }() } Unseen[name] = unseenMessages // Let main process know something has changed notify <- true }
func Login(c *imap.Client, user, pass string) (cmd *imap.Command, err error) { defer c.SetLogMask(Sensitive(c, "LOGIN")) return c.Login(user, pass) }
// login send LOGIN imap command func login(client *imap.Client, user string, password string) (*imap.Command, error) { defer client.SetLogMask(sensitive(client, "LOGIN")) return client.Login(user, password) }
func main() { token := flag.String("token", "", "The Telegram bot token") to := flag.String("to", "", "The mail recipient") from := flag.String("from", "", "The mail sender") server := flag.String("server", "", "The mail server") port := flag.Int("port", 587, "The mail server port") user := flag.String("user", "", "") pass := flag.String("pass", "", "") subject := flag.String("subject", "", "") flag.Parse() bot, err := telebot.NewBot(*token) if err != nil { return } // Fetching Mails and send as Telegram messages var ( c *imap.Client cmd *imap.Command rsp *imap.Response ) c, _ = imap.Dial(*server) defer c.Logout(30 * time.Second) c.Data = nil if c.Caps["STARTTLS"] { c.StartTLS(nil) } if c.State() == imap.Login { c.Login(*user, *pass) } cmd, _ = imap.Wait(c.List("", "%")) c.Data = nil c.Select("INBOX", true) set, _ := imap.NewSeqSet("") set.Add("1:*") cmd, _ = c.Fetch(set, "RFC822.HEADER") for cmd.InProgress() { c.Recv(-1) for _, rsp = range cmd.Data { header := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822.HEADER"]) if msg, _ := mail.ReadMessage(bytes.NewReader(header)); msg != nil { fmt.Println("|--", msg.Header.Get("FROM")) } } cmd.Data = nil c.Data = nil } if rsp, err := cmd.Result(imap.OK); err != nil { if err == imap.ErrAborted { fmt.Println("Fetch command aborted") } else { fmt.Println("Fetch error:", rsp.Info) } } /* * Listen to Telegram messages and send as mail */ messages := make(chan telebot.Message) bot.Listen(messages, 1*time.Second) for message := range messages { m := gomail.NewMessage() m.SetHeader("From", *from) m.SetHeader("To", *to) m.SetHeader("Subject", *subject) m.SetBody("text/html", message.Text) d := gomail.NewPlainDialer(*server, *port, *user, *pass) d.TLSConfig = &tls.Config{InsecureSkipVerify: true} if err := d.DialAndSend(m); err != nil { panic(err) } } }
func (emaill *Email) getEmailContentUseIMAP(w rest.ResponseWriter, r *rest.Request) { addr := r.FormValue("imapAddr") port := r.FormValue("imapPort") user := r.FormValue("user") pwd := r.FormValue("pwd") id := r.FormValue("id") address := addr + ":" + port log.Printf("get email content use imap %s\n", address) var ( c *imap.Client cmd *imap.Command rsp *imap.Response ) // Connect to the server c, err := imap.Dial(addr) if err != nil { log.Printf("dial %s error\n", address) return } // Remember to log out and close the connection when finished defer c.Logout(30 * time.Second) // Print server greeting (first response in the unilateral server data queue) log.Printf("Server says hello:%s", c.Data[0].Info) c.Data = nil // Enable encryption, if supported by the server if c.Caps["STARTTLS"] { c.StartTLS(nil) } // Authenticate if c.State() == imap.Login { c.Login(user, pwd) } // List all top-level mailboxes, wait for the command to finish cmd, _ = imap.Wait(c.List("", "%")) // Print mailbox information log.Printf("\nTop-level mailboxes:") for _, rsp = range cmd.Data { log.Printf("|--%s", rsp.MailboxInfo()) } // Check for new unilateral server data responses for _, rsp = range c.Data { log.Printf("Server data:%s", rsp) } c.Data = nil // Open a mailbox (synchronous command - no need for imap.Wait) c.Select("INBOX", true) log.Printf("\nMailbox status:%s\n", c.Mailbox) if c.Mailbox == nil { resp := EmailJsonResponse{Success: true} w.WriteJson(&resp) return } // Fetch the headers of the 10 most recent messages set, _ := imap.NewSeqSet("") set.Add(id) // if c.Mailbox.Messages >= 10 { // set.AddRange(c.Mailbox.Messages-9, c.Mailbox.Messages) //测试只取最新一封邮件 // } else { // set.Add("1:*") // } cmd, _ = c.Fetch(set, "RFC822.HEADER", "RFC822.TEXT") //指定要获取的内容 // Process responses while the command is running log.Printf("\nget mail [%s] messages:", id) for cmd.InProgress() { // Wait for the next response (no timeout) c.Recv(-1) // Process command data for _, rsp = range cmd.Data { header := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822.HEADER"]) if msg, _ := mail.ReadMessage(bytes.NewReader(header)); msg != nil { subject := msg.Header.Get("Subject") log.Printf("|--%s", subject) realSubject := GetRealSubject(subject) log.Printf("in rest_email.go: get real subject") log.Printf(realSubject) senderAddr := msg.Header.Get("From") recverAddrList := msg.Header.Get("To") realSenderAddr := GetRealSubject(senderAddr) realRecverAddrList := GetRealSubject(recverAddrList) body := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822.TEXT"]) //log.Printf("email body: %s", body) //realBody := GetRealBody(string(body)) headerAndBody := make([]byte, len(header)+len(body)) copy(headerAndBody, header) copy(headerAndBody[len(header):], body) msg, _ := mail.ReadMessage(bytes.NewReader(headerAndBody)) mime, _ := enmime.ParseMIMEBody(msg) realBody := mime.Text //如果原始内容为html,会去掉html元素标签 log.Printf("real body: %s", realBody) //获取MIMEPart所有节点内容 // log.Printf("root ======================") // root := mime.Root // if root != nil { // log.Printf(string(root.Content())) // log.Printf("child==========") // if child := root.FirstChild(); child != nil { // log.Printf(string(child.Content())) // } // } attachments := mime.Attachments log.Printf("attachments len=%d", len(attachments)) count := len(attachments) var attachmentList []Attachment = nil var data EmailContent if count > 0 { attachmentList = make([]Attachment, count) for i := 0; i < len(attachments); i++ { name := attachments[i].FileName() content := attachments[i].Content() //todo encode by base64 log.Printf("name===%s", name) attachmentList[i] = Attachment{Name: name, Body: string(content)} } } data = EmailContent{Subject: realSubject, SenderAddr: realSenderAddr, RecverAddrList: realRecverAddrList, Content: realBody, Attachments: attachmentList} resp := EmailJsonResponse{Success: true, Data: data} w.WriteJson(resp) } } cmd.Data = nil // Process unilateral server data for _, rsp = range c.Data { log.Printf("Server data:%s", rsp) } c.Data = nil } // Check command completion status if rsp, err := cmd.Result(imap.OK); err != nil { if err == imap.ErrAborted { log.Printf("Fetch command aborted\n") } else { log.Printf("Fetch error:%s\n", rsp.Info) } } }
//use imap to delete a mail func (email *Email) DeleteEmail(w rest.ResponseWriter, r *rest.Request) { log.Printf("delete email...") addr := r.FormValue("imapAddr") //port := r.FormValue("smtpPort") user := r.FormValue("user") pwd := r.FormValue("pwd") id := r.FormValue("id") //check params, return error //create imap client var ( c *imap.Client cmd *imap.Command rsp *imap.Response ) // Connect to the server c, _ = imap.Dial(addr) // Remember to log out and close the connection when finished defer c.Logout(30 * time.Second) // Print server greeting (first response in the unilateral server data queue) log.Printf("Server says hello:%s", c.Data[0].Info) c.Data = nil // Enable encryption, if supported by the server if c.Caps["STARTTLS"] { c.StartTLS(nil) } // Authenticate if c.State() == imap.Login { c.Login(user, pwd) } // List all top-level mailboxes, wait for the command to finish cmd, _ = imap.Wait(c.List("", "%")) // Print mailbox information log.Printf("\nTop-level mailboxes:") for _, rsp = range cmd.Data { log.Printf("|--%s", rsp.MailboxInfo()) } // Check for new unilateral server data responses for _, rsp = range c.Data { log.Printf("Server data:%s", rsp) } c.Data = nil // Open a mailbox (synchronous command - no need for imap.Wait) c.Select("INBOX", true) log.Printf("\nMailbox status:%s\n", c.Mailbox) if c.Mailbox == nil { resp := EmailJsonResponse{Success: true} w.WriteJson(&resp) return } //use Expunge to delete a mail set, _ := imap.NewSeqSet("") mid, _ := strconv.Atoi(id) set.AddNum(uint32(mid)) //delete mail cmd, err := c.Expunge(set) if err != nil { log.Printf("%v ", cmd) } else { log.Printf("delete mail ok") } }
func ExampleClient() { // // Note: most of error handling code is omitted for brevity // var ( c *imap.Client cmd *imap.Command rsp *imap.Response ) // Connect to the server c, _ = imap.Dial("imap.example.com") // Remember to log out and close the connection when finished defer c.Logout(30 * time.Second) // Print server greeting (first response in the unilateral server data queue) fmt.Println("Server says hello:", c.Data[0].Info) c.Data = nil // Enable encryption, if supported by the server if c.Caps["STARTTLS"] { c.StartTLS(nil) } // Authenticate if c.State() == imap.Login { c.Login("*****@*****.**", "mysupersecretpassword") } // List all top-level mailboxes, wait for the command to finish cmd, _ = imap.Wait(c.List("", "%")) // Print mailbox information fmt.Println("\nTop-level mailboxes:") for _, rsp = range cmd.Data { fmt.Println("|--", rsp.MailboxInfo()) } // Check for new unilateral server data responses for _, rsp = range c.Data { fmt.Println("Server data:", rsp) } c.Data = nil // Open a mailbox (synchronous command - no need for imap.Wait) c.Select("INBOX", true) fmt.Print("\nMailbox status:\n", c.Mailbox) // Fetch the headers of the 10 most recent messages set, _ := imap.NewSeqSet("") if c.Mailbox.Messages >= 10 { set.AddRange(c.Mailbox.Messages-9, c.Mailbox.Messages) } else { set.Add("1:*") } cmd, _ = c.Fetch(set, "RFC822.HEADER") // Process responses while the command is running fmt.Println("\nMost recent messages:") for cmd.InProgress() { // Wait for the next response (no timeout) c.Recv(-1) // Process command data for _, rsp = range cmd.Data { header := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822.HEADER"]) if msg, _ := mail.ReadMessage(bytes.NewReader(header)); msg != nil { fmt.Println("|--", msg.Header.Get("Subject")) } } cmd.Data = nil // Process unilateral server data for _, rsp = range c.Data { fmt.Println("Server data:", rsp) } c.Data = nil } // Check command completion status if rsp, err := cmd.Result(imap.OK); err != nil { if err == imap.ErrAborted { fmt.Println("Fetch command aborted") } else { fmt.Println("Fetch error:", rsp.Info) } } }