mirror of
https://github.com/ManyakRus/starter.git
synced 2025-02-21 20:47:37 +02:00
567 lines
14 KiB
Go
567 lines
14 KiB
Go
package email_imap
|
|
|
|
import (
|
|
//"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/ManyakRus/starter/contextmain"
|
|
"github.com/ManyakRus/starter/email"
|
|
"github.com/ManyakRus/starter/log"
|
|
"github.com/ManyakRus/starter/micro"
|
|
"github.com/ManyakRus/starter/stopapp"
|
|
"github.com/emersion/go-imap"
|
|
imapModule "github.com/emersion/go-imap/client"
|
|
"github.com/emersion/go-message"
|
|
mail "github.com/emersion/go-message/mail"
|
|
"github.com/joho/godotenv"
|
|
simplemail "github.com/xhit/go-simple-mail/v2"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var Conn *imapModule.Client
|
|
var MailInbox *imap.MailboxStatus // папка inbox
|
|
|
|
// Settings хранит все нужные переменные окружения
|
|
var Settings SettingsINI
|
|
|
|
var FOLDER_NAME_INBOX = `INBOX`
|
|
var ErrEmptyInbox = fmt.Errorf("empty inbox")
|
|
|
|
// SettingsINI - структура для хранения всех нужных переменных окружения
|
|
type SettingsINI struct {
|
|
EMAIL_IMAP_SERVER string
|
|
EMAIL_IMAP_PORT string
|
|
EMAIL_LOGIN string
|
|
EMAIL_PASSWORD string
|
|
EMAIL_SEND_TO_TEST string
|
|
//EMAIL_SUBJECT string
|
|
EMAIL_AUTHENTICATION string
|
|
EMAIL_ENCRYPTION string
|
|
}
|
|
|
|
type Attachment struct {
|
|
Filename string
|
|
Data []byte
|
|
}
|
|
|
|
// Connect - подключение клиента Email
|
|
func Connect() {
|
|
err := Connect_err()
|
|
if err != nil {
|
|
log.Panicln("Connect() error: ", err)
|
|
} else {
|
|
log.Info("Email connected: ", Settings.EMAIL_LOGIN)
|
|
}
|
|
|
|
}
|
|
|
|
// Connect_err - Однократно Устанавливает соединение по требованию
|
|
func Connect_err() error {
|
|
var err error
|
|
|
|
if Settings.EMAIL_LOGIN == "" {
|
|
LoadEnv()
|
|
}
|
|
|
|
strFrom := Settings.EMAIL_LOGIN
|
|
strPass := Settings.EMAIL_PASSWORD
|
|
strHost := Settings.EMAIL_IMAP_SERVER
|
|
|
|
//log.Debugf("Connecting to server %s", strHost)
|
|
|
|
//подключение к серверу
|
|
fn := func() error {
|
|
Conn, err = imapModule.Dial(strHost + ":" + Settings.EMAIL_IMAP_PORT)
|
|
if err != nil {
|
|
err1 := fmt.Errorf("Send() error: %w", err)
|
|
return err1
|
|
}
|
|
return err
|
|
}
|
|
|
|
ctxMain := contextmain.GetContext()
|
|
ctx, cancel := context.WithTimeout(ctxMain, 60*time.Second)
|
|
defer cancel()
|
|
err = micro.GoGo(ctx, fn)
|
|
|
|
//Conn, err := imapModule.Dial(strHost + ":" + strconv.Itoa(Settings.EMAIL_SMTP_PORT))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Login
|
|
fn = func() error {
|
|
err = Conn.Login(strFrom, strPass)
|
|
if err != nil {
|
|
err1 := fmt.Errorf("Send() error: %w", err)
|
|
return err1
|
|
}
|
|
return err
|
|
}
|
|
|
|
ctx, cancel2 := context.WithTimeout(ctxMain, 60*time.Second)
|
|
defer cancel2()
|
|
err = micro.GoGo(ctx, fn)
|
|
|
|
//err = Conn.Login(strFrom, strPass)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Infof("Logged in %s", strFrom)
|
|
|
|
MailInbox = SelectInbox()
|
|
|
|
return nil
|
|
}
|
|
|
|
// SelectInbox - возвращает емайл папку Inbox
|
|
func SelectInbox() *imap.MailboxStatus {
|
|
var MailInbox *imap.MailboxStatus
|
|
MailInbox = SelectFolder(FOLDER_NAME_INBOX)
|
|
|
|
return MailInbox
|
|
}
|
|
|
|
// SelectInbox - возвращает емайл папку Inbox
|
|
func SelectFolder(FolderName string) *imap.MailboxStatus {
|
|
var MailInbox *imap.MailboxStatus
|
|
|
|
if Conn == nil {
|
|
log.Errorf("mailconn.SelectFolder() error conn = nil")
|
|
return MailInbox
|
|
}
|
|
|
|
// Select INBOX
|
|
var err error
|
|
|
|
fn := func() error {
|
|
MailInbox, err = Conn.Select(FolderName, false)
|
|
if err != nil {
|
|
err1 := fmt.Errorf("Send() error: %w", err)
|
|
return err1
|
|
}
|
|
return err
|
|
}
|
|
|
|
ctxMain := contextmain.GetContext()
|
|
ctx, cancel := context.WithTimeout(ctxMain, 60*time.Second)
|
|
defer cancel()
|
|
err = micro.GoGo(ctx, fn)
|
|
|
|
//MailInbox, err = Conn.Select(FolderName, false)
|
|
if err != nil {
|
|
log.Error("mailconn.SelectFolder() Select(", FolderName, ") error: %s", err)
|
|
panic(err)
|
|
}
|
|
if MailInbox == nil {
|
|
log.Error("mailconn.SelectFolder() Select(", FolderName, ") MailInbox=nil, error: %s", err)
|
|
panic(err)
|
|
}
|
|
|
|
return MailInbox
|
|
}
|
|
|
|
// ReplaceMessage -- перемещает сообщение в другую папку
|
|
func ReplaceMessage(msg *imap.Message, FolderName string) error {
|
|
if msg == nil {
|
|
return fmt.Errorf("ReplaceMessage(): msg==nil")
|
|
}
|
|
|
|
SeqSet := FindSeqSet(msg.Uid)
|
|
|
|
var err error
|
|
//ctxMain := contextmain.GetContext()
|
|
//Ctx1, CancelFunc1 := context.WithTimeout(ctxMain, time.Second*30)
|
|
//defer CancelFunc1()
|
|
|
|
err = Conn.UidMove(SeqSet, FolderName)
|
|
|
|
//select {
|
|
//case <-Ctx1.Done():
|
|
// Text1 := "mailconn.MsgNext() Fetch() error: TimeOut"
|
|
// err = errors.New(Text1)
|
|
// return err
|
|
//case err = <-Chan1:
|
|
//}
|
|
////err := mc.conn.UidMove(SeqSet, FolderName)
|
|
|
|
return err
|
|
}
|
|
|
|
// FindSeqSet -- находит SeqSet по номеру сообщения
|
|
func FindSeqSet(Id uint32) *imap.SeqSet {
|
|
|
|
SeqSet := new(imap.SeqSet)
|
|
SeqSet.AddNum(Id)
|
|
|
|
return SeqSet
|
|
}
|
|
|
|
// FindFolderName - возвращает имя папки imap
|
|
func FindFolderName(MainFolderName, SubFolderName string) string {
|
|
Otvet := MainFolderName
|
|
|
|
if SubFolderName != "" {
|
|
Otvet = MainFolderName + `/` + SubFolderName
|
|
}
|
|
|
|
return Otvet
|
|
}
|
|
|
|
// ForwardMessage -- перенаправляет емайл
|
|
func ForwardMessage(msg *imap.Message, email_send_to string) error {
|
|
//BodyText := msg.Body
|
|
//BodyText = "Обращение поступило от: " + msg.Envelope.From.MailboxName + "\n\r<BR>" +
|
|
// "----------------------------------------------------------\n\r<BR><BR>" +
|
|
// BodyText
|
|
|
|
var err error
|
|
//ctxMain := contextmain.GetContext()
|
|
//Ctx1, CancelFunc1 := context.WithTimeout(ctxMain, time.Second*30)
|
|
//defer CancelFunc1()
|
|
|
|
Body, Attachments, err := ReadBody(msg)
|
|
|
|
MassAttachments := make([]simplemail.File, 0)
|
|
for _, v := range Attachments {
|
|
Attachment1 := simplemail.File{}
|
|
Attachment1.Name = v.Filename
|
|
Attachment1.Data = v.Data
|
|
MassAttachments = append(MassAttachments, Attachment1)
|
|
}
|
|
|
|
err = email.SendEmail(email_send_to, Body, msg.Envelope.Subject, MassAttachments)
|
|
|
|
//select {
|
|
//case <-Ctx1.Done():
|
|
// Text1 := "mailconn.MsgNext() Fetch() error: TimeOut"
|
|
// err = errors.New(Text1)
|
|
// return err
|
|
//case err = <-Chan1:
|
|
//}
|
|
|
|
return err
|
|
}
|
|
|
|
// WaitStop - ожидает отмену глобального контекста
|
|
func WaitStop() {
|
|
|
|
select {
|
|
case <-contextmain.GetContext().Done():
|
|
log.Warn("Context app is canceled.")
|
|
}
|
|
|
|
//
|
|
stopapp.WaitTotalMessagesSendingNow("email")
|
|
|
|
//
|
|
CloseConnection()
|
|
stopapp.GetWaitGroup_Main().Done()
|
|
}
|
|
|
|
// StartDB - делает соединение с БД, отключение и др.
|
|
func Start() {
|
|
LoadEnv()
|
|
Connect_err()
|
|
|
|
stopapp.GetWaitGroup_Main().Add(1)
|
|
go WaitStop()
|
|
|
|
}
|
|
|
|
// LoadEnv - загружает переменные окружения в структуру из файла или из переменных окружения
|
|
func LoadEnv() {
|
|
|
|
dir := micro.ProgramDir()
|
|
filename := dir + ".env"
|
|
LoadEnv_FromFile(filename)
|
|
}
|
|
|
|
// LoadEnv_FromFile загружает переменные окружения в структуру из файла или из переменных окружения
|
|
func LoadEnv_FromFile(filename string) {
|
|
//var err error
|
|
//err := godotenv.Load(Filename_Settings)
|
|
//if err != nil {
|
|
// log.Fatal("Error loading " + Filename_Settings + " file, error: " + err.Error())
|
|
//}
|
|
|
|
err := godotenv.Load(filename)
|
|
if err != nil {
|
|
log.Debug("Error parse .env file error: " + err.Error())
|
|
} else {
|
|
log.Info("load .env from file: ", filename)
|
|
}
|
|
|
|
Settings = SettingsINI{}
|
|
Settings.EMAIL_IMAP_SERVER = os.Getenv("EMAIL_IMAP_SERVER")
|
|
Settings.EMAIL_IMAP_PORT = os.Getenv("EMAIL_IMAP_PORT")
|
|
Settings.EMAIL_LOGIN = os.Getenv("EMAIL_LOGIN")
|
|
Settings.EMAIL_PASSWORD = os.Getenv("EMAIL_PASSWORD")
|
|
Settings.EMAIL_SEND_TO_TEST = os.Getenv("EMAIL_SEND_TO_TEST")
|
|
//Settings.EMAIL_SUBJECT = os.Getenv("EMAIL_SUBJECT")
|
|
Settings.EMAIL_AUTHENTICATION = os.Getenv("EMAIL_AUTHENTICATION")
|
|
Settings.EMAIL_ENCRYPTION = os.Getenv("EMAIL_ENCRYPTION")
|
|
|
|
if Settings.EMAIL_IMAP_SERVER == "" {
|
|
log.Panicln("Need fill EMAIL_SMTP_SERVER ! in file ", filename)
|
|
}
|
|
|
|
if Settings.EMAIL_IMAP_PORT == "" {
|
|
log.Panicln("Need fill EMAIL_SMTP_PORT ! in file ", filename)
|
|
}
|
|
|
|
if Settings.EMAIL_LOGIN == "" {
|
|
log.Panicln("Need fill EMAIL_LOGIN ! in file ", filename)
|
|
}
|
|
|
|
if Settings.EMAIL_PASSWORD == "" {
|
|
log.Panicln("Need fill EMAIL_PASSWORD ! in file ", filename)
|
|
}
|
|
|
|
if Settings.EMAIL_SEND_TO_TEST == "" && micro.IsTestApp() == true {
|
|
log.Info("Need fill EMAIL_SEND_TO_TEST ! in file ", filename)
|
|
}
|
|
|
|
//if Settings.EMAIL_SUBJECT == "" {
|
|
// log.Panicln("Need fill EMAIL_SUBJECT ! in file ", filename)
|
|
//}
|
|
|
|
if Settings.EMAIL_AUTHENTICATION == "" {
|
|
log.Warn("Need fill EMAIL_AUTHENTICATION ! in file ", filename)
|
|
}
|
|
|
|
if Settings.EMAIL_ENCRYPTION == "" {
|
|
log.Warn("Need fill EMAIL_ENCRYPTION ! in file ", filename)
|
|
}
|
|
|
|
}
|
|
|
|
// CloseConnection - остановка работы клиента Email
|
|
func CloseConnection() {
|
|
err := CloseConnection_err()
|
|
if err != nil {
|
|
log.Panic("Email imap CloseConnection() error: ", err)
|
|
} else {
|
|
log.Info("Email imap connection closed")
|
|
}
|
|
|
|
}
|
|
|
|
// CloseConnection_err -- закрывает соединение с почтовым сервером
|
|
func CloseConnection_err() error {
|
|
var err error
|
|
|
|
fn := func() error {
|
|
err := Conn.Logout()
|
|
if err != nil {
|
|
err1 := fmt.Errorf("Send() error: %w", err)
|
|
return err1
|
|
}
|
|
return err
|
|
}
|
|
|
|
ctxMain := contextmain.GetContext()
|
|
ctx, cancel := context.WithTimeout(ctxMain, 60*time.Second)
|
|
defer cancel()
|
|
err = micro.GoGo(ctx, fn)
|
|
|
|
//err := Conn.Logout()
|
|
if err != nil {
|
|
log.Printf("MailConn.Logout(): err=\t%v", err)
|
|
}
|
|
|
|
Conn = nil
|
|
|
|
return err
|
|
}
|
|
|
|
// Stat - возвращает количество сообщений и ИД первого непрочитанного
|
|
func Stat() (count, id int) {
|
|
// Select INBOX
|
|
MailInbox := SelectInbox()
|
|
count = int(MailInbox.Messages)
|
|
id = int(MailInbox.UnseenSeqNum)
|
|
|
|
return count, id
|
|
}
|
|
|
|
// Безопасно читает тело сообщения
|
|
func ReadBody(Msg *imap.Message) (BodyText string, Attachments []Attachment, err error) {
|
|
|
|
Attachments = make([]Attachment, 0)
|
|
|
|
chErr := make(chan string, 2)
|
|
if Msg == nil {
|
|
//chErr <- "MsgEmail.readBody().fnRead(): сообщение не присвоена в структуре"
|
|
sError := "MsgEmail.readBody(): Msg == nil"
|
|
err = fmt.Errorf(sError)
|
|
chErr <- sError
|
|
return
|
|
}
|
|
var section imap.BodySectionName
|
|
l := Msg.GetBody(§ion)
|
|
if l == nil {
|
|
sError := "MsgEmail.readBody(): GetBody() = nil"
|
|
err = fmt.Errorf(sError)
|
|
chErr <- sError
|
|
return
|
|
//log.Fatal("Server didn't returned message body")
|
|
}
|
|
|
|
//sBody := l.(*bytes.Buffer).String()
|
|
|
|
// Create a new mail reader
|
|
mr, err := mail.CreateReader(l)
|
|
if err != nil {
|
|
sError := fmt.Sprintf("MsgEmail.readBody(): CreateReader() error: %v", err)
|
|
err = fmt.Errorf(sError)
|
|
chErr <- sError
|
|
return
|
|
//log.Fatal(err)
|
|
}
|
|
|
|
//buf := make([]byte, 0)
|
|
//n, err := mr.Read(buf)
|
|
|
|
// Process each message's part
|
|
//sBody := ""
|
|
BodyText = ""
|
|
for {
|
|
p, err := mr.NextPart()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if message.IsUnknownCharset(err) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
sError := fmt.Sprintf("MsgEmail.readBody(): NextPart() error: %v", err)
|
|
err = fmt.Errorf(sError)
|
|
chErr <- sError
|
|
if strings.Contains(err.Error(), "EOF") {
|
|
break
|
|
}
|
|
}
|
|
|
|
if p == nil {
|
|
continue
|
|
}
|
|
|
|
switch p.Header.(type) {
|
|
case *mail.InlineHeader:
|
|
// This is the message's text (can be plain-text or HTML)
|
|
b, err := ioutil.ReadAll(p.Body)
|
|
if err != nil {
|
|
sError := fmt.Sprintf("MsgEmail.readBody(): ReadAll() error: %v", err)
|
|
err = fmt.Errorf(sError)
|
|
chErr <- sError
|
|
}
|
|
//sBody = sBody + string(b)
|
|
BodyText = string(b)
|
|
break
|
|
|
|
case *mail.AttachmentHeader:
|
|
h := p.Header.(*mail.AttachmentHeader)
|
|
// This is an attachment
|
|
filename, _ := h.Filename()
|
|
//log.Println("Got attachment: %v", filename)
|
|
var Data []byte
|
|
Data, err := ioutil.ReadAll(p.Body)
|
|
if err != nil {
|
|
sError := fmt.Sprintf("MsgEmail.readBody(): ReadAll() error: %v", err)
|
|
err = fmt.Errorf(sError)
|
|
chErr <- sError
|
|
}
|
|
|
|
if filename != "" {
|
|
Attachment1 := Attachment{}
|
|
Attachment1.Filename = filename
|
|
Attachment1.Data = Data
|
|
Attachments = append(Attachments, Attachment1)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//sf.StrBody_ = sBody
|
|
|
|
//body_, err := io.ReadAll(sf.Msg.GetBody(§ion))
|
|
//if err != nil {
|
|
// chErr <- fmt.Sprintf("MsgEmail.readBody().fnRead(): при чтении тела письма, err=\t%v", err)
|
|
//}
|
|
//sf.StrBody_ = html.UnescapeString(string(body_))
|
|
//if err = sf.getEmail(); err != nil {
|
|
// chErr <- fmt.Sprintf("MsgEmail.readBody().fnRead(): при получении адресата письма, err=\t%v", err)
|
|
//}
|
|
|
|
return
|
|
}
|
|
|
|
// Безопасно получает тему сообщения
|
|
func ReadHeader(msg *imap.Message) mail.Header {
|
|
var Header mail.Header
|
|
|
|
// Get the whole message body
|
|
var section imap.BodySectionName
|
|
section = imap.BodySectionName{}
|
|
//items := []imap.FetchItem{section.FetchItem()}
|
|
|
|
r := msg.GetBody(§ion)
|
|
if r == nil {
|
|
log.Fatal("Server didn't returned message body")
|
|
}
|
|
|
|
// Create a new mail reader
|
|
mr, err := mail.CreateReader(r)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
Header = mr.Header
|
|
|
|
return Header
|
|
}
|
|
|
|
// ReadMessage -- возвращает пиьмо с сервера, с номером по порядку=id
|
|
func ReadMessage(id int) (*imap.Message, error) {
|
|
var err error
|
|
Otvet := &imap.Message{}
|
|
|
|
messages := make(chan *imap.Message, 1)
|
|
nextNum := uint32(id)
|
|
seqset := new(imap.SeqSet)
|
|
seqset.AddNum(nextNum)
|
|
var section imap.BodySectionName
|
|
items := []imap.FetchItem{imap.FetchBody, imap.FetchUid, imap.FetchEnvelope, imap.FetchBodyStructure, section.FetchItem()}
|
|
err = Conn.Fetch(seqset, items, messages)
|
|
if err != nil {
|
|
return Otvet, err
|
|
}
|
|
|
|
TimeOutSeconds := 60
|
|
duration := time.Duration(TimeOutSeconds) * time.Second
|
|
Ctx1, CancelFunc1 := context.WithTimeout(contextmain.GetContext(), duration)
|
|
defer CancelFunc1()
|
|
select {
|
|
case <-Ctx1.Done():
|
|
Text1 := fmt.Sprint("mailconn.MsgNext() Fetch() error: TimeOut ", TimeOutSeconds, " seconds")
|
|
err = errors.New(Text1)
|
|
return Otvet, err
|
|
case Otvet = <-messages:
|
|
}
|
|
|
|
if Otvet == nil {
|
|
text1 := fmt.Sprint("mailconn.MsgNext() No email with id: ", nextNum)
|
|
err := errors.New(text1)
|
|
//chRes_ <- text1
|
|
return Otvet, err
|
|
}
|
|
|
|
return Otvet, err
|
|
}
|