mirror of https://github.com/velour/catbase.git
tappd: add plugin
This commit is contained in:
parent
2457d6769e
commit
866b947f42
2
main.go
2
main.go
|
@ -6,6 +6,7 @@ import (
|
|||
"flag"
|
||||
"github.com/velour/catbase/plugins/pagecomment"
|
||||
"github.com/velour/catbase/plugins/talker"
|
||||
"github.com/velour/catbase/plugins/tappd"
|
||||
"github.com/velour/catbase/plugins/topic"
|
||||
"io"
|
||||
"math/rand"
|
||||
|
@ -144,6 +145,7 @@ func main() {
|
|||
b.AddPlugin(leftpad.New(b))
|
||||
b.AddPlugin(dice.New(b))
|
||||
b.AddPlugin(picker.New(b))
|
||||
b.AddPlugin(tappd.New(b))
|
||||
b.AddPlugin(beers.New(b))
|
||||
b.AddPlugin(remember.New(b))
|
||||
b.AddPlugin(your.New(b))
|
||||
|
|
|
@ -306,7 +306,15 @@ var defaultFormats = map[string]string{
|
|||
"raptor": "https://imgflip.com/s/meme/Philosoraptor.jpg",
|
||||
}
|
||||
|
||||
func (p *MemePlugin) findFontSize(config []memeText, fontLocation string, w, h int, sizes []float64) float64 {
|
||||
func FindFontSizeConfigs(configs []memeText, fontLocation string, w, h int, sizes []float64) float64 {
|
||||
texts := []string{}
|
||||
for _, c := range configs {
|
||||
texts = append(texts, c.Text)
|
||||
}
|
||||
return FindFontSize(texts, fontLocation, w, h, sizes)
|
||||
}
|
||||
|
||||
func FindFontSize(config []string, fontLocation string, w, h int, sizes []float64) float64 {
|
||||
fontSize := 12.0
|
||||
|
||||
m := gg.NewContext(w, h)
|
||||
|
@ -320,9 +328,9 @@ func (p *MemePlugin) findFontSize(config []memeText, fontLocation string, w, h i
|
|||
return fontSize
|
||||
}
|
||||
|
||||
w, _ := m.MeasureString(s.Text)
|
||||
w, _ := m.MeasureString(s)
|
||||
if w > longestW {
|
||||
longestStr = s.Text
|
||||
longestStr = s
|
||||
longestW = w
|
||||
}
|
||||
}
|
||||
|
@ -449,6 +457,7 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) {
|
|||
// Apply black stroke
|
||||
m.SetHexColor("#000")
|
||||
strokeSize := 6
|
||||
fontSize := FindFontSizeConfigs(spec.Configs, defaultFont, w, h, fontSizes)
|
||||
for dy := -strokeSize; dy <= strokeSize; dy++ {
|
||||
for dx := -strokeSize; dx <= strokeSize; dx++ {
|
||||
// give it rounded corners
|
||||
|
@ -460,7 +469,6 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) {
|
|||
if fontLocation == "" {
|
||||
fontLocation = defaultFont
|
||||
}
|
||||
fontSize := p.findFontSize(spec.Configs, fontLocation, w, h, fontSizes)
|
||||
m.LoadFontFace(fontLocation, fontSize)
|
||||
x := float64(w)*c.XPerc + float64(dx)
|
||||
y := float64(h)*c.YPerc + float64(dy)
|
||||
|
@ -476,7 +484,6 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) {
|
|||
if fontLocation == "" {
|
||||
fontLocation = defaultFont
|
||||
}
|
||||
fontSize := p.findFontSize(spec.Configs, fontLocation, w, h, fontSizes)
|
||||
m.LoadFontFace(fontLocation, fontSize)
|
||||
x := float64(w) * c.XPerc
|
||||
y := float64(h) * c.YPerc
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
package tappd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/fogleman/gg"
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/velour/catbase/plugins/meme"
|
||||
"image"
|
||||
"image/png"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
)
|
||||
|
||||
func (p *Tappd) getImage(url string) (image.Image, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug().
|
||||
Str("url", url).
|
||||
Msgf("about to decode and crash")
|
||||
img, _, err := image.Decode(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := img.Bounds()
|
||||
w := r.Dx()
|
||||
h := r.Dy()
|
||||
|
||||
maxSz := p.c.GetFloat64("maxImgSz", 750.0)
|
||||
|
||||
if w > h {
|
||||
scale := maxSz / float64(w)
|
||||
w = int(float64(w) * scale)
|
||||
h = int(float64(h) * scale)
|
||||
} else {
|
||||
scale := maxSz / float64(h)
|
||||
w = int(float64(w) * scale)
|
||||
h = int(float64(h) * scale)
|
||||
}
|
||||
|
||||
log.Debug().Msgf("trynig to resize to %v, %v", w, h)
|
||||
img = resize.Resize(uint(w), uint(h), img, resize.Lanczos3)
|
||||
r = img.Bounds()
|
||||
w = r.Dx()
|
||||
h = r.Dy()
|
||||
log.Debug().Msgf("resized to %v, %v", w, h)
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
type textSpec struct {
|
||||
text string
|
||||
// percentage location of text center
|
||||
x float64
|
||||
y float64
|
||||
}
|
||||
|
||||
func defaultSpec() textSpec {
|
||||
return textSpec{
|
||||
x: 0.5,
|
||||
y: 0.9,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Tappd) overlay(img image.Image, texts []textSpec) ([]byte, error) {
|
||||
font := p.c.Get("meme.font", "impact.ttf")
|
||||
fontSizes := []float64{48, 36, 24, 16, 12}
|
||||
r := img.Bounds()
|
||||
w := r.Dx()
|
||||
h := r.Dy()
|
||||
|
||||
txts := []string{}
|
||||
for _, t := range texts {
|
||||
txts = append(txts, t.text)
|
||||
}
|
||||
|
||||
fontSize := meme.FindFontSize(txts, font, w, h, fontSizes)
|
||||
|
||||
m := gg.NewContext(w, h)
|
||||
m.DrawImage(img, 0, 0)
|
||||
for _, spec := range texts {
|
||||
// write some stuff on the image here
|
||||
if err := m.LoadFontFace(font, fontSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply black stroke
|
||||
m.SetHexColor("#000")
|
||||
strokeSize := 6
|
||||
for dy := -strokeSize; dy <= strokeSize; dy++ {
|
||||
for dx := -strokeSize; dx <= strokeSize; dx++ {
|
||||
// give it rounded corners
|
||||
if dx*dx+dy*dy >= strokeSize*strokeSize {
|
||||
continue
|
||||
}
|
||||
x := float64(w)*spec.x + float64(dx)
|
||||
y := float64(h)*spec.y + float64(dy)
|
||||
m.DrawStringAnchored(spec.text, x, y, 0.5, 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
m.SetHexColor("#FFF")
|
||||
x := float64(w) * spec.x
|
||||
y := float64(h) * spec.y
|
||||
m.DrawStringAnchored(spec.text, x, y, 0.5, 0.5)
|
||||
}
|
||||
i := bytes.Buffer{}
|
||||
if err := png.Encode(&i, m.Image()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *Tappd) getAndOverlay(id, srcURL string, texts []textSpec) (imageInfo, error) {
|
||||
baseURL := p.c.Get("BaseURL", ``)
|
||||
u, _ := url.Parse(baseURL)
|
||||
u.Path = path.Join(u.Path, "tappd", id)
|
||||
img, err := p.getImage(srcURL)
|
||||
if err != nil {
|
||||
return imageInfo{}, err
|
||||
}
|
||||
data, err := p.overlay(img, texts)
|
||||
if err != nil {
|
||||
return imageInfo{}, err
|
||||
}
|
||||
bounds := img.Bounds()
|
||||
info := imageInfo{
|
||||
ID: id,
|
||||
SrcURL: srcURL,
|
||||
BotURL: u.String(),
|
||||
Img: img,
|
||||
Repr: data,
|
||||
W: bounds.Dx(),
|
||||
H: bounds.Dy(),
|
||||
}
|
||||
p.imageMap[id] = info
|
||||
log.Debug().
|
||||
Interface("BotURL", info.BotURL).
|
||||
Str("ID", id).
|
||||
Msgf("here's some info")
|
||||
return info, nil
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
package tappd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/velour/catbase/bot"
|
||||
"github.com/velour/catbase/config"
|
||||
"github.com/velour/catbase/connectors/discord"
|
||||
"image"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Tappd struct {
|
||||
b bot.Bot
|
||||
c *config.Config
|
||||
imageMap map[string]imageInfo
|
||||
}
|
||||
|
||||
type imageInfo struct {
|
||||
ID string
|
||||
SrcURL string
|
||||
BotURL string
|
||||
Img image.Image
|
||||
Repr []byte
|
||||
W int
|
||||
H int
|
||||
}
|
||||
|
||||
func New(b bot.Bot) *Tappd {
|
||||
t := &Tappd{
|
||||
b: b,
|
||||
c: b.Config(),
|
||||
imageMap: make(map[string]imageInfo),
|
||||
}
|
||||
t.register()
|
||||
t.registerWeb()
|
||||
t.mkDB()
|
||||
return t
|
||||
}
|
||||
|
||||
func (p *Tappd) mkDB() error {
|
||||
db := p.b.DB()
|
||||
tx, err := db.Beginx()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(`create table if not exists tappd (
|
||||
id integer primary key autoincrement,
|
||||
who string,
|
||||
channel string,
|
||||
message string,
|
||||
ts datetime
|
||||
);`)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Tappd) log(who, channel, message string) error {
|
||||
db := p.b.DB()
|
||||
tx, err := db.Beginx()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(`insert into tappd (who, channel, message, ts) values (?, ?, ? ,?)`,
|
||||
who, channel, message, time.Now())
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Tappd) registerDiscord(d *discord.Discord) {
|
||||
cmd := discordgo.ApplicationCommand{
|
||||
Name: "tap",
|
||||
Description: "tappd a beer in",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionAttachment,
|
||||
Name: "image",
|
||||
Description: "Picture that beer, but on Discord",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "comment",
|
||||
Description: "Comment on that beer",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := d.RegisterSlashCmd(cmd, p.tap); err != nil {
|
||||
log.Error().Err(err).Msgf("could not register")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Tappd) tap(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
who := i.Interaction.Member.Nick
|
||||
channel := i.Interaction.ChannelID
|
||||
msg := fmt.Sprintf("%s checked in: %s",
|
||||
i.Interaction.Member.Nick,
|
||||
i.ApplicationCommandData().Options[1].StringValue())
|
||||
attachID := i.ApplicationCommandData().Options[0].Value.(string)
|
||||
attach := i.ApplicationCommandData().Resolved.Attachments[attachID]
|
||||
spec := defaultSpec()
|
||||
spec.text = msg
|
||||
info, err := p.getAndOverlay(attachID, attach.URL, []textSpec{spec})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error with interaction")
|
||||
err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Error getting the image",
|
||||
Flags: discordgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error with interaction")
|
||||
}
|
||||
return
|
||||
}
|
||||
embed := &discordgo.MessageEmbed{
|
||||
Description: msg,
|
||||
Image: &discordgo.MessageEmbedImage{
|
||||
URL: info.BotURL,
|
||||
Width: info.W,
|
||||
Height: info.H,
|
||||
},
|
||||
}
|
||||
err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Embeds: []*discordgo.MessageEmbed{embed},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error with interaction")
|
||||
return
|
||||
}
|
||||
err = p.log(who, channel, msg)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error recording tap")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Tappd) register() {
|
||||
ht := bot.HandlerTable{
|
||||
{
|
||||
Kind: bot.Startup, IsCmd: false,
|
||||
Regex: regexp.MustCompile(`.*`),
|
||||
Handler: func(r bot.Request) bool {
|
||||
switch conn := r.Conn.(type) {
|
||||
case *discord.Discord:
|
||||
p.registerDiscord(conn)
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
}
|
||||
p.b.RegisterTable(p, ht)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package tappd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (p *Tappd) registerWeb() {
|
||||
r := chi.NewRouter()
|
||||
r.HandleFunc("/", p.serveImage)
|
||||
p.b.RegisterWeb(r, "/tappd/{id}")
|
||||
}
|
||||
|
||||
func (p *Tappd) serveImage(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
imgData, ok := p.imageMap[id]
|
||||
log.Debug().
|
||||
Str("id", id).
|
||||
Str("SrcURL", imgData.SrcURL).
|
||||
Bool("ok", ok).
|
||||
Msgf("creating request")
|
||||
if !ok {
|
||||
w.WriteHeader(404)
|
||||
out, _ := json.Marshal(struct{ err string }{"could not find ID"})
|
||||
w.Write(out)
|
||||
return
|
||||
}
|
||||
w.Write(imgData.Repr)
|
||||
}
|
Loading…
Reference in New Issue