Moved some code around and updated the README
This commit is contained in:
parent
368445e3a5
commit
e9f39d25b4
10 changed files with 135 additions and 149 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -24,3 +24,6 @@ dist/
|
||||||
data
|
data
|
||||||
config
|
config
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
|
# Config/data files
|
||||||
|
*.json
|
||||||
|
|
18
README.md
18
README.md
|
@ -17,7 +17,7 @@ This Software only supports Linux.
|
||||||
# Build
|
# Build
|
||||||
|
|
||||||
To cross-compile the software for `i386`, `amd64`, `arm` and `arm64`, run `build.sh`.
|
To cross-compile the software for `i386`, `amd64`, `arm` and `arm64`, run `build.sh`.
|
||||||
You need a go version >= 1.21 and git.
|
You need Go 1.22.x and git.
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ Example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"api_fetch_interval": 600,
|
"api_fetch_interval": 600,
|
||||||
"datafile": "data",
|
"datafile": "data.json",
|
||||||
"enabled_api_endpoints": [
|
"enabled_api_endpoints": [
|
||||||
"bay",
|
"bay",
|
||||||
"bund"
|
"bund"
|
||||||
|
@ -67,9 +67,9 @@ To show debug messages, set the `loglevel` to `3`.
|
||||||
|
|
||||||
## Filters
|
## Filters
|
||||||
|
|
||||||
You define filters for notices to be sent per recipient. Multiple filters can be set per recipient and multiple criteria can be used per filter. The configuration field for those filters is `include`. See [Configuration](#configuration) for an example.
|
You define filters for notices to be sent (per recipient). Multiple filters can be set per recipient and multiple criteria can be used per filter. The configuration field for those filters is `include`. See [Configuration](#configuration) for an example.
|
||||||
|
|
||||||
It is determined by the following logic, if a notice is included:
|
If a notice is included is determined by the following logic:
|
||||||
|
|
||||||
```
|
```
|
||||||
{criteria, criteria, ... ALL APPLY}
|
{criteria, criteria, ... ALL APPLY}
|
||||||
|
@ -77,7 +77,7 @@ OR {criteria, criteria, ... ALL APPLY}
|
||||||
OR ...
|
OR ...
|
||||||
```
|
```
|
||||||
|
|
||||||
The following criteria are available. Criteria marked with `*` are for optional fields that are not supported by every API endpoint (e.g. https://wid.lsi.bayern.de) - notices from those endpoints will therefore not be included when using those criteria in filters.
|
The following criteria are available. Criteria marked with * are optional fields that are not supported by every API endpoint (e.g. https://wid.lsi.bayern.de) - notices from those endpoints will therefore not be included when using those criteria in filters.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"include": [
|
"include": [
|
||||||
|
@ -122,7 +122,7 @@ Classification can be `"kritisch"`, `"hoch"`, `"mittel"` or `"niedrig"`.
|
||||||
```
|
```
|
||||||
If set to `""`, this criteria will be ignored.
|
If set to `""`, this criteria will be ignored.
|
||||||
|
|
||||||
### min_basescore `*`
|
### min_basescore *
|
||||||
|
|
||||||
Include notices whose basescore (`0` - `100`) is >= `min_basescore`.
|
Include notices whose basescore (`0` - `100`) is >= `min_basescore`.
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ Include notices whose basescore (`0` - `100`) is >= `min_basescore`.
|
||||||
```
|
```
|
||||||
This criteria will be ignored if set to `0`.
|
This criteria will be ignored if set to `0`.
|
||||||
|
|
||||||
### status `*`
|
### status *
|
||||||
|
|
||||||
Include notices with this status. This is usually either `NEU` or `UPDATE`.
|
Include notices with this status. This is usually either `NEU` or `UPDATE`.
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ Include notices with this status. This is usually either `NEU` or `UPDATE`.
|
||||||
```
|
```
|
||||||
If set to `""`, this criteria will be ignored.
|
If set to `""`, this criteria will be ignored.
|
||||||
|
|
||||||
### products_contain `*`
|
### products_contain *
|
||||||
|
|
||||||
Include notices whose product list contains this text.
|
Include notices whose product list contains this text.
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ Include notices whose product list contains this text.
|
||||||
```
|
```
|
||||||
If set to `""`, this criteria will be ignored.
|
If set to `""`, this criteria will be ignored.
|
||||||
|
|
||||||
### no_patch `*`
|
### no_patch *
|
||||||
|
|
||||||
If set to `"true"`, notices where no patch is available will be included.
|
If set to `"true"`, notices where no patch is available will be included.
|
||||||
|
|
||||||
|
|
60
config.go
Normal file
60
config.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) 2024 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ApiFetchInterval int `json:"api_fetch_interval"` // in seconds
|
||||||
|
EnabledApiEndpoints []string `json:"enabled_api_endpoints"`
|
||||||
|
PersistentDataFilePath string `json:"datafile"`
|
||||||
|
LogLevel int `json:"loglevel"`
|
||||||
|
Recipients []Recipient `json:"recipients"`
|
||||||
|
SmtpConfiguration SmtpSettings `json:"smtp"`
|
||||||
|
Template MailTemplateConfig `json:"template"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig() Config {
|
||||||
|
// Initial config
|
||||||
|
c := Config{
|
||||||
|
ApiFetchInterval: 60 * 10, // every 10 minutes,
|
||||||
|
EnabledApiEndpoints: []string{"bay", "bund"},
|
||||||
|
PersistentDataFilePath: "data.json",
|
||||||
|
LogLevel: 2,
|
||||||
|
Recipients: []Recipient{},
|
||||||
|
SmtpConfiguration: SmtpSettings{
|
||||||
|
From: "from@example.org",
|
||||||
|
User: "from@example.org",
|
||||||
|
Password: "SiEhAbEnMiChInSgEsIcHtGeFiLmTdAsDüRfEnSiEnIcHt",
|
||||||
|
ServerHost: "example.org",
|
||||||
|
ServerPort: 587},
|
||||||
|
Template: MailTemplateConfig{
|
||||||
|
SubjectTemplate: "",
|
||||||
|
BodyTemplate: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConfig(config Config) {
|
||||||
|
if len(config.Recipients) < 1 {
|
||||||
|
logger.error("Configuration is incomplete")
|
||||||
|
panic(errors.New("no recipients are configured"))
|
||||||
|
}
|
||||||
|
for _, r := range config.Recipients {
|
||||||
|
if !mailAddressIsValid(r.Address) {
|
||||||
|
logger.error("Configuration includes invalid data")
|
||||||
|
panic(errors.New("'" + r.Address + "' is not a valid e-mail address"))
|
||||||
|
}
|
||||||
|
if len(r.Filters) < 1 {
|
||||||
|
logger.error("Configuration is incomplete")
|
||||||
|
panic(errors.New("recipient " + r.Address + " has no filter defined - at least {'any': true/false} should be configured"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !mailAddressIsValid(config.SmtpConfiguration.From) {
|
||||||
|
logger.error("Configuration includes invalid data")
|
||||||
|
panic(errors.New("'" + config.SmtpConfiguration.From + "' is not a valid e-mail address"))
|
||||||
|
}
|
||||||
|
}
|
85
datastore.go
85
datastore.go
|
@ -4,79 +4,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
ApiFetchInterval int `json:"api_fetch_interval"` // in seconds
|
|
||||||
EnabledApiEndpoints []string `json:"enabled_api_endpoints"`
|
|
||||||
PersistentDataFilePath string `json:"datafile"`
|
|
||||||
LogLevel int `json:"loglevel"`
|
|
||||||
Recipients []Recipient `json:"recipients"`
|
|
||||||
SmtpConfiguration SmtpSettings `json:"smtp"`
|
|
||||||
Template MailTemplateConfig `json:"template"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfig() Config {
|
|
||||||
// Initial config
|
|
||||||
c := Config{
|
|
||||||
ApiFetchInterval: 60 * 10, // every 10 minutes,
|
|
||||||
EnabledApiEndpoints: []string{"bay", "bund"},
|
|
||||||
PersistentDataFilePath: "data",
|
|
||||||
LogLevel: 2,
|
|
||||||
Recipients: []Recipient{},
|
|
||||||
SmtpConfiguration: SmtpSettings{
|
|
||||||
From: "from@example.org",
|
|
||||||
User: "from@example.org",
|
|
||||||
Password: "SiEhAbEnMiChInSgEsIcHtGeFiLmTdAsDüRfEnSiEnIcHt",
|
|
||||||
ServerHost: "example.org",
|
|
||||||
ServerPort: 587},
|
|
||||||
Template: MailTemplateConfig{
|
|
||||||
SubjectTemplate: "",
|
|
||||||
BodyTemplate: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkConfig(config Config) {
|
|
||||||
if len(config.Recipients) < 1 {
|
|
||||||
logger.error("Configuration is incomplete")
|
|
||||||
panic(errors.New("no recipients are configured"))
|
|
||||||
}
|
|
||||||
for _, r := range config.Recipients {
|
|
||||||
if !mailAddressIsValid(r.Address) {
|
|
||||||
logger.error("Configuration includes invalid data")
|
|
||||||
panic(errors.New("'" + r.Address + "' is not a valid e-mail address"))
|
|
||||||
}
|
|
||||||
if len(r.Filters) < 1 {
|
|
||||||
logger.error("Configuration is incomplete")
|
|
||||||
panic(errors.New("recipient " + r.Address + " has no filter defined - at least {'any': true/false} should be configured"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !mailAddressIsValid(config.SmtpConfiguration.From) {
|
|
||||||
logger.error("Configuration includes invalid data")
|
|
||||||
panic(errors.New("'" + config.SmtpConfiguration.From + "' is not a valid e-mail address"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PersistentData struct {
|
|
||||||
// {endpoint id 1: time last published, endpoint id 2: ..., ...}
|
|
||||||
LastPublished map[string]time.Time `json:"last_published"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPersistentData(c Config) PersistentData {
|
|
||||||
// Initial persistent data
|
|
||||||
d := PersistentData{LastPublished: map[string]time.Time{}}
|
|
||||||
for _, e := range apiEndpoints {
|
|
||||||
d.LastPublished[e.Id] = time.Now().Add(-time.Hour * 24) // a day ago
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataStore struct {
|
type DataStore struct {
|
||||||
filepath string
|
filepath string
|
||||||
prettyJSON bool
|
prettyJSON bool
|
||||||
|
@ -92,32 +23,24 @@ func (ds *DataStore) save() error {
|
||||||
} else {
|
} else {
|
||||||
data, err = json.Marshal(ds.data)
|
data, err = json.Marshal(ds.data)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.WriteFile(ds.filepath, data, ds.fileMode)
|
err = os.WriteFile(ds.filepath, data, ds.fileMode)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DataStore) load() error {
|
func (ds *DataStore) load() error {
|
||||||
data, err := os.ReadFile(ds.filepath)
|
data, err := os.ReadFile(ds.filepath)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch ds.data.(type) {
|
switch ds.data.(type) {
|
||||||
case Config:
|
case Config:
|
||||||
d, _ := ds.data.(Config);
|
d, _ := ds.data.(Config);
|
||||||
err = json.Unmarshal(data, &d)
|
err = json.Unmarshal(data, &d)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
ds.data = d
|
ds.data = d
|
||||||
case PersistentData:
|
case PersistentData:
|
||||||
d, _ := ds.data.(PersistentData);
|
d, _ := ds.data.(PersistentData);
|
||||||
err = json.Unmarshal(data, &d)
|
err = json.Unmarshal(data, &d)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
ds.data = d
|
ds.data = d
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,3 +1,3 @@
|
||||||
module github.com/ChaoticByte/wid-notifier
|
module github.com/ChaoticByte/wid-notifier
|
||||||
|
|
||||||
go 1.22.2
|
go 1.22
|
||||||
|
|
51
mail.go
51
mail.go
|
@ -8,7 +8,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"slices"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,22 +48,12 @@ type SmtpSettings struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, cache *map[string][]byte) error {
|
func (r Recipient) sendNotices(notices []WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, cache *map[string][]byte) error {
|
||||||
filteredNotices := []WidNotice{}
|
|
||||||
for _, f := range r.Filters {
|
|
||||||
for _, n := range f.filter(notices) {
|
|
||||||
if !noticeSliceContains(filteredNotices, n) {
|
|
||||||
filteredNotices = append(filteredNotices, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slices.Reverse(filteredNotices)
|
|
||||||
logger.debug(fmt.Sprintf("Including %v of %v notices for recipient %v", len(filteredNotices), len(notices), r.Address))
|
|
||||||
logger.debug("Generating and sending mails to " + r.Address + " ...")
|
logger.debug("Generating and sending mails to " + r.Address + " ...")
|
||||||
cacheHits := 0
|
cacheHits := 0
|
||||||
cacheMisses := 0
|
cacheMisses := 0
|
||||||
mails := [][]byte{}
|
mails := [][]byte{}
|
||||||
for _, n := range filteredNotices {
|
for _, n := range notices {
|
||||||
var data []byte
|
var data []byte
|
||||||
cacheResult := (*cache)[n.Uuid]
|
cacheResult := (*cache)[n.Uuid]
|
||||||
if len(cacheResult) > 0 {
|
if len(cacheResult) > 0 {
|
||||||
|
@ -91,9 +80,7 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla
|
||||||
r.Address,
|
r.Address,
|
||||||
mails,
|
mails,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.debug("Successfully sent all mails to " + r.Address)
|
logger.debug("Successfully sent all mails to " + r.Address)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -102,50 +89,34 @@ func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte)
|
||||||
addr := fmt.Sprintf("%v:%v", smtpConf.ServerHost, smtpConf.ServerPort)
|
addr := fmt.Sprintf("%v:%v", smtpConf.ServerHost, smtpConf.ServerPort)
|
||||||
logger.debug("Connecting to mail server at " + addr + " ...")
|
logger.debug("Connecting to mail server at " + addr + " ...")
|
||||||
connection, err := smtp.Dial(addr)
|
connection, err := smtp.Dial(addr)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer connection.Close()
|
defer connection.Close()
|
||||||
// can leave out connection.Hello
|
// can leave out connection.Hello
|
||||||
hasTlsExt, _ := connection.Extension("starttls")
|
hasTlsExt, _ := connection.Extension("starttls")
|
||||||
if hasTlsExt {
|
if hasTlsExt {
|
||||||
err = connection.StartTLS(&tls.Config{ServerName: smtpConf.ServerHost})
|
err = connection.StartTLS(&tls.Config{ServerName: smtpConf.ServerHost})
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.debug("Mail Server supports TLS")
|
logger.debug("Mail Server supports TLS")
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Mail Server doesn't support TLS")
|
logger.debug("Mail Server doesn't support TLS")
|
||||||
}
|
}
|
||||||
logger.debug("Authenticating to mail server ...")
|
logger.debug("Authenticating to mail server ...")
|
||||||
err = connection.Auth(auth)
|
err = connection.Auth(auth)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
if logger.LogLevel >= 3 {
|
if logger.LogLevel >= 3 {
|
||||||
fmt.Printf("DEBUG %v Sending mails to server ", time.Now().Format("2006/01/02 15:04:05.000000"))
|
fmt.Printf("DEBUG %v Sending mails to server ", time.Now().Format("2006/01/02 15:04:05.000000"))
|
||||||
}
|
}
|
||||||
for _, d := range data {
|
for _, d := range data {
|
||||||
err = connection.Mail(smtpConf.From)
|
err = connection.Mail(smtpConf.From)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = connection.Rcpt(to)
|
err = connection.Rcpt(to)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
writer, err := connection.Data()
|
writer, err := connection.Data()
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write(d)
|
_, err = writer.Write(d)
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = writer.Close()
|
err = writer.Close()
|
||||||
if err != nil {
|
if err != nil { return err }
|
||||||
return err
|
|
||||||
}
|
|
||||||
if logger.LogLevel >= 3 {
|
if logger.LogLevel >= 3 {
|
||||||
print(".")
|
print(".")
|
||||||
}
|
}
|
||||||
|
|
32
main.go
32
main.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,17 +25,13 @@ func main() {
|
||||||
// init
|
// init
|
||||||
logger.info("Initializing ...")
|
logger.info("Initializing ...")
|
||||||
defer logger.info("Exiting ...")
|
defer logger.info("Exiting ...")
|
||||||
|
// open & check config
|
||||||
config := NewDataStore(
|
config := NewDataStore(
|
||||||
configFilePath,
|
configFilePath,
|
||||||
NewConfig(),
|
NewConfig(),
|
||||||
true,
|
true,
|
||||||
0600,
|
0600,
|
||||||
).data.(Config)
|
).data.(Config)
|
||||||
persistent := NewDataStore(
|
|
||||||
config.PersistentDataFilePath,
|
|
||||||
NewPersistentData(config),
|
|
||||||
false,
|
|
||||||
0640)
|
|
||||||
logger.LogLevel = config.LogLevel
|
logger.LogLevel = config.LogLevel
|
||||||
logger.debug("Checking configuration file ...")
|
logger.debug("Checking configuration file ...")
|
||||||
checkConfig(config)
|
checkConfig(config)
|
||||||
|
@ -66,6 +63,12 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// open data file
|
||||||
|
persistent := NewDataStore(
|
||||||
|
config.PersistentDataFilePath,
|
||||||
|
NewPersistentData(config),
|
||||||
|
false,
|
||||||
|
0640)
|
||||||
// main loop
|
// main loop
|
||||||
logger.debug("Entering main loop ...")
|
logger.debug("Entering main loop ...")
|
||||||
for {
|
for {
|
||||||
|
@ -76,7 +79,7 @@ func main() {
|
||||||
logger.info("Querying endpoint '" + a.Id + "' for new notices ...")
|
logger.info("Querying endpoint '" + a.Id + "' for new notices ...")
|
||||||
n, t, err := a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
n, t, err := a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// retry
|
// retry (once)
|
||||||
logger.warn("Couldn't query notices from API endpoint '" + a.Id + "'. Retrying ...")
|
logger.warn("Couldn't query notices from API endpoint '" + a.Id + "'. Retrying ...")
|
||||||
logger.warn(err)
|
logger.warn(err)
|
||||||
n, t, err = a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
n, t, err = a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
||||||
|
@ -96,7 +99,19 @@ func main() {
|
||||||
logger.info("Sending email notifications ...")
|
logger.info("Sending email notifications ...")
|
||||||
recipientsNotified := 0
|
recipientsNotified := 0
|
||||||
for _, r := range config.Recipients {
|
for _, r := range config.Recipients {
|
||||||
err := r.filterAndSendNotices(newNotices, mailTemplate, mailAuth, config.SmtpConfiguration, &cache)
|
// Filter notices for this recipient
|
||||||
|
filteredNotices := []WidNotice{}
|
||||||
|
for _, f := range r.Filters {
|
||||||
|
for _, n := range f.filter(newNotices) {
|
||||||
|
if !noticeSliceContains(filteredNotices, n) {
|
||||||
|
filteredNotices = append(filteredNotices, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slices.Reverse(filteredNotices)
|
||||||
|
logger.debug(fmt.Sprintf("Including %v of %v notices for recipient %v", len(filteredNotices), len(newNotices), r.Address))
|
||||||
|
// Send notices
|
||||||
|
err := r.sendNotices(filteredNotices, mailTemplate, mailAuth, config.SmtpConfiguration, &cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -105,8 +120,7 @@ func main() {
|
||||||
}
|
}
|
||||||
logger.info(fmt.Sprintf("Email notifications sent to %v of %v recipients", recipientsNotified, len(config.Recipients)))
|
logger.info(fmt.Sprintf("Email notifications sent to %v of %v recipients", recipientsNotified, len(config.Recipients)))
|
||||||
}
|
}
|
||||||
t2 := time.Now().UnixMilli()
|
dt := int(time.Now().UnixMilli() - t1)
|
||||||
dt := int(t2 - t1)
|
|
||||||
time.Sleep(time.Millisecond * time.Duration((config.ApiFetchInterval * 1000) - dt))
|
time.Sleep(time.Millisecond * time.Duration((config.ApiFetchInterval * 1000) - dt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
persistent_data.go
Normal file
21
persistent_data.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) 2024 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PersistentData struct {
|
||||||
|
// {endpoint id 1: time last published, endpoint id 2: ..., ...}
|
||||||
|
LastPublished map[string]time.Time `json:"last_published"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPersistentData(c Config) PersistentData {
|
||||||
|
// Initial persistent data
|
||||||
|
d := PersistentData{LastPublished: map[string]time.Time{}}
|
||||||
|
for _, e := range apiEndpoints {
|
||||||
|
d.LastPublished[e.Id] = time.Now().Add(-time.Hour * 24) // a day ago
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
|
@ -42,15 +42,11 @@ func (t MailTemplate) generate(notice WidNotice) (MailContent, error) {
|
||||||
c := MailContent{}
|
c := MailContent{}
|
||||||
buffer := &bytes.Buffer{}
|
buffer := &bytes.Buffer{}
|
||||||
err := t.SubjectTemplate.Execute(buffer, notice)
|
err := t.SubjectTemplate.Execute(buffer, notice)
|
||||||
if err != nil {
|
if err != nil { return c, err }
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
c.Subject = buffer.String()
|
c.Subject = buffer.String()
|
||||||
buffer.Truncate(0) // we can recycle our buffer
|
buffer.Truncate(0) // we can recycle our buffer
|
||||||
err = t.BodyTemplate.Execute(buffer, notice)
|
err = t.BodyTemplate.Execute(buffer, notice)
|
||||||
if err != nil {
|
if err != nil { return c, err }
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
c.Body = buffer.String()
|
c.Body = buffer.String()
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,7 @@ func (e ApiEndpoint) getNotices(since time.Time) ([]WidNotice, time.Time, error)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if res.StatusCode == 200 {
|
if res.StatusCode == 200 {
|
||||||
resBody, err := io.ReadAll(res.Body)
|
resBody, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil { return []WidNotice{}, since, err }
|
||||||
return []WidNotice{}, since, err
|
|
||||||
}
|
|
||||||
var decodedData map[string]interface{}
|
var decodedData map[string]interface{}
|
||||||
if err = json.Unmarshal(resBody, &decodedData); err != nil {
|
if err = json.Unmarshal(resBody, &decodedData); err != nil {
|
||||||
return []WidNotice{}, since, err
|
return []WidNotice{}, since, err
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue