diff --git a/bot/bot.go b/bot/bot.go index d8a3b51..7441945 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -4,10 +4,8 @@ package bot import ( "fmt" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/httprate" + "github.com/velour/catbase/bot/web" "math/rand" - "net/http" "os" "os/signal" "reflect" @@ -15,7 +13,6 @@ import ( "strings" "time" - "github.com/go-chi/chi/v5" "github.com/jmoiron/sqlx" "github.com/rs/zerolog/log" "github.com/velour/catbase/bot/history" @@ -53,8 +50,7 @@ type bot struct { version string - // The entries to the bot's HTTP interface - httpEndPoints []EndPoint + web *web.Web // filters registered by plugins filters map[string]func(string) string @@ -66,16 +62,9 @@ type bot struct { quiet bool - router *chi.Mux - history *history.History } -type EndPoint struct { - Name string `json:"name"` - URL string `json:"url"` -} - // Variable represents a $var replacement type Variable struct { Variable, Value string @@ -107,10 +96,8 @@ func New(config *config.Config, connector Connector) Bot { me: users[0], logIn: logIn, logOut: logOut, - httpEndPoints: make([]EndPoint, 0), filters: make(map[string]func(string) string), callbacks: make(CallbackMap), - router: chi.NewRouter(), history: history.New(historySz), } @@ -119,60 +106,25 @@ func New(config *config.Config, connector Connector) Bot { bot.RefreshPluginBlacklist() bot.RefreshPluginWhitelist() - log.Debug().Msgf("created web router") - - bot.setupHTTP() + bot.web = web.New(bot.config) connector.RegisterEvent(bot.Receive) return bot } -func (b *bot) setupHTTP() { - // Make the http logger optional - // It has never served a purpose in production and with the emojy page, can make a rather noisy log - if b.Config().GetInt("bot.useLogger", 0) == 1 { - b.router.Use(middleware.Logger) - } - - reqCount := b.Config().GetInt("bot.httprate.requests", 500) - reqTime := time.Duration(b.Config().GetInt("bot.httprate.seconds", 5)) - if reqCount > 0 && reqTime > 0 { - b.router.Use(httprate.LimitByIP(reqCount, reqTime*time.Second)) - } - - b.router.Use(middleware.RequestID) - b.router.Use(middleware.Recoverer) - b.router.Use(middleware.StripSlashes) - - b.router.HandleFunc("/", b.serveRoot) - b.router.HandleFunc("/nav", b.serveNav) - b.router.HandleFunc("/navHTML", b.serveNavHTML) - b.router.HandleFunc("/navHTML/{currentPage}", b.serveNavHTML) -} - func (b *bot) ListenAndServe() { addr := b.config.Get("HttpAddr", "127.0.0.1:1337") stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt) go func() { - log.Debug().Msgf("starting web service at %s", addr) - log.Fatal().Err(http.ListenAndServe(addr, b.router)).Msg("bot killed") + b.web.ListenAndServe(addr) }() <-stop b.DefaultConnector().Shutdown() b.Receive(b.DefaultConnector(), Shutdown, msg.Message{}) } -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. @@ -458,3 +410,7 @@ func (b *bot) CheckPassword(secret, password string) bool { } return false } + +func (b *bot) GetWeb() *web.Web { + return b.web +} diff --git a/bot/index.templ b/bot/index.templ deleted file mode 100644 index 8397b71..0000000 --- a/bot/index.templ +++ /dev/null @@ -1,22 +0,0 @@ -package bot - -templ (b *bot) index() { - - - - - - - - catbase - - - -
- @b.Nav("", b.GetWebNavigation()) -
- - - - -} \ No newline at end of file diff --git a/bot/index_templ.go b/bot/index_templ.go deleted file mode 100644 index 33e4dd7..0000000 --- a/bot/index_templ.go +++ /dev/null @@ -1,43 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.2.543 -package bot - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" - -func (b *bot) index() templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) - if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("catbase
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = b.Nav("", b.GetWebNavigation()).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } - return templ_7745c5c3_Err - }) -} diff --git a/bot/interfaces.go b/bot/interfaces.go index c238409..3fb0224 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -4,6 +4,7 @@ package bot import ( "github.com/gabriel-vasile/mimetype" + "github.com/velour/catbase/bot/web" "net/http" "regexp" "strings" @@ -168,20 +169,14 @@ type Bot interface { // RegisterFilter creates a filter function for message processing RegisterFilter(string, func(string) string) - // RegisterWeb records a web endpoint for the UI - 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 - // GetWebNavigation returns the current known web endpoints - GetWebNavigation() []EndPoint + // GetWeb returns the bot's webserver structure + GetWeb() *web.Web // GetPassword generates a unique password for modification commands on the public website GetPassword() string diff --git a/bot/mock.go b/bot/mock.go index cab54c9..9cdb757 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -4,6 +4,7 @@ package bot import ( "fmt" + "github.com/velour/catbase/bot/web" "net/http" "regexp" "strconv" @@ -26,6 +27,8 @@ type MockBot struct { Messages []string Actions []string Reactions []string + + web *web.Web } func (mb *MockBot) Config() *config.Config { return mb.Cfg } @@ -60,9 +63,7 @@ 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) RegisterWebName(_ http.Handler, _, _ string) {} -func (mb *MockBot) RegisterWeb(_ http.Handler, _ string) {} -func (mb *MockBot) GetWebNavigation() []EndPoint { return nil } +func (mb *MockBot) GetWeb() *web.Web { return mb.web } func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...any) bool { return false } @@ -118,6 +119,7 @@ func NewMockBot() *MockBot { Messages: make([]string, 0), Actions: make([]string, 0), } + b.web = web.New(cfg) // If any plugin registered a route, we need to reset those before any new test http.DefaultServeMux = new(http.ServeMux) return &b diff --git a/bot/nav.templ b/bot/nav.templ deleted file mode 100644 index 100fc6c..0000000 --- a/bot/nav.templ +++ /dev/null @@ -1,25 +0,0 @@ -package bot - -templ (b *bot) Nav(currentPage string, items []EndPoint) { - -} \ No newline at end of file diff --git a/bot/nav_templ.go b/bot/nav_templ.go deleted file mode 100644 index b23b217..0000000 --- a/bot/nav_templ.go +++ /dev/null @@ -1,104 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.2.543 -package bot - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" - -func (b *bot) Nav(currentPage string, items []EndPoint) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) - if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } - return templ_7745c5c3_Err - }) -} diff --git a/bot/web.go b/bot/web.go deleted file mode 100644 index 7898331..0000000 --- a/bot/web.go +++ /dev/null @@ -1,43 +0,0 @@ -package bot - -import ( - "encoding/json" - "github.com/go-chi/chi/v5" - "net/http" - "strings" -) - -func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { - b.index().Render(r.Context(), w) -} - -func (b *bot) serveNavHTML(w http.ResponseWriter, r *http.Request) { - currentPage := chi.URLParam(r, "currentPage") - b.Nav(currentPage, b.GetWebNavigation()).Render(r.Context(), w) -} - -func (b *bot) serveNav(w http.ResponseWriter, r *http.Request) { - enc := json.NewEncoder(w) - err := enc.Encode(b.GetWebNavigation()) - if err != nil { - jsonErr, _ := json.Marshal(err) - w.WriteHeader(500) - w.Write(jsonErr) - } -} - -// GetWebNavigation returns a list of bootstrap-vue links -// The parent