Compare commits
No commits in common. "initial" and "master" have entirely different histories.
154
api.go
154
api.go
|
@ -1,154 +0,0 @@
|
||||||
package beebot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (b *BeeBot) apiEndpoints() http.Handler {
|
|
||||||
r := chi.NewRouter()
|
|
||||||
r.Route("/nav", func(r chi.Router) {
|
|
||||||
r.Get("/", b.getNav)
|
|
||||||
})
|
|
||||||
r.Route("/config", func(r chi.Router) {
|
|
||||||
r.Get("/", b.getConfig)
|
|
||||||
r.Post("/", b.setConfig)
|
|
||||||
r.Delete("/", b.deleteConfig)
|
|
||||||
})
|
|
||||||
r.Route("/filters", func(r chi.Router) {
|
|
||||||
r.Get("/", b.getFilters)
|
|
||||||
r.Post("/", b.postFilters)
|
|
||||||
r.Put("/{name}", b.putFilters)
|
|
||||||
r.Delete("/", b.deleteFilters)
|
|
||||||
})
|
|
||||||
r.Route("/log", func(r chi.Router) {
|
|
||||||
r.Get("/", b.getLog)
|
|
||||||
})
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type configEntry struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) getNav(w http.ResponseWriter, r *http.Request) {
|
|
||||||
j, _ := json.Marshal(b.nav)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) getConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
entries := []configEntry{}
|
|
||||||
err := b.db.Select(&entries, `select key, value from config`)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not get configuration entries")
|
|
||||||
w.WriteHeader(500)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
j, _ := json.Marshal(entries)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) setConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
config := configEntry{}
|
|
||||||
body, _ := ioutil.ReadAll(r.Body)
|
|
||||||
err := json.Unmarshal(body, &config)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not get configuration entries")
|
|
||||||
w.WriteHeader(400)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
err = b.c.Set(config.Key, config.Value)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not set configuration entry")
|
|
||||||
w.WriteHeader(400)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) deleteConfig(w http.ResponseWriter, r *http.Request) {
|
|
||||||
config := configEntry{}
|
|
||||||
body, _ := ioutil.ReadAll(r.Body)
|
|
||||||
err := json.Unmarshal(body, &config)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not get configuration entries")
|
|
||||||
w.WriteHeader(400)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
log.Info().Msgf("Deleting config: %s", config.Key)
|
|
||||||
err = b.c.Unset(config.Key)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not unset configuration entry")
|
|
||||||
w.WriteHeader(400)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
resp, _ := json.Marshal(struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
}{"ok"})
|
|
||||||
w.Write(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) getFilters(w http.ResponseWriter, r *http.Request) {
|
|
||||||
filters, err := b.AllFilters()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not get filters")
|
|
||||||
}
|
|
||||||
j, _ := json.Marshal(filters)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) postFilters(w http.ResponseWriter, r *http.Request) {
|
|
||||||
filter := Filter{}
|
|
||||||
body, _ := ioutil.ReadAll(r.Body)
|
|
||||||
err := json.Unmarshal(body, &filter)
|
|
||||||
filter.populate(b.db)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not read filter entry")
|
|
||||||
w.WriteHeader(400)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
err = filter.Save()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not save filter")
|
|
||||||
w.WriteHeader(400)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
out, err := json.Marshal(filter)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not marshal filter output")
|
|
||||||
w.WriteHeader(500)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
w.Write(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) putFilters(w http.ResponseWriter, r *http.Request) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) deleteFilters(w http.ResponseWriter, r *http.Request) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BeeBot) getLog(w http.ResponseWriter, r *http.Request) {
|
|
||||||
f, _ := os.Open(b.logPath)
|
|
||||||
logs, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not open logs")
|
|
||||||
w.WriteHeader(500)
|
|
||||||
j, _ := json.Marshal(err)
|
|
||||||
w.Write(j)
|
|
||||||
}
|
|
||||||
w.Write(logs)
|
|
||||||
}
|
|
185
beebot.go
185
beebot.go
|
@ -1,185 +0,0 @@
|
||||||
package beebot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.chrissexton.org/cws/BeeBot/config"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/jpillora/backoff"
|
|
||||||
"github.com/jzelinskie/geddit"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultAddr is the HTTP service address
|
|
||||||
const DefaultAddr = "127.0.0.1:9595"
|
|
||||||
|
|
||||||
// BeeBot represents our bot
|
|
||||||
type BeeBot struct {
|
|
||||||
reddit string
|
|
||||||
logPath string
|
|
||||||
|
|
||||||
nav map[string]string
|
|
||||||
debug bool
|
|
||||||
|
|
||||||
db *sqlx.DB
|
|
||||||
c *config.Config
|
|
||||||
|
|
||||||
*geddit.OAuthSession
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a BeeBot instance, its database, and connects to reddit
|
|
||||||
func New(dbFilePath, logFilePath string, debug bool) (*BeeBot, error) {
|
|
||||||
db, err := sqlx.Connect("sqlite", dbFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := config.New(db)
|
|
||||||
|
|
||||||
clientID := c.Get("clientid", "")
|
|
||||||
clientSecret := c.Get("clientsecret", "")
|
|
||||||
userAgent := c.Get("userAgent", "BeeBot")
|
|
||||||
baseAddr := c.Get("baseaddr", DefaultAddr)
|
|
||||||
userName := c.Get("username", "")
|
|
||||||
password := c.Get("password", "")
|
|
||||||
reddit := c.Get("reddit", "")
|
|
||||||
|
|
||||||
o, err := geddit.NewOAuthSession(
|
|
||||||
clientID,
|
|
||||||
clientSecret,
|
|
||||||
userAgent,
|
|
||||||
fmt.Sprintf("http://%s/cb", baseAddr),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = o.LoginAuth(userName, password); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b := &BeeBot{
|
|
||||||
reddit: reddit,
|
|
||||||
logPath: logFilePath,
|
|
||||||
nav: make(map[string]string),
|
|
||||||
debug: debug,
|
|
||||||
db: db,
|
|
||||||
c: c,
|
|
||||||
OAuthSession: o,
|
|
||||||
}
|
|
||||||
|
|
||||||
b.setupDB()
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve starts a polling service with exponential backoff
|
|
||||||
func (b *BeeBot) Serve(dur time.Duration, done chan (bool)) {
|
|
||||||
backoff := &backoff.Backoff{
|
|
||||||
Min: time.Duration(b.c.GetInt("backoff.min", 100)) * time.Millisecond,
|
|
||||||
Max: time.Duration(b.c.GetInt("backoff.max", 10)) * time.Second,
|
|
||||||
Factor: b.c.GetFloat64("backoff.factor", 2),
|
|
||||||
Jitter: b.c.GetInt("backoff.jitter", 1) == 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
timer := time.NewTimer(backoff.Duration())
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
timer.Stop()
|
|
||||||
return
|
|
||||||
case <-timer.C:
|
|
||||||
if err := b.Run(); err == nil {
|
|
||||||
backoff.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run triggers a single query and Filter of the reddit
|
|
||||||
func (b *BeeBot) Run() error {
|
|
||||||
|
|
||||||
offenders := map[string]map[string]bool{}
|
|
||||||
|
|
||||||
filters, err := b.AllFilters()
|
|
||||||
if handleErr(err, "Could not get list of filters") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range filters {
|
|
||||||
offenders[f.Name] = map[string]bool{}
|
|
||||||
tmpOffenders := []string{}
|
|
||||||
err := b.db.Select(&tmpOffenders, "select offender from offenders where type=?", f.Name)
|
|
||||||
if handleErr(err, "could not get %s offenders", f.Name) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, o := range tmpOffenders {
|
|
||||||
offenders[f.Name][o] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
comments, err := b.SubredditComments(b.reddit)
|
|
||||||
if handleErr(err, "could not get subreddit comments for %s", b.reddit) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range comments {
|
|
||||||
for _, f := range filters {
|
|
||||||
if f.regex.MatchString(c.Body) {
|
|
||||||
if _, ok := offenders[f.Name][c.Author]; ok {
|
|
||||||
log.Debug().Msgf("Skipping offender %s", c.Author)
|
|
||||||
} else {
|
|
||||||
offenders[f.Name][c.Author] = true
|
|
||||||
_, err = b.db.Exec(`insert into offenders (offender, type) values (?, ?)`, c.Author, f.Name)
|
|
||||||
if handleErr(err, "could not insert raisin offenders") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Debug().Msgf("Processed %d comments", len(comments))
|
|
||||||
|
|
||||||
subOpts := geddit.ListingOptions{
|
|
||||||
Limit: 10,
|
|
||||||
}
|
|
||||||
posts, err := b.SubredditSubmissions(b.reddit, geddit.NewSubmissions, subOpts)
|
|
||||||
for _, p := range posts {
|
|
||||||
for _, f := range filters {
|
|
||||||
if f.regex.MatchString(p.Title) || f.regex.MatchString(p.Selftext) {
|
|
||||||
if _, ok := offenders[f.Name][p.Author]; ok {
|
|
||||||
log.Debug().Msgf("Skipping offender %s", p.Author)
|
|
||||||
} else {
|
|
||||||
offenders[f.Name][p.Author] = true
|
|
||||||
_, err = b.db.Exec(`insert into offenders (offender, type) values (?, ?)`, p.Author, f.Name)
|
|
||||||
if handleErr(err, "could not insert raisin offenders") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Debug().Msgf("Processed %d posts", len(posts))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func in(val string, from []string) bool {
|
|
||||||
for _, e := range from {
|
|
||||||
if e == val {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleErr(err error, message string, extras ...interface{}) bool {
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Msgf(message, extras...)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
73
beebot.json
73
beebot.json
|
@ -1,73 +0,0 @@
|
||||||
{"level":"info","time":"2021-09-27T11:33:13-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:33:13-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:33:13-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key baseaddr is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:33:14-04:00","caller":"/home/cws/src/BeeBot/beebot.go:151","message":"Processed 1 comments"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:33:15-04:00","caller":"/home/cws/src/BeeBot/beebot.go:172","message":"Processed 2 posts"}
|
|
||||||
{"level":"info","time":"2021-09-27T11:38:23-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:38:23-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:38:23-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key baseaddr is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:38:24-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key baseaddr is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T11:39:31-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:39:31-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T11:41:02-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:41:02-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T11:41:56-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:41:56-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T11:42:37-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:42:37-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T11:43:33-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T11:43:33-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:05:12-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:05:12-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:12:01-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:12:01-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:12:29-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:12:29-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:24:40-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:24:40-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:24:41-04:00","caller":"/home/cws/src/BeeBot/web.go:55","message":"using live mode"}
|
|
||||||
{"level":"error","error":"open templates/templates/index.html: no such file or directory","time":"2021-09-27T12:24:43-04:00","caller":"/home/cws/src/BeeBot/web.go:47","message":"Could not read template"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:25:24-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"error","error":"open templates/index.html: file does not exist","time":"2021-09-27T12:25:27-04:00","caller":"/home/cws/src/BeeBot/web.go:47","message":"Could not read template"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:25:54-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:26:09-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:26:09-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:26:10-04:00","caller":"/home/cws/src/BeeBot/web.go:55","message":"using live mode"}
|
|
||||||
{"level":"info","time":"2021-09-27T12:26:32-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:26:32-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T12:26:32-04:00","caller":"/home/cws/src/BeeBot/web.go:54","message":"using live mode"}
|
|
||||||
{"level":"error","error":"open templates/log.html: no such file or directory","time":"2021-09-27T12:26:51-04:00","caller":"/home/cws/src/BeeBot/web.go:46","message":"Could not read template"}
|
|
||||||
{"level":"info","time":"2021-09-27T15:40:20-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T15:40:20-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T15:40:21-04:00","caller":"/home/cws/src/BeeBot/web.go:54","message":"using live mode"}
|
|
||||||
{"level":"error","error":"open templates/filters.html: no such file or directory","time":"2021-09-27T16:03:48-04:00","caller":"/home/cws/src/BeeBot/web.go:46","message":"Could not read template"}
|
|
||||||
{"level":"error","error":"unexpected end of JSON input","time":"2021-09-27T16:18:40-04:00","caller":"/home/cws/src/BeeBot/api.go:63","message":"Could not get configuration entries"}
|
|
||||||
{"level":"error","error":"unexpected end of JSON input","time":"2021-09-27T16:19:26-04:00","caller":"/home/cws/src/BeeBot/api.go:63","message":"Could not get configuration entries"}
|
|
||||||
{"level":"error","error":"unexpected end of JSON input","time":"2021-09-27T16:20:27-04:00","caller":"/home/cws/src/BeeBot/api.go:63","message":"Could not get configuration entries"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:21:45-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:21:45-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:21:46-04:00","caller":"/home/cws/src/BeeBot/web.go:54","message":"using live mode"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:28:19-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:28:19-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:28:20-04:00","caller":"/home/cws/src/BeeBot/web.go:54","message":"using live mode"}
|
|
||||||
{"level":"error","error":"unexpected end of JSON input","time":"2021-09-27T16:28:24-04:00","caller":"/home/cws/src/BeeBot/api.go:82","message":"Could not get configuration entries"}
|
|
||||||
{"level":"error","error":"unexpected end of JSON input","time":"2021-09-27T16:28:32-04:00","caller":"/home/cws/src/BeeBot/api.go:82","message":"Could not get configuration entries"}
|
|
||||||
{"level":"error","error":"unexpected end of JSON input","time":"2021-09-27T16:28:34-04:00","caller":"/home/cws/src/BeeBot/api.go:82","message":"Could not get configuration entries"}
|
|
||||||
{"level":"error","error":"json: cannot unmarshal object into Go value of type string","time":"2021-09-27T16:29:02-04:00","caller":"/home/cws/src/BeeBot/api.go:82","message":"Could not get configuration entries"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:29:37-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:29:37-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:29:38-04:00","caller":"/home/cws/src/BeeBot/web.go:54","message":"using live mode"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:31:47-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:31:47-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:31:48-04:00","caller":"/home/cws/src/BeeBot/web.go:54","message":"using live mode"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:37:11-04:00","caller":"/home/cws/src/BeeBot/cmd/beebot/main.go:47","message":"BeeBot v1.00"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:37:11-04:00","caller":"/home/cws/src/BeeBot/config/config.go:85","message":"WARN: Key useragent is empty"}
|
|
||||||
{"level":"debug","time":"2021-09-27T16:37:12-04:00","caller":"/home/cws/src/BeeBot/web.go:54","message":"using live mode"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:37:13-04:00","caller":"/home/cws/src/BeeBot/api.go:87","message":"Deleting config: "}
|
|
||||||
{"level":"info","time":"2021-09-27T16:38:29-04:00","caller":"/home/cws/src/BeeBot/api.go:87","message":"Deleting config: "}
|
|
||||||
{"level":"info","time":"2021-09-27T16:38:52-04:00","caller":"/home/cws/src/BeeBot/api.go:87","message":"Deleting config: "}
|
|
||||||
{"level":"info","time":"2021-09-27T16:39:25-04:00","caller":"/home/cws/src/BeeBot/api.go:87","message":"Deleting config: "}
|
|
||||||
{"level":"error","error":"json: cannot unmarshal object into Go struct field configEntry.key of type string","time":"2021-09-27T16:39:52-04:00","caller":"/home/cws/src/BeeBot/api.go:82","message":"Could not get configuration entries"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:39:52-04:00","caller":"/home/cws/src/BeeBot/api.go:87","message":"Deleting config: "}
|
|
||||||
{"level":"info","time":"2021-09-27T16:41:08-04:00","caller":"/home/cws/src/BeeBot/api.go:87","message":"Deleting config: key2"}
|
|
||||||
{"level":"info","time":"2021-09-27T16:41:10-04:00","caller":"/home/cws/src/BeeBot/api.go:87","message":"Deleting config: key"}
|
|
|
@ -3,14 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"log"
|
||||||
|
|
||||||
beebot "code.chrissexton.org/cws/BeeBot"
|
|
||||||
"github.com/jzelinskie/geddit"
|
"github.com/jzelinskie/geddit"
|
||||||
"github.com/rs/zerolog"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = 1.0
|
const version = 1.0
|
||||||
|
@ -18,39 +13,51 @@ const version = 1.0
|
||||||
var userAgent = fmt.Sprintf("BeeBot:%.2f (by u/phlyingpenguin)", version)
|
var userAgent = fmt.Sprintf("BeeBot:%.2f (by u/phlyingpenguin)", version)
|
||||||
var scopes = "identity read edit"
|
var scopes = "identity read edit"
|
||||||
|
|
||||||
var debug = flag.Bool("debug", false, "Turn debug printing on")
|
var clientID = flag.String("id", "", "Client ID")
|
||||||
var dbFilePath = flag.String("db", "beebot.db", "Database file path")
|
var clientSecret = flag.String("secret", "", "Client Secret")
|
||||||
var logFilePath = flag.String("log", "beebot.json", "Log file path")
|
var baseAddr = flag.String("url", "127.0.0.1:9595", "Base address")
|
||||||
|
var userName = flag.String("user", "_BeeBot_", "Login name")
|
||||||
|
var password = flag.String("password", "nope", "Login password")
|
||||||
|
var reddit = flag.String("reddit", "MeadTest", "Default reddit")
|
||||||
|
|
||||||
var o *geddit.OAuthSession
|
var o *geddit.OAuthSession
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
log.Printf("BeeBot v%.2f", version)
|
||||||
|
|
||||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
var err error
|
||||||
if *debug {
|
|
||||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
|
o, err = geddit.NewOAuthSession(
|
||||||
logFile, err := os.OpenFile(*logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
*clientID,
|
||||||
|
*clientSecret,
|
||||||
|
userAgent,
|
||||||
|
fmt.Sprintf("http://%s/cb", *baseAddr),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
log.Fatal(1, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
multi := zerolog.MultiLevelWriter(consoleWriter, logFile)
|
err = o.LoginAuth(*userName, *password)
|
||||||
|
|
||||||
log.Logger = zerolog.New(multi).
|
|
||||||
With().Timestamp().Caller().Stack().
|
|
||||||
Logger()
|
|
||||||
|
|
||||||
log.Info().Msgf("BeeBot v%.2f", version)
|
|
||||||
|
|
||||||
b, err := beebot.New(*dbFilePath, *logFilePath, *debug)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("beebot died")
|
log.Fatal(2, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// b.Run()
|
me, err := o.AboutRedditor("_beebot_")
|
||||||
b.ServeWeb()
|
if err != nil {
|
||||||
|
log.Fatal(3, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%+v", me)
|
||||||
|
|
||||||
|
cap, err := o.NewCaptcha()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(4, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
post := geddit.NewTextSubmission(*reddit, "Monthly challenge", "Hey this is a monthly challenge", false, cap)
|
||||||
|
sub, err := o.Submit(post)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(5, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
143
config/config.go
143
config/config.go
|
@ -1,143 +0,0 @@
|
||||||
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config stores any system-wide startup information that cannot be easily configured via
|
|
||||||
// the database
|
|
||||||
type Config struct {
|
|
||||||
*sqlx.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFloat64 returns the config value for a string key
|
|
||||||
// It will first look in the env vars for the key
|
|
||||||
// It will check the db for the key if an env DNE
|
|
||||||
// Finally, it will return a zero value if the key does not exist
|
|
||||||
// It will attempt to convert the value to a float64 if it exists
|
|
||||||
func (c *Config) GetFloat64(key string, fallback float64) float64 {
|
|
||||||
f, err := strconv.ParseFloat(c.GetString(key, fmt.Sprintf("%f", fallback)), 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt64 returns the config value for a string key
|
|
||||||
// It will first look in the env vars for the key
|
|
||||||
// It will check the db for the key if an env DNE
|
|
||||||
// Finally, it will return a zero value if the key does not exist
|
|
||||||
// It will attempt to convert the value to an int if it exists
|
|
||||||
func (c *Config) GetInt64(key string, fallback int64) int64 {
|
|
||||||
i, err := strconv.ParseInt(c.GetString(key, strconv.FormatInt(fallback, 10)), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt returns the config value for a string key
|
|
||||||
// It will first look in the env vars for the key
|
|
||||||
// It will check the db for the key if an env DNE
|
|
||||||
// Finally, it will return a zero value if the key does not exist
|
|
||||||
// It will attempt to convert the value to an int if it exists
|
|
||||||
func (c *Config) GetInt(key string, fallback int) int {
|
|
||||||
i, err := strconv.Atoi(c.GetString(key, strconv.Itoa(fallback)))
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get is a shortcut for GetString
|
|
||||||
func (c *Config) Get(key, fallback string) string {
|
|
||||||
return c.GetString(key, fallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
func envkey(key string) string {
|
|
||||||
key = strings.ToUpper(key)
|
|
||||||
key = strings.Replace(key, ".", "", -1)
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString returns the config value for a string key
|
|
||||||
// It will first look in the env vars for the key
|
|
||||||
// It will check the db for the key if an env DNE
|
|
||||||
// Finally, it will return a zero value if the key does not exist
|
|
||||||
// It will convert the value to a string if it exists
|
|
||||||
func (c *Config) GetString(key, fallback string) string {
|
|
||||||
key = strings.ToLower(key)
|
|
||||||
if v, found := os.LookupEnv(envkey(key)); found {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
var configValue string
|
|
||||||
q := `select value from config where key=?`
|
|
||||||
err := c.DB.Get(&configValue, q, key)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug().Msgf("WARN: Key %s is empty", key)
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
return configValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unset removes config values from the database
|
|
||||||
func (c *Config) Unset(key string) error {
|
|
||||||
q := `delete from config where key=?`
|
|
||||||
tx, err := c.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tx.Exec(q, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set changes the value for a configuration in the database
|
|
||||||
// Note, this is always a string. Use the SetArray for an array helper
|
|
||||||
func (c *Config) Set(key, value string) error {
|
|
||||||
key = strings.ToLower(key)
|
|
||||||
value = strings.Trim(value, "`")
|
|
||||||
q := `insert into config (key,value) values (?, ?)
|
|
||||||
on conflict(key) do update set value=?;`
|
|
||||||
tx, err := c.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tx.Exec(q, key, value, value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New loads a configuration from the specified database
|
|
||||||
func New(db *sqlx.DB) *Config {
|
|
||||||
c := Config{}
|
|
||||||
c.DB = db
|
|
||||||
|
|
||||||
c.MustExec(`create table if not exists config (
|
|
||||||
key string,
|
|
||||||
value string,
|
|
||||||
primary key (key)
|
|
||||||
);`)
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
70
filters.go
70
filters.go
|
@ -1,70 +0,0 @@
|
||||||
package beebot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter represents a comment response for the bot
|
|
||||||
type Filter struct {
|
|
||||||
ID int64
|
|
||||||
Name string
|
|
||||||
RegexStr string `db:"regex"`
|
|
||||||
Template string
|
|
||||||
|
|
||||||
regex *regexp.Regexp
|
|
||||||
db *sqlx.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFilter creates and saves a Filter
|
|
||||||
func (b *BeeBot) NewFilter(name, regex, tpl string) (*Filter, error) {
|
|
||||||
// Verify the template
|
|
||||||
_, err := template.New("tmp").Parse(tpl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = regexp.Compile(regex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f := &Filter{
|
|
||||||
Name: name,
|
|
||||||
RegexStr: regex,
|
|
||||||
Template: tpl,
|
|
||||||
}
|
|
||||||
f.populate(b.db)
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Filter) populate(db *sqlx.DB) {
|
|
||||||
f.regex = regexp.MustCompile(f.RegexStr)
|
|
||||||
f.db = db
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save commits a template to the database
|
|
||||||
func (f *Filter) Save() error {
|
|
||||||
q := `insert into filters (name, regexstr, template) values (?, ? ,?)
|
|
||||||
on conflict(name) do update set regexstr=?, template=?;`
|
|
||||||
res, err := f.db.Exec(q, f.Name, f.RegexStr, f.Template,
|
|
||||||
f.RegexStr, f.Template)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.ID, err = res.LastInsertId()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllFilters returns every filter known
|
|
||||||
func (b *BeeBot) AllFilters() ([]Filter, error) {
|
|
||||||
filters := []Filter{}
|
|
||||||
err := b.db.Select(&filters, `select * from Filters`)
|
|
||||||
if handleErr(err, "could not get Filter list") {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, f := range filters {
|
|
||||||
f.populate(b.db)
|
|
||||||
}
|
|
||||||
return filters, nil
|
|
||||||
}
|
|
30
go.mod
30
go.mod
|
@ -1,37 +1,11 @@
|
||||||
module code.chrissexton.org/cws/BeeBot
|
module code.chrissexton.org/cws/BeeBot
|
||||||
|
|
||||||
go 1.17
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 // indirect
|
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 // indirect
|
||||||
github.com/go-chi/chi v1.5.4
|
|
||||||
github.com/google/go-querystring v1.0.0 // indirect
|
github.com/google/go-querystring v1.0.0 // indirect
|
||||||
github.com/jmoiron/sqlx v1.3.4
|
|
||||||
github.com/jpillora/backoff v1.0.0
|
|
||||||
github.com/jzelinskie/geddit v0.0.0-20190913104144-95ef6806b073
|
github.com/jzelinskie/geddit v0.0.0-20190913104144-95ef6806b073
|
||||||
github.com/rs/zerolog v1.25.0
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
||||||
modernc.org/sqlite v1.13.1
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/golang/protobuf v1.2.0 // indirect
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect
|
|
||||||
golang.org/x/tools v0.1.5 // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
google.golang.org/appengine v1.4.0 // indirect
|
|
||||||
lukechampine.com/uint128 v1.1.1 // indirect
|
|
||||||
modernc.org/cc/v3 v3.34.0 // indirect
|
|
||||||
modernc.org/ccgo/v3 v3.11.2 // indirect
|
|
||||||
modernc.org/libc v1.11.3 // indirect
|
|
||||||
modernc.org/mathutil v1.4.1 // indirect
|
|
||||||
modernc.org/memory v1.0.5 // indirect
|
|
||||||
modernc.org/opt v0.1.1 // indirect
|
|
||||||
modernc.org/strutil v1.1.1 // indirect
|
|
||||||
modernc.org/token v1.0.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
110
go.sum
110
go.sum
|
@ -1,126 +1,22 @@
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
|
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
|
||||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
|
|
||||||
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
|
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
|
|
||||||
github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
|
||||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
|
||||||
github.com/jzelinskie/geddit v0.0.0-20190913104144-95ef6806b073 h1:5SmVkj0GZ8GU4eUF6JOhwZj4GeCxPphxTdZK07R5Q1U=
|
github.com/jzelinskie/geddit v0.0.0-20190913104144-95ef6806b073 h1:5SmVkj0GZ8GU4eUF6JOhwZj4GeCxPphxTdZK07R5Q1U=
|
||||||
github.com/jzelinskie/geddit v0.0.0-20190913104144-95ef6806b073/go.mod h1:KiUhpHWSO6xCSPYKhRXa1LDLtbxZKaFH4NINTP3Lm2Q=
|
github.com/jzelinskie/geddit v0.0.0-20190913104144-95ef6806b073/go.mod h1:KiUhpHWSO6xCSPYKhRXa1LDLtbxZKaFH4NINTP3Lm2Q=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|
||||||
github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II=
|
|
||||||
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b h1:S7hKs0Flbq0bbc9xgYt4stIEG1zNDFqyrPwAX2Wj/sE=
|
|
||||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
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=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
|
||||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
||||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
|
||||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
|
||||||
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
|
||||||
modernc.org/cc/v3 v3.34.0 h1:dFhZc/HKR3qp92sYQxKRRaDMz+sr1bwcFD+m7LSCrAs=
|
|
||||||
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
|
||||||
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
|
||||||
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
|
||||||
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
|
|
||||||
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
|
|
||||||
modernc.org/ccgo/v3 v3.11.2 h1:gqa8PQ2v7SjrhHCgxUO5dzoAJWSLAveJqZTNkPCN0kc=
|
|
||||||
modernc.org/ccgo/v3 v3.11.2/go.mod h1:6kii3AptTDI+nUrM9RFBoIEUEisSWCbdczD9ZwQH2FE=
|
|
||||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|
||||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
|
||||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
|
||||||
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
|
||||||
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
|
||||||
modernc.org/libc v1.11.3 h1:q//spBhqp23lC/if8/o8hlyET57P8mCZqrqftzT2WmY=
|
|
||||||
modernc.org/libc v1.11.3/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
|
||||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
|
||||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
|
||||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
|
||||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
|
||||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sqlite v1.13.1 h1:s/qk6VTTVyQIyhVNWa50whBBcI3+2oREbx85t227iOo=
|
|
||||||
modernc.org/sqlite v1.13.1/go.mod h1:2qO/6jZJrcQaxFUHxOwa6Q6WfiGSsiVj6GXX0Ker+Jg=
|
|
||||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
|
||||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
|
||||||
modernc.org/tcl v1.5.9 h1:DZMfR+RDJRhcrmMEMTJgVIX+Wf5qhfVX0llI0rsc20w=
|
|
||||||
modernc.org/tcl v1.5.9/go.mod h1:bcwjvBJ2u0exY6K35eAmxXBBij5kXb1dHlAWmfhqThE=
|
|
||||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
|
||||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
modernc.org/z v1.1.2 h1:IjjzDsIFbl0wuF2KfwvdyUAJVwxD4iwZ6akLNiDoClM=
|
|
||||||
modernc.org/z v1.1.2/go.mod h1:sj9T1AGBG0dm6SCVzldPOHWrif6XBpooJtbttMn1+Js=
|
|
||||||
|
|
24
schema.go
24
schema.go
|
@ -1,24 +0,0 @@
|
||||||
package beebot
|
|
||||||
|
|
||||||
func (b *BeeBot) setupDB() error {
|
|
||||||
if _, err := b.db.Exec(`create table if not exists filters (
|
|
||||||
id integer primary key autoincrement,
|
|
||||||
name text unique,
|
|
||||||
regex text,
|
|
||||||
template text
|
|
||||||
)`); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := b.db.Exec(`create table if not exists offenders (
|
|
||||||
id integer primary key autoincrement,
|
|
||||||
offender text,
|
|
||||||
filter_name text,
|
|
||||||
|
|
||||||
foreign key(filter_name) references filters(name)
|
|
||||||
)`); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
|
||||||
|
|
||||||
<!-- Load polyfills to support older browsers -->
|
|
||||||
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
|
||||||
|
|
||||||
<!-- Load Vue followed by BootstrapVue -->
|
|
||||||
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
|
||||||
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
|
||||||
<script src="https://unpkg.com/vue-router"></script>
|
|
||||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Memes</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="app">
|
|
||||||
<b-navbar>
|
|
||||||
<b-navbar-brand>BeeBot</b-navbar-brand>
|
|
||||||
<b-navbar-nav>
|
|
||||||
<b-nav-item v-for="(url, name) in nav" :href="url" :active="name === 'Config'">{{ name }}</b-nav-item>
|
|
||||||
</b-navbar-nav>
|
|
||||||
</b-navbar>
|
|
||||||
<b-alert
|
|
||||||
dismissable
|
|
||||||
variant="error"
|
|
||||||
v-if="err"
|
|
||||||
@dismissed="err = ''">
|
|
||||||
{{ err }}
|
|
||||||
</b-alert>
|
|
||||||
<b-form @submit="add">
|
|
||||||
<b-container>
|
|
||||||
<b-row>
|
|
||||||
<b>Note:</b> Environmental configuration values will not be displayed in this list. Unset configuration options appear in the logs.
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="5">
|
|
||||||
<b-input placeholder="Key..." v-model="newConfig.key"></b-input>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="5">
|
|
||||||
<b-input placeholder="Value..." v-model="newConfig.value"></b-input>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="2">
|
|
||||||
<b-button type="submit">Add</b-button>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-container>
|
|
||||||
</b-form>
|
|
||||||
<b-table
|
|
||||||
:items="config"
|
|
||||||
:fields="fields">
|
|
||||||
<template v-slot:cell(rm)="data">
|
|
||||||
<b-button @click="rm(data.item.key)">X</b-button>
|
|
||||||
</template>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var router = new VueRouter({
|
|
||||||
mode: 'history',
|
|
||||||
routes: [],
|
|
||||||
});
|
|
||||||
var app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
router,
|
|
||||||
data: {
|
|
||||||
err: '',
|
|
||||||
nav: [],
|
|
||||||
config: [],
|
|
||||||
newConfig: {key: '', value: ''},
|
|
||||||
fields: [
|
|
||||||
{key: 'key'},
|
|
||||||
{key: 'value'},
|
|
||||||
{key: 'rm', sortable: false}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
axios.get('/api/v1/nav')
|
|
||||||
.catch(err => this.err = err)
|
|
||||||
.then(resp => {
|
|
||||||
this.nav = resp.data;
|
|
||||||
})
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
refresh: function() {
|
|
||||||
axios.get('/api/v1/config')
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
.then(resp => {
|
|
||||||
this.config = resp.data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
add: function(evt) {
|
|
||||||
if (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
evt.stopPropagation();
|
|
||||||
}
|
|
||||||
axios.post('/api/v1/config', this.newConfig)
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
.then(resp => {
|
|
||||||
this.newConfig = {}
|
|
||||||
this.refresh()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
rm: function(key) {
|
|
||||||
axios.delete('/api/v1/config/', {data: {key: key}})
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
.then(resp => {
|
|
||||||
this.newConfig = {}
|
|
||||||
this.refresh()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
|
||||||
|
|
||||||
<!-- Load polyfills to support older browsers -->
|
|
||||||
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
|
||||||
|
|
||||||
<!-- Load Vue followed by BootstrapVue -->
|
|
||||||
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
|
||||||
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
|
||||||
<script src="https://unpkg.com/vue-router"></script>
|
|
||||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Memes</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="app">
|
|
||||||
<b-navbar>
|
|
||||||
<b-navbar-brand>BeeBot</b-navbar-brand>
|
|
||||||
<b-navbar-nav>
|
|
||||||
<b-nav-item v-for="(url, name) in nav" :href="url" :active="name === 'Filters'">{{ name }}</b-nav-item>
|
|
||||||
</b-navbar-nav>
|
|
||||||
</b-navbar>
|
|
||||||
<b-alert
|
|
||||||
dismissable
|
|
||||||
variant="error"
|
|
||||||
v-if="err"
|
|
||||||
@dismissed="err = ''">
|
|
||||||
{{ err }}
|
|
||||||
</b-alert>
|
|
||||||
<b-form @submit="add">
|
|
||||||
<b-container>
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="5">
|
|
||||||
<b-input placeholder="Key..." v-model="newfilters.name"></b-input>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="5">
|
|
||||||
<b-input placeholder="Value..." v-model="newfilters.regex"></b-input>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="10">
|
|
||||||
<b-input placeholder="Template..." v-model="newfilters.template"></b-input>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="2">
|
|
||||||
<b-button type="submit">Add</b-button>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</b-container>
|
|
||||||
</b-form>
|
|
||||||
<b-table
|
|
||||||
:items="filters"
|
|
||||||
:fields="fields">
|
|
||||||
<template v-slot:cell(rm)="data">
|
|
||||||
<b-button @click="rm(data.item.key)">X</b-button>
|
|
||||||
</template>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var router = new VueRouter({
|
|
||||||
mode: 'history',
|
|
||||||
routes: [],
|
|
||||||
});
|
|
||||||
var app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
router,
|
|
||||||
data: {
|
|
||||||
err: '',
|
|
||||||
nav: [],
|
|
||||||
filters: [],
|
|
||||||
newfilters: {key: '', value: ''},
|
|
||||||
fields: [
|
|
||||||
{key: 'key'},
|
|
||||||
{key: 'value'},
|
|
||||||
{key: 'rm', sortable: false}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
axios.get('/api/v1/nav')
|
|
||||||
.catch(err => this.err = err)
|
|
||||||
.then(resp => {
|
|
||||||
this.nav = resp.data;
|
|
||||||
})
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
refresh: function() {
|
|
||||||
axios.get('/api/v1/filters')
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
.then(resp => {
|
|
||||||
this.filters = resp.data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
add: function(evt) {
|
|
||||||
if (evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
evt.stopPropagation();
|
|
||||||
}
|
|
||||||
axios.post('/api/v1/filters', this.newfilters)
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
.then(resp => {
|
|
||||||
this.newfilters = {}
|
|
||||||
this.refresh()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
rm: function(key) {
|
|
||||||
axios.delete('/api/v1/filters', {data: {key: key}})
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
.then(resp => {
|
|
||||||
this.newfilters = {}
|
|
||||||
this.refresh()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
|
||||||
|
|
||||||
<!-- Load polyfills to support older browsers -->
|
|
||||||
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
|
||||||
|
|
||||||
<!-- Load Vue followed by BootstrapVue -->
|
|
||||||
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
|
||||||
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
|
||||||
<script src="https://unpkg.com/vue-router"></script>
|
|
||||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Memes</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="app">
|
|
||||||
<b-navbar>
|
|
||||||
<b-navbar-brand>BeeBot</b-navbar-brand>
|
|
||||||
<b-navbar-nav>
|
|
||||||
<b-nav-item v-for="(url, name) in nav" :href="url" :active="name === 'Index'">{{ name }}</b-nav-item>
|
|
||||||
</b-navbar-nav>
|
|
||||||
</b-navbar>
|
|
||||||
<b-alert
|
|
||||||
dismissable
|
|
||||||
variant="error"
|
|
||||||
v-if="err"
|
|
||||||
@dismissed="err = ''">
|
|
||||||
{{ err }}
|
|
||||||
</b-alert>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var router = new VueRouter({
|
|
||||||
mode: 'history',
|
|
||||||
routes: []
|
|
||||||
});
|
|
||||||
var app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
router,
|
|
||||||
data: {
|
|
||||||
err: '',
|
|
||||||
nav: [],
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
axios.get('/api/v1/nav')
|
|
||||||
.then(resp => {
|
|
||||||
this.nav = resp.data;
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,91 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
|
||||||
|
|
||||||
<!-- Load polyfills to support older browsers -->
|
|
||||||
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
|
||||||
|
|
||||||
<!-- Load Vue followed by BootstrapVue -->
|
|
||||||
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
|
||||||
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
|
||||||
<script src="https://unpkg.com/vue-router"></script>
|
|
||||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Memes</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="app">
|
|
||||||
<b-navbar>
|
|
||||||
<b-navbar-brand>BeeBot</b-navbar-brand>
|
|
||||||
<b-navbar-nav>
|
|
||||||
<b-nav-item v-for="(url, name) in nav" :href="url" :active="name === 'Log'">{{ name }}</b-nav-item>
|
|
||||||
</b-navbar-nav>
|
|
||||||
</b-navbar>
|
|
||||||
<b-alert
|
|
||||||
dismissable
|
|
||||||
variant="error"
|
|
||||||
v-if="err"
|
|
||||||
@dismissed="err = ''">
|
|
||||||
{{ err }}
|
|
||||||
</b-alert>
|
|
||||||
<b-table
|
|
||||||
:items="log"
|
|
||||||
:fields="fields">
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var router = new VueRouter({
|
|
||||||
mode: 'history',
|
|
||||||
routes: [],
|
|
||||||
});
|
|
||||||
var app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
router,
|
|
||||||
data: {
|
|
||||||
err: '',
|
|
||||||
nav: [],
|
|
||||||
log: [],
|
|
||||||
fields: [
|
|
||||||
{key: 'level'},
|
|
||||||
{
|
|
||||||
key: 'time',
|
|
||||||
sortable: true,
|
|
||||||
sortDirection: 'asc'
|
|
||||||
},
|
|
||||||
{key: 'caller'},
|
|
||||||
{key: 'message'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
axios.get('/api/v1/nav')
|
|
||||||
.catch(err => this.err = err)
|
|
||||||
.then(resp => {
|
|
||||||
this.nav = resp.data;
|
|
||||||
})
|
|
||||||
this.refresh()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
refresh: function() {
|
|
||||||
axios.get('/api/v1/log')
|
|
||||||
.catch(err => this.err = err)
|
|
||||||
.then(resp => {
|
|
||||||
let data = resp.data.split("\n");
|
|
||||||
for (let d of data) {
|
|
||||||
try {
|
|
||||||
this.log.push(JSON.parse(d))
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
65
web.go
65
web.go
|
@ -1,65 +0,0 @@
|
||||||
package beebot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed templates/*.html
|
|
||||||
var embeddedFS embed.FS
|
|
||||||
var files fs.FS
|
|
||||||
|
|
||||||
// ServeWeb configures and starts the webserver
|
|
||||||
func (b *BeeBot) ServeWeb() {
|
|
||||||
router := chi.NewRouter()
|
|
||||||
|
|
||||||
files = getFileSystem(b.debug)
|
|
||||||
|
|
||||||
api := b.apiEndpoints()
|
|
||||||
router.Mount("/api/v1", api)
|
|
||||||
|
|
||||||
router.HandleFunc("/", staticPage("index.html"))
|
|
||||||
router.HandleFunc("/filters", staticPage("filters.html"))
|
|
||||||
router.HandleFunc("/config", staticPage("config.html"))
|
|
||||||
router.HandleFunc("/log", staticPage("log.html"))
|
|
||||||
|
|
||||||
b.nav["Filters"] = "/filters"
|
|
||||||
b.nav["Config"] = "/config"
|
|
||||||
b.nav["Log"] = "/log"
|
|
||||||
|
|
||||||
// Don't want to block for this (later)
|
|
||||||
baseAddr := b.c.Get("baseaddr", DefaultAddr)
|
|
||||||
log.Fatal().
|
|
||||||
Err(http.ListenAndServe(baseAddr, router)).
|
|
||||||
Msg("HTTP server")
|
|
||||||
}
|
|
||||||
|
|
||||||
func staticPage(templatePath string) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
index, err := fs.ReadFile(files, templatePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not read template")
|
|
||||||
}
|
|
||||||
w.Write(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFileSystem(useOS bool) fs.FS {
|
|
||||||
if useOS {
|
|
||||||
log.Print("using live mode")
|
|
||||||
return os.DirFS("templates")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Print("using embed mode")
|
|
||||||
fsys, err := fs.Sub(embeddedFS, "templates")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Could not load file templates")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fsys
|
|
||||||
}
|
|
Loading…
Reference in New Issue