imap.go 15 KB

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