147 lines
3.6 KiB
Go
147 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"gitea.com/jolheiser/noncer/announcements"
|
|
"github.com/BrianLeishman/go-imap"
|
|
"github.com/adrg/xdg"
|
|
"github.com/caarlos0/log"
|
|
"github.com/peterbourgon/ff/v3"
|
|
"github.com/peterbourgon/ff/v3/fftoml"
|
|
)
|
|
|
|
func main() {
|
|
if err := run(context.Background()); err != nil {
|
|
log.WithError(err).Fatal("error running noncer")
|
|
}
|
|
}
|
|
|
|
var (
|
|
allowListFlag allowListFlags
|
|
)
|
|
|
|
type allowListFlags []string
|
|
|
|
func (i *allowListFlags) String() string {
|
|
return strings.Join(*i, ",")
|
|
}
|
|
|
|
func (i *allowListFlags) Set(value string) error {
|
|
*i = append(*i, value)
|
|
return nil
|
|
}
|
|
|
|
func run(ctx context.Context) error {
|
|
fs := flag.NewFlagSet("noncer", flag.ExitOnError)
|
|
configFlag := fs.String("config", defaultConfig(), "configuration file")
|
|
fs.StringVar(configFlag, "c", *configFlag, "--config")
|
|
verboseFlag := fs.Bool("verbose", false, "verbose imap output")
|
|
fs.BoolVar(verboseFlag, "v", *verboseFlag, "--verbose")
|
|
userFlag := fs.String("username", "", "imap username")
|
|
fs.StringVar(userFlag, "u", *userFlag, "--username")
|
|
passFlag := fs.String("password", "", "imap password")
|
|
fs.StringVar(passFlag, "pw", *passFlag, "--password")
|
|
hostFlag := fs.String("host", "imap.gmail.com", "imap host")
|
|
fs.StringVar(hostFlag, "h", *hostFlag, "--host")
|
|
portFlag := fs.Int("port", 993, "imap port")
|
|
fs.IntVar(portFlag, "p", *portFlag, "--port")
|
|
intervalFlag := fs.Int("interval", 60, "email fetch interval (seconds)")
|
|
fs.IntVar(intervalFlag, "i", *intervalFlag, "--interval")
|
|
webhookFlag := fs.String("webhook", "", "webhook url")
|
|
fs.StringVar(webhookFlag, "w", *webhookFlag, "--webhook")
|
|
fs.Var(&allowListFlag, "allowlist", "allow list of domains, default is all")
|
|
|
|
if err := ff.Parse(fs, os.Args[1:],
|
|
ff.WithEnvVarPrefix("NONCER"),
|
|
ff.WithConfigFileFlag("config"),
|
|
ff.WithAllowMissingConfigFile(true),
|
|
ff.WithConfigFileParser(fftoml.New().Parse),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if *webhookFlag == "" {
|
|
return fmt.Errorf("webhook url must be provided")
|
|
}
|
|
|
|
imap.Verbose = *verboseFlag
|
|
imap.RetryCount = 3
|
|
im, err := imap.New(*userFlag, *passFlag, *hostFlag, *portFlag)
|
|
if err != nil {
|
|
return fmt.Errorf("could not connect to imap: %w", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
anns := make(chan announcements.Announcement)
|
|
// push announcements to webhook url
|
|
go func() {
|
|
for a := range anns {
|
|
if err := sendWebhook(ctx, *webhookFlag, a); err != nil {
|
|
log.WithError(err).Error("")
|
|
}
|
|
}
|
|
}()
|
|
// listen for announcements
|
|
go func() {
|
|
announcements.Listen(ctx, im, anns, *intervalFlag, allowListFlag)
|
|
}()
|
|
|
|
sc := make(chan os.Signal, 1)
|
|
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
|
|
<-sc
|
|
cancel()
|
|
return nil
|
|
}
|
|
|
|
func sendWebhook(ctx context.Context, webhook string, a announcements.Announcement) error {
|
|
buf := new(bytes.Buffer)
|
|
for i := range a.Contents {
|
|
buf.Reset()
|
|
|
|
msg := Webhook{a.Contents[i]}
|
|
if i == 0 {
|
|
msg = Webhook{fmt.Sprintf("**%s**\n\n%s", a.Subject, a.Contents[i])}
|
|
}
|
|
json.NewEncoder(buf).Encode(msg)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", webhook, buf)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 200 {
|
|
buf.Reset()
|
|
io.Copy(buf, resp.Body)
|
|
return fmt.Errorf("unexpected status code %d:\n%v", resp.StatusCode, buf.String())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Webhook struct {
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
func defaultConfig() string {
|
|
fp, _ := xdg.ConfigFile("noncer/config.toml")
|
|
return fp
|
|
}
|