imap.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. package mail
  2. import (
  3. "errors"
  4. "eta/eta_email_analysis/global"
  5. "eta/eta_email_analysis/utils"
  6. "fmt"
  7. "github.com/emersion/go-imap"
  8. "github.com/emersion/go-imap/client"
  9. "github.com/emersion/go-message"
  10. "github.com/emersion/go-message/mail"
  11. "github.com/h2non/filetype"
  12. "io"
  13. "log"
  14. "os"
  15. "path"
  16. "strings"
  17. "time"
  18. )
  19. type MailMessage struct {
  20. Date time.Time `description:"收件时间"`
  21. Uid uint32 `description:"该邮件在邮箱中的唯一id"`
  22. FromEmail string `description:"发件人邮箱"`
  23. From string `description:"发件人名称"`
  24. Title string `description:"邮件标题"`
  25. Content string `description:"邮件主体正文"`
  26. Resources map[string]string `description:"正文内嵌资源"`
  27. Attachment map[string]string `description:"附件资源"`
  28. }
  29. func ListenMail(mailAddress, folder, userName, password string, readBatchSize, fromEmailIndex int, mailMessageChan chan MailMessage, mailMessageDoneChan chan bool) (err error) { // 收件箱
  30. defer func() {
  31. // 处理结束
  32. mailMessageDoneChan <- true
  33. if err != nil {
  34. fmt.Println("err:", err.Error())
  35. }
  36. }()
  37. // 建立与 IMAP 服务器的连接
  38. c, err := client.DialTLS(mailAddress, nil)
  39. if err != nil {
  40. fmt.Printf("连接 IMAP 服务器失败: %+v \n", err)
  41. return
  42. }
  43. // 最后一定不要忘记退出登录
  44. defer func() {
  45. _ = c.Logout()
  46. }()
  47. // 登录
  48. if err = c.Login(userName, password); err != nil {
  49. fmt.Printf("邮箱[%s] 登录失败: %v \n", fmt.Sprintf("%s:%s", userName, mailAddress), err)
  50. return
  51. }
  52. // 列出当前邮箱中的文件夹
  53. mailboxes := make(chan *imap.MailboxInfo, 10)
  54. done := make(chan error, 1) // 记录错误的 chan
  55. go func() {
  56. done <- c.List("", "*", mailboxes)
  57. }()
  58. log.Println("-->当前邮箱的文件夹 Mailboxes:")
  59. var folderExists bool
  60. for m := range mailboxes {
  61. log.Println("* ", m.Name)
  62. if m.Name == folder {
  63. folderExists = true
  64. }
  65. }
  66. err = <-done
  67. if err != nil {
  68. global.LOG.Errorf("列出邮箱列表时,出现错误:%v \n", err)
  69. return
  70. }
  71. log.Println("-->列出邮箱列表完毕!")
  72. if !folderExists {
  73. err = errors.New(fmt.Sprintf("文件夹[%s] 不存在 \n", folder))
  74. return
  75. }
  76. message.CharsetReader = myCharsetReader
  77. // 选择指定的文件夹
  78. mbox, err := c.Select(folder, false)
  79. if err != nil {
  80. err = errors.New(fmt.Sprintf("选择邮件箱失败: %+v", err))
  81. return
  82. }
  83. //log.Printf("mbox %+v \n", mbox)
  84. log.Printf("当前文件夹[%s]中,总共有 %d 封邮件 \n", folder, mbox.Messages)
  85. if mbox.Messages == 0 {
  86. //log.Fatalf("当前文件夹[%s]中没有邮件", folder)
  87. return
  88. }
  89. // 创建一个序列集,用于批量读取邮件
  90. seqSet := new(imap.SeqSet)
  91. to := mbox.Messages // 此文件下的邮件总数
  92. from := uint32(1)
  93. // 假设需要获取最后4封邮件时
  94. if fromEmailIndex > 0 {
  95. from = uint32(fromEmailIndex)
  96. } else {
  97. var maxNum uint32
  98. //该次监听获取的最大数量
  99. maxNum = 20000
  100. //获取开始的邮件编号
  101. if to > maxNum {
  102. from = to - maxNum + 1
  103. }
  104. }
  105. step := uint32(5)
  106. for i := from; i <= to; {
  107. end := i + step - 1
  108. if end > to {
  109. end = to
  110. }
  111. fmt.Printf("当前剩余%d封邮件待处理\n", to-i+1)
  112. seqSet.Clear()
  113. seqSet.AddRange(i, end) // 添加指定范围内的邮件编号
  114. // 获取整个消息正文
  115. // imap.FetchEnvelope:请求获取邮件的信封数据(例如发件人、收件人、主题等元数据)。
  116. // imap.FetchRFC822:请求获取完整的邮件内容,包括所有头部和正文。
  117. items := []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchRFC822}
  118. // 获取邮件内容 Start
  119. messages := make(chan *imap.Message, readBatchSize) // 创建一个通道,用于接收邮件消息
  120. fetchDone := make(chan error, 1) // 创建一个通道,用于接收错误消息
  121. go func() {
  122. // Fetch方法用于从服务器获取邮件数据,这里请求了邮件的信封和完整内容
  123. fetchDone <- c.Fetch(seqSet, items, messages)
  124. }()
  125. err = <-fetchDone
  126. if err != nil {
  127. global.LOG.Errorf("获取邮件信息出现错误:%v \n", err)
  128. return
  129. }
  130. // 获取邮件内容 End
  131. //log.Println("开始读取邮件内容")
  132. for msg := range messages {
  133. emailMessage, isRead, tmpErr := readEveryMsg(msg)
  134. if tmpErr != nil {
  135. // 移除本地文件
  136. {
  137. for _, v := range emailMessage.Attachment {
  138. os.Remove(v)
  139. }
  140. for _, v := range emailMessage.Resources {
  141. os.Remove(v)
  142. }
  143. }
  144. global.FILE_LOG.Fatalf("读取邮件内容时出现错误:%v \n", tmpErr)
  145. continue
  146. }
  147. // 如果取到了,那么写入待处理chan
  148. if isRead {
  149. // 写入邮件处理chan
  150. mailMessageChan <- emailMessage
  151. }
  152. }
  153. //time.Sleep(time.Second * 5) // 休眠10秒
  154. i = i + step
  155. }
  156. log.Println("读取了所有邮件,完毕!")
  157. return
  158. }
  159. //func ListenMail(mailAddress, folder, userName, password string, readBatchSize int, mailMessageChan chan MailMessage, mailMessageDoneChan chan bool) (err error) { // 收件箱
  160. // defer func() {
  161. // if err != nil {
  162. // fmt.Println("err:", err.Error())
  163. // }
  164. // }()
  165. // // 建立与 IMAP 服务器的连接
  166. // c, err := client.DialTLS(mailAddress, nil)
  167. // if err != nil {
  168. // fmt.Printf("连接 IMAP 服务器失败: %+v \n", err)
  169. // return
  170. // }
  171. // // 最后一定不要忘记退出登录
  172. // defer func() {
  173. // _ = c.Logout()
  174. // }()
  175. //
  176. // // 登录
  177. // if err = c.Login(userName, password); err != nil {
  178. // fmt.Printf("邮箱[%s] 登录失败: %v \n", fmt.Sprintf("%s:%s", userName, mailAddress), err)
  179. // return
  180. // }
  181. // // 列出当前邮箱中的文件夹
  182. // mailboxes := make(chan *imap.MailboxInfo, 10)
  183. // done := make(chan error, 1) // 记录错误的 chan
  184. // go func() {
  185. // done <- c.List("", "*", mailboxes)
  186. // }()
  187. // log.Println("-->当前邮箱的文件夹 Mailboxes:")
  188. //
  189. // var folderExists bool
  190. // for m := range mailboxes {
  191. // log.Println("* ", m.Name)
  192. // if m.Name == folder {
  193. // folderExists = true
  194. // }
  195. // }
  196. //
  197. // if err := <-done; err != nil {
  198. // log.Fatalf("列出邮箱列表时,出现错误:%v \n", err)
  199. // }
  200. // log.Println("-->列出邮箱列表完毕!")
  201. // if !folderExists {
  202. // err = errors.New(fmt.Sprintf("文件夹[%s] 不存在 \n", folder))
  203. // return
  204. // }
  205. //
  206. // // 选择指定的文件夹
  207. // mbox, err := c.Select(folder, false)
  208. // if err != nil {
  209. // err = errors.New(fmt.Sprintf("选择邮件箱失败: %+v", err))
  210. // return
  211. // }
  212. // //log.Printf("mbox %+v \n", mbox)
  213. // log.Printf("当前文件夹[%s]中,总共有 %d 封邮件 \n", folder, mbox.Messages)
  214. // if mbox.Messages == 0 {
  215. // //log.Fatalf("当前文件夹[%s]中没有邮件", folder)
  216. // return
  217. // }
  218. //
  219. // // 创建一个序列集,用于批量读取邮件
  220. // seqSet := new(imap.SeqSet)
  221. //
  222. // // 假设需要获取最后4封邮件时
  223. // from := uint32(1)
  224. // to := mbox.Messages // 此文件下的邮件总数
  225. // if mbox.Messages > 2 {
  226. // from = mbox.Messages - 1
  227. // }
  228. // //from = mbox.Messages - 9
  229. // //to = mbox.Messages - 9
  230. // from = mbox.Messages
  231. // to = mbox.Messages
  232. // seqSet.AddRange(from, to) // 添加指定范围内的邮件编号
  233. //
  234. //
  235. //
  236. // // 获取整个消息正文
  237. // // imap.FetchEnvelope:请求获取邮件的信封数据(例如发件人、收件人、主题等元数据)。
  238. // // imap.FetchRFC822:请求获取完整的邮件内容,包括所有头部和正文。
  239. // items := []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchRFC822}
  240. //
  241. // // 获取邮件内容 Start
  242. // messages := make(chan *imap.Message, readBatchSize) // 创建一个通道,用于接收邮件消息
  243. // fetchDone := make(chan error, 1) // 创建一个通道,用于接收错误消息
  244. // go func() {
  245. // // Fetch方法用于从服务器获取邮件数据,这里请求了邮件的信封和完整内容
  246. // fetchDone <- c.Fetch(seqSet, items, messages)
  247. // }()
  248. //
  249. // if err := <-fetchDone; err != nil {
  250. // log.Fatalf("获取邮件信息出现错误:%v \n", err)
  251. // }
  252. // // 获取邮件内容 End
  253. //
  254. // log.Println("开始读取邮件内容")
  255. // for msg := range messages {
  256. // mailMessage, tmpErr := readEveryMsg(msg)
  257. // if tmpErr != nil {
  258. // global.FILE_LOG.Fatalf("读取邮件内容时出现错误:%v \n", tmpErr)
  259. // continue
  260. // }
  261. // // 写入邮件处理chan
  262. // mailMessageChan <- emailMessage
  263. // }
  264. //
  265. // time.Sleep(time.Second * 5) // 休眠10秒
  266. //
  267. // log.Println("读取了所有邮件,完毕!")
  268. //
  269. // return
  270. //}
  271. // document link: https://github.com/emersion/go-imap/wiki/Fetching-messages
  272. func readEveryMsg(msg *imap.Message) (emailMessage MailMessage, ok bool, err error) {
  273. ok = true
  274. defer func() {
  275. if err != nil {
  276. ok = false
  277. global.FILE_LOG.Errorf("邮件读取失败;Err:%s", err.Error())
  278. }
  279. }()
  280. message.CharsetReader = myCharsetReader
  281. emailMessage.Resources = make(map[string]string) // 内嵌资源
  282. emailMessage.Attachment = make(map[string]string) // 附件
  283. emailMessage.Uid = msg.Uid
  284. htmlStr := ``
  285. textStr := ``
  286. //log.Printf("当前邮件的消息序列号 %+v \n", msg.SeqNum)
  287. //log.Println("-------------------------")
  288. // 获取邮件正文
  289. r := msg.GetBody(&imap.BodySectionName{})
  290. if r == nil {
  291. global.FILE_LOG.Info("服务器没有返回消息内容")
  292. }
  293. mr, err := mail.CreateReader(r)
  294. if err != nil {
  295. //log.Fatalf("邮件读取时出现错误: %v \n", err)
  296. err = errors.New(fmt.Sprintf("邮件读取时出现错误:%v \n", err))
  297. return
  298. }
  299. // 收件时间
  300. {
  301. date, err := mr.Header.Date()
  302. if err != nil {
  303. log.Println("收件时间 异常:", err.Error())
  304. }
  305. emailMessage.Date = date
  306. //log.Println("收件时间 Date:", date)
  307. }
  308. // 发件人
  309. {
  310. from, err := mr.Header.AddressList("From")
  311. if err != nil {
  312. log.Println("发件人 异常:", err.Error())
  313. }
  314. if len(from) > 0 {
  315. emailMessage.FromEmail = from[0].Address
  316. emailMessage.From = from[0].Name
  317. //mailMessage.From = from[0].String()
  318. //log.Println("发件人 From:", from)
  319. }
  320. }
  321. //if to, err := mr.Header.AddressList("To"); err == nil {
  322. // log.Println("收件人 To:", to)
  323. //}
  324. //log.Printf("抄送 Cc: %+v \n", msg.Envelope.Cc)
  325. // 邮件标题
  326. subject, err := mr.Header.Subject()
  327. if err != nil {
  328. log.Println("邮件主题 Subject ERR:", err)
  329. } else {
  330. //log.Println("邮件主题 Subject:", subject)
  331. }
  332. emailMessage.Title = subject
  333. // 过滤
  334. if isIgnore(emailMessage) {
  335. ok = false
  336. return
  337. }
  338. for {
  339. p, tmpErr := mr.NextPart()
  340. if tmpErr == io.EOF {
  341. break
  342. } else if tmpErr != nil {
  343. global.FILE_LOG.Errorf("读取邮件内容时出现错误:%v \n", tmpErr)
  344. err = tmpErr
  345. return
  346. }
  347. bodyBytes, _ := io.ReadAll(p.Body)
  348. if err != nil {
  349. //log.Fatalf("读取邮件部分时出现错误:%v \n", err)
  350. err = errors.New(fmt.Sprintf("读取邮件部分时出现错误:%v \n", err))
  351. return
  352. }
  353. switch h := p.Header.(type) {
  354. case *mail.InlineHeader:
  355. // 这是消息的文本(可以是纯文本或 HTML)
  356. contentType := h.Get("Content-Type")
  357. //log.Println("消息内容content-type:", contentType)
  358. if strings.HasPrefix(contentType, "text/plain") {
  359. //log.Printf("得到正文 -> TEXT: %v \n", string(bodyBytes))
  360. textStr += string(bodyBytes)
  361. } else if strings.HasPrefix(contentType, "text/html") {
  362. //log.Printf("得到正文 -> HTML: %v \n", len(b))
  363. //log.Printf("得到正文 -> HTML: %v \n", string(bodyBytes))
  364. htmlStr += string(bodyBytes)
  365. }
  366. // 这是内嵌资源
  367. if cid := p.Header.Get("Content-ID"); cid != "" {
  368. // 确定文件后缀
  369. fileSuffix := determineFileSuffix(bodyBytes)
  370. fileName := fmt.Sprintf("%s%s.%s", global.CONFIG.Serve.StaticDir, cid[1:len(cid)-1], fileSuffix)
  371. err = utils.SaveToFile(bodyBytes, fileName)
  372. if err != nil {
  373. //log.Fatalf("保存文件时出现错误:%v \n", err)
  374. err = errors.New(fmt.Sprintf("保存文件时出现错误:%v \n", err))
  375. return
  376. }
  377. emailMessage.Resources[cid] = fileName
  378. }
  379. break
  380. case *mail.AttachmentHeader:
  381. // 这是一个附件
  382. filename, _ := h.Filename()
  383. //log.Printf("得到附件: %v,content-type:%s \n", filename, p.Header.Get("Content-Type"))
  384. saveName := fmt.Sprint(msg.SeqNum, utils.MD5(filename), time.Now().Format(utils.FormatDateTimeUnSpace), time.Now().Nanosecond(), path.Ext(filename))
  385. filePath := fmt.Sprintf("%s%s%s%s", global.CONFIG.Serve.StaticDir, `file`, string(os.PathSeparator), saveName)
  386. err = utils.SaveToFile(bodyBytes, filePath)
  387. if err != nil {
  388. //log.Fatalf("保存文件时出现错误:%v \n", err)
  389. err = errors.New(fmt.Sprintf("保存文件时出现错误:%v \n", err))
  390. return
  391. }
  392. // 这是附件资源
  393. if contentDisposition := p.Header.Get("Content-Disposition"); contentDisposition != "" {
  394. if strings.HasPrefix(contentDisposition, "attachment") {
  395. emailMessage.Attachment[filename] = filePath
  396. }
  397. } else if cid := p.Header.Get("Content-ID"); cid != "" {
  398. // 这是内嵌资源
  399. emailMessage.Resources[cid] = filePath
  400. }
  401. //else {
  402. // mailMessage.Attachment[filename] = filePath
  403. //}
  404. break
  405. default:
  406. global.FILE_LOG.Info("未知格式:", h)
  407. //log.Println(h)
  408. }
  409. }
  410. emailMessage.Content = htmlStr
  411. if emailMessage.Content == `` {
  412. emailMessage.Content = textStr
  413. }
  414. //log.Println("一封邮件读取完毕")
  415. //log.Printf("------------------------- \n\n")
  416. return
  417. }
  418. // 根据文件内容确定文件后缀
  419. func determineFileSuffix(content []byte) string {
  420. kind, err := filetype.Match(content)
  421. if err != nil {
  422. global.FILE_LOG.Error("无法确定文件类型:%v \n", err)
  423. return ".bin"
  424. }
  425. return kind.Extension
  426. }
  427. // isIgnore
  428. // @Description: 校验是否忽略的邮件
  429. // @author: Roc
  430. // @datetime 2024-09-30 16:09:34
  431. // @param emailMessage MailMessage
  432. // @return bool
  433. func isIgnore(emailMessage MailMessage) bool {
  434. // 发件人中包含待过滤的字符串,那么就过滤
  435. lowerFrom := strings.ToLower(emailMessage.From)
  436. for _, email := range global.CONFIG.Email.IgnoreEmail {
  437. if utils.ContainsWholeWord(lowerFrom, email) {
  438. global.FILE_LOG.Infof("发件人包含%s,过滤掉,标题:%s", email, emailMessage.From)
  439. return true
  440. }
  441. }
  442. // 邮件标题中包含待过滤的字符串(大小写敏感的标题),那么就过滤
  443. for _, email := range global.CONFIG.Email.IgnoreEmailCaseSensitive {
  444. if utils.ContainsWholeWord(emailMessage.From, email) {
  445. global.FILE_LOG.Infof("发件人包含%s,过滤掉,标题:%s", email, emailMessage.From)
  446. return true
  447. }
  448. }
  449. // 发件人中包含待过滤的字符串,那么就过滤
  450. lowerFromAddress := strings.ToLower(emailMessage.FromEmail)
  451. for _, emailAddress := range global.CONFIG.Email.IgnoreEmailAddress {
  452. if utils.ContainsWholeWord(lowerFromAddress, emailAddress) {
  453. global.FILE_LOG.Infof("发件人邮箱包含%s,过滤掉,标题:%s", emailAddress, emailMessage.FromEmail)
  454. return true
  455. }
  456. }
  457. // 邮件标题中包含待过滤的字符串(大小写敏感的标题),那么就过滤
  458. for _, emailAddress := range global.CONFIG.Email.IgnoreEmailAddressCaseSensitive {
  459. if utils.ContainsWholeWord(emailMessage.FromEmail, emailAddress) {
  460. global.FILE_LOG.Infof("发件人邮箱包含%s,过滤掉,标题:%s", emailAddress, emailMessage.FromEmail)
  461. return true
  462. }
  463. }
  464. // 邮件标题中包含待过滤的字符串,那么就过滤
  465. lowerTitle := strings.ToLower(emailMessage.Title)
  466. for _, title := range global.CONFIG.Email.IgnoreEmailTitle {
  467. if utils.ContainsWholeWord(lowerTitle, title) {
  468. global.FILE_LOG.Infof("邮件标题包含%s,过滤掉,标题:%s", title, emailMessage.Title)
  469. return true
  470. }
  471. }
  472. // 邮件标题中包含待过滤的字符串(大小写敏感的标题),那么就过滤
  473. for _, title := range global.CONFIG.Email.IgnoreEmailTitleCaseSensitive {
  474. if utils.ContainsWholeWord(emailMessage.Title, title) {
  475. global.FILE_LOG.Infof("邮件标题包含%s,过滤掉,标题:%s", title, emailMessage.Title)
  476. return true
  477. }
  478. }
  479. return false
  480. }