Compare commits

...

2 Commits

Author SHA1 Message Date
Chris Sexton 7ba9d94ac2 counter: add api, bot: change routes
* many routes changed by adding the chi router
* counter has an authenticated API to increment and decrement
2021-07-28 11:41:01 -04:00
Chris Sexton c47a4f7c6f web: migrate endpoint registration to chi 2021-07-28 11:41:01 -04:00
26 changed files with 363 additions and 188 deletions

View File

@ -11,6 +11,8 @@ import (
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot/msg"
@ -59,6 +61,8 @@ type bot struct {
passwordCreated time.Time
quiet bool
router *chi.Mux
}
type EndPoint struct {
@ -98,6 +102,7 @@ func New(config *config.Config, connector Connector) Bot {
httpEndPoints: make([]EndPoint, 0),
filters: make(map[string]func(string) string),
callbacks: make(CallbackMap),
router: chi.NewRouter(),
}
bot.migrateDB()
@ -105,14 +110,34 @@ func New(config *config.Config, connector Connector) Bot {
bot.RefreshPluginBlacklist()
bot.RefreshPluginWhitelist()
http.HandleFunc("/", bot.serveRoot)
http.HandleFunc("/nav", bot.serveNav)
log.Debug().Msgf("created web router")
bot.router.Use(middleware.Logger)
bot.router.Use(middleware.StripSlashes)
bot.router.HandleFunc("/", bot.serveRoot)
bot.router.HandleFunc("/nav", bot.serveNav)
connector.RegisterEvent(bot.Receive)
return bot
}
func (b *bot) ListenAndServe() {
addr := b.config.Get("HttpAddr", "127.0.0.1:1337")
log.Debug().Msgf("starting web service at %s", addr)
log.Fatal().Err(http.ListenAndServe(addr, b.router)).Msg("bot killed")
}
func (b *bot) RegisterWeb(r http.Handler, root string) {
b.router.Mount(root, r)
}
func (b *bot) RegisterWebName(r http.Handler, root, name string) {
b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root})
b.router.Mount(root, r)
}
// DefaultConnector is the main connector used for the bot
// If more than one connector is on, some users may not see all messages if this is used.
// Usage should be limited to out-of-band communications such as timed messages.
@ -143,19 +168,19 @@ func (b *bot) migrateDB() {
name string,
value string
);`); err != nil {
log.Fatal().Err(err).Msgf("Initial DB migration create variables table")
log.Fatal().Err(err).Msgf("Initial db migration create variables table")
}
if _, err := b.DB().Exec(`create table if not exists pluginBlacklist (
channel string,
name string,
primary key (channel, name)
);`); err != nil {
log.Fatal().Err(err).Msgf("Initial DB migration create blacklist table")
log.Fatal().Err(err).Msgf("Initial db migration create blacklist table")
}
if _, err := b.DB().Exec(`create table if not exists pluginWhitelist (
name string primary key
);`); err != nil {
log.Fatal().Err(err).Msgf("Initial DB migration create whitelist table")
log.Fatal().Err(err).Msgf("Initial db migration create whitelist table")
}
}
@ -280,10 +305,6 @@ func (b *bot) Register(p Plugin, kind Kind, cb Callback) {
b.RegisterRegex(p, kind, r, resp)
}
func (b *bot) RegisterWeb(root, name string) {
b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root})
}
// GetPassword returns a random password generated for the bot
// Passwords expire in 24h and are used for the web interface
func (b *bot) GetPassword() string {
@ -373,6 +394,9 @@ func PluginName(p Plugin) string {
}
func (b *bot) CheckPassword(secret, password string) bool {
if password == "" {
return false
}
if b.password == password {
return true
}

View File

@ -3,6 +3,7 @@
package bot
import (
"net/http"
"regexp"
"github.com/jmoiron/sqlx"
@ -134,7 +135,13 @@ type Bot interface {
RegisterFilter(string, func(string) string)
// RegisterWeb records a web endpoint for the UI
RegisterWeb(string, string)
RegisterWebName(http.Handler, string, string)
// RegisterWeb records a web endpoint for the API
RegisterWeb(http.Handler, string)
// Start the HTTP service
ListenAndServe()
// DefaultConnector returns the base connector, which may not be the only connector
DefaultConnector() Connector
@ -198,6 +205,9 @@ type Connector interface {
// GetChannelName returns the channel ID for a human-friendly name (if possible)
GetChannelID(id string) string
// Get any web handlers the connector exposes
GetRouter() (http.Handler, string)
}
// Plugin interface used for compatibility with the Plugin interface

View File

@ -57,7 +57,8 @@ func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback)
func (mb *MockBot) RegisterTable(p Plugin, hs HandlerTable) {}
func (mb *MockBot) RegisterRegex(p Plugin, kind Kind, r *regexp.Regexp, h ResponseHandler) {}
func (mb *MockBot) RegisterRegexCmd(p Plugin, kind Kind, r *regexp.Regexp, h ResponseHandler) {}
func (mb *MockBot) RegisterWeb(_, _ string) {}
func (mb *MockBot) RegisterWebName(_ http.Handler, _, _ string) {}
func (mb *MockBot) RegisterWeb(_ http.Handler, _ string) {}
func (mb *MockBot) GetWebNavigation() []EndPoint { return nil }
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
return false
@ -124,3 +125,4 @@ func (mb *MockBot) GetWhitelist() []string { return []string
func (mb *MockBot) OnBlacklist(ch, p string) bool { return false }
func (mb *MockBot) URLFormat(title, url string) string { return title + url }
func (mb *MockBot) CheckPassword(secret, password string) bool { return true }
func (mb *MockBot) ListenAndServe() {}

View File

@ -36,7 +36,7 @@ type Secret struct {
// 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
// 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 {
@ -49,7 +49,7 @@ func (c *Config) GetFloat64(key string, fallback float64) float64 {
// 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
// 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 {
@ -62,7 +62,7 @@ func (c *Config) GetInt64(key string, fallback int64) int64 {
// 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
// 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 {
@ -86,7 +86,7 @@ func envkey(key string) string {
// 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
// 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 {
@ -124,7 +124,7 @@ func (c *Config) GetMap(key string, fallback map[string]string) map[string]strin
// GetArray returns the string slice config value for a string key
// It will first look in the env vars for the key with ;; separated values
// Look, I'm too lazy to do parsing to ensure that a comma is what the user meant
// It will check the DB for the key if an env DNE
// 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
// This will do no conversion.
func (c *Config) GetArray(key string, fallback []string) []string {

View File

@ -3,6 +3,7 @@ package discord
import (
"errors"
"fmt"
"net/http"
"strings"
"github.com/velour/catbase/bot/msg"
@ -24,7 +25,7 @@ type Discord struct {
}
func New(config *config.Config) *Discord {
client, err := discordgo.New("Bot " + config.Get("DISCORDBOTTOKEN", ""))
client, err := discordgo.New("b " + config.Get("DISCORDBOTTOKEN", ""))
if err != nil {
log.Fatal().Err(err).Msg("Could not connect to Discord")
}
@ -34,6 +35,9 @@ func New(config *config.Config) *Discord {
}
return d
}
func (d *Discord) GetRouter() (http.Handler, string) {
return nil, ""
}
func (d *Discord) RegisterEvent(callback bot.Callback) {
d.event = callback

View File

@ -5,6 +5,7 @@ package irc
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
@ -52,6 +53,10 @@ func New(c *config.Config) *Irc {
return &i
}
func (i *Irc) GetRouter() (http.Handler, string) {
return nil, ""
}
func (i *Irc) RegisterEvent(f bot.Callback) {
i.event = f
}

View File

@ -174,6 +174,10 @@ func New(c *config.Config) *Slack {
}
}
func (s *Slack) GetRouter() (http.Handler, string) {
return nil, ""
}
func (s *Slack) Send(kind bot.Kind, args ...interface{}) (string, error) {
switch kind {
case bot.Message:

View File

@ -17,6 +17,7 @@ import (
"text/template"
"time"
"github.com/go-chi/chi/v5"
zerowidth "github.com/trubitsyn/go-zero-width"
"github.com/rs/zerolog/log"
@ -44,6 +45,7 @@ const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .Topi
type SlackApp struct {
config *config.Config
api *slack.Client
router *chi.Mux
botToken string
userToken string
@ -84,6 +86,7 @@ func New(c *config.Config) *SlackApp {
return &SlackApp{
api: api,
router: chi.NewRouter(),
config: c,
botToken: token,
userToken: c.Get("slack.usertoken", "NONE"),
@ -103,10 +106,14 @@ func (s *SlackApp) RegisterEvent(f bot.Callback) {
s.event = f
}
func (s *SlackApp) GetRouter() (http.Handler, string) {
return s.router, "/evt"
}
func (s *SlackApp) Serve() error {
s.populateEmojiList()
http.HandleFunc("/evt", func(w http.ResponseWriter, r *http.Request) {
s.router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
body := buf.String()
@ -696,7 +703,6 @@ func (s *SlackApp) Profile(identifier string) (user.User, error) {
}
for _, u := range users {
log.Debug().Str("Name", u.Name).Str("ID", u.ID).Msgf("Looking for %s", identifier)
if u.Name == identifier || u.ID == identifier {
return user.User{
ID: u.ID,

1
go.mod
View File

@ -22,6 +22,7 @@ require (
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 // indirect
github.com/go-chi/chi/v5 v5.0.3
github.com/go-stack/stack v1.8.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gocolly/colly v1.2.0

2
go.sum
View File

@ -45,6 +45,8 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 h1:WXb3TSNmHp2vHoCro
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 h1:GOfMz6cRgTJ9jWV0qAezv642OhPnKEG7gtUjJSdStHE=
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17/go.mod h1:HfkOCN6fkKKaPSAeNq/er3xObxTW4VLeY6UUK895gLQ=
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=

11
main.go
View File

@ -6,7 +6,6 @@ import (
"flag"
"io"
"math/rand"
"net/http"
"os"
"time"
@ -67,7 +66,7 @@ import (
var (
key = flag.String("set", "", "Configuration key to set")
val = flag.String("val", "", "Configuration value to set")
initDB = flag.Bool("init", false, "Initialize the configuration DB")
initDB = flag.Bool("init", false, "Initialize the configuration db")
prettyLog = flag.Bool("pretty", false, "Use pretty console logger")
debug = flag.Bool("debug", false, "Turn on debug logging")
)
@ -121,6 +120,10 @@ func main() {
b := bot.New(c, client)
if r, path := client.GetRouter(); r != nil {
b.RegisterWeb(r, path)
}
b.AddPlugin(admin.New(b))
b.AddPlugin(secrets.New(b))
b.AddPlugin(giphy.New(b))
@ -169,7 +172,5 @@ func main() {
b.Receive(client, bot.Startup, msg.Message{})
addr := c.Get("HttpAddr", "127.0.0.1:1337")
log.Debug().Msgf("starting web service at %s", addr)
log.Fatal().Err(http.ListenAndServe(addr, nil)).Msg("bot killed")
b.ListenAndServe()
}

View File

@ -9,18 +9,21 @@ import (
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
)
func (p *AdminPlugin) registerWeb() {
http.HandleFunc("/vars/api", p.handleVarsAPI)
http.HandleFunc("/vars", p.handleVars)
p.bot.RegisterWeb("/vars", "Variables")
http.HandleFunc("/apppass/verify", p.handleAppPassCheck)
http.HandleFunc("/apppass/api", p.handleAppPassAPI)
http.HandleFunc("/apppass", p.handleAppPass)
p.bot.RegisterWeb("/apppass", "App Pass")
r := chi.NewRouter()
r.HandleFunc("/api", p.handleVarsAPI)
r.HandleFunc("/", p.handleVars)
p.bot.RegisterWebName(r, "/vars", "Variables")
r = chi.NewRouter()
r.HandleFunc("/verify", p.handleAppPassCheck)
r.HandleFunc("/api", p.handleAppPassAPI)
r.HandleFunc("/", p.handleAppPass)
p.bot.RegisterWebName(r, "/apppass", "App Pass")
}
func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) {

View File

@ -19,6 +19,7 @@ import (
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/nfnt/resize"
@ -589,7 +590,9 @@ func (p *BeersPlugin) untappdLoop(c bot.Connector, channel string) {
}
func (p *BeersPlugin) registerWeb() {
http.HandleFunc("/beers/img/", p.img)
r := chi.NewRouter()
r.HandleFunc("/img", p.img)
p.b.RegisterWeb(r, "/beers")
}
func (p *BeersPlugin) img(w http.ResponseWriter, r *http.Request) {

View File

@ -8,6 +8,7 @@ import (
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
@ -32,9 +33,14 @@ func New(b bot.Bot) *CliPlugin {
}
func (p *CliPlugin) registerWeb() {
http.HandleFunc("/cli/api", p.handleWebAPI)
http.HandleFunc("/cli", p.handleWeb)
p.bot.RegisterWeb("/cli", "CLI")
r := chi.NewRouter()
r.HandleFunc("/api", p.handleWebAPI)
r.HandleFunc("/", p.handleWeb)
p.bot.RegisterWebName(r, "/cli", "CLI")
}
func (p *CliPlugin) GetRouter() (http.Handler, string) {
return nil, ""
}
func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {

161
plugins/counter/api.go Normal file
View File

@ -0,0 +1,161 @@
package counter
import (
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
)
func (p *CounterPlugin) registerWeb() {
r := chi.NewRouter()
r.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementAPI(1))
r.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementAPI(-1))
r.HandleFunc("/api", p.handleCounterAPI)
r.HandleFunc("/", p.handleCounter)
p.b.RegisterWebName(r, "/counter", "Counter")
}
func (p *CounterPlugin) mkIncrementAPI(delta int) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
userName := chi.URLParam(r, "user")
itemName := chi.URLParam(r, "item")
secret, pass, ok := r.BasicAuth()
if !ok || !p.b.CheckPassword(secret, pass) {
err := fmt.Errorf("unauthorized access")
log.Error().
Err(err).
Msg("error authenticating user")
w.WriteHeader(401)
j, _ := json.Marshal(struct {
Status bool
Error string
}{false, err.Error()})
fmt.Fprint(w, string(j))
return
}
// Try to find an ID if possible
u, err := p.b.DefaultConnector().Profile(userName)
if err != nil {
log.Error().Err(err).Msg("error finding user")
w.WriteHeader(400)
j, _ := json.Marshal(struct {
Status bool
Error error
}{false, err})
fmt.Fprint(w, string(j))
return
}
item, err := GetUserItem(p.db, userName, u.ID, itemName)
if err != nil {
log.Error().Err(err).Msg("error finding item")
w.WriteHeader(400)
j, _ := json.Marshal(struct {
Status bool
Error error
}{false, err})
fmt.Fprint(w, string(j))
return
}
req := &bot.Request{
Conn: p.b.DefaultConnector(),
Kind: bot.Message,
Msg: msg.Message{
User: &u,
ChannelName: "#API",
Body: fmt.Sprintf("%s += %d", itemName, delta),
Time: time.Now(),
},
Values: nil,
Args: nil,
}
item.UpdateDelta(req, delta)
msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API",
userName, itemName, delta, item.Count, p.cfg.Get("nick", "catbase"))
for _, ch := range p.cfg.GetArray("channels", []string{}) {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
}
j, _ := json.Marshal(struct{ Status bool }{true})
fmt.Fprint(w, string(j))
}
}
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, html)
}
func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
info := struct {
User string
Thing string
Action string
Password string
}{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&info)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
log.Debug().
Interface("postbody", info).
Msg("Got a POST")
if p.b.CheckPassword("", info.Password) {
w.WriteHeader(http.StatusForbidden)
j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"})
w.Write(j)
return
}
nick, id := p.resolveUser(bot.Request{Conn: p.b.DefaultConnector()}, info.User)
item, err := GetUserItem(p.db, nick, id, info.Thing)
if err != nil {
log.Error().
Err(err).
Str("subject", info.User).
Str("itemName", info.Thing).
Msg("error finding item")
w.WriteHeader(404)
fmt.Fprint(w, err)
return
}
if info.Action == "++" {
item.UpdateDelta(nil, 1)
} else if info.Action == "--" {
item.UpdateDelta(nil, -1)
} else {
w.WriteHeader(400)
fmt.Fprint(w, "Invalid increment")
return
}
}
all, err := GetAllItems(p.db)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
data, err := json.Marshal(all)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
fmt.Fprint(w, string(data))
}
type Update struct {
Who string
What string
Amount int
}

View File

@ -2,15 +2,14 @@ package counter
import (
"database/sql"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"regexp"
"strconv"
"strings"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/config"
"github.com/jmoiron/sqlx"
@ -21,8 +20,9 @@ import (
// This is a counter plugin to count arbitrary things.
type CounterPlugin struct {
Bot bot.Bot
DB *sqlx.DB
b bot.Bot
db *sqlx.DB
cfg *config.Config
}
type Item struct {
@ -50,7 +50,7 @@ func GetAllItems(db *sqlx.DB) ([]Item, error) {
if err != nil {
return nil, err
}
// Don't forget to embed the DB into all of that shiz
// Don't forget to embed the db into all of that shiz
for i := range items {
items[i].DB = db
}
@ -69,7 +69,7 @@ func GetItems(db *sqlx.DB, nick, id string) ([]Item, error) {
if err != nil {
return nil, err
}
// Don't forget to embed the DB into all of that shiz
// Don't forget to embed the db into all of that shiz
for i := range items {
items[i].DB = db
}
@ -254,7 +254,7 @@ func (i *Item) Delete() error {
}
func (p *CounterPlugin) migrate(r bot.Request) bool {
db := p.DB
db := p.db
nicks := []string{}
err := db.Select(&nicks, `select distinct nick from counter where userid is null`)
@ -321,8 +321,9 @@ func New(b bot.Bot) *CounterPlugin {
}
cp := &CounterPlugin{
Bot: b,
DB: b.DB(),
b: b,
db: b.DB(),
cfg: b.Config(),
}
b.RegisterRegex(cp, bot.Startup, regexp.MustCompile(`.*`), cp.migrate)
@ -367,15 +368,15 @@ func (p *CounterPlugin) mkAliasCmd(r bot.Request) bool {
what := r.Values["what"]
to := r.Values["to"]
if what == "" || to == "" {
p.Bot.Send(r.Conn, bot.Message, fmt.Sprintf("You must provide all fields for an alias: %s", mkAliasRegex))
p.b.Send(r.Conn, bot.Message, fmt.Sprintf("You must provide all fields for an alias: %s", mkAliasRegex))
return true
}
if _, err := MkAlias(p.DB, what, to); err != nil {
if _, err := MkAlias(p.db, what, to); err != nil {
log.Error().Err(err).Msg("Could not mkalias")
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, "We're gonna need too much DB space to make an alias for your mom.")
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "We're gonna need too much db space to make an alias for your mom.")
return true
}
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("Created alias %s -> %s",
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("Created alias %s -> %s",
what, to))
return true
}
@ -383,15 +384,15 @@ func (p *CounterPlugin) mkAliasCmd(r bot.Request) bool {
func (p *CounterPlugin) rmAliasCmd(r bot.Request) bool {
what := r.Values["what"]
if what == "" {
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, "You must specify an alias to remove.")
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "You must specify an alias to remove.")
return true
}
if err := RmAlias(p.DB, what); err != nil {
if err := RmAlias(p.db, what); err != nil {
log.Error().Err(err).Msg("could not RmAlias")
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, "`sudo rm your mom` => Nope, she's staying with me.")
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "`sudo rm your mom` => Nope, she's staying with me.")
return true
}
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, "`sudo rm your mom`")
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "`sudo rm your mom`")
return true
}
@ -401,10 +402,10 @@ func (p *CounterPlugin) leaderboardCmd(r bot.Request) bool {
what := r.Values["what"]
if what == "" {
cmd = func() ([]Item, error) { return LeaderAll(p.DB) }
cmd = func() ([]Item, error) { return LeaderAll(p.db) }
} else {
itNameTxt = fmt.Sprintf(" for %s", what)
cmd = func() ([]Item, error) { return Leader(p.DB, what) }
cmd = func() ([]Item, error) { return Leader(p.db, what) }
}
its, err := cmd()
@ -412,7 +413,7 @@ func (p *CounterPlugin) leaderboardCmd(r bot.Request) bool {
log.Error().Err(err).Msg("Error with leaderboard")
return false
} else if len(its) == 0 {
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, "There are not enough entries for a leaderboard.")
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "There are not enough entries for a leaderboard.")
return true
}
@ -424,7 +425,7 @@ func (p *CounterPlugin) leaderboardCmd(r bot.Request) bool {
it.Item,
)
}
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, out)
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, out)
return true
}
@ -432,20 +433,20 @@ func (p *CounterPlugin) resetCmd(r bot.Request) bool {
nick, id := p.resolveUser(r, "")
channel := r.Msg.Channel
items, err := GetItems(p.DB, nick, id)
items, err := GetItems(p.db, nick, id)
if err != nil {
log.Error().
Err(err).
Str("nick", nick).
Msg("Error getting items to reset")
p.Bot.Send(r.Conn, bot.Message, channel, "Something is technically wrong with your counters.")
p.b.Send(r.Conn, bot.Message, channel, "Something is technically wrong with your counters.")
return true
}
log.Debug().Msgf("Items: %+v", items)
for _, item := range items {
item.Delete()
}
p.Bot.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s, you are as new, my son.", nick))
p.b.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s, you are as new, my son.", nick))
return true
}
@ -466,14 +467,14 @@ func (p *CounterPlugin) inspectCmd(r bot.Request) bool {
Str("id", id).
Msg("Getting counter")
// pull all of the items associated with "subject"
items, err := GetItems(p.DB, nick, id)
items, err := GetItems(p.db, nick, id)
if err != nil {
log.Error().
Err(err).
Str("nick", nick).
Str("id", id).
Msg("Error retrieving items")
p.Bot.Send(c, bot.Message, channel, "Something went wrong finding that counter;")
p.b.Send(c, bot.Message, channel, "Something went wrong finding that counter;")
return true
}
@ -493,11 +494,11 @@ func (p *CounterPlugin) inspectCmd(r bot.Request) bool {
resp += "."
if count == 0 {
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has no counters.", nick))
p.b.Send(c, bot.Message, channel, fmt.Sprintf("%s has no counters.", nick))
return true
}
p.Bot.Send(c, bot.Message, channel, resp)
p.b.Send(c, bot.Message, channel, resp)
return true
}
@ -507,7 +508,7 @@ func (p *CounterPlugin) clearCmd(r bot.Request) bool {
channel := r.Msg.Channel
c := r.Conn
it, err := GetUserItem(p.DB, nick, id, itemName)
it, err := GetUserItem(p.db, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -515,7 +516,7 @@ func (p *CounterPlugin) clearCmd(r bot.Request) bool {
Str("id", id).
Str("itemName", itemName).
Msg("Error getting item to remove")
p.Bot.Send(c, bot.Message, channel, "Something went wrong removing that counter;")
p.b.Send(c, bot.Message, channel, "Something went wrong removing that counter;")
return true
}
err = it.Delete()
@ -526,11 +527,11 @@ func (p *CounterPlugin) clearCmd(r bot.Request) bool {
Str("id", id).
Str("itemName", itemName).
Msg("Error removing item")
p.Bot.Send(c, bot.Message, channel, "Something went wrong removing that counter;")
p.b.Send(c, bot.Message, channel, "Something went wrong removing that counter;")
return true
}
p.Bot.Send(c, bot.Action, channel, fmt.Sprintf("chops a few %s out of his brain",
p.b.Send(c, bot.Action, channel, fmt.Sprintf("chops a few %s out of his brain",
itemName))
return true
}
@ -545,10 +546,10 @@ func (p *CounterPlugin) countCmd(r bot.Request) bool {
}
var item Item
item, err := GetUserItem(p.DB, nick, id, itemName)
item, err := GetUserItem(p.db, nick, id, itemName)
switch {
case err == sql.ErrNoRows:
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("I don't think %s has any %s.",
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("I don't think %s has any %s.",
nick, itemName))
return true
case err != nil:
@ -561,7 +562,7 @@ func (p *CounterPlugin) countCmd(r bot.Request) bool {
return true
}
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("%s has %d %s.", nick, item.Count,
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("%s has %d %s.", nick, item.Count,
itemName))
return true
@ -575,7 +576,7 @@ func (p *CounterPlugin) incrementCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// ++ those fuckers
item, err := GetUserItem(p.DB, nick, id, itemName)
item, err := GetUserItem(p.db, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -588,7 +589,7 @@ func (p *CounterPlugin) incrementCmd(r bot.Request) bool {
}
log.Debug().Msgf("About to update item: %#v", item)
item.UpdateDelta(&r, 1)
p.Bot.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
p.b.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
item.Count, item.Item))
return true
}
@ -601,7 +602,7 @@ func (p *CounterPlugin) decrementCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// -- those fuckers
item, err := GetUserItem(p.DB, nick, id, itemName)
item, err := GetUserItem(p.db, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -613,7 +614,7 @@ func (p *CounterPlugin) decrementCmd(r bot.Request) bool {
return false
}
item.UpdateDelta(&r, -1)
p.Bot.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
p.b.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
item.Count, item.Item))
return true
}
@ -623,7 +624,7 @@ func (p *CounterPlugin) addToCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// += those fuckers
item, err := GetUserItem(p.DB, nick, id, itemName)
item, err := GetUserItem(p.db, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -637,7 +638,7 @@ func (p *CounterPlugin) addToCmd(r bot.Request) bool {
n, _ := strconv.Atoi(r.Values["amount"])
log.Debug().Msgf("About to update item by %d: %#v", n, item)
item.UpdateDelta(&r, n)
p.Bot.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
p.b.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
item.Count, item.Item))
return true
}
@ -647,7 +648,7 @@ func (p *CounterPlugin) removeFromCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// -= those fuckers
item, err := GetUserItem(p.DB, nick, id, itemName)
item, err := GetUserItem(p.db, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -661,19 +662,19 @@ func (p *CounterPlugin) removeFromCmd(r bot.Request) bool {
n, _ := strconv.Atoi(r.Values["amount"])
log.Debug().Msgf("About to update item by -%d: %#v", n, item)
item.UpdateDelta(&r, -n)
p.Bot.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
p.b.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s has %d %s.", nick,
item.Count, item.Item))
return true
}
// Help responds to help requests. Every plugin must implement a help function.
func (p *CounterPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
p.Bot.Send(c, bot.Message, message.Channel, "You can set counters incrementally by using "+
p.b.Send(c, bot.Message, message.Channel, "You can set counters incrementally by using "+
"`<noun>++` and `<noun>--`. You can see all of your counters using "+
"`inspect`, erase them with `clear`, and view single counters with "+
"`count`.")
p.Bot.Send(c, bot.Message, message.Channel, "You can create aliases with `!mkalias <alias> <original>`")
p.Bot.Send(c, bot.Message, message.Channel, "You can remove aliases with `!rmalias <alias>`")
p.b.Send(c, bot.Message, message.Channel, "You can create aliases with `!mkalias <alias> <original>`")
p.b.Send(c, bot.Message, message.Channel, "You can remove aliases with `!rmalias <alias>`")
return true
}
@ -688,7 +689,7 @@ func (p *CounterPlugin) teaMatchCmd(r bot.Request) bool {
itemName := strings.ToLower(submatches[1])
// We will specifically allow :tea: to keep compatability
item, err := GetUserItem(p.DB, nick, id, itemName)
item, err := GetUserItem(p.db, nick, id, itemName)
if err != nil || (item.Count == 0 && item.Item != ":tea:") {
log.Error().
Err(err).
@ -703,7 +704,7 @@ func (p *CounterPlugin) teaMatchCmd(r bot.Request) bool {
delta = -1
}
item.UpdateDelta(&r, delta)
p.Bot.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s... %s has %d %s",
p.b.Send(r.Conn, bot.Message, channel, fmt.Sprintf("%s... %s has %d %s",
strings.Join(everyDayImShuffling([]string{"bleep", "bloop", "blop"}), "-"), nick, item.Count, itemName))
return true
}
@ -717,84 +718,6 @@ func everyDayImShuffling(vals []string) []string {
return ret
}
func (p *CounterPlugin) registerWeb() {
http.HandleFunc("/counter/api", p.handleCounterAPI)
http.HandleFunc("/counter", p.handleCounter)
p.Bot.RegisterWeb("/counter", "Counter")
}
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, html)
}
func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
info := struct {
User string
Thing string
Action string
Password string
}{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&info)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
log.Debug().
Interface("postbody", info).
Msg("Got a POST")
if p.Bot.CheckPassword("", info.Password) {
w.WriteHeader(http.StatusForbidden)
j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"})
w.Write(j)
return
}
nick, id := p.resolveUser(bot.Request{Conn: p.Bot.DefaultConnector()}, info.User)
item, err := GetUserItem(p.DB, nick, id, info.Thing)
if err != nil {
log.Error().
Err(err).
Str("subject", info.User).
Str("itemName", info.Thing).
Msg("error finding item")
w.WriteHeader(404)
fmt.Fprint(w, err)
return
}
if info.Action == "++" {
item.UpdateDelta(nil, 1)
} else if info.Action == "--" {
item.UpdateDelta(nil, -1)
} else {
w.WriteHeader(400)
fmt.Fprint(w, "Invalid increment")
return
}
}
all, err := GetAllItems(p.DB)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
data, err := json.Marshal(all)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
fmt.Fprint(w, string(data))
}
type Update struct {
Who string
What string
Amount int
}
type updateFunc func(bot.Request, Update)
var updateFuncs = []updateFunc{}

View File

@ -14,6 +14,7 @@ import (
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
@ -43,7 +44,7 @@ type alias struct {
}
func (a *alias) resolve(db *sqlx.DB) (*Factoid, error) {
// perform DB query to fill the To field
// perform db query to fill the To field
q := `select fact, next from factoid_alias where fact=?`
var next alias
err := db.Get(&next, q, a.Next)
@ -801,10 +802,11 @@ func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) {
// Register any web URLs desired
func (p *FactoidPlugin) registerWeb() {
http.HandleFunc("/factoid/api", p.serveAPI)
http.HandleFunc("/factoid/req", p.serveQuery)
http.HandleFunc("/factoid", p.serveQuery)
p.Bot.RegisterWeb("/factoid", "Factoid")
r := chi.NewRouter()
r.HandleFunc("/api", p.serveAPI)
r.HandleFunc("/req", p.serveQuery)
r.HandleFunc("/", p.serveQuery)
p.Bot.RegisterWebName(r, "/factoid", "Factoid")
}
func linkify(text string) template.HTML {

View File

@ -2,9 +2,9 @@ package git
import (
"fmt"
"net/http"
"regexp"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"gopkg.in/go-playground/webhooks.v5/github"
"gopkg.in/go-playground/webhooks.v5/gitlab"
@ -77,7 +77,9 @@ func (p *GitPlugin) register() {
}
func (p *GitPlugin) registerWeb() {
http.HandleFunc("/git/gitea/event", p.giteaEvent)
http.HandleFunc("/git/github/event", p.githubEvent)
http.HandleFunc("/git/gitlab/event", p.gitlabEvent)
r := chi.NewRouter()
r.HandleFunc("/gitea/event", p.giteaEvent)
r.HandleFunc("/github/event", p.githubEvent)
r.HandleFunc("/gitlab/event", p.gitlabEvent)
p.b.RegisterWeb(r, "/git")
}

View File

@ -361,6 +361,9 @@ func (p *MemePlugin) checkMeme(imgURL string) (int, int, error) {
}
img, err := DownloadTemplate(u)
if err != nil {
return 0, 0, err
}
return img.Bounds().Dx(), img.Bounds().Dy(), err
}

View File

@ -7,19 +7,21 @@ import (
"net/url"
"sort"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
)
func (p *MemePlugin) registerWeb(c bot.Connector) {
http.HandleFunc("/slash/meme", p.slashMeme(c))
http.HandleFunc("/meme/img/", p.img)
http.HandleFunc("/meme/all", p.all)
http.HandleFunc("/meme/add", p.addMeme)
http.HandleFunc("/meme/rm", p.rmMeme)
http.HandleFunc("/meme", p.webRoot)
p.bot.RegisterWeb("/meme", "Memes")
r := chi.NewRouter()
r.HandleFunc("/slash", p.slashMeme(c))
r.HandleFunc("/img", p.img)
r.HandleFunc("/all", p.all)
r.HandleFunc("/add", p.addMeme)
r.HandleFunc("/rm", p.rmMeme)
r.HandleFunc("/", p.webRoot)
p.bot.RegisterWebName(r, "/meme", "Memes")
}
type webResp struct {

View File

@ -2,6 +2,6 @@ package webshit
//func TestWebshit_Check(t *testing.T) {
// mb := bot.NewMockBot()
// ws := New(mb.DB())
// ws := New(mb.db())
// ws.checkBids()
//}

View File

@ -34,7 +34,7 @@ func makeMessage(nick, payload string, r *regexp.Regexp) bot.Request {
func makePlugin(t *testing.T) (*RememberPlugin, *fact.FactoidPlugin, *bot.MockBot) {
mb := bot.NewMockBot()
f := fact.New(mb) // for DB table
f := fact.New(mb) // for db table
p := New(mb)
assert.NotNil(t, p)
return p, f, mb

View File

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
@ -28,16 +29,17 @@ func New(b bot.Bot) *SecretsPlugin {
}
func (p *SecretsPlugin) registerWeb() {
http.HandleFunc("/secrets/add", p.handleRegister)
http.HandleFunc("/secrets/remove", p.handleRemove)
http.HandleFunc("/secrets/all", p.handleAll)
http.HandleFunc("/secrets/test", func(w http.ResponseWriter, r *http.Request) {
r := chi.NewRouter()
r.HandleFunc("/add", p.handleRegister)
r.HandleFunc("/remove", p.handleRemove)
r.HandleFunc("/all", p.handleAll)
r.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
value := r.URL.Query().Get("test")
j, _ := json.Marshal(map[string]string{"value": value})
w.Write(j)
})
http.HandleFunc("/secrets", p.handleIndex)
p.b.RegisterWeb("/secrets", "Secrets")
r.HandleFunc("/", p.handleIndex)
p.b.RegisterWebName(r, "/secrets", "Secrets")
}
func (p *SecretsPlugin) registerSecret(key, value string) error {

View File

@ -6,6 +6,7 @@ import (
"regexp"
"strings"
"github.com/go-chi/chi/v5"
twilio "github.com/kevinburke/twilio-go"
"github.com/rs/zerolog/log"
@ -118,7 +119,9 @@ func (p *SMSPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, ar
}
func (p *SMSPlugin) registerWeb() {
http.HandleFunc("/sms/new", p.receive)
r := chi.NewRouter()
r.HandleFunc("/new", p.receive)
p.b.RegisterWeb(r, "/sms")
}
func (p *SMSPlugin) receive(w http.ResponseWriter, r *http.Request) {

View File

@ -10,6 +10,7 @@ import (
"os/exec"
"strings"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
@ -171,7 +172,8 @@ func (p *TalkerPlugin) allCows() []string {
}
func (p *TalkerPlugin) registerWeb(c bot.Connector) {
http.HandleFunc("/slash/cowsay", func(w http.ResponseWriter, r *http.Request) {
r := chi.NewRouter()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
log.Debug().Msgf("Cowsay:\n%+v", r.PostForm.Get("text"))
channel := r.PostForm.Get("channel_id")
@ -184,4 +186,5 @@ func (p *TalkerPlugin) registerWeb(c bot.Connector) {
p.bot.Send(c, bot.Message, channel, msg)
w.WriteHeader(200)
})
p.bot.RegisterWeb(r, "/cowsay")
}

View File

@ -11,6 +11,7 @@ import (
"text/template"
"time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
@ -85,7 +86,9 @@ func New(b bot.Bot) *TwitchPlugin {
}
func (p *TwitchPlugin) registerWeb() {
http.HandleFunc("/isstreaming/", p.serveStreaming)
r := chi.NewRouter()
r.HandleFunc("/", p.serveStreaming)
p.bot.RegisterWeb(r, "/isstreaming")
}
func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) {