counter: add api, bot: change routes

* many routes changed by adding the chi router
* counter has an authenticated API to increment and decrement
This commit is contained in:
Chris Sexton 2021-07-28 11:32:59 -04:00 committed by Chris Sexton
parent c47a4f7c6f
commit 7ba9d94ac2
21 changed files with 124 additions and 31 deletions

View File

@ -129,8 +129,11 @@ func (b *bot) ListenAndServe() {
log.Fatal().Err(http.ListenAndServe(addr, b.router)).Msg("bot killed") log.Fatal().Err(http.ListenAndServe(addr, b.router)).Msg("bot killed")
} }
func (b *bot) RegisterWeb(r http.Handler, root, name string) { func (b *bot) RegisterWeb(r http.Handler, root string) {
log.Debug().Msgf("registering %s at %s", name, root) b.router.Mount(root, r)
}
func (b *bot) RegisterWebName(r http.Handler, root, name string) {
b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root}) b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root})
b.router.Mount(root, r) b.router.Mount(root, r)
} }
@ -391,6 +394,9 @@ func PluginName(p Plugin) string {
} }
func (b *bot) CheckPassword(secret, password string) bool { func (b *bot) CheckPassword(secret, password string) bool {
if password == "" {
return false
}
if b.password == password { if b.password == password {
return true return true
} }

View File

@ -135,7 +135,10 @@ type Bot interface {
RegisterFilter(string, func(string) string) RegisterFilter(string, func(string) string)
// RegisterWeb records a web endpoint for the UI // RegisterWeb records a web endpoint for the UI
RegisterWeb(http.Handler, string, string) RegisterWebName(http.Handler, string, string)
// RegisterWeb records a web endpoint for the API
RegisterWeb(http.Handler, string)
// Start the HTTP service // Start the HTTP service
ListenAndServe() ListenAndServe()
@ -202,6 +205,9 @@ type Connector interface {
// GetChannelName returns the channel ID for a human-friendly name (if possible) // GetChannelName returns the channel ID for a human-friendly name (if possible)
GetChannelID(id string) string GetChannelID(id string) string
// Get any web handlers the connector exposes
GetRouter() (http.Handler, string)
} }
// Plugin interface used for compatibility with the Plugin interface // 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) RegisterTable(p Plugin, hs HandlerTable) {}
func (mb *MockBot) RegisterRegex(p Plugin, kind Kind, r *regexp.Regexp, h ResponseHandler) {} 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) 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) GetWebNavigation() []EndPoint { return nil }
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool { func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
return false 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) OnBlacklist(ch, p string) bool { return false }
func (mb *MockBot) URLFormat(title, url string) string { return title + url } func (mb *MockBot) URLFormat(title, url string) string { return title + url }
func (mb *MockBot) CheckPassword(secret, password string) bool { return true } func (mb *MockBot) CheckPassword(secret, password string) bool { return true }
func (mb *MockBot) ListenAndServe() {}

View File

@ -3,6 +3,7 @@ package discord
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/http"
"strings" "strings"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
@ -34,6 +35,9 @@ func New(config *config.Config) *Discord {
} }
return d return d
} }
func (d *Discord) GetRouter() (http.Handler, string) {
return nil, ""
}
func (d *Discord) RegisterEvent(callback bot.Callback) { func (d *Discord) RegisterEvent(callback bot.Callback) {
d.event = callback d.event = callback

View File

@ -5,6 +5,7 @@ package irc
import ( import (
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"strings" "strings"
"time" "time"
@ -52,6 +53,10 @@ func New(c *config.Config) *Irc {
return &i return &i
} }
func (i *Irc) GetRouter() (http.Handler, string) {
return nil, ""
}
func (i *Irc) RegisterEvent(f bot.Callback) { func (i *Irc) RegisterEvent(f bot.Callback) {
i.event = f 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) { func (s *Slack) Send(kind bot.Kind, args ...interface{}) (string, error) {
switch kind { switch kind {
case bot.Message: case bot.Message:

View File

@ -17,6 +17,7 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/go-chi/chi/v5"
zerowidth "github.com/trubitsyn/go-zero-width" zerowidth "github.com/trubitsyn/go-zero-width"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -44,6 +45,7 @@ const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .Topi
type SlackApp struct { type SlackApp struct {
config *config.Config config *config.Config
api *slack.Client api *slack.Client
router *chi.Mux
botToken string botToken string
userToken string userToken string
@ -84,6 +86,7 @@ func New(c *config.Config) *SlackApp {
return &SlackApp{ return &SlackApp{
api: api, api: api,
router: chi.NewRouter(),
config: c, config: c,
botToken: token, botToken: token,
userToken: c.Get("slack.usertoken", "NONE"), userToken: c.Get("slack.usertoken", "NONE"),
@ -103,10 +106,14 @@ func (s *SlackApp) RegisterEvent(f bot.Callback) {
s.event = f s.event = f
} }
func (s *SlackApp) GetRouter() (http.Handler, string) {
return s.router, "/evt"
}
func (s *SlackApp) Serve() error { func (s *SlackApp) Serve() error {
s.populateEmojiList() 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 := new(bytes.Buffer)
buf.ReadFrom(r.Body) buf.ReadFrom(r.Body)
body := buf.String() body := buf.String()

View File

@ -120,6 +120,10 @@ func main() {
b := bot.New(c, client) b := bot.New(c, client)
if r, path := client.GetRouter(); r != nil {
b.RegisterWeb(r, path)
}
b.AddPlugin(admin.New(b)) b.AddPlugin(admin.New(b))
b.AddPlugin(secrets.New(b)) b.AddPlugin(secrets.New(b))
b.AddPlugin(giphy.New(b)) b.AddPlugin(giphy.New(b))

View File

@ -18,12 +18,12 @@ func (p *AdminPlugin) registerWeb() {
r := chi.NewRouter() r := chi.NewRouter()
r.HandleFunc("/api", p.handleVarsAPI) r.HandleFunc("/api", p.handleVarsAPI)
r.HandleFunc("/", p.handleVars) r.HandleFunc("/", p.handleVars)
p.bot.RegisterWeb(r, "/vars", "Variables") p.bot.RegisterWebName(r, "/vars", "Variables")
r = chi.NewRouter() r = chi.NewRouter()
r.HandleFunc("/verify", p.handleAppPassCheck) r.HandleFunc("/verify", p.handleAppPassCheck)
r.HandleFunc("/api", p.handleAppPassAPI) r.HandleFunc("/api", p.handleAppPassAPI)
r.HandleFunc("/", p.handleAppPass) r.HandleFunc("/", p.handleAppPass)
p.bot.RegisterWeb(r, "/apppass", "App Pass") p.bot.RegisterWebName(r, "/apppass", "App Pass")
} }
func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) { func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) {

View File

@ -19,6 +19,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/nfnt/resize" "github.com/nfnt/resize"
@ -589,7 +590,9 @@ func (p *BeersPlugin) untappdLoop(c bot.Connector, channel string) {
} }
func (p *BeersPlugin) registerWeb() { 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) { func (p *BeersPlugin) img(w http.ResponseWriter, r *http.Request) {

View File

@ -36,7 +36,11 @@ func (p *CliPlugin) registerWeb() {
r := chi.NewRouter() r := chi.NewRouter()
r.HandleFunc("/api", p.handleWebAPI) r.HandleFunc("/api", p.handleWebAPI)
r.HandleFunc("/", p.handleWeb) r.HandleFunc("/", p.handleWeb)
p.bot.RegisterWeb(r, "/cli", "CLI") 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) { func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {

View File

@ -10,7 +10,6 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user"
) )
func (p *CounterPlugin) registerWeb() { func (p *CounterPlugin) registerWeb() {
@ -19,19 +18,53 @@ func (p *CounterPlugin) registerWeb() {
r.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementAPI(-1)) r.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementAPI(-1))
r.HandleFunc("/api", p.handleCounterAPI) r.HandleFunc("/api", p.handleCounterAPI)
r.HandleFunc("/", p.handleCounter) r.HandleFunc("/", p.handleCounter)
p.b.RegisterWeb(r, "/counter", "Counter") p.b.RegisterWebName(r, "/counter", "Counter")
} }
func (p *CounterPlugin) mkIncrementAPI(delta int) func(w http.ResponseWriter, r *http.Request) { func (p *CounterPlugin) mkIncrementAPI(delta int) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
vars := map[string]string{} userName := chi.URLParam(r, "user")
userName := vars["user"] itemName := chi.URLParam(r, "item")
itemName := vars["item"]
item, err := GetUserItem(p.db, userName, "", itemName) secret, pass, ok := r.BasicAuth()
if err != nil { 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 return
} }
u := user.New(userName)
req := &bot.Request{ req := &bot.Request{
Conn: p.b.DefaultConnector(), Conn: p.b.DefaultConnector(),
Kind: bot.Message, Kind: bot.Message,
@ -45,8 +78,8 @@ func (p *CounterPlugin) mkIncrementAPI(delta int) func(w http.ResponseWriter, r
Args: nil, Args: nil,
} }
item.UpdateDelta(req, delta) item.UpdateDelta(req, delta)
msg := fmt.Sprintf("%s changed their %s counter by %d via the amazing %s API", msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API",
userName, itemName, delta, p.cfg.Get("nick", "catbase")) userName, itemName, delta, item.Count, p.cfg.Get("nick", "catbase"))
for _, ch := range p.cfg.GetArray("channels", []string{}) { for _, ch := range p.cfg.GetArray("channels", []string{}) {
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
} }

View File

@ -323,6 +323,7 @@ func New(b bot.Bot) *CounterPlugin {
cp := &CounterPlugin{ cp := &CounterPlugin{
b: b, b: b,
db: b.DB(), db: b.DB(),
cfg: b.Config(),
} }
b.RegisterRegex(cp, bot.Startup, regexp.MustCompile(`.*`), cp.migrate) b.RegisterRegex(cp, bot.Startup, regexp.MustCompile(`.*`), cp.migrate)

View File

@ -806,7 +806,7 @@ func (p *FactoidPlugin) registerWeb() {
r.HandleFunc("/api", p.serveAPI) r.HandleFunc("/api", p.serveAPI)
r.HandleFunc("/req", p.serveQuery) r.HandleFunc("/req", p.serveQuery)
r.HandleFunc("/", p.serveQuery) r.HandleFunc("/", p.serveQuery)
p.Bot.RegisterWeb(r, "/factoid", "Factoid") p.Bot.RegisterWebName(r, "/factoid", "Factoid")
} }
func linkify(text string) template.HTML { func linkify(text string) template.HTML {

View File

@ -2,9 +2,9 @@ package git
import ( import (
"fmt" "fmt"
"net/http"
"regexp" "regexp"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"gopkg.in/go-playground/webhooks.v5/github" "gopkg.in/go-playground/webhooks.v5/github"
"gopkg.in/go-playground/webhooks.v5/gitlab" "gopkg.in/go-playground/webhooks.v5/gitlab"
@ -77,7 +77,9 @@ func (p *GitPlugin) register() {
} }
func (p *GitPlugin) registerWeb() { func (p *GitPlugin) registerWeb() {
http.HandleFunc("/git/gitea/event", p.giteaEvent) r := chi.NewRouter()
http.HandleFunc("/git/github/event", p.githubEvent) r.HandleFunc("/gitea/event", p.giteaEvent)
http.HandleFunc("/git/gitlab/event", p.gitlabEvent) 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) img, err := DownloadTemplate(u)
if err != nil {
return 0, 0, err
}
return img.Bounds().Dx(), img.Bounds().Dy(), err return img.Bounds().Dx(), img.Bounds().Dy(), err
} }

View File

@ -21,7 +21,7 @@ func (p *MemePlugin) registerWeb(c bot.Connector) {
r.HandleFunc("/add", p.addMeme) r.HandleFunc("/add", p.addMeme)
r.HandleFunc("/rm", p.rmMeme) r.HandleFunc("/rm", p.rmMeme)
r.HandleFunc("/", p.webRoot) r.HandleFunc("/", p.webRoot)
p.bot.RegisterWeb(r, "/meme", "Memes") p.bot.RegisterWebName(r, "/meme", "Memes")
} }
type webResp struct { type webResp struct {

View File

@ -39,7 +39,7 @@ func (p *SecretsPlugin) registerWeb() {
w.Write(j) w.Write(j)
}) })
r.HandleFunc("/", p.handleIndex) r.HandleFunc("/", p.handleIndex)
p.b.RegisterWeb(r, "/secrets", "Secrets") p.b.RegisterWebName(r, "/secrets", "Secrets")
} }
func (p *SecretsPlugin) registerSecret(key, value string) error { func (p *SecretsPlugin) registerSecret(key, value string) error {

View File

@ -6,6 +6,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/go-chi/chi/v5"
twilio "github.com/kevinburke/twilio-go" twilio "github.com/kevinburke/twilio-go"
"github.com/rs/zerolog/log" "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() { 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) { func (p *SMSPlugin) receive(w http.ResponseWriter, r *http.Request) {

View File

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

View File

@ -11,6 +11,7 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
@ -85,7 +86,9 @@ func New(b bot.Bot) *TwitchPlugin {
} }
func (p *TwitchPlugin) registerWeb() { 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) { func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) {