diff --git a/commands/img/img.go b/commands/img/img.go index 1a6fb24..c7d9fd1 100644 --- a/commands/img/img.go +++ b/commands/img/img.go @@ -1,10 +1,20 @@ package img import ( + "bufio" + "bytes" + _ "embed" "errors" + "image" + "image/color" + "image/jpeg" + "image/png" + "io" "net/http" + "strings" "time" + "git.sr.ht/~sbinet/gg" "github.com/Fluffy-Bean/lynxie/app" "github.com/Fluffy-Bean/lynxie/utils" "github.com/bwmarrin/discordgo" @@ -16,10 +26,15 @@ var client = http.Client{ Timeout: 10 * time.Second, } +//go:embed resources/Impact.ttf +var resourceImpactFont []byte + func RegisterImgCommands(a *app.App) { a.RegisterCommand("saveable", registerSaveable(a)) - a.RegisterCommandAlias("gif", "saveable") + + a.RegisterCommand("caption", registerCaption(a)) + a.RegisterCommandAlias("c", "caption") } func registerSaveable(a *app.App) app.Callback { @@ -67,7 +82,7 @@ func registerSaveable(a *app.App) app.Callback { }, Files: []*discordgo.File{ { - Name: "image.gif", + Name: "saveable.gif", ContentType: "image/gif", Reader: res.Body, }, @@ -79,6 +94,141 @@ func registerSaveable(a *app.App) app.Callback { } } +func registerCaption(a *app.App) app.Callback { + return func(h *app.Handler, args []string) app.Error { + fileEndpoint, err := getClosestImage(h) + if err != nil { + return app.Error{ + Msg: "Could not get image", + Err: err, + } + } + + req, err := http.NewRequest(http.MethodGet, fileEndpoint, nil) + if err != nil { + return app.Error{ + Msg: "", + Err: err, + } + } + + if req.ContentLength > maxFileSize { + return app.Error{ + Msg: "Could not get image", + Err: errors.New("requested file is too big"), + } + } + + res, err := client.Do(req) + if err != nil { + return app.Error{ + Msg: "", + Err: err, + } + } + defer res.Body.Close() + + buff, err := io.ReadAll(res.Body) + if err != nil { + return app.Error{ + Msg: "Failed to read image", + Err: err, + } + } + + var img image.Image + switch http.DetectContentType(buff) { + case "image/png": + img, err = png.Decode(bytes.NewReader(buff)) + if err != nil { + return app.Error{ + Msg: "Failed to decode PNG", + Err: err, + } + } + break + case "image/jpeg": + img, err = jpeg.Decode(bytes.NewReader(buff)) + if err != nil { + return app.Error{ + Msg: "Failed to decode JPEG", + Err: err, + } + } + break + default: + return app.Error{ + Msg: "Unknown or unsupported image format", + Err: errors.New("Unknown or unsupported image format " + http.DetectContentType(buff)), + } + } + + fontSize := float64(img.Bounds().Dx() / 25) + if fontSize < 16 { + fontSize = 16 + } else if fontSize > 50 { + fontSize = 50 + } + + canvas := gg.NewContext(img.Bounds().Dx(), img.Bounds().Dy()+200) + err = canvas.LoadFontFaceFromBytes(resourceImpactFont, fontSize) + if err != nil { + return app.Error{ + Msg: "Failed to load font", + Err: err, + } + } + + canvas.SetColor(color.RGBA{R: 255, G: 255, B: 255, A: 255}) + canvas.Clear() + + canvas.SetColor(color.RGBA{R: 0, G: 0, B: 0, A: 255}) + canvas.DrawStringWrapped( + strings.Join(args, " "), + float64(img.Bounds().Dx()/2), + 100, + 0.5, + 0.5, + float64(img.Bounds().Dx()), + 1.5, + gg.AlignCenter, + ) + + canvas.DrawImage(img, 0, 200) + + var export bytes.Buffer + err = canvas.EncodeJPG(bufio.NewWriter(&export), &jpeg.Options{ + Quality: 100, + }) + if err != nil { + return app.Error{ + Msg: "Failed to encode JPEG", + Err: err, + } + } + + h.Session.ChannelMessageSendComplex(h.Message.ChannelID, &discordgo.MessageSend{ + Embed: &discordgo.MessageEmbed{ + Title: "Caption", + Image: &discordgo.MessageEmbedImage{ + URL: "attachment://caption.jpeg", + }, + Color: utils.ColorFromRGB(255, 255, 255), + }, + Files: []*discordgo.File{ + { + Name: "caption.jpeg", + ContentType: "image/jpeg", + Reader: bytes.NewReader(export.Bytes()), + }, + }, + Reference: h.Reference, + }) + + return app.Error{} + } +} + func getClosestImage(h *app.Handler) (string, error) { // Get message attachments if len(h.Message.Attachments) >= 1 { diff --git a/commands/img/resources/Impact.ttf b/commands/img/resources/Impact.ttf new file mode 100644 index 0000000..b442871 Binary files /dev/null and b/commands/img/resources/Impact.ttf differ diff --git a/go.mod b/go.mod index f4221ef..ac43e7a 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,18 @@ module github.com/Fluffy-Bean/lynxie go 1.24.0 -require github.com/bwmarrin/discordgo v0.28.1 +require ( + git.sr.ht/~sbinet/gg v0.6.0 + github.com/bwmarrin/discordgo v0.28.1 +) require ( + github.com/campoy/embedmd v1.0.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.36.0 // indirect + golang.org/x/image v0.21.0 // indirect golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index cb8fabb..09a164a 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,31 @@ +git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= +git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= +git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= +git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= +golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=