Compare commits

...

7 Commits

Author SHA1 Message Date
Chris Sexton def86a3c7e list: implement last as a reply to the last message 2024-03-19 11:29:36 -04:00
Chris Sexton 35499ee213 meme: use httpin 2024-03-19 11:29:36 -04:00
Chris Sexton 674e30125b fact: use httpin 2024-03-19 11:29:36 -04:00
Chris Sexton 28e9fc2480 secrets: use httpin 2024-03-19 11:29:36 -04:00
Chris Sexton b73e64ad72 admin: apppass uses httpin 2024-03-19 11:29:36 -04:00
Chris Sexton a6d224c87b emojy: handle with httpin 2024-03-19 11:29:36 -04:00
Chris Sexton 81fb3dd068 counter: try out httpin 2024-03-19 11:29:36 -04:00
17 changed files with 296 additions and 348 deletions

View File

@ -63,6 +63,12 @@ type File struct {
mime *mimetype.MIME mime *mimetype.MIME
} }
type MessageReference struct {
MessageID string `json:"message_id"`
ChannelID string `json:"channel_id,omitempty"`
GuildID string `json:"guild_id,omitempty"`
}
func (f File) Mime() *mimetype.MIME { func (f File) Mime() *mimetype.MIME {
if f.mime == nil { if f.mime == nil {
f.mime = mimetype.Detect(f.Data) f.mime = mimetype.Detect(f.Data)

View File

@ -11,6 +11,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
httpin_integration "github.com/ggicci/httpin/integration"
) )
type Web struct { type Web struct {
@ -77,6 +79,8 @@ func (ws *Web) setupHTTP() {
ws.router.Use(middleware.Recoverer) ws.router.Use(middleware.Recoverer)
ws.router.Use(middleware.StripSlashes) ws.router.Use(middleware.StripSlashes)
httpin_integration.UseGochiURLParam("path", chi.URLParam)
ws.router.HandleFunc("/", ws.serveRoot) ws.router.HandleFunc("/", ws.serveRoot)
ws.router.HandleFunc("/nav", ws.serveNav) ws.router.HandleFunc("/nav", ws.serveNav)
ws.router.HandleFunc("/navHTML", ws.serveNavHTML) ws.router.HandleFunc("/navHTML", ws.serveNavHTML)

View File

@ -116,6 +116,7 @@ func (d *Discord) sendMessage(channel, message string, meMessage bool, args ...a
embeds := []*discordgo.MessageEmbed{} embeds := []*discordgo.MessageEmbed{}
files := []*discordgo.File{} files := []*discordgo.File{}
var ref *discordgo.MessageReference
for _, arg := range args { for _, arg := range args {
switch a := arg.(type) { switch a := arg.(type) {
@ -141,6 +142,12 @@ func (d *Discord) sendMessage(channel, message string, meMessage bool, args ...a
ContentType: a.ContentType(), ContentType: a.ContentType(),
Reader: bytes.NewBuffer(a.Data), Reader: bytes.NewBuffer(a.Data),
}) })
case bot.MessageReference:
ref = &discordgo.MessageReference{
MessageID: a.MessageID,
ChannelID: a.ChannelID,
GuildID: a.GuildID,
}
} }
} }
@ -148,6 +155,7 @@ func (d *Discord) sendMessage(channel, message string, meMessage bool, args ...a
Content: message, Content: message,
Embeds: embeds, Embeds: embeds,
Files: files, Files: files,
Reference: ref,
} }
log.Debug(). log.Debug().

6
go.mod
View File

@ -17,7 +17,8 @@ require (
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90
github.com/forPelevin/gomoji v1.1.6 github.com/forPelevin/gomoji v1.1.6
github.com/gabriel-vasile/mimetype v1.4.1 github.com/gabriel-vasile/mimetype v1.4.1
github.com/go-chi/chi/v5 v5.0.7 github.com/ggicci/httpin v0.16.0
github.com/go-chi/chi/v5 v5.0.11
github.com/go-chi/httprate v0.7.0 github.com/go-chi/httprate v0.7.0
github.com/gocolly/colly v1.2.0 github.com/gocolly/colly v1.2.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
@ -32,7 +33,7 @@ require (
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254
github.com/rs/zerolog v1.28.0 github.com/rs/zerolog v1.28.0
github.com/slack-go/slack v0.11.3 github.com/slack-go/slack v0.11.3
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.4
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.14.0
@ -57,6 +58,7 @@ require (
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc // indirect github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc // indirect
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 // indirect github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 // indirect
github.com/ggicci/owl v0.7.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect github.com/go-stack/stack v1.8.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect

14
go.sum
View File

@ -120,9 +120,13 @@ github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkF
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M= github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 h1:GOfMz6cRgTJ9jWV0qAezv642OhPnKEG7gtUjJSdStHE= github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 h1:GOfMz6cRgTJ9jWV0qAezv642OhPnKEG7gtUjJSdStHE=
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17/go.mod h1:HfkOCN6fkKKaPSAeNq/er3xObxTW4VLeY6UUK895gLQ= github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17/go.mod h1:HfkOCN6fkKKaPSAeNq/er3xObxTW4VLeY6UUK895gLQ=
github.com/ggicci/httpin v0.16.0 h1:ZR6RXH1xNWg39xqM33V7iz7PP/GuR7vc3aHa2g5mWo4=
github.com/ggicci/httpin v0.16.0/go.mod h1:whE/5nx1jCp//UQ6rgNpq2WNxOr9FV0OpxMnQQC0Xvs=
github.com/ggicci/owl v0.7.0 h1:+AMlCR0AY7j72q7hjtN4pm8VJiikwpROtMgvPnXtuik=
github.com/ggicci/owl v0.7.0/go.mod h1:TRPWshRwYej6uES//YW5aNgLB370URwyta1Ytfs7KXs=
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/httprate v0.7.0 h1:8W0dF7Xa2Duz2p8ncGaehIphrxQGNlOtoGY0+NRRfjQ= github.com/go-chi/httprate v0.7.0 h1:8W0dF7Xa2Duz2p8ncGaehIphrxQGNlOtoGY0+NRRfjQ=
github.com/go-chi/httprate v0.7.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A= github.com/go-chi/httprate v0.7.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -215,6 +219,7 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -252,6 +257,7 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a h1:Z7+SSApKiwPjNic+NF9+j7h657Uyvdp/jA3iTKhpj4E= github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a h1:Z7+SSApKiwPjNic+NF9+j7h657Uyvdp/jA3iTKhpj4E=
@ -372,8 +378,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA= github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA=
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/trubitsyn/go-zero-width v1.0.1 h1:AAZhtyGXW79T5BouAF0R9FtDhGcp7IGbLZo2Id3N+m8= github.com/trubitsyn/go-zero-width v1.0.1 h1:AAZhtyGXW79T5BouAF0R9FtDhGcp7IGbLZo2Id3N+m8=

View File

@ -24,17 +24,17 @@ templ (a *AdminPlugin) page() {
<button hx-put="/apppass/api" hx-target="#data" class="submit success button">New</button> <button hx-put="/apppass/api" hx-target="#data" class="submit success button">New</button>
</div> </div>
</div> </div>
</form>
<div class="grid-x"> <div class="grid-x">
<div class="cell" id="data"></div> <div class="cell" id="data"></div>
</div> </div>
</form>
</div> </div>
} }
templ (a *AdminPlugin) showPassword(entry PassEntry) { templ (a *AdminPlugin) showPassword(entry PassEntry) {
<div><span>ID</span><span>{ fmt.Sprintf("%d", entry.ID) }</span></div> <div><span style="margin-right: 2em">ID</span><span>{ fmt.Sprintf(" %d", entry.ID) }</span></div>
<div><span>Password</span><span>{ entry.Secret }:{ entry.Pass }</span></div> <div><span style="margin-right: 2em">Password</span><span> { entry.Secret }:{ entry.Pass }</span></div>
} }
templ (a *AdminPlugin) entries(items []PassEntry) { templ (a *AdminPlugin) entries(items []PassEntry) {
@ -51,7 +51,6 @@ templ (a *AdminPlugin) entries(items []PassEntry) {
hx-delete="/apppass/api" hx-delete="/apppass/api"
hx-confirm={ fmt.Sprintf("Are you sure you want to delete %d?", entry.ID) } hx-confirm={ fmt.Sprintf("Are you sure you want to delete %d?", entry.ID) }
hx-target="#data" hx-target="#data"
hx-include="this,[name='password'],[name='secret']"
name="id" value={ fmt.Sprintf("%d", entry.ID) }>X</button> name="id" value={ fmt.Sprintf("%d", entry.ID) }>X</button>
{ fmt.Sprintf("%d", entry.ID) } { fmt.Sprintf("%d", entry.ID) }
</li> </li>

View File

@ -25,7 +25,7 @@ func (a *AdminPlugin) page() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"grid-container\"><form><div class=\"grid-x grid-margin-x align-bottom\"><h2>App Pass</h2></div><div class=\"grid-x grid-margin-x align-bottom\"><div class=\"cell auto\"><label for=\"password\">Password: <input type=\"text\" name=\"password\"></label></div><div class=\"cell auto\"><label for=\"secret\">Secret: <input type=\"text\" name=\"secret\"></label></div><div class=\"cell auto\"><button hx-post=\"/apppass/api\" hx-target=\"#data\" class=\"button\">List</button> <button hx-put=\"/apppass/api\" hx-target=\"#data\" class=\"submit success button\">New</button></div></div></form><div class=\"grid-x\"><div class=\"cell\" id=\"data\"></div></div></div>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"grid-container\"><form><div class=\"grid-x grid-margin-x align-bottom\"><h2>App Pass</h2></div><div class=\"grid-x grid-margin-x align-bottom\"><div class=\"cell auto\"><label for=\"password\">Password: <input type=\"text\" name=\"password\"></label></div><div class=\"cell auto\"><label for=\"secret\">Secret: <input type=\"text\" name=\"secret\"></label></div><div class=\"cell auto\"><button hx-post=\"/apppass/api\" hx-target=\"#data\" class=\"button\">List</button> <button hx-put=\"/apppass/api\" hx-target=\"#data\" class=\"submit success button\">New</button></div></div><div class=\"grid-x\"><div class=\"cell\" id=\"data\"></div></div></form></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -49,27 +49,27 @@ func (a *AdminPlugin) showPassword(entry PassEntry) templ.Component {
templ_7745c5c3_Var2 = templ.NopComponent templ_7745c5c3_Var2 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><span>ID</span><span>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><span style=\"margin-right: 2em\">ID</span><span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(" %d", entry.ID)) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(" %d", entry.ID))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 35, Col: 59} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 35, Col: 86}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div><div><span>Password</span><span>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div><div><span style=\"margin-right: 2em\">Password</span><span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Secret) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Secret)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 50} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 77}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -82,7 +82,7 @@ func (a *AdminPlugin) showPassword(entry PassEntry) templ.Component {
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Pass) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Pass)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 65} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 92}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -135,7 +135,7 @@ func (a *AdminPlugin) entries(items []PassEntry) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" hx-include=\"this,[name=&#39;password&#39;],[name=&#39;secret&#39;]\" name=\"id\" value=\"") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" name=\"id\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -150,7 +150,7 @@ func (a *AdminPlugin) entries(items []PassEntry) templ.Component {
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID)) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 55, Col: 57} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 54, Col: 57}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -192,7 +192,7 @@ func renderError(err error) templ.Component {
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error()) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 63, Col: 22} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 62, Col: 22}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -234,7 +234,7 @@ func vars(items []configEntry) templ.Component {
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Key) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Key)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 79, Col: 38} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 78, Col: 38}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -247,7 +247,7 @@ func vars(items []configEntry) templ.Component {
var templ_7745c5c3_Var12 string var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Value) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Value)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 79, Col: 61} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 78, Col: 61}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@ -4,13 +4,12 @@ import (
"context" "context"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"encoding/json"
"fmt" "fmt"
"github.com/ggicci/httpin"
"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"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -21,8 +20,15 @@ func (p *AdminPlugin) registerWeb() {
r.HandleFunc("/", p.handleVars) r.HandleFunc("/", p.handleVars)
p.bot.GetWeb().RegisterWebName(r, "/vars", "Variables") p.bot.GetWeb().RegisterWebName(r, "/vars", "Variables")
r = chi.NewRouter() r = chi.NewRouter()
r.HandleFunc("/verify", p.handleAppPassCheck) r.With(httpin.NewInput(AppPassCheckReq{})).
r.HandleFunc("/api", p.handleAppPassAPI) HandleFunc("/verify", p.handleAppPassCheck)
//r.HandleFunc("/api", p.handleAppPassAPI)
r.With(httpin.NewInput(AppPassAPIReq{})).
Put("/api", p.handleAppPassAPIPut)
r.With(httpin.NewInput(AppPassAPIReq{})).
Delete("/api", p.handleAppPassAPIDelete)
r.With(httpin.NewInput(AppPassAPIReq{})).
Post("/api", p.handleAppPassAPIPost)
r.HandleFunc("/", p.handleAppPass) r.HandleFunc("/", p.handleAppPass)
p.bot.GetWeb().RegisterWebName(r, "/apppass", "App Pass") p.bot.GetWeb().RegisterWebName(r, "/apppass", "App Pass")
} }
@ -32,8 +38,8 @@ func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) {
} }
type PassEntry struct { type PassEntry struct {
ID int64 `json:"id"` ID int64 `json:"id" in:"form=id;query=password"`
Secret string `json:"secret"` Secret string `json:"secret" in:"form=secret;query=password"`
// Should be null unless inserting a new entry // Should be null unless inserting a new entry
Pass string `json:"pass"` Pass string `json:"pass"`
@ -57,57 +63,55 @@ func (p *PassEntry) Compare(pass string) bool {
return true return true
} }
func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) { type AppPassCheckReq struct {
req := struct {
Secret string `json:"secret"` Secret string `json:"secret"`
Password string `json:"password"` Password string `json:"password"`
}{} }
body, _ := ioutil.ReadAll(r.Body)
_ = json.Unmarshal(body, &req) func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) {
if p.bot.CheckPassword(req.Secret, req.Password) { input := r.Context().Value(httpin.Input).(*AppPassCheckReq)
if p.bot.CheckPassword(input.Secret, input.Password) {
w.WriteHeader(204) w.WriteHeader(204)
} else { } else {
w.WriteHeader(403) w.WriteHeader(403)
} }
} }
func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) { type AppPassAPIReq struct {
r.ParseForm() Password string `in:"form=password;query=password"`
PassEntry PassEntry
}
func (p *AdminPlugin) checkAPIInput(w http.ResponseWriter, r *http.Request) *AppPassAPIReq {
input := r.Context().Value(httpin.Input).(*AppPassAPIReq)
if input.Password == "" && input.PassEntry.Secret == "" {
b, _ := io.ReadAll(r.Body) b, _ := io.ReadAll(r.Body)
query, _ := url.ParseQuery(string(b)) query, _ := url.ParseQuery(string(b))
secret := r.FormValue("secret") input.Password = query.Get("password")
password := r.FormValue("password") input.PassEntry.ID, _ = strconv.ParseInt(query.Get("id"), 10, 64)
id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64) input.PassEntry.Secret = query.Get("secret")
if !r.Form.Has("secret") && query.Has("secret") {
secret = query.Get("secret")
} }
if !r.Form.Has("password") && query.Has("password") {
password = query.Get("password") log.Printf("checkAPIInput: %#v", input)
}
if !r.Form.Has("id") && query.Has("id") { if input.PassEntry.Secret == "" {
id, _ = strconv.ParseInt(query.Get("id"), 10, 64)
}
req := struct {
Password string `json:"password"`
PassEntry PassEntry `json:"passEntry"`
}{
password,
PassEntry{
ID: id,
Secret: secret,
},
}
if req.PassEntry.Secret == "" {
writeErr(r.Context(), w, fmt.Errorf("missing secret")) writeErr(r.Context(), w, fmt.Errorf("missing secret"))
return return nil
} }
if req.Password == "" || !p.bot.CheckPassword(req.PassEntry.Secret, req.Password) { if input.Password == "" || !p.bot.CheckPassword(input.PassEntry.Secret, input.Password) {
writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password")) writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password"))
return nil
}
return input
}
func (p *AdminPlugin) handleAppPassAPIPut(w http.ResponseWriter, r *http.Request) {
input := p.checkAPIInput(w, r)
if input == nil {
return return
} }
switch r.Method { if string(input.PassEntry.Pass) == "" {
case http.MethodPut:
if string(req.PassEntry.Pass) == "" {
c := 10 c := 10
b := make([]byte, c) b := make([]byte, c)
_, err := rand.Read(b) _, err := rand.Read(b)
@ -115,21 +119,21 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
fmt.Println("error:", err) fmt.Println("error:", err)
return return
} }
req.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b)) input.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b))
} }
q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)` q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)`
req.PassEntry.EncodePass() input.PassEntry.EncodePass()
check := bcrypt.CompareHashAndPassword(req.PassEntry.encodedPass, []byte(req.PassEntry.Pass)) check := bcrypt.CompareHashAndPassword(input.PassEntry.encodedPass, []byte(input.PassEntry.Pass))
log.Debug(). log.Debug().
Str("secret", req.PassEntry.Secret). Str("secret", input.PassEntry.Secret).
Str("encoded", string(req.PassEntry.encodedPass)). Str("encoded", string(input.PassEntry.encodedPass)).
Str("password", string(req.PassEntry.Pass)). Str("password", string(input.PassEntry.Pass)).
Interface("check", check). Interface("check", check).
Msg("debug pass creation") Msg("debug pass creation")
res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost) res, err := p.db.Exec(q, input.PassEntry.Secret, input.PassEntry.encodedPass, input.PassEntry.Cost)
if err != nil { if err != nil {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return return
@ -139,25 +143,37 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return return
} }
req.PassEntry.ID = id input.PassEntry.ID = id
p.showPassword(req.PassEntry).Render(r.Context(), w) p.showPassword(input.PassEntry).Render(r.Context(), w)
return }
case http.MethodDelete:
if req.PassEntry.ID <= 0 { func (p *AdminPlugin) handleAppPassAPIDelete(w http.ResponseWriter, r *http.Request) {
input := p.checkAPIInput(w, r)
if input == nil {
return
}
if input.PassEntry.ID <= 0 {
writeErr(r.Context(), w, fmt.Errorf("missing ID")) writeErr(r.Context(), 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, input.PassEntry.ID)
if err != nil { if err != nil {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return return
} }
p.handleAppPassAPIPost(w, r)
}
func (p *AdminPlugin) handleAppPassAPIPost(w http.ResponseWriter, r *http.Request) {
input := p.checkAPIInput(w, r)
if input == nil {
return
} }
q := `select id,secret from apppass where secret = ?` q := `select id,secret from apppass where secret = ?`
passEntries := []PassEntry{} passEntries := []PassEntry{}
err := p.db.Select(&passEntries, q, req.PassEntry.Secret)
err := p.db.Select(&passEntries, q, input.PassEntry.Secret)
if err != nil { if err != nil {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return return

View File

@ -3,10 +3,8 @@ package counter
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "github.com/ggicci/httpin"
"net/http" "net/http"
"net/url"
"strconv"
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -23,34 +21,48 @@ func (p *CounterPlugin) registerWeb() {
dur := time.Duration(seconds) * time.Second dur := time.Duration(seconds) * time.Second
subrouter := chi.NewRouter() subrouter := chi.NewRouter()
subrouter.Use(httprate.LimitByIP(requests, dur)) subrouter.Use(httprate.LimitByIP(requests, dur))
subrouter.HandleFunc("/api/users/{user}/items/{item}/increment/{delta}", p.mkIncrementByNAPI(1)) subrouter.With(httpin.NewInput(CounterChangeReq{})).
subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement/{delta}", p.mkIncrementByNAPI(-1)) HandleFunc("/api/users/{user}/items/{item}/increment/{delta}", p.mkIncrementByNAPI(1))
subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1)) subrouter.With(httpin.NewInput(CounterChangeReq{})).
subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementByNAPI(-1)) HandleFunc("/api/users/{user}/items/{item}/decrement/{delta}", p.mkIncrementByNAPI(-1))
subrouter.With(httpin.NewInput(CounterChangeReq{})).
HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1))
subrouter.With(httpin.NewInput(CounterChangeReq{})).
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.With(httpin.NewInput(CounterChangeReq{})).
r.HandleFunc("/users/{user}/items/{item}/decrement", p.incHandler(-1)) HandleFunc("/users/{user}/items/{item}/increment", p.incHandler(1))
r.With(httpin.NewInput(CounterChangeReq{})).
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.GetWeb().RegisterWebName(r, "/counter", "Counter")
} }
type CounterChangeReq struct {
UserName string `in:"path=user"`
Item string `in:"path=item"`
Password string `in:"form=password"`
Delta int `in:"path=delta"`
Body struct {
Message string `json:"message"`
} `in:"body=json"`
}
func (p *CounterPlugin) incHandler(delta int) http.HandlerFunc { func (p *CounterPlugin) incHandler(delta int) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
userName, _ := url.QueryUnescape(chi.URLParam(r, "user")) input := r.Context().Value(httpin.Input).(*CounterChangeReq)
itemName, _ := url.QueryUnescape(chi.URLParam(r, "item")) if !p.b.CheckPassword("", input.Password) {
pass := r.FormValue("password")
if !p.b.CheckPassword("", pass) {
w.WriteHeader(401) w.WriteHeader(401)
fmt.Fprintf(w, "error") fmt.Fprintf(w, "error")
return return
} }
item, err := p.delta(userName, itemName, "", delta) item, err := p.delta(input.UserName, input.Item, "", delta)
if err != nil { if err != nil {
w.WriteHeader(500) w.WriteHeader(500)
fmt.Fprintf(w, "error") fmt.Fprintf(w, "error")
return return
} }
p.renderItem(userName, item).Render(r.Context(), w) p.renderItem(input.UserName, item).Render(r.Context(), w)
} }
} }
@ -103,13 +115,11 @@ func (p *CounterPlugin) delta(userName, itemName, personalMessage string, delta
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) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
userName, _ := url.QueryUnescape(chi.URLParam(r, "user")) input := r.Context().Value(httpin.Input).(*CounterChangeReq)
itemName, _ := url.QueryUnescape(chi.URLParam(r, "item")) if input.Delta == 0 {
delta, err := strconv.Atoi(chi.URLParam(r, "delta")) input.Delta = direction
if err != nil || delta == 0 {
delta = direction
} else { } else {
delta = delta * direction input.Delta = input.Delta * direction
} }
secret, pass, ok := r.BasicAuth() secret, pass, ok := r.BasicAuth()
@ -127,15 +137,12 @@ 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 := "" personalMsg := ""
if inputMsg, ok := postData["message"]; ok { if input.Body.Message != "" {
personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg) personalMsg = fmt.Sprintf("\nMessage: %s", input.Body.Message)
} }
if _, err := p.delta(userName, itemName, personalMsg, delta*direction); err != nil { if _, err := p.delta(input.UserName, input.Item, personalMsg, input.Delta*direction); 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 {

View File

@ -1,104 +0,0 @@
<html>
<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>
<title>Counters</title>
</head>
<body>
<div id="app">
<b-navbar>
<b-navbar-brand>Counters</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.url" :active="item.name === 'Counter'">{{ item.name }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert
dismissable
:show="err"
variant="error">
{{ 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 v-for="(counter, user) in counters">
{{ user }}:
<b-container>
<b-row v-for="(count, thing) in counter">
<b-col offset="1">
{{ thing }}:
</b-col>
<b-col>
{{ count }}
</b-col>
<b-col cols="2">
<button @click="subtract(user,thing,count)">-</button>
<button @click="add(user,thing,count)">+</button>
</b-col>
</b-row>
</b-container>
</b-row>
</b-container>
</div>
<script>
function convertData(data) {
var newData = {};
for (let i = 0; i < data.length; i++) {
let entry = data[i]
if (newData[entry.Nick] === undefined) {
newData[entry.Nick] = {}
}
newData[entry.Nick][entry.Item] = entry.Count;
}
return newData;
}
var app = new Vue({
el: '#app',
data: {
err: '',
nav: [],
answer: '',
correct: 0,
counters: {}
},
mounted() {
axios.get('/nav')
.then(resp => {
this.nav = resp.data;
})
.catch(err => console.log(err))
axios.get('/counter/api')
.then(resp => (this.counters = convertData(resp.data)))
.catch(err => (this.err = err));
},
methods: {
add(user, thing, count) {
axios.post('/counter/api',
{user: user, thing: thing, action: '++', password: this.answer})
.then(resp => {this.counters = convertData(resp.data); this.err = '';})
.catch(err => this.err = err);
},
subtract(user, thing, count) {
axios.post('/counter/api',
{user: user, thing: thing, action: '--', password: this.answer})
.then(resp => {this.counters = convertData(resp.data); this.err = '';})
.catch(err => this.err = err);
}
}
})
</script>
</body>
</html>

View File

@ -12,6 +12,7 @@ templ (p *EmojyPlugin) uploadIndex() {
@p.emojyNav() @p.emojyNav()
</div> </div>
</div> </div>
<form hx-post="/emojy/upload" enctype="multipart/form-data">
<div class="grid-x"> <div class="grid-x">
<div class="cell"> <div class="cell">
<label>Passphrase</label> <label>Passphrase</label>
@ -19,12 +20,13 @@ templ (p *EmojyPlugin) uploadIndex() {
</div> </div>
<div class="cell"> <div class="cell">
<label>File <label>File
<input type="file" /> <input type="file" name="attachment" />
</label> </label>
</div> </div>
<div class="cell"> <div class="cell">
<button class="button" hx-post="/emojy/upload">Submit</button> <button class="button" type="submit">Submit</button>
</div> </div>
</div> </div>
</form>
</div> </div>
} }

View File

@ -31,7 +31,7 @@ func (p *EmojyPlugin) uploadIndex() templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><div class=\"grid-x\"><div class=\"cell\"><label>Passphrase</label> <input type=\"text\" name=\"password\" placeholder=\"Password...\"></div><div class=\"cell\"><label>File <input type=\"file\"></label></div><div class=\"cell\"><button class=\"button\" hx-post=\"/emojy/upload\">Submit</button></div></div></div>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div><form hx-post=\"/emojy/upload\" enctype=\"multipart/form-data\"><div class=\"grid-x\"><div class=\"cell\"><label>Passphrase</label> <input type=\"text\" name=\"password\" placeholder=\"Password...\"></div><div class=\"cell\"><label>File <input type=\"file\" name=\"attachment\"></label></div><div class=\"cell\"><button class=\"button\" type=\"submit\">Submit</button></div></div></form></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@ -4,6 +4,7 @@ import (
"embed" "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ggicci/httpin"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -23,8 +24,10 @@ func (p *EmojyPlugin) registerWeb() {
r := chi.NewRouter() r := chi.NewRouter()
r.HandleFunc("/all", p.handleAll) r.HandleFunc("/all", p.handleAll)
r.HandleFunc("/allFiles", p.handleAllFiles) r.HandleFunc("/allFiles", p.handleAllFiles)
r.HandleFunc("/upload", p.handleUpload) r.With(httpin.NewInput(UploadReq{})).
r.HandleFunc("/file/{name}", p.handleEmojy) Post("/upload", p.handleUpload)
r.With(httpin.NewInput(EmojyReq{})).
HandleFunc("/file/{name}", p.handleEmojy)
r.HandleFunc("/stats", p.handleStats) r.HandleFunc("/stats", p.handleStats)
r.HandleFunc("/list", p.handleList) r.HandleFunc("/list", p.handleList)
r.HandleFunc("/new", p.handleUploadForm) r.HandleFunc("/new", p.handleUploadForm)
@ -98,8 +101,15 @@ func (p *EmojyPlugin) handleAllFiles(w http.ResponseWriter, r *http.Request) {
} }
} }
type UploadReq struct {
Password string `in:"form=password"`
Attachment *httpin.File `in:"form=attachment"`
}
func (p *EmojyPlugin) handleUpload(w http.ResponseWriter, r *http.Request) { func (p *EmojyPlugin) handleUpload(w http.ResponseWriter, r *http.Request) {
newFilePath, err := p.FileSave(r) input := r.Context().Value(httpin.Input).(*UploadReq)
log.Printf("handleUpload: %#v", input)
newFilePath, err := p.FileSave(input)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("could not upload file") log.Error().Err(err).Msgf("could not upload file")
w.WriteHeader(500) w.WriteHeader(500)
@ -111,60 +121,46 @@ func (p *EmojyPlugin) handleUpload(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "success") fmt.Fprintf(w, "success")
} }
func (p *EmojyPlugin) FileSave(r *http.Request) (string, error) { func (p *EmojyPlugin) FileSave(input *UploadReq) (string, error) {
if err := r.ParseMultipartForm(32 << 20); err != nil { if !p.b.CheckPassword("", input.Password) {
if err != http.ErrNotMultipart {
return "", err
}
if err := r.ParseForm(); err != nil {
return "", err
}
}
if r.MultipartForm == nil || len(r.MultipartForm.File) == 0 {
return "", fmt.Errorf("no files")
}
password := r.FormValue("password")
if password != p.b.GetPassword() {
return "", fmt.Errorf("incorrect password") return "", fmt.Errorf("incorrect password")
} }
for _, fileHeaders := range r.MultipartForm.File { file := input.Attachment
for _, fileHeader := range fileHeaders { emojyFileName := file.Filename()
body, err := fileHeader.Open()
if err != nil {
return "", fmt.Errorf("error opening part %q: %s", fileHeader.Filename, err)
}
emojyFileName := fileHeader.Filename
emojyName := strings.TrimSuffix(emojyFileName, filepath.Ext(emojyFileName)) emojyName := strings.TrimSuffix(emojyFileName, filepath.Ext(emojyFileName))
if ok, _, _, _ := p.isKnownEmojy(emojyName); ok { if ok, _, _, _ := p.isKnownEmojy(emojyName); ok {
return "", fmt.Errorf("emojy already exists") return "", fmt.Errorf("emojy already exists")
} }
contentType := fileHeader.Header.Get("Content-Type") contentType := file.MIMEHeader().Get("Content-Type")
if !strings.HasPrefix(contentType, "image") { if !strings.HasPrefix(contentType, "image") {
return "", fmt.Errorf("incorrect mime type - given: %s", contentType) return "", fmt.Errorf("incorrect mime type - given: %s", contentType)
} }
fullPath := filepath.Clean(filepath.Join(p.emojyPath, emojyFileName)) fullPath := filepath.Clean(filepath.Join(p.emojyPath, emojyFileName))
_ = os.MkdirAll(p.emojyPath, os.ModePerm) _ = os.MkdirAll(p.emojyPath, os.ModePerm)
log.Debug().Msgf("trying to create/open file: %s", fullPath) log.Debug().Msgf("trying to create/open file: %s", fullPath)
file, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, os.ModePerm) outFile, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil { if err != nil {
return "", err return "", err
} }
defer file.Close() defer outFile.Close()
_, err = io.Copy(file, body) inFile, _ := file.ReadAll()
stream, _ := file.OpenReceiveStream()
_, err = outFile.Write(inFile)
_, err = io.Copy(outFile, stream)
if err != nil { if err != nil {
return "", err return "", err
} }
return emojyFileName, nil return emojyFileName, nil
} }
}
return "", fmt.Errorf("did not find file") type EmojyReq struct {
Name string `in:"path=name"`
} }
func (p *EmojyPlugin) handleEmojy(w http.ResponseWriter, r *http.Request) { func (p *EmojyPlugin) handleEmojy(w http.ResponseWriter, r *http.Request) {
fname := chi.URLParam(r, "name") input := r.Context().Value(httpin.Input).(*EmojyReq)
contents, err := ioutil.ReadFile(path.Join(p.emojyPath, fname)) contents, err := ioutil.ReadFile(path.Join(p.emojyPath, input.Name))
if err != nil { if err != nil {
w.WriteHeader(404) w.WriteHeader(404)
out, _ := json.Marshal(struct{ err error }{err}) out, _ := json.Marshal(struct{ err error }{err})

View File

@ -3,10 +3,9 @@ package fact
import ( import (
"embed" "embed"
"fmt" "fmt"
"github.com/ggicci/httpin"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"html/template"
"net/http" "net/http"
"strings"
) )
//go:embed *.html //go:embed *.html
@ -15,26 +14,20 @@ var embeddedFS embed.FS
// Register any web URLs desired // Register any web URLs desired
func (p *FactoidPlugin) registerWeb() { func (p *FactoidPlugin) registerWeb() {
r := chi.NewRouter() r := chi.NewRouter()
r.Post("/search", p.handleSearch) r.With(httpin.NewInput(SearchReq{})).
r.HandleFunc("/req", p.serveQuery) Post("/search", p.handleSearch)
r.HandleFunc("/", p.serveQuery) r.Get("/", p.serveQuery)
p.b.GetWeb().RegisterWebName(r, "/factoid", "Factoid") p.b.GetWeb().RegisterWebName(r, "/factoid", "Factoid")
} }
func linkify(text string) template.HTML { type SearchReq struct {
parts := strings.Fields(text) Query string `in:"query"`
for i, word := range parts {
if strings.HasPrefix(word, "http") {
parts[i] = fmt.Sprintf("<a href=\"%s\">%s</a>", word, word)
}
}
return template.HTML(strings.Join(parts, " "))
} }
func (p *FactoidPlugin) handleSearch(w http.ResponseWriter, r *http.Request) { func (p *FactoidPlugin) handleSearch(w http.ResponseWriter, r *http.Request) {
query := r.FormValue("query") input := r.Context().Value(httpin.Input).(*SearchReq)
entries, err := getFacts(p.db, query, "") entries, err := getFacts(p.db, input.Query, "")
if err != nil { if err != nil {
w.WriteHeader(500) w.WriteHeader(500)
fmt.Fprint(w, err) fmt.Fprint(w, err)

View File

@ -45,9 +45,10 @@ func (p *LastPlugin) migrate() error {
_, err = tx.Exec(`create table if not exists last ( _, err = tx.Exec(`create table if not exists last (
day integer, day integer,
channel string not null, channel string not null,
ts int not null, time int not null,
who string not null, nick string not null,
message string not null, body string not null,
message_id string not null,
constraint last_key primary key (day, channel) on conflict replace constraint last_key primary key (day, channel) on conflict replace
)`) )`)
if err != nil { if err != nil {
@ -134,8 +135,8 @@ func (p *LastPlugin) recordLast(r bot.Request) bool {
} }
_, err := p.db.Exec( _, err := p.db.Exec(
`insert into last values (?, ?, ?, ?, ?)`, `insert into last (day, channel, time, body, nick, message_id) values (?, ?, ?, ?, ?, ?)`,
day.Unix(), ch, time.Now().Unix(), who, r.Msg.Body) day.Unix(), ch, time.Now().Unix(), r.Msg.Body, who, r.Msg.ID)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Could not record last.") log.Error().Err(err).Msgf("Could not record last.")
} }
@ -143,11 +144,13 @@ func (p *LastPlugin) recordLast(r bot.Request) bool {
} }
type last struct { type last struct {
Day int64 ID int64 `db:"id"`
TS int64 Day int64 `db:"day"`
Channel string Time int64 `db:"time"`
Who string Channel string `db:"channel"`
Message string Nick string `db:"nick"`
Body string `db:"body"`
MessageID string `db:"message_id"`
} }
func (p *LastPlugin) yesterdaysLast(ch string) (last, error) { func (p *LastPlugin) yesterdaysLast(ch string) (last, error) {
@ -189,6 +192,11 @@ func (p *LastPlugin) sayLast(c bot.Connector, chFrom, chTo string, force bool) {
} }
return return
} }
msg := fmt.Sprintf(`%s killed the channel last night by saying "%s"`, l.Who, l.Message) msg := fmt.Sprintf(`%s killed the channel last night by saying "%s"`, l.Nick, l.Body)
p.b.Send(c, bot.Message, chTo, msg) guildID := p.c.Get("discord.guildid", "")
p.b.Send(c, bot.Message, chTo, msg, bot.MessageReference{
MessageID: l.MessageID,
ChannelID: l.Channel,
GuildID: guildID,
})
} }

View File

@ -3,6 +3,7 @@ package meme
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ggicci/httpin"
"net/http" "net/http"
"net/url" "net/url"
"sort" "sort"
@ -17,8 +18,10 @@ 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.Get("/img", p.img)
r.Put("/save/{name}", p.saveMeme) r.With(httpin.NewInput(SaveReq{})).
r.Post("/add", p.saveMeme) Put("/save/{name}", p.saveMeme)
r.With(httpin.NewInput(SaveReq{})).
Post("/add", p.saveMeme)
r.Delete("/rm/{name}", p.rmMeme) r.Delete("/rm/{name}", p.rmMeme)
r.Get("/edit/{name}", p.editMeme) r.Get("/edit/{name}", p.editMeme)
r.Get("/", p.webRoot) r.Get("/", p.webRoot)
@ -85,31 +88,33 @@ func (p *MemePlugin) rmMeme(w http.ResponseWriter, r *http.Request) {
mkCheckError(w)(err) mkCheckError(w)(err)
} }
func (p *MemePlugin) saveMeme(w http.ResponseWriter, r *http.Request) { type SaveReq struct {
name := chi.URLParam(r, "name") Name string `in:"path=name"`
if name == "" { Config string `in:"form=config"`
name = r.FormValue("name") URL string `in:"form=url"`
} }
func (p *MemePlugin) saveMeme(w http.ResponseWriter, r *http.Request) {
input := r.Context().Value(httpin.Input).(*SaveReq)
checkError := mkCheckError(w) checkError := mkCheckError(w)
formats := p.c.GetMap("meme.memes", defaultFormats) formats := p.c.GetMap("meme.memes", defaultFormats)
formats[name] = r.FormValue("url") formats[input.Name] = input.URL
err := p.c.SetMap("meme.memes", formats) err := p.c.SetMap("meme.memes", formats)
checkError(err) checkError(err)
config := r.FormValue("config") if input.Config == "" {
if config == "" { input.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[input.Name] = input.Config
err = p.c.SetMap("meme.memeconfigs", configs) err = p.c.SetMap("meme.memeconfigs", configs)
checkError(err) checkError(err)
meme := webResp{ meme := webResp{
Name: name, Name: input.Name,
URL: formats[name], URL: formats[input.Name],
Config: configs[name], Config: configs[input.Name],
} }
p.Show(meme).Render(r.Context(), w) p.Show(meme).Render(r.Context(), w)

View File

@ -3,6 +3,7 @@ package secrets
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ggicci/httpin"
"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"
@ -31,9 +32,10 @@ func New(b bot.Bot) bot.Plugin {
func (p *SecretsPlugin) registerWeb() { func (p *SecretsPlugin) registerWeb() {
r := chi.NewRouter() r := chi.NewRouter()
r.HandleFunc("/add", p.handleRegister) r.With(httpin.NewInput(RegisterReq{})).
r.HandleFunc("/remove", p.handleRemove) Post("/add", p.handleRegister)
r.HandleFunc("/", p.handleIndex) r.Delete("/remove", p.handleRemove)
r.Get("/", p.handleIndex)
p.b.GetWeb().RegisterWebName(r, "/secrets", "Secrets") p.b.GetWeb().RegisterWebName(r, "/secrets", "Secrets")
} }
@ -71,14 +73,15 @@ func (p *SecretsPlugin) handleAll(w http.ResponseWriter, r *http.Request) {
p.sendKeys(w, r) p.sendKeys(w, r)
} }
func (p *SecretsPlugin) handleRegister(w http.ResponseWriter, r *http.Request) { type RegisterReq struct {
if checkMethod(http.MethodPost, w, r) { Key string `in:"form=key"`
log.Debug().Msgf("failed post %s", r.Method) Value string `in:"form=value"`
return
} }
func (p *SecretsPlugin) handleRegister(w http.ResponseWriter, r *http.Request) {
input := r.Context().Value(httpin.Input).(*RegisterReq)
checkError := mkCheckError(w) checkError := mkCheckError(w)
key, value := r.FormValue("key"), r.FormValue("value") err := p.c.RegisterSecret(input.Key, input.Value)
err := p.c.RegisterSecret(key, value)
if checkError(err) { if checkError(err) {
return return
} }
@ -86,9 +89,6 @@ func (p *SecretsPlugin) handleRegister(w http.ResponseWriter, r *http.Request) {
} }
func (p *SecretsPlugin) handleRemove(w http.ResponseWriter, r *http.Request) { func (p *SecretsPlugin) handleRemove(w http.ResponseWriter, r *http.Request) {
if checkMethod(http.MethodDelete, w, r) {
return
}
checkError := mkCheckError(w) checkError := mkCheckError(w)
b, err := io.ReadAll(r.Body) b, err := io.ReadAll(r.Body)
if checkError(err) { if checkError(err) {