Compare commits

..

No commits in common. "def86a3c7e14cb31784d340ea218806ef0dcd92c" and "f602c2ec8d1c89435e0e9aea87039df46bcbd15e" have entirely different histories.

17 changed files with 351 additions and 299 deletions

View File

@ -63,12 +63,6 @@ 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,8 +11,6 @@ import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
httpin_integration "github.com/ggicci/httpin/integration"
) )
type Web struct { type Web struct {
@ -79,8 +77,6 @@ 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,7 +116,6 @@ 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) {
@ -142,20 +141,13 @@ 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,
}
} }
} }
data := &discordgo.MessageSend{ data := &discordgo.MessageSend{
Content: message, Content: message,
Embeds: embeds, Embeds: embeds,
Files: files, Files: files,
Reference: ref,
} }
log.Debug(). log.Debug().

6
go.mod
View File

@ -17,8 +17,7 @@ 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/ggicci/httpin v0.16.0 github.com/go-chi/chi/v5 v5.0.7
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
@ -33,7 +32,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.4 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.14.0
@ -58,7 +57,6 @@ 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,13 +120,9 @@ 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.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.7/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=
@ -219,7 +215,6 @@ 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=
@ -257,7 +252,6 @@ 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=
@ -378,8 +372,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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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 style="margin-right: 2em">ID</span><span>{ fmt.Sprintf(" %d", entry.ID) }</span></div> <div><span>ID</span><span>{ fmt.Sprintf("%d", entry.ID) }</span></div>
<div><span style="margin-right: 2em">Password</span><span> { entry.Secret }:{ entry.Pass }</span></div> <div><span>Password</span><span>{ entry.Secret }:{ entry.Pass }</span></div>
} }
templ (a *AdminPlugin) entries(items []PassEntry) { templ (a *AdminPlugin) entries(items []PassEntry) {
@ -51,6 +51,7 @@ 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><div class=\"grid-x\"><div class=\"cell\" id=\"data\"></div></div></form></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></form><div class=\"grid-x\"><div class=\"cell\" id=\"data\"></div></div></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 style=\"margin-right: 2em\">ID</span><span>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><span>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: 86} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 35, Col: 59}
} }
_, 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 style=\"margin-right: 2em\">Password</span><span>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div><div><span>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: 77} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 50}
} }
_, 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: 92} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 65}
} }
_, 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\" name=\"id\" value=\"") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" hx-include=\"this,[name=&#39;password&#39;],[name=&#39;secret&#39;]\" 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: 54, Col: 57} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 55, 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: 62, Col: 22} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 63, 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: 78, Col: 38} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 79, 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: 78, Col: 61} return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 79, 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,12 +4,13 @@ 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"
@ -20,15 +21,8 @@ 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.With(httpin.NewInput(AppPassCheckReq{})). r.HandleFunc("/verify", p.handleAppPassCheck)
HandleFunc("/verify", p.handleAppPassCheck) r.HandleFunc("/api", p.handleAppPassAPI)
//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")
} }
@ -38,8 +32,8 @@ func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) {
} }
type PassEntry struct { type PassEntry struct {
ID int64 `json:"id" in:"form=id;query=password"` ID int64 `json:"id"`
Secret string `json:"secret" in:"form=secret;query=password"` Secret string `json:"secret"`
// Should be null unless inserting a new entry // Should be null unless inserting a new entry
Pass string `json:"pass"` Pass string `json:"pass"`
@ -63,117 +57,107 @@ func (p *PassEntry) Compare(pass string) bool {
return true return true
} }
type AppPassCheckReq struct {
Secret string `json:"secret"`
Password string `json:"password"`
}
func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) { func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) {
input := r.Context().Value(httpin.Input).(*AppPassCheckReq) req := struct {
if p.bot.CheckPassword(input.Secret, input.Password) { Secret string `json:"secret"`
Password string `json:"password"`
}{}
body, _ := ioutil.ReadAll(r.Body)
_ = json.Unmarshal(body, &req)
if p.bot.CheckPassword(req.Secret, req.Password) {
w.WriteHeader(204) w.WriteHeader(204)
} else { } else {
w.WriteHeader(403) w.WriteHeader(403)
} }
} }
type AppPassAPIReq struct { func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
Password string `in:"form=password;query=password"` r.ParseForm()
PassEntry PassEntry b, _ := io.ReadAll(r.Body)
} query, _ := url.ParseQuery(string(b))
secret := r.FormValue("secret")
func (p *AdminPlugin) checkAPIInput(w http.ResponseWriter, r *http.Request) *AppPassAPIReq { password := r.FormValue("password")
input := r.Context().Value(httpin.Input).(*AppPassAPIReq) id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64)
if !r.Form.Has("secret") && query.Has("secret") {
if input.Password == "" && input.PassEntry.Secret == "" { secret = query.Get("secret")
b, _ := io.ReadAll(r.Body)
query, _ := url.ParseQuery(string(b))
input.Password = query.Get("password")
input.PassEntry.ID, _ = strconv.ParseInt(query.Get("id"), 10, 64)
input.PassEntry.Secret = query.Get("secret")
} }
if !r.Form.Has("password") && query.Has("password") {
log.Printf("checkAPIInput: %#v", input) password = query.Get("password")
}
if input.PassEntry.Secret == "" { if !r.Form.Has("id") && query.Has("id") {
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 nil
}
if input.Password == "" || !p.bot.CheckPassword(input.PassEntry.Secret, input.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
} }
if string(input.PassEntry.Pass) == "" { if req.Password == "" || !p.bot.CheckPassword(req.PassEntry.Secret, req.Password) {
c := 10 writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password"))
b := make([]byte, c) return
_, err := rand.Read(b) }
switch r.Method {
case http.MethodPut:
if string(req.PassEntry.Pass) == "" {
c := 10
b := make([]byte, c)
_, err := rand.Read(b)
if err != nil {
fmt.Println("error:", err)
return
}
req.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b))
}
q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)`
req.PassEntry.EncodePass()
check := bcrypt.CompareHashAndPassword(req.PassEntry.encodedPass, []byte(req.PassEntry.Pass))
log.Debug().
Str("secret", req.PassEntry.Secret).
Str("encoded", string(req.PassEntry.encodedPass)).
Str("password", string(req.PassEntry.Pass)).
Interface("check", check).
Msg("debug pass creation")
res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost)
if err != nil { if err != nil {
fmt.Println("error:", err) writeErr(r.Context(), w, err)
return return
} }
input.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b)) id, err := res.LastInsertId()
} if err != nil {
q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)` writeErr(r.Context(), w, err)
input.PassEntry.EncodePass() return
}
req.PassEntry.ID = id
p.showPassword(req.PassEntry).Render(r.Context(), w)
return
case http.MethodDelete:
check := bcrypt.CompareHashAndPassword(input.PassEntry.encodedPass, []byte(input.PassEntry.Pass)) if req.PassEntry.ID <= 0 {
writeErr(r.Context(), w, fmt.Errorf("missing ID"))
log.Debug(). return
Str("secret", input.PassEntry.Secret). }
Str("encoded", string(input.PassEntry.encodedPass)). q := `delete from apppass where id = ?`
Str("password", string(input.PassEntry.Pass)). _, err := p.db.Exec(q, req.PassEntry.ID)
Interface("check", check). if err != nil {
Msg("debug pass creation") writeErr(r.Context(), w, err)
return
res, err := p.db.Exec(q, input.PassEntry.Secret, input.PassEntry.encodedPass, input.PassEntry.Cost) }
if err != nil {
writeErr(r.Context(), w, err)
return
}
id, err := res.LastInsertId()
if err != nil {
writeErr(r.Context(), w, err)
return
}
input.PassEntry.ID = id
p.showPassword(input.PassEntry).Render(r.Context(), w)
}
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"))
return
}
q := `delete from apppass where id = ?`
_, err := p.db.Exec(q, input.PassEntry.ID)
if err != nil {
writeErr(r.Context(), w, err)
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,8 +3,10 @@ package counter
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ggicci/httpin" "io"
"net/http" "net/http"
"net/url"
"strconv"
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@ -21,48 +23,34 @@ 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.With(httpin.NewInput(CounterChangeReq{})). subrouter.HandleFunc("/api/users/{user}/items/{item}/increment/{delta}", p.mkIncrementByNAPI(1))
HandleFunc("/api/users/{user}/items/{item}/increment/{delta}", p.mkIncrementByNAPI(1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement/{delta}", p.mkIncrementByNAPI(-1))
subrouter.With(httpin.NewInput(CounterChangeReq{})). subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1))
HandleFunc("/api/users/{user}/items/{item}/decrement/{delta}", p.mkIncrementByNAPI(-1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", 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.With(httpin.NewInput(CounterChangeReq{})). r.HandleFunc("/users/{user}/items/{item}/increment", p.incHandler(1))
HandleFunc("/users/{user}/items/{item}/increment", p.incHandler(1)) r.HandleFunc("/users/{user}/items/{item}/decrement", 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) {
input := r.Context().Value(httpin.Input).(*CounterChangeReq) userName, _ := url.QueryUnescape(chi.URLParam(r, "user"))
if !p.b.CheckPassword("", input.Password) { itemName, _ := url.QueryUnescape(chi.URLParam(r, "item"))
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(input.UserName, input.Item, "", delta) item, err := p.delta(userName, itemName, "", 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(input.UserName, item).Render(r.Context(), w) p.renderItem(userName, item).Render(r.Context(), w)
} }
} }
@ -115,11 +103,13 @@ 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) {
input := r.Context().Value(httpin.Input).(*CounterChangeReq) userName, _ := url.QueryUnescape(chi.URLParam(r, "user"))
if input.Delta == 0 { itemName, _ := url.QueryUnescape(chi.URLParam(r, "item"))
input.Delta = direction delta, err := strconv.Atoi(chi.URLParam(r, "delta"))
if err != nil || delta == 0 {
delta = direction
} else { } else {
input.Delta = input.Delta * direction delta = delta * direction
} }
secret, pass, ok := r.BasicAuth() secret, pass, ok := r.BasicAuth()
@ -137,12 +127,15 @@ 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 input.Body.Message != "" { if inputMsg, ok := postData["message"]; ok {
personalMsg = fmt.Sprintf("\nMessage: %s", input.Body.Message) personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg)
} }
if _, err := p.delta(input.UserName, input.Item, personalMsg, input.Delta*direction); err != nil { if _, err := p.delta(userName, itemName, personalMsg, 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 {

104
plugins/counter/index.html Normal file
View File

@ -0,0 +1,104 @@
<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,7 +12,6 @@ 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>
@ -20,13 +19,12 @@ templ (p *EmojyPlugin) uploadIndex() {
</div> </div>
<div class="cell"> <div class="cell">
<label>File <label>File
<input type="file" name="attachment" /> <input type="file" />
</label> </label>
</div> </div>
<div class="cell"> <div class="cell">
<button class="button" type="submit">Submit</button> <button class="button" hx-post="/emojy/upload">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><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>") _, 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>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

View File

@ -4,7 +4,6 @@ import (
"embed" "embed"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/ggicci/httpin"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -24,10 +23,8 @@ 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.With(httpin.NewInput(UploadReq{})). r.HandleFunc("/upload", p.handleUpload)
Post("/upload", p.handleUpload) r.HandleFunc("/file/{name}", p.handleEmojy)
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)
@ -101,15 +98,8 @@ 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) {
input := r.Context().Value(httpin.Input).(*UploadReq) newFilePath, err := p.FileSave(r)
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)
@ -121,46 +111,60 @@ func (p *EmojyPlugin) handleUpload(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "success") fmt.Fprintf(w, "success")
} }
func (p *EmojyPlugin) FileSave(input *UploadReq) (string, error) { func (p *EmojyPlugin) FileSave(r *http.Request) (string, error) {
if !p.b.CheckPassword("", input.Password) { if err := r.ParseMultipartForm(32 << 20); err != nil {
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")
} }
file := input.Attachment for _, fileHeaders := range r.MultipartForm.File {
emojyFileName := file.Filename() for _, fileHeader := range fileHeaders {
emojyName := strings.TrimSuffix(emojyFileName, filepath.Ext(emojyFileName)) body, err := fileHeader.Open()
if ok, _, _, _ := p.isKnownEmojy(emojyName); ok { if err != nil {
return "", fmt.Errorf("emojy already exists") return "", fmt.Errorf("error opening part %q: %s", fileHeader.Filename, err)
}
emojyFileName := fileHeader.Filename
emojyName := strings.TrimSuffix(emojyFileName, filepath.Ext(emojyFileName))
if ok, _, _, _ := p.isKnownEmojy(emojyName); ok {
return "", fmt.Errorf("emojy already exists")
}
contentType := fileHeader.Header.Get("Content-Type")
if !strings.HasPrefix(contentType, "image") {
return "", fmt.Errorf("incorrect mime type - given: %s", contentType)
}
fullPath := filepath.Clean(filepath.Join(p.emojyPath, emojyFileName))
_ = os.MkdirAll(p.emojyPath, os.ModePerm)
log.Debug().Msgf("trying to create/open file: %s", fullPath)
file, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
return "", err
}
defer file.Close()
_, err = io.Copy(file, body)
if err != nil {
return "", err
}
return emojyFileName, nil
}
} }
contentType := file.MIMEHeader().Get("Content-Type") return "", fmt.Errorf("did not find file")
if !strings.HasPrefix(contentType, "image") {
return "", fmt.Errorf("incorrect mime type - given: %s", contentType)
}
fullPath := filepath.Clean(filepath.Join(p.emojyPath, emojyFileName))
_ = os.MkdirAll(p.emojyPath, os.ModePerm)
log.Debug().Msgf("trying to create/open file: %s", fullPath)
outFile, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
return "", err
}
defer outFile.Close()
inFile, _ := file.ReadAll()
stream, _ := file.OpenReceiveStream()
_, err = outFile.Write(inFile)
_, err = io.Copy(outFile, stream)
if err != nil {
return "", err
}
return emojyFileName, nil
}
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) {
input := r.Context().Value(httpin.Input).(*EmojyReq) fname := chi.URLParam(r, "name")
contents, err := ioutil.ReadFile(path.Join(p.emojyPath, input.Name)) contents, err := ioutil.ReadFile(path.Join(p.emojyPath, fname))
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,9 +3,10 @@ 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
@ -14,20 +15,26 @@ 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.With(httpin.NewInput(SearchReq{})). r.Post("/search", p.handleSearch)
Post("/search", p.handleSearch) r.HandleFunc("/req", p.serveQuery)
r.Get("/", p.serveQuery) r.HandleFunc("/", p.serveQuery)
p.b.GetWeb().RegisterWebName(r, "/factoid", "Factoid") p.b.GetWeb().RegisterWebName(r, "/factoid", "Factoid")
} }
type SearchReq struct { func linkify(text string) template.HTML {
Query string `in:"query"` parts := strings.Fields(text)
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) {
input := r.Context().Value(httpin.Input).(*SearchReq) query := r.FormValue("query")
entries, err := getFacts(p.db, input.Query, "") entries, err := getFacts(p.db, query, "")
if err != nil { if err != nil {
w.WriteHeader(500) w.WriteHeader(500)
fmt.Fprint(w, err) fmt.Fprint(w, err)

View File

@ -45,10 +45,9 @@ 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,
time int not null, ts int not null,
nick string not null, who string not null,
body string not null, message 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 {
@ -135,8 +134,8 @@ func (p *LastPlugin) recordLast(r bot.Request) bool {
} }
_, err := p.db.Exec( _, err := p.db.Exec(
`insert into last (day, channel, time, body, nick, message_id) values (?, ?, ?, ?, ?, ?)`, `insert into last values (?, ?, ?, ?, ?)`,
day.Unix(), ch, time.Now().Unix(), r.Msg.Body, who, r.Msg.ID) day.Unix(), ch, time.Now().Unix(), who, r.Msg.Body)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Could not record last.") log.Error().Err(err).Msgf("Could not record last.")
} }
@ -144,13 +143,11 @@ func (p *LastPlugin) recordLast(r bot.Request) bool {
} }
type last struct { type last struct {
ID int64 `db:"id"` Day int64
Day int64 `db:"day"` TS int64
Time int64 `db:"time"` Channel string
Channel string `db:"channel"` Who string
Nick string `db:"nick"` Message string
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) {
@ -192,11 +189,6 @@ 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.Nick, l.Body) msg := fmt.Sprintf(`%s killed the channel last night by saying "%s"`, l.Who, l.Message)
guildID := p.c.Get("discord.guildid", "") p.b.Send(c, bot.Message, chTo, msg)
p.b.Send(c, bot.Message, chTo, msg, bot.MessageReference{
MessageID: l.MessageID,
ChannelID: l.Channel,
GuildID: guildID,
})
} }

View File

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

View File

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