Compare commits

..

3 Commits

Author SHA1 Message Date
dependabot[bot] b027de2f27
build(deps): bump github.com/itchyny/gojq from 0.12.8 to 0.12.9
Bumps [github.com/itchyny/gojq](https://github.com/itchyny/gojq) from 0.12.8 to 0.12.9.
- [Release notes](https://github.com/itchyny/gojq/releases)
- [Changelog](https://github.com/itchyny/gojq/blob/main/CHANGELOG.md)
- [Commits](https://github.com/itchyny/gojq/compare/v0.12.8...v0.12.9)

---
updated-dependencies:
- dependency-name: github.com/itchyny/gojq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-14 12:40:42 +00:00
Chris Sexton e93b6d07ab tappd: orient images 2022-10-14 08:39:26 -04:00
Chris Sexton 866b947f42 tappd: add plugin 2022-10-13 20:23:10 -04:00
7 changed files with 378 additions and 11 deletions

6
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/cenkalti/backoff/v4 v4.1.3
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff
github.com/chrissexton/sentiment v0.0.0-20190927141846-d69c422ba035
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90
github.com/forPelevin/gomoji v1.1.6
github.com/gabriel-vasile/mimetype v1.4.1
@ -46,6 +47,7 @@ require (
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/disintegration/gift v1.1.2 // indirect
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc // indirect
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect
github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17 // indirect
@ -82,9 +84,9 @@ require (
github.com/ttacon/libphonenumber v1.1.0 // indirect
golang.org/x/exp v0.0.0-20191014171548-69215a2ee97e // indirect
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/text v0.3.7 // indirect
gonum.org/v1/gonum v0.6.0 // indirect
google.golang.org/appengine v1.6.5 // indirect

12
go.sum
View File

@ -44,6 +44,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/gift v1.1.2 h1:9ZyHJr+kPamiH10FX3Pynt1AxFUob812bU9Wt4GMzhs=
github.com/disintegration/gift v1.1.2/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec h1:YrB6aVr9touOt75I9O1SiancmR2GMg45U9UYf0gtgWg=
github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec/go.mod h1:K0KBFIr1gWu/C1Gp10nFAcAE4hsB7JxE6OgLijrJ8Sk=
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc h1:tP7tkU+vIsEOKiK+l/NSLN4uUtkyuxc6hgYpQeCWAeI=
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc/go.mod h1:ORH5Qp2bskd9NzSfKqAF7tKfONsEkCarTE5ESr/RVBw=
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA=
@ -202,8 +206,8 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -217,8 +221,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -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))

View File

@ -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

145
plugins/tappd/image.go Normal file
View File

@ -0,0 +1,145 @@
package tappd
import (
"bytes"
"github.com/disintegration/imageorient"
"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
}
img, _, err := imageorient.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
}

176
plugins/tappd/tappd.go Normal file
View File

@ -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)
}

31
plugins/tappd/web.go Normal file
View File

@ -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)
}