initial commit
This commit is contained in:
commit
dc5d5b0653
|
@ -0,0 +1 @@
|
||||||
|
var/*
|
|
@ -0,0 +1,60 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Webhook is the parent structure fired off to Discord.
|
||||||
|
type Webhook struct {
|
||||||
|
Embeds []Embed `json:"embeds,omitifempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbedField is an individual field being embedded in a message.
|
||||||
|
type EmbedField struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Inline bool `json:"inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbedFooter is the message footer.
|
||||||
|
type EmbedFooter struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
IconURL string `json:"icon_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embed is a set of embedded fields and a footer.
|
||||||
|
type Embed struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Fields []EmbedField `json:"fields"`
|
||||||
|
Footer EmbedFooter `json:"footer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send returns a request for a Discord webhook.
|
||||||
|
func Send(whurl string, w Webhook) *http.Request {
|
||||||
|
data, err := json.Marshal(&w)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, whurl, bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the response from Discord.
|
||||||
|
func Validate(resp *http.Response) error {
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return NewError(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
module tulpa.dev/cadey/snoo2nebby
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require github.com/turnage/graw v0.0.0-20201204201853-a177df1b5c91 // indirect
|
|
@ -0,0 +1,23 @@
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/turnage/graw v0.0.0-20201204201853-a177df1b5c91 h1:vYoyWnsUWuvaLGe6369mItyePB2EVFRjrvkev7xFuGQ=
|
||||||
|
github.com/turnage/graw v0.0.0-20201204201853-a177df1b5c91/go.mod h1:aAkq4I/q1izZSSwHvzhDn9NA+eGxgTSuibwP3MZRlQY=
|
||||||
|
github.com/turnage/redditproto v0.0.0-20151223012412-afedf1b6eddb h1:qR56NGRvs2hTUbkn6QF8bEJzxPIoMw3Np3UigBeJO5A=
|
||||||
|
github.com/turnage/redditproto v0.0.0-20151223012412-afedf1b6eddb/go.mod h1:GyqJdEoZSNoxKDb7Z2Lu/bX63jtFukwpaTP9ZIS5Ei0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
@ -0,0 +1,83 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/turnage/graw/reddit"
|
||||||
|
"github.com/turnage/graw/streams"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UA is the user agent for Reddit
|
||||||
|
const UA = `NixOS:tulpa.dev/cadey/snoo2nebby:v0.1.0 (by /u/shadowh511)`
|
||||||
|
|
||||||
|
var (
|
||||||
|
webhookFile = flag.String("webhook-file", "./var/webhook.txt", "where the Discord webhook file is located")
|
||||||
|
subreddit = flag.String("subreddit", "tulpas", "the subreddit to monitor")
|
||||||
|
pokeFreq = flag.Duration("poke-frequency", 5*time.Minute, "how often the bot should poke the feed")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
script, err := reddit.NewScript(UA, *pokeFreq)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whSlc, err := os.ReadFile(*webhookFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
whURL := string(bytes.TrimSpace(whSlc))
|
||||||
|
|
||||||
|
kill := make(chan bool)
|
||||||
|
errs := make(chan error)
|
||||||
|
|
||||||
|
stream, err := streams.Subreddits(script, kill, errs, *subreddit)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(errs chan error) {
|
||||||
|
for err := range errs {
|
||||||
|
log.Printf("%v", err)
|
||||||
|
}
|
||||||
|
}(errs)
|
||||||
|
|
||||||
|
log.Printf("listening for new posts on /r/%s", *subreddit)
|
||||||
|
|
||||||
|
for post := range stream {
|
||||||
|
log.Printf("got new post: by /u/%s: %q %s", post.Author, post.URL, post.Title)
|
||||||
|
wh := Webhook{
|
||||||
|
Embeds: []Embed{
|
||||||
|
{
|
||||||
|
Title: post.Title,
|
||||||
|
URL: post.URL,
|
||||||
|
Description: post.SelfText,
|
||||||
|
Footer: EmbedFooter{
|
||||||
|
Text: "by /u/" + post.Author,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := Send(whURL, wh)
|
||||||
|
req.Header.Set("User-Agent", UA)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
errs <- fmt.Errorf("can't send webhook: %w", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = Validate(resp)
|
||||||
|
if err != nil {
|
||||||
|
errs <- fmt.Errorf("can't validate response: %w", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewError creates an Error based on an expected HTTP status code vs data populated
|
||||||
|
// from an HTTP response.
|
||||||
|
//
|
||||||
|
// This consumes the body of the HTTP response.
|
||||||
|
func NewError(wantStatusCode int, resp *http.Response) error {
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
loc, err := resp.Location()
|
||||||
|
if err != nil {
|
||||||
|
loc = resp.Request.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Error{
|
||||||
|
WantStatus: wantStatusCode,
|
||||||
|
GotStatus: resp.StatusCode,
|
||||||
|
URL: loc,
|
||||||
|
Method: resp.Request.Method,
|
||||||
|
ResponseBody: string(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is a web response error. Use this when API calls don't work out like you wanted them to.
|
||||||
|
type Error struct {
|
||||||
|
WantStatus, GotStatus int
|
||||||
|
URL *url.URL
|
||||||
|
Method string
|
||||||
|
ResponseBody string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return fmt.Sprintf("%s %s: wanted status code %d, got: %d: %v", e.Method, e.URL, e.WantStatus, e.GotStatus, e.ResponseBody)
|
||||||
|
}
|
Reference in New Issue