watch a specific folder

This commit is contained in:
Colin 2024-05-04 09:24:38 -04:00
parent 31c56a3030
commit 3ea64f2fb6
13 changed files with 172 additions and 112 deletions

View File

@ -2,30 +2,57 @@
## Setup
Before running the `imap-json-fetcher`, ensure that the following environment variables are set:
Before running the `imap-json-fetcher`, ensure that you have configured the necessary environment variables. This script uses these variables to securely connect to your IMAP server and fetch emails from a specified mailbox.
- `IMAP_HOST`: Your IMAP server address (e.g., `imap.example.com:993`).
- `IMAP_USERNAME`: Your email username.
- `IMAP_PASSWORD`: Your email password.
### Required Environment Variables
You can set these environment variables in a Unix-like terminal as follows:
Set the following environment variables in your system:
- `IMAP_HOST`: The address of your IMAP server, including the port if necessary (e.g., `imap.example.com:993`).
- `IMAP_USERNAME`: Your email account username.
- `IMAP_PASSWORD`: Your email account password.
- `IMAP_FOLDER`: The specific folder to monitor for new emails. If not specified, the script defaults to monitoring the "INBOX".
You can set these variables in a Unix-like environment using the following commands:
```bash
export IMAP_HOST="your_imap_server_address"
export IMAP_USERNAME="your_username"
export IMAP_PASSWORD="your_password"
export IMAP_FOLDER="your_folder_name" # Optional, defaults to "INBOX"
```
## Running the Script
### Running the Script
To run the `imap-json-fetcher`, navigate to the `dist` directory where the binary is located, and use the following command:
Navigate to the `dist` directory where the binary is compiled, and run the script using the following command:
```bash
./imap-json-fetcher
```
This assumes that you have already compiled the binary and placed it in the `dist` directory.
### Docker Container Example
For running in a Docker container, you might set it up like this, passing the environment variables into your container. Replace specifics to fit your architecture and environment settings:
```bash
docker run --env IMAP_HOST=$IMAP_HOST \
--env IMAP_USERNAME=$IMAP_USERNAME \
--env IMAP_PASSWORD=$IMAP_PASSWORD \
--env IMAP_FOLDER=$IMAP_FOLDER \
--name imap-json-fetcher \
your_docker_image
```
This command assumes that you've built a Docker image named `your_docker_image` that encapsulates your application.
## Output
The `imap-json-fetcher` will listen for new emails and output the extracted email addresses in separate JSON files, one per email, named in the format `email-seqNum.json`.
The `imap-json-fetcher` listens for new emails arriving in the specified IMAP folder. For each new email, it extracts relevant data and saves this information in separate JSON files. The inclusion of the `IMAP_FOLDER` environment variable allows flexibility in choosing which folder to monitor, beyond just the default "INBOX".
### File Naming Convention
Output files are named using a timestamp to ensure uniqueness: `email-yyyyMMdd-HHmmss.json`. This format prevents overwriting files and helps in organizing data chronologically.
## Flexibility in Folder Selection
The `IMAP_FOLDER` environment variable provides users the flexibility to monitor various folders within their email account, such as "Sent", "Archive", or custom labels, depending on their specific requirements.

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1 @@
main.go:2:1: expected 'package', found 'import'

View File

@ -0,0 +1,21 @@
{
"body": "test9\r\n",
"date": "2024-05-04T13:15:07Z",
"from": [
{
"PersonalName": "",
"AtDomainList": "",
"MailboxName": "imapfetcher",
"HostName": "nixc.us"
}
],
"subject": "test9",
"to": [
{
"PersonalName": "",
"AtDomainList": "",
"MailboxName": "imapfetcher",
"HostName": "nixc.us"
}
]
}

11
go.mod
View File

@ -1,14 +1,3 @@
module imap-json-fetcher
go 1.21.1
require (
github.com/emersion/go-imap v1.2.1
github.com/emersion/go-message v0.15.0
)
require (
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect
golang.org/x/text v0.3.7 // indirect
)

12
go.sum
View File

@ -1,12 +0,0 @@
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
github.com/emersion/go-message v0.15.0 h1:urgKGqt2JAc9NFJcgncQcohHdiYb803YTH9OQwHBHIY=
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

99
main.go
View File

@ -1,4 +1,3 @@
package main
import (
"encoding/json"
@ -11,6 +10,7 @@ import (
"github.com/emersion/go-imap"
"github.com/emersion/go-imap/client"
"github.com/emersion/go-imap-idle"
"github.com/emersion/go-message/mail"
)
@ -18,67 +18,96 @@ func main() {
host := os.Getenv("IMAP_HOST")
username := os.Getenv("IMAP_USERNAME")
password := os.Getenv("IMAP_PASSWORD")
folder := os.Getenv("IMAP_FOLDER")
if folder == "" {
folder = "INBOX"
}
if host == "" || username == "" || password == "" {
log.Fatal("Environment variables IMAP_HOST, IMAP_USERNAME, and IMAP_PASSWORD must be set")
log.Fatal("Required environment variables are not set")
}
log.Println("Connecting to server...")
// Connect to the IMAP server
c, err := client.DialTLS(host, nil)
if err != nil {
log.Fatalf("Failed to connect to IMAP server: %v", err)
}
log.Println("Connected")
defer c.Logout()
// Login
if err := c.Login(username, password); err != nil {
log.Fatalf("Failed to log in: %v", err)
}
log.Println("Logged in")
// Select INBOX
_, err = c.Select("INBOX", false)
log.Println("Logged in and authenticated")
// Ensure the selected folder is continuously monitored
for {
if err := monitorFolder(c, folder); err != nil {
log.Printf("Error monitoring folder %s: %v", folder, err)
log.Println("Attempting to reconnect in 30 seconds...")
time.Sleep(30 * time.Second)
continue
}
}
}
func monitorFolder(c *client.Client, folder string) error {
_, err := c.Select(folder, false)
if err != nil {
log.Fatalf("Failed to select INBOX: %v", err)
return fmt.Errorf("failed to select '%s': %w", folder, err)
}
// Set up criteria to search for new unseen emails
criteria := imap.NewSearchCriteria()
criteria.WithoutFlags = []string{imap.SeenFlag}
ids, err := c.Search(criteria)
if err != nil {
log.Fatalf("Failed to search emails: %v", err)
}
if len(ids) == 0 {
log.Println("No new emails")
return
}
log.Printf("Monitoring folder: %s", folder)
idleClient := idle.NewClient(c)
done := make(chan error, 1)
seqset := new(imap.SeqSet)
seqset.AddNum(ids...)
section := &imap.BodySectionName{}
items := []imap.FetchItem{section.FetchItem(), imap.FetchEnvelope}
messages := make(chan *imap.Message)
go func() {
if err := c.Fetch(seqset, items, messages); err != nil {
log.Fatalf("Failed to fetch emails: %v", err)
done <- idleClient.Idle(nil)
}()
for {
select {
case err := <-done:
if err != nil {
return fmt.Errorf("idle error: %w", err)
}
return nil
case <-time.After(29 * time.Minute): // Re-initiate IDLE approximately every 29 minutes
if err := idleClient.Terminate(); err != nil {
log.Printf("Error terminating IDLE: %v", err)
}
return fmt.Errorf("reinitializing IDLE")
}
}
}
func processNewMessages(c *client.Client, mbox *imap.MailboxStatus) {
seqset := new(imap.SeqSet)
seqset.AddRange(mbox.Messages, mbox.Messages)
messages := make(chan *imap.Message, 10)
go func() {
if err := c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope, imap.FetchBody}, messages); err != nil {
log.Fatalf("Failed to fetch new messages: %v", err)
}
}()
counter := 0
for msg := range messages {
processMessage(msg)
}
}
// processMessage processes a single message, extracting data and saving to JSON
func processMessage(msg *imap.Message) {
if msg == nil {
continue
log.Println("No message to process")
return
}
body := msg.GetBody(section)
body := msg.GetBody(&imap.BodySectionName{})
if body == nil {
log.Println("No body fetched for this message")
continue
return
}
r, err := mail.CreateReader(body)
@ -122,12 +151,10 @@ func main() {
}
timestamp := time.Now().Format("20060102-150405")
fileName := fmt.Sprintf("email-%s-%d.json", timestamp, counter)
counter++
fileName := fmt.Sprintf("email-%s.json", timestamp)
err = os.WriteFile(fileName, emailJSON, 0644)
if err != nil {
log.Fatalf("Failed to write file %s: %v", fileName, err)
}
log.Printf("Saved %s", fileName)
}
}