Skip to content

Commit

Permalink
Moved some code around and updated the README
Browse files Browse the repository at this point in the history
  • Loading branch information
ChaoticByte committed Jul 22, 2024
1 parent 368445e commit e9f39d2
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 149 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ dist/
data
config
*.bak

# Config/data files
*.json
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This Software only supports Linux.
# Build

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

Expand All @@ -34,7 +34,7 @@ Example:
```json
{
"api_fetch_interval": 600,
"datafile": "data",
"datafile": "data.json",
"enabled_api_endpoints": [
"bay",
"bund"
Expand Down Expand Up @@ -67,17 +67,17 @@ To show debug messages, set the `loglevel` to `3`.

## 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}
OR {criteria, criteria, ... ALL APPLY}
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
"include": [
Expand Down Expand Up @@ -122,7 +122,7 @@ Classification can be `"kritisch"`, `"hoch"`, `"mittel"` or `"niedrig"`.
```
If set to `""`, this criteria will be ignored.

### min_basescore `*`
### min_basescore *

Include notices whose basescore (`0` - `100`) is >= `min_basescore`.

Expand All @@ -131,7 +131,7 @@ Include notices whose basescore (`0` - `100`) is >= `min_basescore`.
```
This criteria will be ignored if set to `0`.

### status `*`
### status *

Include notices with this status. This is usually either `NEU` or `UPDATE`.

Expand All @@ -140,7 +140,7 @@ Include notices with this status. This is usually either `NEU` or `UPDATE`.
```
If set to `""`, this criteria will be ignored.

### products_contain `*`
### products_contain *

Include notices whose product list contains this text.

Expand All @@ -149,7 +149,7 @@ Include notices whose product list contains this text.
```
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.

Expand Down
60 changes: 60 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -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: "[email protected]",
User: "[email protected]",
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 changes: 4 additions & 81 deletions datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,10 @@ package main

import (
"encoding/json"
"errors"
"io/fs"
"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: "[email protected]",
User: "[email protected]",
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 {
filepath string
prettyJSON bool
Expand All @@ -92,32 +23,24 @@ func (ds *DataStore) save() error {
} else {
data, err = json.Marshal(ds.data)
}
if err != nil {
return err
}
if err != nil { return err }
err = os.WriteFile(ds.filepath, data, ds.fileMode)
return err
}

func (ds *DataStore) load() error {
data, err := os.ReadFile(ds.filepath)
if err != nil {
return err
}
if err != nil { return err }
switch ds.data.(type) {
case Config:
d, _ := ds.data.(Config);
err = json.Unmarshal(data, &d)
if err != nil {
return err
}
if err != nil { return err }
ds.data = d
case PersistentData:
d, _ := ds.data.(PersistentData);
err = json.Unmarshal(data, &d)
if err != nil {
return err
}
if err != nil { return err }
ds.data = d
}
return err
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/ChaoticByte/wid-notifier

go 1.22.2
go 1.22
51 changes: 11 additions & 40 deletions mail.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"fmt"
"net/mail"
"net/smtp"
"slices"
"time"
)

Expand Down Expand Up @@ -49,22 +48,12 @@ type SmtpSettings struct {
Password string `json:"password"`
}

func (r Recipient) filterAndSendNotices(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))
func (r Recipient) sendNotices(notices []WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, cache *map[string][]byte) error {
logger.debug("Generating and sending mails to " + r.Address + " ...")
cacheHits := 0
cacheMisses := 0
mails := [][]byte{}
for _, n := range filteredNotices {
for _, n := range notices {
var data []byte
cacheResult := (*cache)[n.Uuid]
if len(cacheResult) > 0 {
Expand All @@ -91,9 +80,7 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla
r.Address,
mails,
)
if err != nil {
return err
}
if err != nil { return err }
logger.debug("Successfully sent all mails to " + r.Address)
return nil
}
Expand All @@ -102,50 +89,34 @@ func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte)
addr := fmt.Sprintf("%v:%v", smtpConf.ServerHost, smtpConf.ServerPort)
logger.debug("Connecting to mail server at " + addr + " ...")
connection, err := smtp.Dial(addr)
if err != nil {
return err
}
if err != nil { return err }
defer connection.Close()
// can leave out connection.Hello
hasTlsExt, _ := connection.Extension("starttls")
if hasTlsExt {
err = connection.StartTLS(&tls.Config{ServerName: smtpConf.ServerHost})
if err != nil {
return err
}
if err != nil { return err }
logger.debug("Mail Server supports TLS")
} else {
logger.debug("Mail Server doesn't support TLS")
}
logger.debug("Authenticating to mail server ...")
err = connection.Auth(auth)
if err != nil {
return err
}
if err != nil { return err }
if logger.LogLevel >= 3 {
fmt.Printf("DEBUG %v Sending mails to server ", time.Now().Format("2006/01/02 15:04:05.000000"))
}
for _, d := range data {
err = connection.Mail(smtpConf.From)
if err != nil {
return err
}
if err != nil { return err }
err = connection.Rcpt(to)
if err != nil {
return err
}
if err != nil { return err }
writer, err := connection.Data()
if err != nil {
return err
}
if err != nil { return err }
_, err = writer.Write(d)
if err != nil {
return err
}
if err != nil { return err }
err = writer.Close()
if err != nil {
return err
}
if err != nil { return err }
if logger.LogLevel >= 3 {
print(".")
}
Expand Down
Loading

0 comments on commit e9f39d2

Please sign in to comment.