mirror of https://github.com/velour/catbase.git
Compare commits
No commits in common. "b20da607bcd4c1733b8dd1b1f94ce1659b6fe457" and "c089a80ffcf2368e09e45a1f2675d6762242127e" have entirely different histories.
b20da607bc
...
c089a80ffc
|
@ -20,6 +20,7 @@ _cgo_export.*
|
||||||
_testmain.go
|
_testmain.go
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
|
catbase
|
||||||
config.json
|
config.json
|
||||||
*.db
|
*.db
|
||||||
vendor
|
vendor
|
||||||
|
@ -76,4 +77,3 @@ impact.ttf
|
||||||
.env
|
.env
|
||||||
rathaus_discord.sh
|
rathaus_discord.sh
|
||||||
emojy
|
emojy
|
||||||
catbase
|
|
||||||
|
|
60
bot/bot.go
60
bot/bot.go
|
@ -4,8 +4,10 @@ package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/velour/catbase/bot/web"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/go-chi/httprate"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -13,6 +15,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot/history"
|
"github.com/velour/catbase/bot/history"
|
||||||
|
@ -50,7 +53,8 @@ type bot struct {
|
||||||
|
|
||||||
version string
|
version string
|
||||||
|
|
||||||
web *web.Web
|
// The entries to the bot's HTTP interface
|
||||||
|
httpEndPoints []EndPoint
|
||||||
|
|
||||||
// filters registered by plugins
|
// filters registered by plugins
|
||||||
filters map[string]func(string) string
|
filters map[string]func(string) string
|
||||||
|
@ -62,9 +66,16 @@ type bot struct {
|
||||||
|
|
||||||
quiet bool
|
quiet bool
|
||||||
|
|
||||||
|
router *chi.Mux
|
||||||
|
|
||||||
history *history.History
|
history *history.History
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EndPoint struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
// Variable represents a $var replacement
|
// Variable represents a $var replacement
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
Variable, Value string
|
Variable, Value string
|
||||||
|
@ -96,8 +107,10 @@ func New(config *config.Config, connector Connector) Bot {
|
||||||
me: users[0],
|
me: users[0],
|
||||||
logIn: logIn,
|
logIn: logIn,
|
||||||
logOut: logOut,
|
logOut: logOut,
|
||||||
|
httpEndPoints: make([]EndPoint, 0),
|
||||||
filters: make(map[string]func(string) string),
|
filters: make(map[string]func(string) string),
|
||||||
callbacks: make(CallbackMap),
|
callbacks: make(CallbackMap),
|
||||||
|
router: chi.NewRouter(),
|
||||||
history: history.New(historySz),
|
history: history.New(historySz),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,25 +119,60 @@ func New(config *config.Config, connector Connector) Bot {
|
||||||
bot.RefreshPluginBlacklist()
|
bot.RefreshPluginBlacklist()
|
||||||
bot.RefreshPluginWhitelist()
|
bot.RefreshPluginWhitelist()
|
||||||
|
|
||||||
bot.web = web.New(bot.config)
|
log.Debug().Msgf("created web router")
|
||||||
|
|
||||||
|
bot.setupHTTP()
|
||||||
|
|
||||||
connector.RegisterEvent(bot.Receive)
|
connector.RegisterEvent(bot.Receive)
|
||||||
|
|
||||||
return bot
|
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() {
|
func (b *bot) ListenAndServe() {
|
||||||
addr := b.config.Get("HttpAddr", "127.0.0.1:1337")
|
addr := b.config.Get("HttpAddr", "127.0.0.1:1337")
|
||||||
stop := make(chan os.Signal, 1)
|
stop := make(chan os.Signal, 1)
|
||||||
signal.Notify(stop, os.Interrupt)
|
signal.Notify(stop, os.Interrupt)
|
||||||
go func() {
|
go func() {
|
||||||
b.web.ListenAndServe(addr)
|
log.Debug().Msgf("starting web service at %s", addr)
|
||||||
|
log.Fatal().Err(http.ListenAndServe(addr, b.router)).Msg("bot killed")
|
||||||
}()
|
}()
|
||||||
<-stop
|
<-stop
|
||||||
b.DefaultConnector().Shutdown()
|
b.DefaultConnector().Shutdown()
|
||||||
b.Receive(b.DefaultConnector(), Shutdown, msg.Message{})
|
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
|
// 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.
|
// 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.
|
// Usage should be limited to out-of-band communications such as timed messages.
|
||||||
|
@ -410,7 +458,3 @@ func (b *bot) CheckPassword(secret, password string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) GetWeb() *web.Web {
|
|
||||||
return b.web
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!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@^2/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@^2/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@^2/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>catbase</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<b-navbar>
|
||||||
|
<b-navbar-brand>catbase</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.url">{{ item.name }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: [],
|
||||||
|
},
|
||||||
|
mounted: function() {
|
||||||
|
axios.get('/nav')
|
||||||
|
.then(resp => {
|
||||||
|
this.nav = resp.data;
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -4,7 +4,6 @@ package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/velour/catbase/bot/web"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -169,14 +168,20 @@ type Bot interface {
|
||||||
// RegisterFilter creates a filter function for message processing
|
// RegisterFilter creates a filter function for message processing
|
||||||
RegisterFilter(string, func(string) string)
|
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
|
// Start the HTTP service
|
||||||
ListenAndServe()
|
ListenAndServe()
|
||||||
|
|
||||||
// DefaultConnector returns the base connector, which may not be the only connector
|
// DefaultConnector returns the base connector, which may not be the only connector
|
||||||
DefaultConnector() Connector
|
DefaultConnector() Connector
|
||||||
|
|
||||||
// GetWeb returns the bot's webserver structure
|
// GetWebNavigation returns the current known web endpoints
|
||||||
GetWeb() *web.Web
|
GetWebNavigation() []EndPoint
|
||||||
|
|
||||||
// GetPassword generates a unique password for modification commands on the public website
|
// GetPassword generates a unique password for modification commands on the public website
|
||||||
GetPassword() string
|
GetPassword() string
|
||||||
|
|
|
@ -4,7 +4,6 @@ package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/velour/catbase/bot/web"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -27,8 +26,6 @@ type MockBot struct {
|
||||||
Messages []string
|
Messages []string
|
||||||
Actions []string
|
Actions []string
|
||||||
Reactions []string
|
Reactions []string
|
||||||
|
|
||||||
web *web.Web
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *MockBot) Config() *config.Config { return mb.Cfg }
|
func (mb *MockBot) Config() *config.Config { return mb.Cfg }
|
||||||
|
@ -63,7 +60,9 @@ 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) GetWeb() *web.Web { return mb.web }
|
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 ...any) bool {
|
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...any) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -119,7 +118,6 @@ func NewMockBot() *MockBot {
|
||||||
Messages: make([]string, 0),
|
Messages: make([]string, 0),
|
||||||
Actions: 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
|
// If any plugin registered a route, we need to reset those before any new test
|
||||||
http.DefaultServeMux = new(http.ServeMux)
|
http.DefaultServeMux = new(http.ServeMux)
|
||||||
return &b
|
return &b
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/">catbase</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{{- $currentPage := .CurrentPage -}}
|
||||||
|
{{range .Items}}
|
||||||
|
<li class="nav-item">
|
||||||
|
{{ if (eq $currentPage .Name) }}
|
||||||
|
<a class="nav-link active" aria-current="page" href="{{.URL}}">{{.Name}}</a>
|
||||||
|
{{ else }}
|
||||||
|
<a class="nav-link" href="{{.URL}}">{{.Name}}</a>
|
||||||
|
{{ end }}
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
|
@ -0,0 +1,59 @@
|
||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var embeddedFS embed.FS
|
||||||
|
|
||||||
|
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
|
index, _ := embeddedFS.ReadFile("index.html")
|
||||||
|
w.Write(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) serveNavHTML(w http.ResponseWriter, r *http.Request) {
|
||||||
|
currentPage := chi.URLParam(r, "currentPage")
|
||||||
|
tpl := template.Must(template.ParseFS(embeddedFS, "nav.html"))
|
||||||
|
if err := tpl.Execute(w, struct {
|
||||||
|
CurrentPage string
|
||||||
|
Items []EndPoint
|
||||||
|
}{currentPage, b.GetWebNavigation()}); err != nil {
|
||||||
|
log.Error().Err(err).Msg("template error")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, "Error parsing nav template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 <b-nav-item> links
|
||||||
|
// The parent <nav> is not included so each page may display it as
|
||||||
|
// best fits
|
||||||
|
func (b *bot) GetWebNavigation() []EndPoint {
|
||||||
|
endpoints := b.httpEndPoints
|
||||||
|
moreEndpoints := b.config.GetArray("bot.links", []string{})
|
||||||
|
for _, e := range moreEndpoints {
|
||||||
|
link := strings.SplitN(e, ":", 2)
|
||||||
|
if len(link) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
endpoints = append(endpoints, EndPoint{link[0], link[1]})
|
||||||
|
}
|
||||||
|
return endpoints
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
templ (w *Web) Header(title string) {
|
|
||||||
<head>
|
|
||||||
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
|
||||||
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
if title != "" {
|
|
||||||
<title>catbase - { title }</title>
|
|
||||||
} else {
|
|
||||||
<title>catbase</title>
|
|
||||||
}
|
|
||||||
</head>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (w *Web) Footer() {
|
|
||||||
<script src="//unpkg.com/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script src="//unpkg.com/htmx.org@1.9.10" integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC" crossorigin="anonymous"></script>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (w *Web) Index(title string, contents templ.Component) {
|
|
||||||
<!DOCTYPE html />
|
|
||||||
<html lang="en">
|
|
||||||
@w.Header(title)
|
|
||||||
<body>
|
|
||||||
|
|
||||||
@w.Nav(title)
|
|
||||||
|
|
||||||
if contents != nil {
|
|
||||||
@contents
|
|
||||||
}
|
|
||||||
|
|
||||||
@w.Footer()
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (w *Web) Nav(currentPage string) {
|
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand" href="/">catbase</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
for _, item := range w.GetWebNavigation() {
|
|
||||||
<li class="nav-item">
|
|
||||||
if currentPage == item.Name {
|
|
||||||
<a class="nav-link active" aria-current="page" href={ templ.URL(item.URL) }>{ item.Name }</a>
|
|
||||||
} else {
|
|
||||||
<a class="nav-link" href={ templ.URL(item.URL) }>{ item.Name }</a>
|
|
||||||
}
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
}
|
|
|
@ -1,230 +0,0 @@
|
||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.2.543
|
|
||||||
package web
|
|
||||||
|
|
||||||
//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 (w *Web) Header(title string) 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("<head><!-- Load required Bootstrap and BootstrapVue CSS --><link type=\"text/css\" rel=\"stylesheet\" href=\"//unpkg.com/bootstrap/dist/css/bootstrap.min.css\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta charset=\"UTF-8\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if title != "" {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<title>catbase - ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 9, Col: 36}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</title>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<title>catbase</title>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</head>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Web) Footer() 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_Var3 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var3 == nil {
|
|
||||||
templ_7745c5c3_Var3 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script src=\"//unpkg.com/bootstrap/dist/js/bootstrap.bundle.min.js\"></script><script src=\"//unpkg.com/htmx.org@1.9.10\" integrity=\"sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC\" crossorigin=\"anonymous\"></script>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Web) Index(title string, contents templ.Component) 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_Var4 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var4 == nil {
|
|
||||||
templ_7745c5c3_Var4 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html /><html lang=\"en\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = w.Header(title).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<body>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = w.Nav(title).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if contents != nil {
|
|
||||||
templ_7745c5c3_Err = contents.Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = w.Footer().Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Web) Nav(currentPage string) 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_Var5 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var5 == nil {
|
|
||||||
templ_7745c5c3_Var5 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"navbar navbar-expand-lg bg-body-tertiary\"><div class=\"container-fluid\"><a class=\"navbar-brand\" href=\"/\">catbase</a> <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarNav\" aria-controls=\"navbarNav\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"><span class=\"navbar-toggler-icon\"></span></button><div class=\"collapse navbar-collapse\" id=\"navbarNav\"><ul class=\"navbar-nav\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, item := range w.GetWebNavigation() {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"nav-item\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if currentPage == item.Name {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"nav-link active\" aria-current=\"page\" href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(item.URL)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var7 string
|
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 50, Col: 119}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a class=\"nav-link\" href=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var8 templ.SafeURL = templ.URL(item.URL)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var9 string
|
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 52, Col: 92}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></div></div></nav>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
105
bot/web/web.go
105
bot/web/web.go
|
@ -1,105 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
|
||||||
"github.com/go-chi/httprate"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/velour/catbase/config"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Web struct {
|
|
||||||
config *config.Config
|
|
||||||
router *chi.Mux
|
|
||||||
httpEndPoints []EndPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
type EndPoint struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetWebNavigation returns a list of bootstrap-vue <b-nav-item> links
|
|
||||||
// The parent <nav> is not included so each page may display it as
|
|
||||||
// best fits
|
|
||||||
func (ws *Web) GetWebNavigation() []EndPoint {
|
|
||||||
endpoints := ws.httpEndPoints
|
|
||||||
moreEndpoints := ws.config.GetArray("bot.links", []string{})
|
|
||||||
for _, e := range moreEndpoints {
|
|
||||||
link := strings.SplitN(e, ":", 2)
|
|
||||||
if len(link) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
endpoints = append(endpoints, EndPoint{link[0], link[1]})
|
|
||||||
}
|
|
||||||
return endpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *Web) serveRoot(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ws.Index("Home", nil).Render(r.Context(), w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *Web) serveNavHTML(w http.ResponseWriter, r *http.Request) {
|
|
||||||
currentPage := chi.URLParam(r, "currentPage")
|
|
||||||
ws.Nav(currentPage).Render(r.Context(), w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *Web) serveNav(w http.ResponseWriter, r *http.Request) {
|
|
||||||
enc := json.NewEncoder(w)
|
|
||||||
err := enc.Encode(ws.GetWebNavigation())
|
|
||||||
if err != nil {
|
|
||||||
jsonErr, _ := json.Marshal(err)
|
|
||||||
w.WriteHeader(500)
|
|
||||||
w.Write(jsonErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *Web) 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 ws.config.GetInt("bot.useLogger", 0) == 1 {
|
|
||||||
ws.router.Use(middleware.Logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqCount := ws.config.GetInt("bot.httprate.requests", 500)
|
|
||||||
reqTime := time.Duration(ws.config.GetInt("bot.httprate.seconds", 5))
|
|
||||||
if reqCount > 0 && reqTime > 0 {
|
|
||||||
ws.router.Use(httprate.LimitByIP(reqCount, reqTime*time.Second))
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.router.Use(middleware.RequestID)
|
|
||||||
ws.router.Use(middleware.Recoverer)
|
|
||||||
ws.router.Use(middleware.StripSlashes)
|
|
||||||
|
|
||||||
ws.router.HandleFunc("/", ws.serveRoot)
|
|
||||||
ws.router.HandleFunc("/nav", ws.serveNav)
|
|
||||||
ws.router.HandleFunc("/navHTML", ws.serveNavHTML)
|
|
||||||
ws.router.HandleFunc("/navHTML/{currentPage}", ws.serveNavHTML)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *Web) RegisterWeb(r http.Handler, root string) {
|
|
||||||
ws.router.Mount(root, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *Web) RegisterWebName(r http.Handler, root, name string) {
|
|
||||||
ws.httpEndPoints = append(ws.httpEndPoints, EndPoint{name, root})
|
|
||||||
ws.router.Mount(root, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *Web) ListenAndServe(addr string) {
|
|
||||||
log.Debug().Msgf("starting web service at %s", addr)
|
|
||||||
log.Fatal().Err(http.ListenAndServe(addr, ws.router)).Msg("bot killed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(config *config.Config) *Web {
|
|
||||||
w := &Web{
|
|
||||||
config: config,
|
|
||||||
router: chi.NewRouter(),
|
|
||||||
}
|
|
||||||
w.setupHTTP()
|
|
||||||
return w
|
|
||||||
}
|
|
19
go.mod
19
go.mod
|
@ -5,12 +5,11 @@ go 1.18
|
||||||
require (
|
require (
|
||||||
code.chrissexton.org/cws/getaoc v0.0.0-20231202052842-1b2a337b799d
|
code.chrissexton.org/cws/getaoc v0.0.0-20231202052842-1b2a337b799d
|
||||||
github.com/ChimeraCoder/anaconda v2.0.0+incompatible
|
github.com/ChimeraCoder/anaconda v2.0.0+incompatible
|
||||||
github.com/PuerkitoBio/goquery v1.8.1
|
github.com/PuerkitoBio/goquery v1.8.0
|
||||||
github.com/a-h/templ v0.2.543
|
|
||||||
github.com/andrewstuart/openai v0.8.0
|
github.com/andrewstuart/openai v0.8.0
|
||||||
github.com/bwmarrin/discordgo v0.26.1
|
github.com/bwmarrin/discordgo v0.26.1
|
||||||
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598
|
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1
|
github.com/cenkalti/backoff/v4 v4.1.3
|
||||||
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff
|
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff
|
||||||
github.com/chrissexton/sentiment v0.0.0-20190927141846-d69c422ba035
|
github.com/chrissexton/sentiment v0.0.0-20190927141846-d69c422ba035
|
||||||
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
|
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
|
||||||
|
@ -35,7 +34,7 @@ require (
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
github.com/trubitsyn/go-zero-width v1.0.1
|
github.com/trubitsyn/go-zero-width v1.0.1
|
||||||
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158
|
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90
|
||||||
gopkg.in/go-playground/webhooks.v5 v5.17.0
|
gopkg.in/go-playground/webhooks.v5 v5.17.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,8 +77,8 @@ require (
|
||||||
github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a // indirect
|
github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a // indirect
|
||||||
github.com/kevinburke/go.uuid v1.2.0 // indirect
|
github.com/kevinburke/go.uuid v1.2.0 // indirect
|
||||||
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
|
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect
|
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
@ -102,10 +101,10 @@ require (
|
||||||
github.com/ttacon/libphonenumber v1.1.0 // indirect
|
github.com/ttacon/libphonenumber v1.1.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/net v0.7.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.7.0 // indirect
|
||||||
gonum.org/v1/gonum v0.6.0 // indirect
|
gonum.org/v1/gonum v0.6.0 // indirect
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
||||||
|
|
43
go.sum
43
go.sum
|
@ -45,10 +45,8 @@ github.com/ChimeraCoder/anaconda v2.0.0+incompatible/go.mod h1:TCt3MijIq3Qqo9SBt
|
||||||
github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7 h1:r+EmXjfPosKO4wfiMLe1XQictsIlhErTufbWUsjOTZs=
|
github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7 h1:r+EmXjfPosKO4wfiMLe1XQictsIlhErTufbWUsjOTZs=
|
||||||
github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7/go.mod h1:b2EuEMLSG9q3bZ95ql1+8oVqzzrTNSiOQqSXWFBzxeI=
|
github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7/go.mod h1:b2EuEMLSG9q3bZ95ql1+8oVqzzrTNSiOQqSXWFBzxeI=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||||
github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk=
|
|
||||||
github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA=
|
|
||||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
@ -80,8 +78,8 @@ github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598 h1:j2XRGH5Y5uWtBYXGw
|
||||||
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598/go.mod h1:sduMkaHcXDIWurl/Bd/z0rNEUHw5tr6LUA9IO8E9o0o=
|
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598/go.mod h1:sduMkaHcXDIWurl/Bd/z0rNEUHw5tr6LUA9IO8E9o0o=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
@ -199,7 +197,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
@ -276,13 +274,11 @@ github.com/leso-kn/go-sqlite3 v0.0.0-20230710125852-03158dc838ed h1:lM1oz49yOQhE
|
||||||
github.com/leso-kn/go-sqlite3 v0.0.0-20230710125852-03158dc838ed/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/leso-kn/go-sqlite3 v0.0.0-20230710125852-03158dc838ed/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
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/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mmcdole/gofeed v1.1.3 h1:pdrvMb18jMSLidGp8j0pLvc9IGziX4vbmvVqmLH6z8o=
|
github.com/mmcdole/gofeed v1.1.3 h1:pdrvMb18jMSLidGp8j0pLvc9IGziX4vbmvVqmLH6z8o=
|
||||||
|
@ -389,7 +385,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
@ -405,9 +400,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
@ -444,7 +438,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -482,10 +475,8 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -503,8 +494,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -548,15 +539,11 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -564,9 +551,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
@ -614,7 +600,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
6
main.go
6
main.go
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
//go:generate templ generate
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"github.com/velour/catbase/plugins/gpt"
|
"github.com/velour/catbase/plugins/gpt"
|
||||||
|
@ -47,6 +45,7 @@ import (
|
||||||
"github.com/velour/catbase/plugins/admin"
|
"github.com/velour/catbase/plugins/admin"
|
||||||
"github.com/velour/catbase/plugins/babbler"
|
"github.com/velour/catbase/plugins/babbler"
|
||||||
"github.com/velour/catbase/plugins/beers"
|
"github.com/velour/catbase/plugins/beers"
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"github.com/velour/catbase/plugins/couldashouldawoulda"
|
"github.com/velour/catbase/plugins/couldashouldawoulda"
|
||||||
"github.com/velour/catbase/plugins/counter"
|
"github.com/velour/catbase/plugins/counter"
|
||||||
"github.com/velour/catbase/plugins/dice"
|
"github.com/velour/catbase/plugins/dice"
|
||||||
|
@ -129,7 +128,7 @@ func main() {
|
||||||
b := bot.New(c, client)
|
b := bot.New(c, client)
|
||||||
|
|
||||||
if r, path := client.GetRouter(); r != nil {
|
if r, path := client.GetRouter(); r != nil {
|
||||||
b.GetWeb().RegisterWeb(r, path)
|
b.RegisterWeb(r, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.AddPlugin(admin.New(b))
|
b.AddPlugin(admin.New(b))
|
||||||
|
@ -167,6 +166,7 @@ func main() {
|
||||||
b.AddPlugin(twitter.New(b))
|
b.AddPlugin(twitter.New(b))
|
||||||
b.AddPlugin(git.New(b))
|
b.AddPlugin(git.New(b))
|
||||||
b.AddPlugin(impossible.New(b))
|
b.AddPlugin(impossible.New(b))
|
||||||
|
b.AddPlugin(cli.New(b))
|
||||||
b.AddPlugin(aoc.New(b))
|
b.AddPlugin(aoc.New(b))
|
||||||
b.AddPlugin(meme.New(b))
|
b.AddPlugin(meme.New(b))
|
||||||
b.AddPlugin(achievements.New(b))
|
b.AddPlugin(achievements.New(b))
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
package admin
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
templ (a *AdminPlugin) page() {
|
|
||||||
<div class="container">
|
|
||||||
<form>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<label for="password">Password:
|
|
||||||
<input type="text" name="password"></input>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<label for="secret">Secret:
|
|
||||||
<input type="text" name="secret"></input>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<button hx-post="/apppass/api" hx-target="#data" class="btn btn-primary">List</button>
|
|
||||||
<button hx-put="/apppass/api" hx-target="#data" class="btn btn-secondary">New</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div id="data"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (a *AdminPlugin) showPassword(entry PassEntry) {
|
|
||||||
<div><span>ID</span><span>{ fmt.Sprintf("%d", entry.ID) }</span></div>
|
|
||||||
<div><span>Password</span><span>{ entry.Secret }:{ entry.Pass }</span></div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (a *AdminPlugin) entries(items []PassEntry) {
|
|
||||||
<div>
|
|
||||||
if len(items) == 0 {
|
|
||||||
<span>No items</span>
|
|
||||||
}
|
|
||||||
<ul>
|
|
||||||
for _, entry := range items {
|
|
||||||
<li>
|
|
||||||
<button href="#"
|
|
||||||
class="btn btn-danger"
|
|
||||||
hx-delete="/apppass/api"
|
|
||||||
hx-confirm={ fmt.Sprintf("Are you sure you want to delete %d?", entry.ID) }
|
|
||||||
hx-target="#data"
|
|
||||||
hx-include="this,[name='password'],[name='secret']"
|
|
||||||
name="id" value={ fmt.Sprintf("%d", entry.ID) }>X</button>
|
|
||||||
{ fmt.Sprintf("%d", entry.ID) }
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ renderError(err error) {
|
|
||||||
<div>{ err.Error() }</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ vars(items []configEntry) {
|
|
||||||
<div class="container">
|
|
||||||
<table class="table-responsive table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Key</th>
|
|
||||||
<th>Value</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
for _, item := range items {
|
|
||||||
<tr>
|
|
||||||
<td>{ item.Key }</td><td>{ item.Value }</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
if len(items) == 0 {
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">No data</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
|
@ -1,276 +0,0 @@
|
||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.2.543
|
|
||||||
package admin
|
|
||||||
|
|
||||||
//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"
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func (a *AdminPlugin) page() 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("<div class=\"container\"><form><div class=\"row\"><div class=\"col\"><label for=\"password\">Password: <input type=\"text\" name=\"password\"></label></div><div class=\"col\"><label for=\"secret\">Secret: <input type=\"text\" name=\"secret\"></label></div><div class=\"col\"><button hx-post=\"/apppass/api\" hx-target=\"#data\" class=\"btn btn-primary\">List</button> <button hx-put=\"/apppass/api\" hx-target=\"#data\" class=\"btn btn-secondary\">New</button></div></div></form><div class=\"row\"><div id=\"data\"></div></div></div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AdminPlugin) showPassword(entry PassEntry) 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_Var2 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var2 == nil {
|
|
||||||
templ_7745c5c3_Var2 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><span>ID</span><span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 32, Col: 59}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div><div><span>Password</span><span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Secret)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 33, Col: 50}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Pass)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 33, Col: 65}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AdminPlugin) entries(items []PassEntry) 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_Var6 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var6 == nil {
|
|
||||||
templ_7745c5c3_Var6 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if len(items) == 0 {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span>No items</span>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, entry := range items {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><button href=\"#\" class=\"btn btn-danger\" hx-delete=\"/apppass/api\" hx-confirm=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("Are you sure you want to delete %d?", entry.ID)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" hx-include=\"this,[name='password'],[name='secret']\" name=\"id\" value=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("%d", entry.ID)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">X</button> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var7 string
|
|
||||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 51, Col: 57}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderError(err error) 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_Var8 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var8 == nil {
|
|
||||||
templ_7745c5c3_Var8 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var9 string
|
|
||||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error())
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 59, Col: 22}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func vars(items []configEntry) 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_Var10 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var10 == nil {
|
|
||||||
templ_7745c5c3_Var10 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"container\"><table class=\"table-responsive table-striped\"><thead><tr><th>Key</th><th>Value</th></tr></thead> <tbody>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, item := range items {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var11 string
|
|
||||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Key)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 74, Col: 38}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var12 string
|
|
||||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Value)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 74, Col: 61}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(items) == 0 {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td colspan=\"2\">No data</td></tr>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table></div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -32,8 +34,10 @@ func makeMessage(payload string, r *regexp.Regexp) bot.Request {
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
|
c := cli.CliPlugin{}
|
||||||
values := bot.ParseValues(r, payload)
|
values := bot.ParseValues(r, payload)
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &c,
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Values: values,
|
Values: values,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
<!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@^2/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@^2/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@^2/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Vars</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<b-navbar>
|
||||||
|
<b-navbar-brand>App Pass</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'App Pass'">{{ item.name }}
|
||||||
|
</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
<div class="alert alert-warning alert-dismissible fade show" role="alert" v-if="err != ''">
|
||||||
|
<b-button type="link" class="close" data-dismiss="alert" aria-label="Close" @click="err = ''">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</b-button>
|
||||||
|
{{ err }}
|
||||||
|
</div>
|
||||||
|
<b-form>
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="5">Password:</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-input v-model="password"/>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="5">Secret:</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-input v-model="entry.secret"/>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row>
|
||||||
|
<b-col>
|
||||||
|
<b-button @click="list">List</b-button>
|
||||||
|
</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-button @click="newPass">New</b-button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</b-form>
|
||||||
|
<b-container v-show="showPassword" style="padding: 2em">
|
||||||
|
<b-row align-h="center">
|
||||||
|
<b-col align-self="center" cols="1">ID:</b-col>
|
||||||
|
<b-col align-self="center" cols="3">{{ entry.id }}</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row align-h="center">
|
||||||
|
<b-col align-self="center" cols="1">Password:</b-col>
|
||||||
|
<b-col align-self="center" cols="3">{{ entry.secret }}:{{ showPassword }}</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row align-h="center">
|
||||||
|
<b-col align-self="center" class="text-center" cols="6">Note: this password will only be displayed once. For
|
||||||
|
single-field entry passwords, use the secret:password format.
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
<b-container>
|
||||||
|
<b-row style="padding-top: 2em;">
|
||||||
|
<b-col>
|
||||||
|
<ul>
|
||||||
|
<li v-for="entry in entries" key="id">
|
||||||
|
<a @click="rm(entry)" href="#">X</a> {{entry.id}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
entry: {
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
|
password: '',
|
||||||
|
showPassword: '',
|
||||||
|
nav: [],
|
||||||
|
entries: [],
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
axios.get('/nav')
|
||||||
|
.then(resp => {
|
||||||
|
this.nav = resp.data;
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err))
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
rm: function (data) {
|
||||||
|
this.showPassword = '';
|
||||||
|
this.entry.id = data.id
|
||||||
|
axios.delete('/apppass/api', {
|
||||||
|
data: {
|
||||||
|
password: this.password,
|
||||||
|
passEntry: this.entry
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.getData()
|
||||||
|
})
|
||||||
|
.catch(({response}) => {
|
||||||
|
console.log('error: ' + response.data.err)
|
||||||
|
this.err = response.data.err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
list: function () {
|
||||||
|
this.showPassword = '';
|
||||||
|
this.getData();
|
||||||
|
},
|
||||||
|
newPass: function () {
|
||||||
|
axios.put('/apppass/api', {
|
||||||
|
password: this.password,
|
||||||
|
passEntry: this.entry
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
this.getData()
|
||||||
|
this.showPassword = resp.data.pass
|
||||||
|
this.entry.id = resp.data.id
|
||||||
|
})
|
||||||
|
.catch(({response}) => {
|
||||||
|
console.log('error: ' + response.data.err)
|
||||||
|
this.err = response.data.err
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getData: function () {
|
||||||
|
axios.post('/apppass/api', {
|
||||||
|
password: this.password,
|
||||||
|
passEntry: this.entry
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
this.entries = resp.data;
|
||||||
|
})
|
||||||
|
.catch(({response}) => {
|
||||||
|
console.log('error: ' + response.data.err)
|
||||||
|
this.err = response.data.err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>vars</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.4"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div hx-get="/navHTML/Variables" hx-trigger="load" hx-swap="outerHTML"></div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<table class="table-responsive table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Key</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{.Key}}</td><td>{{.Value}}</td>
|
||||||
|
</tr>
|
||||||
|
{{else}}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">No data</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,34 +1,39 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var embeddedFS embed.FS
|
||||||
|
|
||||||
func (p *AdminPlugin) registerWeb() {
|
func (p *AdminPlugin) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
r.HandleFunc("/api", p.handleVarsAPI)
|
||||||
r.HandleFunc("/", p.handleVars)
|
r.HandleFunc("/", p.handleVars)
|
||||||
p.bot.GetWeb().RegisterWebName(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.GetWeb().RegisterWebName(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) {
|
||||||
p.bot.GetWeb().Index("App Pass", p.page()).Render(r.Context(), w)
|
index, _ := embeddedFS.ReadFile("apppass.html")
|
||||||
|
w.Write(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PassEntry struct {
|
type PassEntry struct {
|
||||||
|
@ -72,41 +77,26 @@ func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
|
func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
|
||||||
b, _ := io.ReadAll(r.Body)
|
|
||||||
query, _ := url.ParseQuery(string(b))
|
|
||||||
secret := r.FormValue("secret")
|
|
||||||
password := r.FormValue("password")
|
|
||||||
id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64)
|
|
||||||
if !r.Form.Has("secret") && query.Has("secret") {
|
|
||||||
secret = query.Get("secret")
|
|
||||||
}
|
|
||||||
if !r.Form.Has("password") && query.Has("password") {
|
|
||||||
password = query.Get("password")
|
|
||||||
}
|
|
||||||
if !r.Form.Has("id") && query.Has("id") {
|
|
||||||
id, _ = strconv.ParseInt(query.Get("id"), 10, 64)
|
|
||||||
}
|
|
||||||
req := struct {
|
req := struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
PassEntry PassEntry `json:"passEntry"`
|
PassEntry PassEntry `json:"passEntry"`
|
||||||
}{
|
}{}
|
||||||
password,
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
PassEntry{
|
_ = json.Unmarshal(body, &req)
|
||||||
ID: id,
|
|
||||||
Secret: secret,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if req.PassEntry.Secret == "" {
|
if req.PassEntry.Secret == "" {
|
||||||
writeErr(r.Context(), w, fmt.Errorf("missing secret"))
|
writeErr(w, fmt.Errorf("missing secret"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.Password == "" || !p.bot.CheckPassword(req.PassEntry.Secret, req.Password) {
|
if req.Password == "" || !p.bot.CheckPassword(req.PassEntry.Secret, req.Password) {
|
||||||
writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password"))
|
writeErr(w, fmt.Errorf("missing or incorrect password"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
|
if req.PassEntry.Secret == "" {
|
||||||
|
writeErr(w, fmt.Errorf("missing secret"))
|
||||||
|
return
|
||||||
|
}
|
||||||
if string(req.PassEntry.Pass) == "" {
|
if string(req.PassEntry.Pass) == "" {
|
||||||
c := 10
|
c := 10
|
||||||
b := make([]byte, c)
|
b := make([]byte, c)
|
||||||
|
@ -131,27 +121,27 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost)
|
res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErr(r.Context(), w, err)
|
writeErr(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
id, err := res.LastInsertId()
|
id, err := res.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErr(r.Context(), w, err)
|
writeErr(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req.PassEntry.ID = id
|
req.PassEntry.ID = id
|
||||||
p.showPassword(req.PassEntry).Render(r.Context(), w)
|
j, _ := json.Marshal(req.PassEntry)
|
||||||
|
fmt.Fprint(w, string(j))
|
||||||
return
|
return
|
||||||
case http.MethodDelete:
|
case http.MethodDelete:
|
||||||
|
|
||||||
if req.PassEntry.ID <= 0 {
|
if req.PassEntry.ID <= 0 {
|
||||||
writeErr(r.Context(), w, fmt.Errorf("missing ID"))
|
writeErr(w, fmt.Errorf("missing ID"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q := `delete from apppass where id = ?`
|
q := `delete from apppass where id = ?`
|
||||||
_, err := p.db.Exec(q, req.PassEntry.ID)
|
_, err := p.db.Exec(q, req.PassEntry.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErr(r.Context(), w, err)
|
writeErr(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,15 +149,22 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
passEntries := []PassEntry{}
|
passEntries := []PassEntry{}
|
||||||
err := p.db.Select(&passEntries, q, req.PassEntry.Secret)
|
err := p.db.Select(&passEntries, q, req.PassEntry.Secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErr(r.Context(), w, err)
|
writeErr(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.entries(passEntries).Render(r.Context(), w)
|
j, _ := json.Marshal(passEntries)
|
||||||
|
_, _ = fmt.Fprint(w, string(j))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeErr(ctx context.Context, w http.ResponseWriter, err error) {
|
func writeErr(w http.ResponseWriter, err error) {
|
||||||
log.Error().Err(err).Msg("apppass error")
|
log.Error().Err(err).Msg("apppass error")
|
||||||
renderError(err).Render(ctx, w)
|
j, _ := json.Marshal(struct {
|
||||||
|
Err string `json:"err"`
|
||||||
|
}{
|
||||||
|
err.Error(),
|
||||||
|
})
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprint(w, string(j))
|
||||||
}
|
}
|
||||||
|
|
||||||
type configEntry struct {
|
type configEntry struct {
|
||||||
|
@ -176,6 +173,7 @@ type configEntry struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) {
|
func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tpl := template.Must(template.ParseFS(embeddedFS, "vars.html"))
|
||||||
var configEntries []configEntry
|
var configEntries []configEntry
|
||||||
q := `select key, value from config`
|
q := `select key, value from config`
|
||||||
err := p.db.Select(&configEntries, q)
|
err := p.db.Select(&configEntries, q)
|
||||||
|
@ -188,5 +186,37 @@ func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.bot.GetWeb().Index("Variables", vars(configEntries)).Render(r.Context(), w)
|
if err := tpl.Execute(w, struct {
|
||||||
|
Items []configEntry
|
||||||
|
}{configEntries}); err != nil {
|
||||||
|
log.Error().Err(err).Msg("template error")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, "Error parsing template")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AdminPlugin) handleVarsAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var configEntries []struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
q := `select key, value from config`
|
||||||
|
err := p.db.Select(&configEntries, q)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Error getting config entries.")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, e := range configEntries {
|
||||||
|
if strings.Contains(e.Value, ";;") {
|
||||||
|
e.Value = strings.ReplaceAll(e.Value, ";;", ", ")
|
||||||
|
e.Value = fmt.Sprintf("[%s]", e.Value)
|
||||||
|
configEntries[i] = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j, _ := json.Marshal(configEntries)
|
||||||
|
fmt.Fprintf(w, "%s", j)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -14,11 +16,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string, r *regexp.Regexp) bot.Request {
|
func makeMessage(payload string, r *regexp.Regexp) bot.Request {
|
||||||
|
c := &cli.CliPlugin{}
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: c,
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Values: bot.ParseValues(r, payload),
|
Values: bot.ParseValues(r, payload),
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
|
@ -268,6 +272,7 @@ func TestHelp(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
bp.help(nil, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
c := &cli.CliPlugin{}
|
||||||
|
bp.help(c, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -598,7 +598,7 @@ func (p *BeersPlugin) untappdLoop(c bot.Connector, channel string) {
|
||||||
func (p *BeersPlugin) registerWeb() {
|
func (p *BeersPlugin) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.HandleFunc("/img/{id}", p.img)
|
r.HandleFunc("/img/{id}", p.img)
|
||||||
p.b.GetWeb().RegisterWeb(r, "/beers")
|
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) {
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -19,8 +21,10 @@ func makeMessage(payload string, r *regexp.Regexp) bot.Request {
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
|
c := &cli.CliPlugin{}
|
||||||
values := bot.ParseValues(r, payload)
|
values := bot.ParseValues(r, payload)
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: c,
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Values: values,
|
Values: values,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
|
@ -132,6 +136,6 @@ func TestBeersReport(t *testing.T) {
|
||||||
|
|
||||||
func TestHelp(t *testing.T) {
|
func TestHelp(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.help(nil, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
b.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/bot/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var embeddedFS embed.FS
|
||||||
|
|
||||||
|
type CliPlugin struct {
|
||||||
|
bot bot.Bot
|
||||||
|
db *sqlx.DB
|
||||||
|
cache string
|
||||||
|
counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(b bot.Bot) *CliPlugin {
|
||||||
|
cp := &CliPlugin{
|
||||||
|
bot: b,
|
||||||
|
}
|
||||||
|
cp.registerWeb()
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CliPlugin) registerWeb() {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.HandleFunc("/api", p.handleWebAPI)
|
||||||
|
r.HandleFunc("/", p.handleWeb)
|
||||||
|
p.bot.RegisterWebName(r, "/cli", "CLI")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CliPlugin) Shutdown() {}
|
||||||
|
|
||||||
|
func (p *CliPlugin) GetRouter() (http.Handler, string) {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
fmt.Fprintf(w, "Incorrect HTTP method")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}{}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bot.Receive(p, bot.Message, msg.Message{
|
||||||
|
User: &user.User{
|
||||||
|
ID: info.User,
|
||||||
|
Name: info.User,
|
||||||
|
Admin: false,
|
||||||
|
},
|
||||||
|
Channel: "web",
|
||||||
|
Body: info.Payload,
|
||||||
|
Raw: info.Payload,
|
||||||
|
Command: true,
|
||||||
|
Time: time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
info.User = p.bot.WhoAmI()
|
||||||
|
info.Payload = p.cache
|
||||||
|
p.cache = ""
|
||||||
|
|
||||||
|
data, err := json.Marshal(info)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
|
||||||
|
index, _ := embeddedFS.ReadFile("index.html")
|
||||||
|
w.Write(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completing the Connector interface, but will not actually be a connector
|
||||||
|
func (p *CliPlugin) RegisterEvent(cb bot.Callback) {}
|
||||||
|
func (p *CliPlugin) Send(kind bot.Kind, args ...any) (string, error) {
|
||||||
|
switch kind {
|
||||||
|
case bot.Message:
|
||||||
|
fallthrough
|
||||||
|
case bot.Action:
|
||||||
|
fallthrough
|
||||||
|
case bot.Reply:
|
||||||
|
fallthrough
|
||||||
|
case bot.Reaction:
|
||||||
|
p.cache += args[1].(string) + "\n"
|
||||||
|
}
|
||||||
|
id := fmt.Sprintf("%d", p.counter)
|
||||||
|
p.counter++
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
func (p *CliPlugin) GetEmojiList(bool) map[string]string { return nil }
|
||||||
|
func (p *CliPlugin) Serve() error { return nil }
|
||||||
|
func (p *CliPlugin) Who(s string) []string { return nil }
|
||||||
|
func (p *CliPlugin) Profile(name string) (user.User, error) {
|
||||||
|
return user.User{}, fmt.Errorf("unimplemented")
|
||||||
|
}
|
||||||
|
func (p *CliPlugin) Emojy(name string) string { return name }
|
||||||
|
func (p *CliPlugin) DeleteEmojy(name string) error { return nil }
|
||||||
|
func (p *CliPlugin) UploadEmojy(emojy, path string) error { return nil }
|
||||||
|
func (p *CliPlugin) URLFormat(title, url string) string {
|
||||||
|
return fmt.Sprintf("%s (%s)", title, url)
|
||||||
|
}
|
||||||
|
func (p *CliPlugin) GetChannelName(id string) string { return id }
|
||||||
|
func (p *CliPlugin) GetChannelID(name string) string { return name }
|
||||||
|
func (p *CliPlugin) GetRoles() ([]bot.Role, error) { return []bot.Role{}, nil }
|
||||||
|
func (p *CliPlugin) SetRole(userID, roleID string) error { return nil }
|
||||||
|
func (p *CliPlugin) Nick(string) error { return nil }
|
|
@ -0,0 +1,132 @@
|
||||||
|
<!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@^2/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@^2/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@^2/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>CLI</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<b-navbar>
|
||||||
|
<b-navbar-brand>CLI</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'CLI'">{{ item.name }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
<b-alert
|
||||||
|
dismissable
|
||||||
|
variant="error"
|
||||||
|
:show="err">
|
||||||
|
{{ err }}
|
||||||
|
</b-alert>
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="5">Password:</b-col>
|
||||||
|
<b-col><b-input v-model="answer"></b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row>
|
||||||
|
<b-form-textarea
|
||||||
|
v-sticky-scroll
|
||||||
|
disabled
|
||||||
|
id="textarea"
|
||||||
|
v-model="text"
|
||||||
|
placeholder="The bot will respond here..."
|
||||||
|
rows="10"
|
||||||
|
max-rows="10"
|
||||||
|
no-resize
|
||||||
|
></b-form-textarea>
|
||||||
|
</b-row>
|
||||||
|
<b-form
|
||||||
|
@submit="send">
|
||||||
|
<b-row>
|
||||||
|
<b-col>
|
||||||
|
<b-form-input
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
v-model="user"></b-form-input>
|
||||||
|
</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-form-input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter something to send to the bot"
|
||||||
|
v-model="input"
|
||||||
|
autocomplete="off"
|
||||||
|
></b-form-input>
|
||||||
|
</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-button type="submit" :disabled="!authenticated">Send</b-button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-form>
|
||||||
|
</b-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: [],
|
||||||
|
answer: '',
|
||||||
|
correct: 0,
|
||||||
|
textarea: [],
|
||||||
|
user: '',
|
||||||
|
input: '',
|
||||||
|
},
|
||||||
|
mounted: function() {
|
||||||
|
axios.get('/nav')
|
||||||
|
.then(resp => {
|
||||||
|
this.nav = resp.data;
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err))
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
authenticated: function() {
|
||||||
|
if (this.user !== '')
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
text: function() {
|
||||||
|
return this.textarea.join('\n');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addText(user, text) {
|
||||||
|
this.textarea.push(user + ": " + text);
|
||||||
|
const len = this.textarea.length;
|
||||||
|
if (this.textarea.length > 10)
|
||||||
|
this.textarea = this.textarea.slice(len-10, len);
|
||||||
|
},
|
||||||
|
send(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation()
|
||||||
|
if (!this.authenticated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = {user: this.user, payload: this.input, password: this.answer};
|
||||||
|
this.addText(this.user, this.input);
|
||||||
|
this.input = "";
|
||||||
|
axios.post('/cli/api', payload)
|
||||||
|
.then(resp => {
|
||||||
|
const data = resp.data;
|
||||||
|
this.addText(data.user, data.payload.trim());
|
||||||
|
this.err = '';
|
||||||
|
})
|
||||||
|
.catch(err => (this.err = err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
|
@ -19,6 +21,7 @@ func makeMessage(payload string) bot.Request {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package counter
|
package counter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/velour/catbase/bot/user"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -16,6 +18,9 @@ import (
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var embeddedFS embed.FS
|
||||||
|
|
||||||
func (p *CounterPlugin) registerWeb() {
|
func (p *CounterPlugin) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
requests := p.cfg.GetInt("counter.requestsPer", 1)
|
requests := p.cfg.GetInt("counter.requestsPer", 1)
|
||||||
|
@ -28,77 +33,9 @@ func (p *CounterPlugin) registerWeb() {
|
||||||
subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1))
|
subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1))
|
||||||
subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementByNAPI(-1))
|
subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementByNAPI(-1))
|
||||||
r.Mount("/", subrouter)
|
r.Mount("/", subrouter)
|
||||||
r.HandleFunc("/users/{user}/items/{item}/increment", p.incHandler(1))
|
r.HandleFunc("/api", p.handleCounterAPI)
|
||||||
r.HandleFunc("/users/{user}/items/{item}/decrement", p.incHandler(-1))
|
|
||||||
r.HandleFunc("/", p.handleCounter)
|
r.HandleFunc("/", p.handleCounter)
|
||||||
p.b.GetWeb().RegisterWebName(r, "/counter", "Counter")
|
p.b.RegisterWebName(r, "/counter", "Counter")
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CounterPlugin) incHandler(delta int) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
userName, _ := url.QueryUnescape(chi.URLParam(r, "user"))
|
|
||||||
itemName, _ := url.QueryUnescape(chi.URLParam(r, "item"))
|
|
||||||
pass := r.FormValue("password")
|
|
||||||
if !p.b.CheckPassword("", pass) {
|
|
||||||
w.WriteHeader(401)
|
|
||||||
fmt.Fprintf(w, "error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
item, err := p.delta(userName, itemName, "", delta)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(500)
|
|
||||||
fmt.Fprintf(w, "error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.renderItem(userName, item).Render(r.Context(), w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CounterPlugin) delta(userName, itemName, personalMessage string, delta int) (Item, error) {
|
|
||||||
// Try to find an ID if possible
|
|
||||||
id := ""
|
|
||||||
u, err := p.b.DefaultConnector().Profile(userName)
|
|
||||||
if err == nil {
|
|
||||||
id = u.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
item, err := GetUserItem(p.db, userName, id, itemName)
|
|
||||||
if err != nil {
|
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
|
|
||||||
chs := p.cfg.GetMap("counter.channelItems", map[string]string{})
|
|
||||||
ch, ok := chs[itemName]
|
|
||||||
req := &bot.Request{
|
|
||||||
Conn: p.b.DefaultConnector(),
|
|
||||||
Kind: bot.Message,
|
|
||||||
Msg: msg.Message{
|
|
||||||
User: &u,
|
|
||||||
// Noting here that we're only going to do goals in a "default"
|
|
||||||
// channel even if it should send updates to others.
|
|
||||||
Channel: ch,
|
|
||||||
Body: fmt.Sprintf("%s += %d", itemName, delta),
|
|
||||||
Time: time.Now(),
|
|
||||||
},
|
|
||||||
Values: nil,
|
|
||||||
Args: nil,
|
|
||||||
}
|
|
||||||
msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API. %s",
|
|
||||||
userName, itemName, delta, item.Count+delta, p.cfg.Get("nick", "catbase"), personalMessage)
|
|
||||||
if !ok {
|
|
||||||
chs := p.cfg.GetArray("counter.channels", []string{})
|
|
||||||
for _, ch := range chs {
|
|
||||||
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
|
|
||||||
req.Msg.Channel = ch
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
|
|
||||||
req.Msg.Channel = ch
|
|
||||||
}
|
|
||||||
if err := item.UpdateDelta(req, delta); err != nil {
|
|
||||||
return item, err
|
|
||||||
}
|
|
||||||
return item, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWriter, r *http.Request) {
|
func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -127,15 +64,15 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(r.Body)
|
// Try to find an ID if possible
|
||||||
postData := map[string]string{}
|
id := ""
|
||||||
err = json.Unmarshal(body, &postData)
|
u, err := p.b.DefaultConnector().Profile(userName)
|
||||||
personalMsg := ""
|
if err == nil {
|
||||||
if inputMsg, ok := postData["message"]; ok {
|
id = u.ID
|
||||||
personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := p.delta(userName, itemName, personalMsg, delta*direction); err != nil {
|
item, err := GetUserItem(p.db, userName, id, itemName)
|
||||||
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("error finding item")
|
log.Error().Err(err).Msg("error finding item")
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
j, _ := json.Marshal(struct {
|
j, _ := json.Marshal(struct {
|
||||||
|
@ -146,13 +83,130 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(r.Body)
|
||||||
|
postData := map[string]string{}
|
||||||
|
err = json.Unmarshal(body, &postData)
|
||||||
|
personalMsg := ""
|
||||||
|
if inputMsg, ok := postData["message"]; ok {
|
||||||
|
personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
chs := p.cfg.GetMap("counter.channelItems", map[string]string{})
|
||||||
|
ch, ok := chs[itemName]
|
||||||
|
if len(chs) == 0 || !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req := &bot.Request{
|
||||||
|
Conn: p.b.DefaultConnector(),
|
||||||
|
Kind: bot.Message,
|
||||||
|
Msg: msg.Message{
|
||||||
|
User: &u,
|
||||||
|
// Noting here that we're only going to do goals in a "default"
|
||||||
|
// channel even if it should send updates to others.
|
||||||
|
Channel: ch,
|
||||||
|
Body: fmt.Sprintf("%s += %d", itemName, delta),
|
||||||
|
Time: time.Now(),
|
||||||
|
},
|
||||||
|
Values: nil,
|
||||||
|
Args: nil,
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API. %s",
|
||||||
|
userName, itemName, delta, item.Count+delta*direction, p.cfg.Get("nick", "catbase"), personalMsg)
|
||||||
|
if !ok {
|
||||||
|
chs := p.cfg.GetArray("counter.channels", []string{})
|
||||||
|
for _, ch := range chs {
|
||||||
|
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
|
||||||
|
req.Msg.Channel = ch
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg)
|
||||||
|
req.Msg.Channel = ch
|
||||||
|
}
|
||||||
|
item.UpdateDelta(req, delta*direction)
|
||||||
j, _ := json.Marshal(struct{ Status bool }{true})
|
j, _ := json.Marshal(struct{ Status bool }{true})
|
||||||
fmt.Fprint(w, string(j))
|
fmt.Fprint(w, string(j))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
|
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
|
||||||
p.b.GetWeb().Index("Counter", p.index()).Render(r.Context(), w)
|
index, _ := embeddedFS.ReadFile("index.html")
|
||||||
|
w.Write(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
req := bot.Request{
|
||||||
|
Conn: p.b.DefaultConnector(),
|
||||||
|
Kind: bot.Message,
|
||||||
|
Msg: msg.Message{
|
||||||
|
User: &user.User{
|
||||||
|
ID: "",
|
||||||
|
Name: info.User,
|
||||||
|
Admin: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// resolveUser requires a "full" request object so we are faking it
|
||||||
|
nick, id := p.resolveUser(req, 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)
|
||||||
|
log.Error().Err(err).Msg("Error getting items")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(all)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
log.Error().Err(err).Msg("Error marshaling items")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update represents a change that gets sent off to other plugins such as goals
|
// Update represents a change that gets sent off to other plugins such as goals
|
||||||
|
|
|
@ -45,7 +45,7 @@ type alias struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetItems returns all counters
|
// GetItems returns all counters
|
||||||
func GetAllItemsByUser(db *sqlx.DB) (map[string][]Item, error) {
|
func GetAllItems(db *sqlx.DB) ([]Item, error) {
|
||||||
var items []Item
|
var items []Item
|
||||||
err := db.Select(&items, `select * from counter`)
|
err := db.Select(&items, `select * from counter`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -55,11 +55,7 @@ func GetAllItemsByUser(db *sqlx.DB) (map[string][]Item, error) {
|
||||||
for i := range items {
|
for i := range items {
|
||||||
items[i].DB = db
|
items[i].DB = db
|
||||||
}
|
}
|
||||||
out := map[string][]Item{}
|
return items, nil
|
||||||
for _, it := range items {
|
|
||||||
out[it.Nick] = append(out[it.Nick], it)
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetItems returns all counters for a subject
|
// GetItems returns all counters for a subject
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
package counter
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func urlFor(who, what, dir string) string {
|
|
||||||
return fmt.Sprintf("/counter/users/%s/items/%s/%s", who, what, dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CounterPlugin) allItems() map[string][]Item {
|
|
||||||
items, err := GetAllItemsByUser(p.db)
|
|
||||||
if err != nil {
|
|
||||||
return map[string][]Item{"error": []Item{}}
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (p *CounterPlugin) index() {
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<label>Password: <input type="text" name="password" /></label>
|
|
||||||
</div>
|
|
||||||
for user, items := range p.allItems() {
|
|
||||||
<div class="row">
|
|
||||||
{ user }:
|
|
||||||
<div class="container">
|
|
||||||
for _, thing := range items {
|
|
||||||
@p.renderItem(user, thing)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (p *CounterPlugin) renderItem(user string, item Item) {
|
|
||||||
<div class="row" id={ fmt.Sprintf("item%d", item.ID) }>
|
|
||||||
<div class="col offset-1">
|
|
||||||
{ item.Item }
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{ fmt.Sprintf("%d", item.Count) }
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<button
|
|
||||||
hx-target={ "#"+fmt.Sprintf("item%d", item.ID) }
|
|
||||||
hx-include="[name='password']"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-post={ urlFor(user, item.Item, "decrement") }
|
|
||||||
>-</button>
|
|
||||||
<button
|
|
||||||
hx-target={ "#"+fmt.Sprintf("item%d", item.ID) }
|
|
||||||
hx-include="[name='password']"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-post={ urlFor(user, item.Item, "increment") }
|
|
||||||
>+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.2.543
|
|
||||||
package counter
|
|
||||||
|
|
||||||
//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"
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func urlFor(who, what, dir string) string {
|
|
||||||
return fmt.Sprintf("/counter/users/%s/items/%s/%s", who, what, dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CounterPlugin) allItems() map[string][]Item {
|
|
||||||
items, err := GetAllItemsByUser(p.db)
|
|
||||||
if err != nil {
|
|
||||||
return map[string][]Item{"error": []Item{}}
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CounterPlugin) 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("<div class=\"container\"><div class=\"row\"><label>Password: <input type=\"text\" name=\"password\"></label></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for user, items := range p.allItems() {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"row\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var2 string
|
|
||||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/counter/counter.templ`, Line: 23, Col: 22}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(":<div class=\"container\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, thing := range items {
|
|
||||||
templ_7745c5c3_Err = p.renderItem(user, thing).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CounterPlugin) renderItem(user string, item Item) 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_Var3 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var3 == nil {
|
|
||||||
templ_7745c5c3_Var3 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"row\" id=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("item%d", item.ID)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"col offset-1\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.Item)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/counter/counter.templ`, Line: 37, Col: 23}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"col\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var5 string
|
|
||||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Count))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/counter/counter.templ`, Line: 40, Col: 43}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"col-2\"><button hx-target=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + fmt.Sprintf("item%d", item.ID)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-include=\"[name='password']\" hx-swap=\"outerHTML\" hx-post=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(urlFor(user, item.Item, "decrement")))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">-</button> <button hx-target=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + fmt.Sprintf("item%d", item.ID)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-include=\"[name='password']\" hx-swap=\"outerHTML\" hx-post=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(urlFor(user, item.Item, "increment")))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">+</button></div></div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -4,12 +4,12 @@ package counter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/velour/catbase/config"
|
|
||||||
"github.com/velour/catbase/connectors/irc"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
|
@ -33,7 +33,7 @@ func makeMessage(payload string, r *regexp.Regexp) bot.Request {
|
||||||
}
|
}
|
||||||
values := bot.ParseValues(r, payload)
|
values := bot.ParseValues(r, payload)
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
Conn: irc.New(&config.Config{}),
|
Conn: &cli.CliPlugin{},
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
User: &user.User{Name: "tester", ID: "id"},
|
User: &user.User{Name: "tester", ID: "id"},
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -280,6 +280,6 @@ func TestInspectMe(t *testing.T) {
|
||||||
func TestHelp(t *testing.T) {
|
func TestHelp(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.help(nil, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Greater(t, len(mb.Messages), 1)
|
assert.Greater(t, len(mb.Messages), 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
func (p *Cowboy) registerWeb() {
|
func (p *Cowboy) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.HandleFunc("/img/{overlay}/{what}", p.handleImage)
|
r.HandleFunc("/img/{overlay}/{what}", p.handleImage)
|
||||||
p.b.GetWeb().RegisterWeb(r, "/cowboy")
|
p.b.RegisterWeb(r, "/cowboy")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Cowboy) handleImage(w http.ResponseWriter, r *http.Request) {
|
func (p *Cowboy) handleImage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -19,6 +21,7 @@ func makeMessage(payload string) bot.Request {
|
||||||
}
|
}
|
||||||
values := bot.ParseValues(rollRegex, payload)
|
values := bot.ParseValues(rollRegex, payload)
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Values: values,
|
Values: values,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
|
@ -64,6 +67,6 @@ func TestHelp(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.help(nil, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ func (p *EmojyPlugin) registerWeb() {
|
||||||
r.HandleFunc("/list", p.handlePage("list.html"))
|
r.HandleFunc("/list", p.handlePage("list.html"))
|
||||||
r.HandleFunc("/new", p.handlePage("upload.html"))
|
r.HandleFunc("/new", p.handlePage("upload.html"))
|
||||||
r.HandleFunc("/", p.handleIndex)
|
r.HandleFunc("/", p.handleIndex)
|
||||||
p.b.GetWeb().RegisterWebName(r, "/emojy", "Emojys")
|
p.b.RegisterWebName(r, "/emojy", "Emojys")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EmojyPlugin) handleIndex(w http.ResponseWriter, r *http.Request) {
|
func (p *EmojyPlugin) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -19,7 +19,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.b.GetWeb().RegisterWebName(r, "/factoid", "Factoid")
|
p.b.RegisterWebName(r, "/factoid", "Factoid")
|
||||||
}
|
}
|
||||||
|
|
||||||
func linkify(text string) template.HTML {
|
func linkify(text string) template.HTML {
|
||||||
|
|
|
@ -81,5 +81,5 @@ func (p *GitPlugin) registerWeb() {
|
||||||
r.HandleFunc("/gitea/event", p.giteaEvent)
|
r.HandleFunc("/gitea/event", p.giteaEvent)
|
||||||
r.HandleFunc("/github/event", p.githubEvent)
|
r.HandleFunc("/github/event", p.githubEvent)
|
||||||
r.HandleFunc("/gitlab/event", p.gitlabEvent)
|
r.HandleFunc("/gitlab/event", p.gitlabEvent)
|
||||||
p.b.GetWeb().RegisterWeb(r, "/git")
|
p.b.RegisterWeb(r, "/git")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ package leftpad
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -16,6 +18,7 @@ func makeMessage(payload string) bot.Request {
|
||||||
values := bot.ParseValues(leftpadRegex, payload)
|
values := bot.ParseValues(leftpadRegex, payload)
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Values: values,
|
Values: values,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
package meme
|
|
||||||
|
|
||||||
templ (p *MemePlugin) index(all webResps) {
|
|
||||||
<div class="container">
|
|
||||||
<form>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-3">
|
|
||||||
<input type="text" name="name" placeholder="Name..." />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<input type="text" name="url" placeholder="URL..." />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<textarea name="config">
|
|
||||||
</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<button class="btn btn-primary"
|
|
||||||
hx-post="/meme/add"
|
|
||||||
hx-target="#newMemes"
|
|
||||||
>Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div id="newMemes">
|
|
||||||
</div>
|
|
||||||
for _, meme := range all {
|
|
||||||
@p.Show(meme)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (p *MemePlugin) Show(meme webResp) {
|
|
||||||
<div class="row" id={ meme.Name }>
|
|
||||||
<div class="col-3">
|
|
||||||
{ meme.Name }
|
|
||||||
<img
|
|
||||||
class="img-thumbnail rounded"
|
|
||||||
alt={ meme.Name }
|
|
||||||
src={ meme.URL } />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<pre>
|
|
||||||
{ meme.Config }
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<button class="btn btn-primary"
|
|
||||||
hx-get={ "/meme/edit/"+meme.Name }
|
|
||||||
hx-target={ "#"+meme.Name }
|
|
||||||
>Edit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (p *MemePlugin) Edit(meme webResp) {
|
|
||||||
<form>
|
|
||||||
<div class="row" id={ meme.Name }>
|
|
||||||
<div class="col-3">
|
|
||||||
<img
|
|
||||||
class="img-thumbnail rounded"
|
|
||||||
alt={ meme.Name }
|
|
||||||
src={ meme.URL } />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<textarea name="config">
|
|
||||||
{ meme.Config }
|
|
||||||
</textarea>
|
|
||||||
<input type="text" name="url" value={ meme.URL } />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<button class="btn btn-primary"
|
|
||||||
hx-put={ "/meme/save/"+meme.Name }
|
|
||||||
hx-target={ "#"+meme.Name }
|
|
||||||
>Save</button>
|
|
||||||
<button class="btn btn-danger"
|
|
||||||
hx-delete={ "/meme/rm/"+meme.Name }
|
|
||||||
hx-target={ "#"+meme.Name }
|
|
||||||
>Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
|
@ -1,236 +0,0 @@
|
||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.2.543
|
|
||||||
package meme
|
|
||||||
|
|
||||||
//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 (p *MemePlugin) index(all webResps) 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("<div class=\"container\"><form><div class=\"row\"><div class=\"col-3\"><input type=\"text\" name=\"name\" placeholder=\"Name...\"></div><div class=\"col-3\"><input type=\"text\" name=\"url\" placeholder=\"URL...\"></div><div class=\"col-3\"><textarea name=\"config\"></textarea></div><div class=\"col-3\"><button class=\"btn btn-primary\" hx-post=\"/meme/add\" hx-target=\"#newMemes\">Save</button></div></div></form><div id=\"newMemes\"></div>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, meme := range all {
|
|
||||||
templ_7745c5c3_Err = p.Show(meme).Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MemePlugin) Show(meme webResp) 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_Var2 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var2 == nil {
|
|
||||||
templ_7745c5c3_Var2 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"row\" id=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"col-3\"><img class=\"img-thumbnail rounded\" alt=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" src=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.URL))
|
|
||||||
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
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(meme.Name)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/meme/meme.templ`, Line: 39, Col: 27}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div class=\"col-3\"><pre>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var4 string
|
|
||||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(meme.Config)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/meme/meme.templ`, Line: 43, Col: 29}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</pre></div><div class=\"col-3\"><button class=\"btn btn-primary\" hx-get=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("/meme/edit/" + meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Edit</button></div></div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MemePlugin) Edit(meme webResp) 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_Var5 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var5 == nil {
|
|
||||||
templ_7745c5c3_Var5 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form><div class=\"row\" id=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"col-3\"><img class=\"img-thumbnail rounded\" alt=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" src=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.URL))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><div class=\"col-3\"><textarea name=\"config\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var6 string
|
|
||||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(meme.Config)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/meme/meme.templ`, Line: 66, Col: 29}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</textarea> <input type=\"text\" name=\"url\" value=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(meme.URL))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><div class=\"col-3\"><button class=\"btn btn-primary\" hx-put=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("/meme/save/" + meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Save</button> <button class=\"btn btn-danger\" hx-delete=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("/meme/rm/" + meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString("#" + meme.Name))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Delete</button></div></div></form>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -20,13 +20,12 @@ var embeddedFS embed.FS
|
||||||
func (p *MemePlugin) registerWeb(c bot.Connector) {
|
func (p *MemePlugin) registerWeb(c bot.Connector) {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.HandleFunc("/slash", p.slashMeme(c))
|
r.HandleFunc("/slash", p.slashMeme(c))
|
||||||
r.Get("/img", p.img)
|
r.HandleFunc("/img", p.img)
|
||||||
r.Put("/save/{name}", p.saveMeme)
|
r.HandleFunc("/all", p.all)
|
||||||
r.Post("/add", p.saveMeme)
|
r.HandleFunc("/add", p.addMeme)
|
||||||
r.Delete("/rm/{name}", p.rmMeme)
|
r.HandleFunc("/rm", p.rmMeme)
|
||||||
r.Get("/edit/{name}", p.editMeme)
|
r.HandleFunc("/", p.webRoot)
|
||||||
r.Get("/", p.webRoot)
|
p.bot.RegisterWebName(r, "/meme", "Memes")
|
||||||
p.bot.GetWeb().RegisterWebName(r, "/meme", "Memes")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type webResp struct {
|
type webResp struct {
|
||||||
|
@ -44,7 +43,7 @@ type ByName struct{ webResps }
|
||||||
|
|
||||||
func (s ByName) Less(i, j int) bool { return s.webResps[i].Name < s.webResps[j].Name }
|
func (s ByName) Less(i, j int) bool { return s.webResps[i].Name < s.webResps[j].Name }
|
||||||
|
|
||||||
func (p *MemePlugin) all() webResps {
|
func (p *MemePlugin) all(w http.ResponseWriter, r *http.Request) {
|
||||||
memes := p.c.GetMap("meme.memes", defaultFormats)
|
memes := p.c.GetMap("meme.memes", defaultFormats)
|
||||||
configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
|
configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ func (p *MemePlugin) all() webResps {
|
||||||
for n, u := range memes {
|
for n, u := range memes {
|
||||||
config, ok := configs[n]
|
config, ok := configs[n]
|
||||||
if !ok {
|
if !ok {
|
||||||
b, _ := json.MarshalIndent(p.defaultFormatConfig(), " ", " ")
|
b, _ := json.Marshal(p.defaultFormatConfig())
|
||||||
config = string(b)
|
config = string(b)
|
||||||
}
|
}
|
||||||
realURL, err := url.Parse(u)
|
realURL, err := url.Parse(u)
|
||||||
|
@ -65,7 +64,13 @@ func (p *MemePlugin) all() webResps {
|
||||||
}
|
}
|
||||||
sort.Sort(ByName{values})
|
sort.Sort(ByName{values})
|
||||||
|
|
||||||
return values
|
out, err := json.Marshal(values)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
log.Error().Err(err).Msgf("could not serve all memes route")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkCheckError(w http.ResponseWriter) func(error) bool {
|
func mkCheckError(w http.ResponseWriter) func(error) bool {
|
||||||
|
@ -82,61 +87,56 @@ func mkCheckError(w http.ResponseWriter) func(error) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MemePlugin) rmMeme(w http.ResponseWriter, r *http.Request) {
|
func (p *MemePlugin) rmMeme(w http.ResponseWriter, r *http.Request) {
|
||||||
name := chi.URLParam(r, "name")
|
if r.Method != http.MethodDelete {
|
||||||
formats := p.c.GetMap("meme.memes", defaultFormats)
|
w.WriteHeader(405)
|
||||||
delete(formats, name)
|
fmt.Fprintf(w, "Incorrect HTTP method")
|
||||||
err := p.c.SetMap("meme.memes", formats)
|
return
|
||||||
mkCheckError(w)(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *MemePlugin) saveMeme(w http.ResponseWriter, r *http.Request) {
|
|
||||||
name := chi.URLParam(r, "name")
|
|
||||||
if name == "" {
|
|
||||||
name = r.FormValue("name")
|
|
||||||
}
|
}
|
||||||
checkError := mkCheckError(w)
|
checkError := mkCheckError(w)
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
values := webResp{}
|
||||||
|
err := decoder.Decode(&values)
|
||||||
|
if checkError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
formats := p.c.GetMap("meme.memes", defaultFormats)
|
formats := p.c.GetMap("meme.memes", defaultFormats)
|
||||||
formats[name] = r.FormValue("url")
|
delete(formats, values.Name)
|
||||||
err := p.c.SetMap("meme.memes", formats)
|
err = p.c.SetMap("meme.memes", formats)
|
||||||
|
checkError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MemePlugin) addMeme(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
w.WriteHeader(405)
|
||||||
|
fmt.Fprintf(w, "Incorrect HTTP method")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkError := mkCheckError(w)
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
values := webResp{}
|
||||||
|
err := decoder.Decode(&values)
|
||||||
|
if checkError(err) {
|
||||||
|
log.Error().Err(err).Msgf("could not decode body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug().Msgf("POSTed values: %+v", values)
|
||||||
|
formats := p.c.GetMap("meme.memes", defaultFormats)
|
||||||
|
formats[values.Name] = values.URL
|
||||||
|
err = p.c.SetMap("meme.memes", formats)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
config := r.FormValue("config")
|
if values.Config == "" {
|
||||||
if config == "" {
|
values.Config = p.defaultFormatConfigJSON()
|
||||||
config = p.defaultFormatConfigJSON()
|
|
||||||
}
|
}
|
||||||
configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
|
configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
|
||||||
configs[name] = config
|
configs[values.Name] = values.Config
|
||||||
err = p.c.SetMap("meme.memeconfigs", configs)
|
err = p.c.SetMap("meme.memeconfigs", configs)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
meme := webResp{
|
|
||||||
Name: name,
|
|
||||||
URL: formats[name],
|
|
||||||
Config: configs[name],
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Show(meme).Render(r.Context(), w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MemePlugin) webRoot(w http.ResponseWriter, r *http.Request) {
|
func (p *MemePlugin) webRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
p.bot.GetWeb().Index("Meme", p.index(p.all())).Render(r.Context(), w)
|
index, _ := embeddedFS.ReadFile("index.html")
|
||||||
}
|
w.Write(index)
|
||||||
|
|
||||||
func (p *MemePlugin) editMeme(w http.ResponseWriter, r *http.Request) {
|
|
||||||
name := chi.URLParam(r, "name")
|
|
||||||
memes := p.c.GetMap("meme.memes", defaultFormats)
|
|
||||||
configs := p.c.GetMap("meme.memeconfigs", map[string]string{})
|
|
||||||
meme, ok := memes[name]
|
|
||||||
if !ok {
|
|
||||||
fmt.Fprintf(w, "Didn't find that meme.")
|
|
||||||
}
|
|
||||||
resp := webResp{
|
|
||||||
Name: name,
|
|
||||||
URL: meme,
|
|
||||||
Config: configs[name],
|
|
||||||
}
|
|
||||||
p.Edit(resp).Render(r.Context(), w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MemePlugin) img(w http.ResponseWriter, r *http.Request) {
|
func (p *MemePlugin) img(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -12,6 +12,8 @@ import (
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
|
@ -43,6 +45,7 @@ func makeMessage(payload string) bot.Request {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
|
|
|
@ -18,7 +18,7 @@ type PageComment struct {
|
||||||
c *config.Config
|
c *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(b bot.Bot) bot.Plugin {
|
func New(b bot.Bot) *PageComment {
|
||||||
p := &PageComment{
|
p := &PageComment{
|
||||||
b: b,
|
b: b,
|
||||||
c: b.Config(),
|
c: b.Config(),
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"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"
|
"github.com/velour/catbase/bot/user"
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) bot.Request {
|
func makeMessage(payload string) bot.Request {
|
||||||
|
@ -21,6 +22,7 @@ func makeMessage(payload string) bot.Request {
|
||||||
}
|
}
|
||||||
values := bot.ParseValues(pickRegex, payload)
|
values := bot.ParseValues(pickRegex, payload)
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Values: values,
|
Values: values,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -18,6 +20,7 @@ func makeMessage(nick, payload string, r *regexp.Regexp) bot.Request {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Values: bot.ParseValues(r, payload),
|
Values: bot.ParseValues(r, payload),
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
|
|
|
@ -4,6 +4,7 @@ package reminder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -23,7 +24,7 @@ func makeMessageBy(payload, by string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return nil, bot.Message, msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: by},
|
User: &user.User{Name: by},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -223,6 +224,6 @@ func TestLimitList(t *testing.T) {
|
||||||
func TestHelp(t *testing.T) {
|
func TestHelp(t *testing.T) {
|
||||||
c, mb := setup(t)
|
c, mb := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.help(nil, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ type RolesPlugin struct {
|
||||||
h bot.HandlerTable
|
h bot.HandlerTable
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(b bot.Bot) bot.Plugin {
|
func New(b bot.Bot) *RolesPlugin {
|
||||||
p := &RolesPlugin{
|
p := &RolesPlugin{
|
||||||
b: b,
|
b: b,
|
||||||
c: b.Config(),
|
c: b.Config(),
|
||||||
|
|
|
@ -2,6 +2,7 @@ package rss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return nil, bot.Message, msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<!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@^2/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@^2/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@^2/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/vue-router@^2"></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>Memes</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'Meme'" :key="item.key">{{ item.name }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
<b-alert
|
||||||
|
dismissable
|
||||||
|
variant="error"
|
||||||
|
:show="err != ''"
|
||||||
|
@dismissed="err = ''">
|
||||||
|
{{ err }}
|
||||||
|
</b-alert>
|
||||||
|
<b-form @submit="add">
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="3">
|
||||||
|
<b-input placeholder="Key..." v-model="secret.key"></b-input>
|
||||||
|
</b-col>
|
||||||
|
<b-col cols="3">
|
||||||
|
<b-input placeholder="Value..." v-model="secret.value"></b-input>
|
||||||
|
</b-col>
|
||||||
|
<b-col cols="3">
|
||||||
|
<b-button type="submit">Add Secret</b-button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row style="padding-top: 2em;">
|
||||||
|
<b-col>
|
||||||
|
<ul>
|
||||||
|
<li v-for="key in results" key="key"><a @click="rm(key)" href="#">X</a> {{key}}</li>
|
||||||
|
</ul>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</b-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var router = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
routes: []
|
||||||
|
});
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
router,
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: [],
|
||||||
|
secret: {key: '', value: ''},
|
||||||
|
results: [],
|
||||||
|
fields: [
|
||||||
|
{key: 'key', sortable: true},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
axios.get('/nav')
|
||||||
|
.then(resp => {
|
||||||
|
this.nav = resp.data;
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err))
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refresh: function () {
|
||||||
|
axios.get('/secrets/all')
|
||||||
|
.then(resp => {
|
||||||
|
this.results = resp.data
|
||||||
|
this.err = ''
|
||||||
|
})
|
||||||
|
.catch(err => (this.err = err))
|
||||||
|
},
|
||||||
|
add: function (evt) {
|
||||||
|
if (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
}
|
||||||
|
axios.post('/secrets/add', this.secret)
|
||||||
|
.then(resp => {
|
||||||
|
this.results = resp.data;
|
||||||
|
this.secret.key = '';
|
||||||
|
this.secret.value = '';
|
||||||
|
this.refresh();
|
||||||
|
})
|
||||||
|
.catch(err => this.err = err)
|
||||||
|
},
|
||||||
|
rm: function (key) {
|
||||||
|
if (confirm("Are you sure you want to delete this meme?")) {
|
||||||
|
axios.delete('/secrets/remove', {data: {key: key}})
|
||||||
|
.then(resp => {
|
||||||
|
this.refresh();
|
||||||
|
})
|
||||||
|
.catch(err => this.err = err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,25 +1,28 @@
|
||||||
package secrets
|
package secrets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/config"
|
"github.com/velour/catbase/config"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var embeddedFS embed.FS
|
||||||
|
|
||||||
type SecretsPlugin struct {
|
type SecretsPlugin struct {
|
||||||
b bot.Bot
|
b bot.Bot
|
||||||
c *config.Config
|
c *config.Config
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(b bot.Bot) bot.Plugin {
|
func New(b bot.Bot) *SecretsPlugin {
|
||||||
p := &SecretsPlugin{
|
p := &SecretsPlugin{
|
||||||
b: b,
|
b: b,
|
||||||
c: b.Config(),
|
c: b.Config(),
|
||||||
|
@ -33,8 +36,14 @@ func (p *SecretsPlugin) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.HandleFunc("/add", p.handleRegister)
|
r.HandleFunc("/add", p.handleRegister)
|
||||||
r.HandleFunc("/remove", p.handleRemove)
|
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)
|
||||||
|
})
|
||||||
r.HandleFunc("/", p.handleIndex)
|
r.HandleFunc("/", p.handleIndex)
|
||||||
p.b.GetWeb().RegisterWebName(r, "/secrets", "Secrets")
|
p.b.RegisterWebName(r, "/secrets", "Secrets")
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkCheckError(w http.ResponseWriter) func(error) bool {
|
func mkCheckError(w http.ResponseWriter) func(error) bool {
|
||||||
|
@ -59,12 +68,20 @@ func checkMethod(method string, w http.ResponseWriter, r *http.Request) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SecretsPlugin) keys() []string {
|
|
||||||
return p.c.SecretKeys()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SecretsPlugin) sendKeys(w http.ResponseWriter, r *http.Request) {
|
func (p *SecretsPlugin) sendKeys(w http.ResponseWriter, r *http.Request) {
|
||||||
p.keysList().Render(r.Context(), w)
|
checkError := mkCheckError(w)
|
||||||
|
log.Debug().Msgf("Keys before refresh: %v", p.c.SecretKeys())
|
||||||
|
err := p.c.RefreshSecrets()
|
||||||
|
log.Debug().Msgf("Keys after refresh: %v", p.c.SecretKeys())
|
||||||
|
if checkError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys, err := json.Marshal(p.c.SecretKeys())
|
||||||
|
if checkError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write(keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SecretsPlugin) handleAll(w http.ResponseWriter, r *http.Request) {
|
func (p *SecretsPlugin) handleAll(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -72,13 +89,21 @@ func (p *SecretsPlugin) handleAll(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SecretsPlugin) handleRegister(w http.ResponseWriter, r *http.Request) {
|
func (p *SecretsPlugin) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Debug().Msgf("handleRegister")
|
||||||
if checkMethod(http.MethodPost, w, r) {
|
if checkMethod(http.MethodPost, w, r) {
|
||||||
log.Debug().Msgf("failed post %s", r.Method)
|
log.Debug().Msgf("failed post %s", r.Method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkError := mkCheckError(w)
|
checkError := mkCheckError(w)
|
||||||
key, value := r.FormValue("key"), r.FormValue("value")
|
decoder := json.NewDecoder(r.Body)
|
||||||
err := p.c.RegisterSecret(key, value)
|
secret := config.Secret{}
|
||||||
|
err := decoder.Decode(&secret)
|
||||||
|
log.Debug().Msgf("decoding: %s", err)
|
||||||
|
if checkError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug().Msgf("Secret: %s", secret)
|
||||||
|
err = p.c.RegisterSecret(secret.Key, secret.Value)
|
||||||
if checkError(err) {
|
if checkError(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -90,16 +115,13 @@ func (p *SecretsPlugin) handleRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkError := mkCheckError(w)
|
checkError := mkCheckError(w)
|
||||||
b, err := io.ReadAll(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
secret := config.Secret{}
|
||||||
|
err := decoder.Decode(&secret)
|
||||||
if checkError(err) {
|
if checkError(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q, err := url.ParseQuery(string(b))
|
err = p.c.RemoveSecret(secret.Key)
|
||||||
if checkError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
secret := q.Get("key")
|
|
||||||
err = p.c.RemoveSecret(secret)
|
|
||||||
if checkError(err) {
|
if checkError(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -107,5 +129,6 @@ func (p *SecretsPlugin) handleRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SecretsPlugin) handleIndex(w http.ResponseWriter, r *http.Request) {
|
func (p *SecretsPlugin) handleIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
p.b.GetWeb().Index("Secrets", p.index()).Render(r.Context(), w)
|
index, _ := embeddedFS.ReadFile("index.html")
|
||||||
|
w.Write(index)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
package secrets
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
templ (s *SecretsPlugin) index() {
|
|
||||||
<div class="container">
|
|
||||||
<form hx-post="/secrets/add" hx-target="#data">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-3">
|
|
||||||
<input placeholder="Key..." name="key" />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<input placeholder="Value..." name="value" />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<button class="btn btn-primary" type="submit">Add Secret</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="row" style="padding-top: 2em;">
|
|
||||||
<div id="data">
|
|
||||||
@s.keysList()
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ (s *SecretsPlugin) keysList() {
|
|
||||||
<ul>
|
|
||||||
for _, key := range s.keys() {
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
class="btn btn-danger"
|
|
||||||
hx-delete="/secrets/remove"
|
|
||||||
hx-confirm={ fmt.Sprintf("Are you sure you want to delete %s?", key) }
|
|
||||||
hx-target="#data"
|
|
||||||
hx-include="this"
|
|
||||||
name="key" value={ key }>X</button>
|
|
||||||
{ key }</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
// Code generated by templ - DO NOT EDIT.
|
|
||||||
|
|
||||||
// templ: version: v0.2.543
|
|
||||||
package secrets
|
|
||||||
|
|
||||||
//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"
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func (s *SecretsPlugin) 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("<div class=\"container\"><form hx-post=\"/secrets/add\" hx-target=\"#data\"><div class=\"row\"><div class=\"col-3\"><input placeholder=\"Key...\" name=\"key\"></div><div class=\"col-3\"><input placeholder=\"Value...\" name=\"value\"></div><div class=\"col-3\"><button class=\"btn btn-primary\" type=\"submit\">Add Secret</button></div></div></form><div class=\"row\" style=\"padding-top: 2em;\"><div id=\"data\">")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = s.keysList().Render(ctx, templ_7745c5c3_Buffer)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div></div>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SecretsPlugin) keysList() 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_Var2 := templ.GetChildren(ctx)
|
|
||||||
if templ_7745c5c3_Var2 == nil {
|
|
||||||
templ_7745c5c3_Var2 = templ.NopComponent
|
|
||||||
}
|
|
||||||
ctx = templ.ClearChildren(ctx)
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
for _, key := range s.keys() {
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><button class=\"btn btn-danger\" hx-delete=\"/secrets/remove\" hx-confirm=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("Are you sure you want to delete %s?", key)))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" hx-include=\"this\" name=\"key\" value=\"")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(key))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">X</button> ")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
var templ_7745c5c3_Var3 string
|
|
||||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(key)
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/secrets/secrets.templ`, Line: 38, Col: 17}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul>")
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -121,7 +121,7 @@ func (p *SMSPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, ar
|
||||||
func (p *SMSPlugin) registerWeb() {
|
func (p *SMSPlugin) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.HandleFunc("/new", p.receive)
|
r.HandleFunc("/new", p.receive)
|
||||||
p.b.GetWeb().RegisterWeb(r, "/sms")
|
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) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package stock
|
package stock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return nil, bot.Message, msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
|
|
@ -186,5 +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.GetWeb().RegisterWeb(r, "/cowsay")
|
p.bot.RegisterWeb(r, "/cowsay")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package talker
|
package talker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return nil, bot.Message, msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -78,6 +79,6 @@ func TestHelp(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.help(nil, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
func (p *Tappd) registerWeb() {
|
func (p *Tappd) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.HandleFunc("/", p.serveImage)
|
r.HandleFunc("/", p.serveImage)
|
||||||
p.b.GetWeb().RegisterWeb(r, "/tappd/{id}")
|
p.b.RegisterWeb(r, "/tappd/{id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Tappd) getImg(id string) ([]byte, error) {
|
func (p *Tappd) getImg(id string) ([]byte, error) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package tldr
|
package tldr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -28,6 +29,7 @@ func makeMessageBy(payload, by string) bot.Request {
|
||||||
}
|
}
|
||||||
|
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
User: &user.User{Name: by},
|
User: &user.User{Name: by},
|
||||||
|
|
|
@ -102,7 +102,7 @@ func (p *Twitch) registerWeb() {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.HandleFunc("/online", p.onlineCB)
|
r.HandleFunc("/online", p.onlineCB)
|
||||||
r.HandleFunc("/offline", p.offlineCB)
|
r.HandleFunc("/offline", p.offlineCB)
|
||||||
p.b.GetWeb().RegisterWeb(r, "/twitch")
|
p.b.RegisterWeb(r, "/twitch")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Twitch) register() {
|
func (p *Twitch) register() {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package twitch
|
package twitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return nil, bot.Message, msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -18,6 +20,7 @@ func makeMessage(payload string) bot.Request {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return bot.Request{
|
return bot.Request{
|
||||||
|
Conn: &cli.CliPlugin{},
|
||||||
Kind: bot.Message,
|
Kind: bot.Message,
|
||||||
Msg: msg.Message{
|
Msg: msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
|
|
Loading…
Reference in New Issue