imap.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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. "golang.org/x/text/encoding/simplifiedchinese"
  13. "golang.org/x/text/transform"
  14. "io"
  15. "log"
  16. "path"
  17. "strings"
  18. "time"
  19. )
  20. type MailMessage struct {
  21. Title string `description:"邮件标题"`
  22. Content string `description:"邮件主体正文"`
  23. Resources map[string]string `description:"正文内嵌资源"`
  24. Attachment map[string]string `description:"附件资源"`
  25. }
  26. func ListenMail(mailAddress, folder, userName, password string, readBatchSize int, mailMessageChan chan MailMessage, mailMessageDoneChan chan bool) (err error) { // 收件箱
  27. defer func() {
  28. if err != nil {
  29. fmt.Println("err:", err.Error())
  30. }
  31. }()
  32. // 建立与 IMAP 服务器的连接
  33. c, err := client.DialTLS(mailAddress, nil)
  34. if err != nil {
  35. fmt.Printf("连接 IMAP 服务器失败: %+v \n", err)
  36. return
  37. }
  38. // 最后一定不要忘记退出登录
  39. defer func() {
  40. _ = c.Logout()
  41. }()
  42. // 登录
  43. if err = c.Login(userName, password); err != nil {
  44. fmt.Printf("邮箱[%s] 登录失败: %v \n", fmt.Sprintf("%s:%s", userName, mailAddress), err)
  45. return
  46. }
  47. // 列出当前邮箱中的文件夹
  48. mailboxes := make(chan *imap.MailboxInfo, 10)
  49. done := make(chan error, 1) // 记录错误的 chan
  50. go func() {
  51. done <- c.List("", "*", mailboxes)
  52. }()
  53. log.Println("-->当前邮箱的文件夹 Mailboxes:")
  54. var folderExists bool
  55. for m := range mailboxes {
  56. log.Println("* ", m.Name)
  57. if m.Name == folder {
  58. folderExists = true
  59. }
  60. }
  61. if err := <-done; err != nil {
  62. log.Fatalf("列出邮箱列表时,出现错误:%v \n", err)
  63. }
  64. log.Println("-->列出邮箱列表完毕!")
  65. if !folderExists {
  66. err = errors.New(fmt.Sprintf("文件夹[%s] 不存在 \n", folder))
  67. return
  68. }
  69. // 选择指定的文件夹
  70. mbox, err := c.Select(folder, false)
  71. if err != nil {
  72. err = errors.New(fmt.Sprintf("选择邮件箱失败: %+v", err))
  73. return
  74. }
  75. //log.Printf("mbox %+v \n", mbox)
  76. log.Printf("当前文件夹[%s]中,总共有 %d 封邮件 \n", folder, mbox.Messages)
  77. if mbox.Messages == 0 {
  78. //log.Fatalf("当前文件夹[%s]中没有邮件", folder)
  79. return
  80. }
  81. // 创建一个序列集,用于批量读取邮件
  82. seqSet := new(imap.SeqSet)
  83. // 假设需要获取最后4封邮件时
  84. from := uint32(1)
  85. to := mbox.Messages // 此文件下的邮件总数
  86. if to > 13 {
  87. from = to - 13
  88. }
  89. step := uint32(2)
  90. for i := from; i <= to; {
  91. end := i + step - 1
  92. if end > to {
  93. end = to
  94. }
  95. seqSet.Clear()
  96. seqSet.AddRange(i, end) // 添加指定范围内的邮件编号
  97. // 获取整个消息正文
  98. // imap.FetchEnvelope:请求获取邮件的信封数据(例如发件人、收件人、主题等元数据)。
  99. // imap.FetchRFC822:请求获取完整的邮件内容,包括所有头部和正文。
  100. items := []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchRFC822}
  101. // 获取邮件内容 Start
  102. messages := make(chan *imap.Message, readBatchSize) // 创建一个通道,用于接收邮件消息
  103. fetchDone := make(chan error, 1) // 创建一个通道,用于接收错误消息
  104. go func() {
  105. // Fetch方法用于从服务器获取邮件数据,这里请求了邮件的信封和完整内容
  106. fetchDone <- c.Fetch(seqSet, items, messages)
  107. }()
  108. if err := <-fetchDone; err != nil {
  109. log.Fatalf("获取邮件信息出现错误:%v \n", err)
  110. }
  111. // 获取邮件内容 End
  112. log.Println("开始读取邮件内容")
  113. for msg := range messages {
  114. mailMessage, tmpErr := readEveryMsg(msg)
  115. if tmpErr != nil {
  116. global.FILE_LOG.Fatalf("读取邮件内容时出现错误:%v \n", tmpErr)
  117. continue
  118. }
  119. // 写入邮件处理chan
  120. mailMessageChan <- mailMessage
  121. }
  122. //time.Sleep(time.Second * 5) // 休眠10秒
  123. i = i + step
  124. }
  125. log.Println("读取了所有邮件,完毕!")
  126. mailMessageDoneChan <- true
  127. return
  128. }
  129. //func ListenMail(mailAddress, folder, userName, password string, readBatchSize int, mailMessageChan chan MailMessage, mailMessageDoneChan chan bool) (err error) { // 收件箱
  130. // defer func() {
  131. // if err != nil {
  132. // fmt.Println("err:", err.Error())
  133. // }
  134. // }()
  135. // // 建立与 IMAP 服务器的连接
  136. // c, err := client.DialTLS(mailAddress, nil)
  137. // if err != nil {
  138. // fmt.Printf("连接 IMAP 服务器失败: %+v \n", err)
  139. // return
  140. // }
  141. // // 最后一定不要忘记退出登录
  142. // defer func() {
  143. // _ = c.Logout()
  144. // }()
  145. //
  146. // // 登录
  147. // if err = c.Login(userName, password); err != nil {
  148. // fmt.Printf("邮箱[%s] 登录失败: %v \n", fmt.Sprintf("%s:%s", userName, mailAddress), err)
  149. // return
  150. // }
  151. // // 列出当前邮箱中的文件夹
  152. // mailboxes := make(chan *imap.MailboxInfo, 10)
  153. // done := make(chan error, 1) // 记录错误的 chan
  154. // go func() {
  155. // done <- c.List("", "*", mailboxes)
  156. // }()
  157. // log.Println("-->当前邮箱的文件夹 Mailboxes:")
  158. //
  159. // var folderExists bool
  160. // for m := range mailboxes {
  161. // log.Println("* ", m.Name)
  162. // if m.Name == folder {
  163. // folderExists = true
  164. // }
  165. // }
  166. //
  167. // if err := <-done; err != nil {
  168. // log.Fatalf("列出邮箱列表时,出现错误:%v \n", err)
  169. // }
  170. // log.Println("-->列出邮箱列表完毕!")
  171. // if !folderExists {
  172. // err = errors.New(fmt.Sprintf("文件夹[%s] 不存在 \n", folder))
  173. // return
  174. // }
  175. //
  176. // // 选择指定的文件夹
  177. // mbox, err := c.Select(folder, false)
  178. // if err != nil {
  179. // err = errors.New(fmt.Sprintf("选择邮件箱失败: %+v", err))
  180. // return
  181. // }
  182. // //log.Printf("mbox %+v \n", mbox)
  183. // log.Printf("当前文件夹[%s]中,总共有 %d 封邮件 \n", folder, mbox.Messages)
  184. // if mbox.Messages == 0 {
  185. // //log.Fatalf("当前文件夹[%s]中没有邮件", folder)
  186. // return
  187. // }
  188. //
  189. // // 创建一个序列集,用于批量读取邮件
  190. // seqSet := new(imap.SeqSet)
  191. //
  192. // // 假设需要获取最后4封邮件时
  193. // from := uint32(1)
  194. // to := mbox.Messages // 此文件下的邮件总数
  195. // if mbox.Messages > 2 {
  196. // from = mbox.Messages - 1
  197. // }
  198. // //from = mbox.Messages - 9
  199. // //to = mbox.Messages - 9
  200. // from = mbox.Messages
  201. // to = mbox.Messages
  202. // seqSet.AddRange(from, to) // 添加指定范围内的邮件编号
  203. //
  204. //
  205. //
  206. // // 获取整个消息正文
  207. // // imap.FetchEnvelope:请求获取邮件的信封数据(例如发件人、收件人、主题等元数据)。
  208. // // imap.FetchRFC822:请求获取完整的邮件内容,包括所有头部和正文。
  209. // items := []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchRFC822}
  210. //
  211. // // 获取邮件内容 Start
  212. // messages := make(chan *imap.Message, readBatchSize) // 创建一个通道,用于接收邮件消息
  213. // fetchDone := make(chan error, 1) // 创建一个通道,用于接收错误消息
  214. // go func() {
  215. // // Fetch方法用于从服务器获取邮件数据,这里请求了邮件的信封和完整内容
  216. // fetchDone <- c.Fetch(seqSet, items, messages)
  217. // }()
  218. //
  219. // if err := <-fetchDone; err != nil {
  220. // log.Fatalf("获取邮件信息出现错误:%v \n", err)
  221. // }
  222. // // 获取邮件内容 End
  223. //
  224. // log.Println("开始读取邮件内容")
  225. // for msg := range messages {
  226. // mailMessage, tmpErr := readEveryMsg(msg)
  227. // if tmpErr != nil {
  228. // global.FILE_LOG.Fatalf("读取邮件内容时出现错误:%v \n", tmpErr)
  229. // continue
  230. // }
  231. // // 写入邮件处理chan
  232. // mailMessageChan <- mailMessage
  233. // }
  234. //
  235. // time.Sleep(time.Second * 5) // 休眠10秒
  236. //
  237. // log.Println("读取了所有邮件,完毕!")
  238. //
  239. // return
  240. //}
  241. // 定义一个自定义的 CharsetReader 函数,它能够处理 gb2312 和 gbk 字符集
  242. func myCharsetReader(charset string, input io.Reader) (io.Reader, error) {
  243. switch strings.ToLower(charset) {
  244. case "gb2312", "gbk":
  245. reader := transform.NewReader(input, simplifiedchinese.GBK.NewDecoder())
  246. return reader, nil
  247. case "utf-8":
  248. return input, nil
  249. default:
  250. return input, fmt.Errorf("unsupported charset: %s", charset)
  251. }
  252. }
  253. // document link: https://github.com/emersion/go-imap/wiki/Fetching-messages
  254. func readEveryMsg(msg *imap.Message) (mailMessage MailMessage, err error) {
  255. message.CharsetReader = myCharsetReader
  256. mailMessage.Resources = make(map[string]string) // 内嵌资源
  257. mailMessage.Attachment = make(map[string]string) // 附件
  258. htmlStr := ``
  259. textStr := ``
  260. rootPath := `C:\Users\123\go\src\eta\eta_email_analysis\static\`
  261. //log.Printf("当前邮件的消息序列号 %+v \n", msg.SeqNum)
  262. //log.Println("-------------------------")
  263. // 获取邮件正文
  264. r := msg.GetBody(&imap.BodySectionName{})
  265. if r == nil {
  266. log.Fatal("服务器没有返回消息内容")
  267. }
  268. mr, err := mail.CreateReader(r)
  269. if err != nil {
  270. //log.Fatalf("邮件读取时出现错误: %v \n", err)
  271. err = errors.New(fmt.Sprintf("邮件读取时出现错误:%v \n", err))
  272. return
  273. }
  274. //if date, err := mr.Header.Date(); err == nil {
  275. // log.Println("收件时间 Date:", date)
  276. //}
  277. //if from, err := mr.Header.AddressList("From"); err == nil {
  278. // log.Println("发件人 From:", from)
  279. //}
  280. //if to, err := mr.Header.AddressList("To"); err == nil {
  281. // log.Println("收件人 To:", to)
  282. //}
  283. //log.Printf("抄送 Cc: %+v \n", msg.Envelope.Cc)
  284. // 邮件标题
  285. subject, err := mr.Header.Subject()
  286. if err != nil {
  287. log.Println("邮件主题 Subject ERR:", err)
  288. }
  289. log.Println("邮件主题 Subject:", subject)
  290. mailMessage.Title = subject
  291. for {
  292. p, tmpErr := mr.NextPart()
  293. if tmpErr == io.EOF {
  294. break
  295. } else if tmpErr != nil {
  296. //log.Fatalf("读取邮件内容时出现错误:%v \n", tmpErr)
  297. err = tmpErr
  298. return
  299. }
  300. bodyBytes, _ := io.ReadAll(p.Body)
  301. if err != nil {
  302. //log.Fatalf("读取邮件部分时出现错误:%v \n", err)
  303. err = errors.New(fmt.Sprintf("读取邮件部分时出现错误:%v \n", err))
  304. return
  305. }
  306. switch h := p.Header.(type) {
  307. case *mail.InlineHeader:
  308. // 这是消息的文本(可以是纯文本或 HTML)
  309. contentType := h.Get("Content-Type")
  310. //log.Println("消息内容content-type:", contentType)
  311. if strings.HasPrefix(contentType, "text/plain") {
  312. //log.Printf("得到正文 -> TEXT: %v \n", string(bodyBytes))
  313. textStr += string(bodyBytes)
  314. } else if strings.HasPrefix(contentType, "text/html") {
  315. //log.Printf("得到正文 -> HTML: %v \n", len(b))
  316. //log.Printf("得到正文 -> HTML: %v \n", string(bodyBytes))
  317. htmlStr += string(bodyBytes)
  318. }
  319. // 这是内嵌资源
  320. if cid := p.Header.Get("Content-ID"); cid != "" {
  321. // 确定文件后缀
  322. fileSuffix := determineFileSuffix(bodyBytes)
  323. fileName := fmt.Sprintf("%s%s.%s", rootPath, cid[1:len(cid)-1], fileSuffix)
  324. err = utils.SaveToFile(bodyBytes, fileName)
  325. if err != nil {
  326. //log.Fatalf("保存文件时出现错误:%v \n", err)
  327. err = errors.New(fmt.Sprintf("保存文件时出现错误:%v \n", err))
  328. return
  329. }
  330. mailMessage.Resources[cid] = fileName
  331. }
  332. break
  333. case *mail.AttachmentHeader:
  334. // 这是一个附件
  335. filename, _ := h.Filename()
  336. //log.Printf("得到附件: %v,content-type:%s \n", filename, p.Header.Get("Content-Type"))
  337. saveName := fmt.Sprint(msg.SeqNum, utils.MD5(filename), time.Now().Format(utils.FormatDateTimeUnSpace), time.Now().Nanosecond(), path.Ext(filename))
  338. filePath := fmt.Sprintf("%s%s%s", rootPath, `file\`, saveName)
  339. err = utils.SaveToFile(bodyBytes, filePath)
  340. if err != nil {
  341. //log.Fatalf("保存文件时出现错误:%v \n", err)
  342. err = errors.New(fmt.Sprintf("保存文件时出现错误:%v \n", err))
  343. return
  344. }
  345. // 这是附件资源
  346. if contentDisposition := p.Header.Get("Content-Disposition"); contentDisposition != "" {
  347. if strings.HasPrefix(contentDisposition, "attachment") {
  348. mailMessage.Attachment[filename] = filePath
  349. }
  350. } else if cid := p.Header.Get("Content-ID"); cid != "" {
  351. // 这是内嵌资源
  352. mailMessage.Resources[cid] = filePath
  353. }
  354. //else {
  355. // mailMessage.Attachment[filename] = filePath
  356. //}
  357. break
  358. default:
  359. log.Println(h)
  360. }
  361. }
  362. mailMessage.Content = htmlStr
  363. if mailMessage.Content == `` {
  364. mailMessage.Content = textStr
  365. }
  366. //log.Println("一封邮件读取完毕")
  367. //log.Printf("------------------------- \n\n")
  368. return
  369. }
  370. // 根据文件内容确定文件后缀
  371. func determineFileSuffix(content []byte) string {
  372. kind, err := filetype.Match(content)
  373. if err != nil {
  374. log.Printf("无法确定文件类型:%v \n", err)
  375. return ".bin"
  376. }
  377. return kind.Extension
  378. }