noncer/main.go
jolheiser cef03746c1
feat: update for Gitea
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2022-11-04 11:52:29 -05:00

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
}