Experimental image captions

This commit is contained in:
Michał Gdula 2025-04-24 21:18:44 +01:00
parent 72cad30983
commit 29bdf1b0ae
4 changed files with 177 additions and 6 deletions

View file

@ -1,10 +1,20 @@
package img package img
import ( import (
"bufio"
"bytes"
_ "embed"
"errors" "errors"
"image"
"image/color"
"image/jpeg"
"image/png"
"io"
"net/http" "net/http"
"strings"
"time" "time"
"git.sr.ht/~sbinet/gg"
"github.com/Fluffy-Bean/lynxie/app" "github.com/Fluffy-Bean/lynxie/app"
"github.com/Fluffy-Bean/lynxie/utils" "github.com/Fluffy-Bean/lynxie/utils"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
@ -16,10 +26,15 @@ var client = http.Client{
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
} }
//go:embed resources/Impact.ttf
var resourceImpactFont []byte
func RegisterImgCommands(a *app.App) { func RegisterImgCommands(a *app.App) {
a.RegisterCommand("saveable", registerSaveable(a)) a.RegisterCommand("saveable", registerSaveable(a))
a.RegisterCommandAlias("gif", "saveable") a.RegisterCommandAlias("gif", "saveable")
a.RegisterCommand("caption", registerCaption(a))
a.RegisterCommandAlias("c", "caption")
} }
func registerSaveable(a *app.App) app.Callback { func registerSaveable(a *app.App) app.Callback {
@ -67,7 +82,7 @@ func registerSaveable(a *app.App) app.Callback {
}, },
Files: []*discordgo.File{ Files: []*discordgo.File{
{ {
Name: "image.gif", Name: "saveable.gif",
ContentType: "image/gif", ContentType: "image/gif",
Reader: res.Body, 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) { func getClosestImage(h *app.Handler) (string, error) {
// Get message attachments // Get message attachments
if len(h.Message.Attachments) >= 1 { if len(h.Message.Attachments) >= 1 {

Binary file not shown.

10
go.mod
View file

@ -2,10 +2,18 @@ module github.com/Fluffy-Bean/lynxie
go 1.24.0 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 ( 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/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/crypto v0.36.0 // indirect
golang.org/x/image v0.21.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
) )

19
go.sum
View file

@ -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 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= 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.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 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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.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 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 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/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.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 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/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.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= 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=