From a76f6bfb0ce263933824607bf3531f2aefdd8a81 Mon Sep 17 00:00:00 2001 From: Simon Rieger Date: Thu, 23 Nov 2023 14:04:44 +0100 Subject: [PATCH] First Commit --- .idea/.gitignore | 8 ++ .idea/mail-reminder.iml | 9 +++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ Dockerfile | 30 +++++++ README.md | 58 ++++++++++++++ cron-job.sh | 3 + docker-compose.yml | 15 ++++ go.mod | 16 ++++ go.sum | 27 +++++++ ics/subdir/test.ics | 49 +++++++++++ ics/test.ics | 49 +++++++++++ main.go | 174 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 452 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/mail-reminder.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 cron-job.sh create mode 100644 docker-compose.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 ics/subdir/test.ics create mode 100644 ics/test.ics create mode 100644 main.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/mail-reminder.iml b/.idea/mail-reminder.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/mail-reminder.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..9da5d9b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bdebb2f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# syntax=docker/dockerfile:1 + +# Build the application from source +FROM golang:1.21.4 AS build-stage + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY *.go ./ + +RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-mail-reminder + +# Run the tests in the container +FROM build-stage AS run-test-stage +RUN go test -v ./... + +# Deploy the application binary into a lean image +FROM gcr.io/distroless/base-debian11 AS build-release-stage + +WORKDIR / + +COPY --from=build-stage /docker-mail-reminder /docker-mail-reminder + +#EXPOSE 8080 + +USER nonroot:nonroot + +ENTRYPOINT ["/docker-mail-reminder"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..6aaca08 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Your Go Application + +This is a simple Go application that does iCalendar files (.ics) analyzes and sends notifications by email based on the events in these iCalendar files. + +## Prerequisites + +Before you begin, ensure you have the following installed: + +- [Docker](https://docs.docker.com/get-docker/) +- [Docker Compose](https://docs.docker.com/compose/install/) + +## Getting Started + +1. **Clone the repository:** + + ```bash + git clone + ``` + +2. **Create an environment variables file:** + + Create a file named `.env` in your project directory. Fill this file with the required environment variables: + + ```env + FROM_EMAIL=user@example.com + TO_EMAIL=recipient@example.com + EMAIL_PASSWORD=your_password + SMTP_HOST=smtp.example.com + SMTP_PORT=587 + ICS_DIR=/path/to/ics/files + ``` + + Customize the values according to your application. + +3. **Build and run the Docker container:** + + ```bash + docker-compose up --build + ``` + + This command uses Docker Compose to build and run the container, loading environment variables from the `.env` file. + +4. **Access your application:** + + Open your web browser and go to [http://localhost:8080](http://localhost:8080). + +## Customizing the Docker Image + +- If your application uses additional environment variables, add them to the `.env` file. +- Customize the Dockerfile or docker-compose.yml if needed. + +## Contributing + +If you'd like to contribute, please fork the repository and create a pull request. Feel free to open an issue if you encounter any problems or have suggestions. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/cron-job.sh b/cron-job.sh new file mode 100644 index 0000000..3c17e8c --- /dev/null +++ b/cron-job.sh @@ -0,0 +1,3 @@ +#!/bin/sh +echo "0 6 * * * /usr/local/bin/docker-compose -f /opt/containers/mail-reminder/docker-compose.yml up --build --exit-code-from go-app" > /etc/crontabs/root +crond -f -l 8 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..39e2553 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3' + +services: + go-app: + build: + context: . + env_file: .env + volumes: + - /opt/containers/mailu/dav/collection-root:/dav:ro + + cron: + image: alpine:latest + volumes: + - ./cron-job.sh:/etc/periodic/daily/cron-job + command: ["crond", "-f", "-d", "8"] \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f1237ba --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module mail-reminder + +go 1.21 + +require ( + github.com/apognu/gocal v0.9.0 + github.com/sirupsen/logrus v1.9.3 + gopkg.in/mail.v2 v2.3.1 +) + +require ( + github.com/ChannelMeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5205731 --- /dev/null +++ b/go.sum @@ -0,0 +1,27 @@ +github.com/ChannelMeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:N5Vqww5QISEHsWHOWDEx4PzdIay3Cg0Jp7zItq2ZAro= +github.com/ChannelMeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:GnKXcK+7DYNy/8w2Ex//Uql4IgfaU82Cd5rWKb7ah00= +github.com/apognu/gocal v0.9.0 h1:2lGdZprjYs9A6l1RTEmapmpE1PiDbXNX8bUVqZt3vm4= +github.com/apognu/gocal v0.9.0/go.mod h1:ZOJfNOqpz8aasi3uqzDu+eWTT6VuEa/TvQWiYYWlb80= +github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:o64h9XF42kVEUuhuer2ehqrlX8rZmvQSU0+Vpj1rF6Q= +github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ics/subdir/test.ics b/ics/subdir/test.ics new file mode 100644 index 0000000..d34baea --- /dev/null +++ b/ics/subdir/test.ics @@ -0,0 +1,49 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//ical.marudot.com//iCal Event Maker +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/Berlin +LAST-MODIFIED:20230407T050750Z +TZURL:https://www.tzurl.org/zoneinfo-outlook/Europe/Berlin +X-LIC-LOCATION:Europe/Berlin +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20231123T113949Z +UID:1700739536627-74911@ical.marudot.com +DTSTART;TZID=Europe/Berlin:20231123T120000 +DTEND;TZID=Europe/Berlin:20231123T120000 +SUMMARY:test1 +DESCRIPTION:test1 +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20231123T113949Z +UID:1700739551336-72964@ical.marudot.com +DTSTART;TZID=Europe/Berlin:20231123T120000 +DTEND;TZID=Europe/Berlin:20231124T120000 +SUMMARY:test2 +DESCRIPTION:test2 +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20231123T113949Z +UID:1700739565150-71554@ical.marudot.com +DTSTART;TZID=Europe/Berlin:20231124T120000 +DTEND;TZID=Europe/Berlin:20231125T120000 +SUMMARY:test3 +DESCRIPTION:test3 +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/ics/test.ics b/ics/test.ics new file mode 100644 index 0000000..d34baea --- /dev/null +++ b/ics/test.ics @@ -0,0 +1,49 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//ical.marudot.com//iCal Event Maker +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/Berlin +LAST-MODIFIED:20230407T050750Z +TZURL:https://www.tzurl.org/zoneinfo-outlook/Europe/Berlin +X-LIC-LOCATION:Europe/Berlin +BEGIN:DAYLIGHT +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:CET +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20231123T113949Z +UID:1700739536627-74911@ical.marudot.com +DTSTART;TZID=Europe/Berlin:20231123T120000 +DTEND;TZID=Europe/Berlin:20231123T120000 +SUMMARY:test1 +DESCRIPTION:test1 +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20231123T113949Z +UID:1700739551336-72964@ical.marudot.com +DTSTART;TZID=Europe/Berlin:20231123T120000 +DTEND;TZID=Europe/Berlin:20231124T120000 +SUMMARY:test2 +DESCRIPTION:test2 +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20231123T113949Z +UID:1700739565150-71554@ical.marudot.com +DTSTART;TZID=Europe/Berlin:20231124T120000 +DTEND;TZID=Europe/Berlin:20231125T120000 +SUMMARY:test3 +DESCRIPTION:test3 +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..1e1d178 --- /dev/null +++ b/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "crypto/tls" + "fmt" + "github.com/apognu/gocal" + log "github.com/sirupsen/logrus" + gomail "gopkg.in/mail.v2" + "os" + "strconv" + "strings" + "time" +) + +var ( + fromString string + toAddresses []string + passwordString string + hostString string + hostPortString int +) + +func main() { + // Configure logrus + log.SetFormatter(&log.TextFormatter{}) + log.SetOutput(os.Stdout) + log.SetLevel(log.InfoLevel) + + log.Info("Hello World!") + + // Initialisiere err mit einem Fehlerwert + var err error + + // Read environment variables + fromString = os.Getenv("FROM_EMAIL") + toAddresses = strings.Split(os.Getenv("TO_EMAIL"), ",") + passwordString = os.Getenv("EMAIL_PASSWORD") + hostString = os.Getenv("SMTP_HOST") + hostPortString, err = strconv.Atoi(os.Getenv("SMTP_PORT")) + + // Überprüfung auf Fehler + if err != nil { + fmt.Println("Fehler bei der Umwandlung. Verwende Standardwert 587.") + // Setze einen Standardwert, zum Beispiel 0 + hostPortString = 587 + } + + // Get the current working directory + dir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + log.Infof("Current Working Directory is = %s", dir) + + // Read and split multiple folders + folderList := os.Getenv("ICS_DIR") + if folderList == "" || fromString == "" || len(toAddresses) == 0 || passwordString == "" || hostString == "" { + log.Fatal("Es fehlen noch einige Parameter!!!\nICS_DIR, FROM_EMAIL, TO_EMAIL, EMAIL_PASSWORD, SMTP_HOST") + } + + folders := strings.Split(folderList, ",") + for _, folder := range folders { + log.Infof("folder: %s from: %s to: %s password: %s host: %s", folder, fromString, toAddresses, passwordString, hostString) + listFilesForFolder(folder) + } +} + +func listFilesForFolder(folder string) { + // Öffne das Verzeichnis + dir, err := os.Open(folder) + if err != nil { + log.Fatal("Fehler beim Öffnen des Ordners:", err) + return + } + defer dir.Close() + + // Lies alle Dateien im Verzeichnis + dateien, err := dir.Readdir(0) + if err != nil { + log.Fatal("Fehler beim Lesen des Verzeichnisses:", err) + return + } + + // Durchlaufe die Liste der Dateien und gebe ihre Namen aus + for _, datei := range dateien { + // Überprüfe, ob es sich um ein Verzeichnis handelt. Wenn ja, ignoriere es. + if datei.IsDir() { + continue + } + + // Hier kannst du die Dateinamen ausgeben oder damit arbeiten. + log.Println(datei.Name()) + + getNotifications(folder + "/" + datei.Name()) + } +} + +func getNotifications(file string) { + f, err := os.Open(file) + if err != nil { + log.Fatal(err) + } + defer f.Close() + + var tzMapping = map[string]string{ + "My Super Zone": "Europe/Berlin", + } + + gocal.SetTZMapper(func(s string) (*time.Location, error) { + if tzid, ok := tzMapping[s]; ok { + return time.LoadLocation(tzid) + } + return nil, fmt.Errorf("") + }) + + start, end := truncateToDay(time.Now()), truncateToDay(time.Now()).Add(24*60*time.Minute) + + c := gocal.NewParser(f) + c.Start, c.End = &start, &end + c.Parse() + + for _, e := range c.Events { + log.Infof("%s on %s", e.Summary, e.Start) + + messageSubject := fmt.Sprintf("Es existiert für heute ein neuer Kalendereintrag Namens: %s", e.Summary) + log.Println(messageSubject) + messageText := fmt.Sprintf("Der Termin beginnt heute um: %s und endet um: %s.", e.Start, e.End) + if len(e.Location) != 0 { + messageText += fmt.Sprintf("\n\nEr findet in %s statt.", e.Location) + } + if len(e.Description) != 0 { + messageText += fmt.Sprintf("\n\nFolgende Notiz existiert in diesen Eintrag: \n%s", e.Description) + } + messageText += "\n\n This email is a service from mail-reminder Version 1.0 written in Golang. \n Delivered by Simon Rieger" + + for _, toAddress := range toAddresses { + sendMail(messageSubject, messageText, toAddress) + } + } +} + +func truncateToDay(t time.Time) time.Time { + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) +} + +func sendMail(messageSubject string, messageText string, toAddress string) { + m := gomail.NewMessage() + + // Set E-Mail sender + m.SetHeader("From", fromString) + + // Set E-Mail receivers + m.SetHeader("To", toAddress) + + // Set E-Mail subject + m.SetHeader("Subject", messageSubject) + + // Set E-Mail body. You can set plain text or html with text/html + m.SetBody("text/plain", messageText) + + // Settings for SMTP server + d := gomail.NewDialer(hostString, hostPortString, fromString, passwordString) + + // This is only needed when SSL/TLS certificate is not valid on server. + // In production this should be set to false. + d.TLSConfig = &tls.Config{InsecureSkipVerify: true} + + // Now send E-Mail + if err := d.DialAndSend(m); err != nil { + log.Fatal(err) + panic(err) + } + log.Infof("Email Message Sent Successfully") +}