commit 17e92e85579150b13e172179ab38949da0e1e0a3 Author: Michael Date: Fri Jul 2 21:09:38 2021 +0100 first commit Signed-off-by: Michael diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a257473 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +gomail +logs +msgs +config.yaml +go.sum \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f927fde --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Gomail + +Command line application for pulling imap mail messages and saving them as eml files diff --git a/config.go b/config.go new file mode 100644 index 0000000..52f2093 --- /dev/null +++ b/config.go @@ -0,0 +1,40 @@ +package main + +import ( + "io/ioutil" + "log" + + "gopkg.in/yaml.v3" +) + +// Config application configuration +type Config struct { + Addr string `yaml:"addr"` + Username string `yaml:"username"` + Password string `yaml:"password"` + Logger struct { + Path string `yaml:"path"` + Mode string `yaml:"mode"` + Level int `yaml:"level"` + } +} + +// NewConfig - create new config instance from file path +func NewConfig(configFile string) (config *Config) { + file, err := ioutil.ReadFile(configFile) + if err != nil { + log.Fatalf("Error loading yaml config file %v", err) + } + if err = yaml.Unmarshal(file, &config); err != nil { + log.Fatalf("Error Unmarshaling yaml config file %v", err) + } + return config +} + +// NewFile create new config instance from file +func NewFile(configFile []byte) (config *Config) { + if err := yaml.Unmarshal(configFile, &config); err != nil { + log.Fatalf("Error Unmarshaling yaml config file %v", err) + } + return config +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2941c7d --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module git.0cd.xyz/michael/gomail + +go 1.16 + +require ( + git.0cd.xyz/michael/gtools v0.1.5 + github.com/emersion/go-imap v1.1.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +) diff --git a/mail/mail.go b/mail/mail.go new file mode 100644 index 0000000..6acc8d9 --- /dev/null +++ b/mail/mail.go @@ -0,0 +1,98 @@ +package mail + +import ( + "errors" + "io/ioutil" + "log" + "strconv" + "time" + + "git.0cd.xyz/michael/gtools/colour" + "github.com/emersion/go-imap" + "github.com/emersion/go-imap/client" +) + +type Mail struct { + Client *client.Client +} + +type Message struct { + SeqNum uint32 + Subject string + To []*imap.Address + From []*imap.Address + Date time.Time + Body imap.Literal +} + +type Messages struct { + Message []Message +} + +func Login(addr, username, password string) (mail *Mail, err error) { + colour.Green("Connecting to %s\n", addr) + client, err := client.DialTLS(addr, nil) + if err != nil { + return nil, err + } + if err = client.Login(username, password); err != nil { + return nil, err + } + log.Println("Logged in as", username) + return &Mail{ + Client: client, + }, nil +} + +func (mail *Mail) GetMessages(mailbox string, msgs int32) (*Messages, error) { + var messages Messages + mbox, err := mail.Client.Select(mailbox, true) + if err != nil { + return nil, err + } + if mbox.Messages == 0 { + return nil, errors.New("no messages in mailbox") + } + seqSet := new(imap.SeqSet) + seqSet.AddRange(1, mbox.Messages) + var section imap.BodySectionName + + imapMessages := make(chan *imap.Message, msgs) + done := make(chan error, 1) + go func() { + done <- mail.Client.Fetch(seqSet, []imap.FetchItem{imap.FetchEnvelope, section.FetchItem()}, imapMessages) + }() + + for msg := range imapMessages { + body := msg.GetBody(§ion) + if body == nil { + return nil, errors.New("server didn't returned message body") + } + message := Message{ + SeqNum: msg.SeqNum, + Subject: msg.Envelope.Subject, + To: msg.Envelope.To, + From: msg.Envelope.From, + Date: msg.Envelope.Date, + Body: body, + } + messages.Message = append(messages.Message, message) + } + if err := <-done; err != nil { + return nil, err + } + return &messages, nil +} + +func (mail *Mail) WriteMessages(messages *Messages) error { + for _, msg := range messages.Message { + body, err := ioutil.ReadAll(msg.Body) + if err != nil { + return err + } + if err := ioutil.WriteFile("./msgs/"+strconv.Itoa(int(msg.SeqNum))+"_"+msg.To[0].Address()+".eml", body, 0644); err != nil { + return err + } + } + return nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..cd70f20 --- /dev/null +++ b/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "git.0cd.xyz/michael/gomail/mail" + "git.0cd.xyz/michael/gtools/logger" +) + +func main() { + mailbox, msgs := ui() + cfg := NewConfig("./config.yaml") + logger := logger.New(cfg.Logger.Path, cfg.Logger.Mode, cfg.Logger.Level) + + mail, err := mail.Login(cfg.Addr, cfg.Username, cfg.Password) + if err != nil { + logger.Error.Fatal(err) + } + defer mail.Client.Logout() + + messages, err := mail.GetMessages(*mailbox, int32(*msgs)) + if err != nil { + logger.Error.Fatal(err) + } + if err := mail.WriteMessages(messages); err != nil { + logger.Error.Fatal(err) + } +} + +func ui() (mailbox *string, msgs *int) { + flag.Usage = func() { + fmt.Printf("Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + } + + mailbox = flag.String("mailbox", "inbox", "mailbox to scan messages") + msgs = flag.Int("msgs", 100, "Number of messages to pull") + + flag.Parse() + return mailbox, msgs +}