diff --git a/plugins/counter/api.go b/plugins/counter/api.go index 25a63c5..2aac9dc 100644 --- a/plugins/counter/api.go +++ b/plugins/counter/api.go @@ -1,10 +1,8 @@ package counter import ( - "embed" "encoding/json" "fmt" - "github.com/velour/catbase/bot/user" "io" "net/http" "net/url" @@ -18,9 +16,6 @@ import ( "github.com/velour/catbase/bot/msg" ) -//go:embed *.html -var embeddedFS embed.FS - func (p *CounterPlugin) registerWeb() { r := chi.NewRouter() requests := p.cfg.GetInt("counter.requestsPer", 1) @@ -33,11 +28,79 @@ func (p *CounterPlugin) registerWeb() { subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementByNAPI(-1)) r.Mount("/", subrouter) - r.HandleFunc("/api", p.handleCounterAPI) + r.HandleFunc("/users/{user}/items/{item}/increment", p.incHandler(1)) + r.HandleFunc("/users/{user}/items/{item}/decrement", p.incHandler(-1)) r.HandleFunc("/", p.handleCounter) p.b.GetWeb().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) { return func(w http.ResponseWriter, r *http.Request) { userName, _ := url.QueryUnescape(chi.URLParam(r, "user")) @@ -64,15 +127,15 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri return } - // Try to find an ID if possible - id := "" - u, err := p.b.DefaultConnector().Profile(userName) - if err == nil { - id = u.ID + 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) } - item, err := GetUserItem(p.db, userName, id, itemName) - if err != nil { + if _, err := p.delta(userName, itemName, personalMsg, delta*direction); err != nil { log.Error().Err(err).Msg("error finding item") w.WriteHeader(400) j, _ := json.Marshal(struct { @@ -83,130 +146,13 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri 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}) fmt.Fprint(w, string(j)) } } func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) { - 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)) + p.b.GetWeb().Index("Counter", p.index()).Render(r.Context(), w) } // Update represents a change that gets sent off to other plugins such as goals diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 42ee952..ef329f5 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -45,7 +45,7 @@ type alias struct { } // GetItems returns all counters -func GetAllItems(db *sqlx.DB) ([]Item, error) { +func GetAllItemsByUser(db *sqlx.DB) (map[string][]Item, error) { var items []Item err := db.Select(&items, `select * from counter`) if err != nil { @@ -55,7 +55,11 @@ func GetAllItems(db *sqlx.DB) ([]Item, error) { for i := range items { items[i].DB = db } - return items, nil + out := map[string][]Item{} + for _, it := range items { + out[it.Nick] = append(out[it.Nick], it) + } + return out, nil } // GetItems returns all counters for a subject diff --git a/plugins/counter/counter.templ b/plugins/counter/counter.templ new file mode 100644 index 0000000..7012bf5 --- /dev/null +++ b/plugins/counter/counter.templ @@ -0,0 +1,58 @@ +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() { +
+
+ +
+ for user, items := range p.allItems() { +
+ { user }: +
+ for _, thing := range items { + @p.renderItem(user, thing) + } +
+
+ } +
+} + +templ (p *CounterPlugin) renderItem(user string, item Item) { +
+
+ { item.Item } +
+
+ { fmt.Sprintf("%d", item.Count) } +
+
+ + +
+
+} \ No newline at end of file diff --git a/plugins/counter/counter_templ.go b/plugins/counter/counter_templ.go new file mode 100644 index 0000000..dc45771 --- /dev/null +++ b/plugins/counter/counter_templ.go @@ -0,0 +1,172 @@ +// 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("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for user, items := range p.allItems() { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + 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(":
") + 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("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +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("
") + 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("
") + 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("
") + 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 + }) +}