diff --git a/go.mod b/go.mod
index ec313e7..218f526 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
git.fromouter.space/crunchy-rocks/draw2d v0.0.0-20190208101535-675e82cb6412
git.fromouter.space/crunchy-rocks/emoji v0.0.0-20181116142102-2188aadaf093
git.fromouter.space/crunchy-rocks/freetype v0.0.0-20181116104610-3115318f2577
- git.fromouter.space/hamcha/tg v0.1.0
+ git.fromouter.space/hamcha/tg v0.2.1
git.sr.ht/~hamcha/containers v0.0.3
github.com/cockroachdb/pebble v0.0.0-20220323190648-7ad3551b2050
github.com/disintegration/imaging v1.6.2
diff --git a/go.sum b/go.sum
index 59be4d5..b87b0ab 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,8 @@ git.fromouter.space/crunchy-rocks/emoji v0.0.0-20181116142102-2188aadaf093 h1:pa
git.fromouter.space/crunchy-rocks/emoji v0.0.0-20181116142102-2188aadaf093/go.mod h1:bmXZYbLNSGivLP7yQlcQ605b1H1L8kzxAk8A7sAEWx0=
git.fromouter.space/crunchy-rocks/freetype v0.0.0-20181116104610-3115318f2577 h1:rntW5kRu8s7dN8n7WWLHzAVZrnk7NS9YCdEQwA/O9pk=
git.fromouter.space/crunchy-rocks/freetype v0.0.0-20181116104610-3115318f2577/go.mod h1:MlK0wT7XDuAuw0l/Z+Es2OHozCbBb2r9VxOy8bU58ow=
-git.fromouter.space/hamcha/tg v0.1.0 h1:cJwL8pElkBtaDn7Bxa14zvlnBTTK8LdcCtcbBg7hEvk=
-git.fromouter.space/hamcha/tg v0.1.0/go.mod h1:aIFj7n5FP+Zr/Zv6I6Kq4ZqhRxC12gXFQcC3iOakv9M=
+git.fromouter.space/hamcha/tg v0.2.1 h1:cN14BhgbRLwYs4LkiKCKD9lDxwzbrpuRkQ6Buk4LdFw=
+git.fromouter.space/hamcha/tg v0.2.1/go.mod h1:aIFj7n5FP+Zr/Zv6I6Kq4ZqhRxC12gXFQcC3iOakv9M=
git.sr.ht/~hamcha/containers v0.0.3 h1:obG9X8s5iOIahVe+EGpkBDYmUAO78oTi9Y9gRurt334=
git.sr.ht/~hamcha/containers v0.0.3/go.mod h1:RiZphUpy9t6EnL4Gf6uzByM9QrBoqRCEPo7kz2wzbhE=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
diff --git a/main.go b/main.go
index d5f19f9..7cfb8f9 100644
--- a/main.go
+++ b/main.go
@@ -9,9 +9,11 @@ import (
"git.fromouter.space/crunchy-rocks/clessy-ng/modules"
"git.fromouter.space/crunchy-rocks/clessy-ng/modules/macro"
+ "git.fromouter.space/crunchy-rocks/clessy-ng/modules/meme"
"git.fromouter.space/crunchy-rocks/clessy-ng/modules/metafora"
"git.fromouter.space/crunchy-rocks/clessy-ng/modules/proverbio"
"git.fromouter.space/crunchy-rocks/clessy-ng/modules/remind"
+ "git.fromouter.space/crunchy-rocks/clessy-ng/modules/snapchat"
"git.fromouter.space/crunchy-rocks/clessy-ng/modules/unsplash"
"git.fromouter.space/crunchy-rocks/clessy-ng/utils"
@@ -26,6 +28,8 @@ var mods = map[string]modules.Module{
"macro": ¯o.Module{},
"remind": &remind.Module{},
"unsplash": &unsplash.Module{},
+ "snapchat": &snapchat.Module{},
+ "meme": &meme.Module{},
}
func checkErr(err error, message string, args ...interface{}) {
diff --git a/modules/meme/mod.go b/modules/meme/mod.go
new file mode 100644
index 0000000..fb546a1
--- /dev/null
+++ b/modules/meme/mod.go
@@ -0,0 +1,233 @@
+package meme
+
+import (
+ "bytes"
+ "image"
+ _ "image/gif"
+ "image/jpeg"
+ _ "image/png"
+ "log"
+ "math/rand"
+ "os"
+ "strings"
+ "time"
+
+ "git.fromouter.space/crunchy-rocks/clessy-ng/modules"
+ "git.fromouter.space/crunchy-rocks/clessy-ng/utils"
+
+ "git.fromouter.space/crunchy-rocks/draw2d"
+ "git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
+ "git.fromouter.space/crunchy-rocks/emoji"
+ "git.fromouter.space/crunchy-rocks/freetype"
+ "git.fromouter.space/hamcha/tg"
+)
+
+var memeFontData draw2d.FontData
+
+type Module struct {
+ client *tg.Telegram
+ name string
+ emojis emoji.Table
+}
+
+func (m *Module) Initialize(options modules.ModuleOptions) error {
+ m.client = options.API
+ m.name = options.Name
+ m.emojis = options.Emojis
+
+ rand.Seed(time.Now().Unix())
+
+ fontfile := utils.RequireEnv("CLESSY_MEME_FONT")
+ bytes, err := os.ReadFile(fontfile)
+ if err != nil {
+ return err
+ }
+
+ font, err := freetype.ParseFont(bytes)
+ if err != nil {
+ return err
+ }
+
+ memeFontData = draw2d.FontData{
+ Name: "impact",
+ Family: draw2d.FontFamilySans,
+ Style: 0,
+ }
+ draw2d.RegisterFont(memeFontData, font)
+
+ log.Println("[meme] Loaded!")
+
+ return nil
+}
+
+func (m *Module) OnUpdate(update tg.APIUpdate) {
+ // Not a message? Ignore
+ if update.Message == nil {
+ return
+ }
+ message := *update.Message
+
+ // Make replies work
+ if message.ReplyTo != nil && message.Text != nil && message.ReplyTo.Photo != nil {
+ message.Photo = message.ReplyTo.Photo
+ message.Caption = message.Text
+ }
+
+ if message.Photo != nil && message.Caption != nil {
+ caption := *(message.Caption)
+ if strings.HasPrefix(caption, "/meme ") && len(caption) > 6 {
+ idx := strings.Index(caption, ";")
+ if idx < 0 {
+ m.client.SendTextMessage(tg.ClientTextMessageData{
+ ChatID: message.Chat.ChatID,
+ Text: "Formato: /meme TESTO IN ALTO;TESTO IN BASSO",
+ ReplyID: &message.MessageID,
+ })
+ return
+ }
+
+ txtup := caption[6:idx]
+ txtdw := caption[idx+1:]
+
+ maxsz := 0
+ photo := tg.APIPhotoSize{}
+ for _, curphoto := range message.Photo {
+ if curphoto.Width > maxsz {
+ maxsz = curphoto.Width
+ photo = curphoto
+ }
+ }
+
+ byt, err := m.client.GetFile(tg.FileRequestData{
+ FileID: photo.FileID,
+ })
+ if err != nil {
+ log.Printf("[memegen] Received error: %s\n", err.Error())
+ m.client.SendTextMessage(tg.ClientTextMessageData{
+ ChatID: message.Chat.ChatID,
+ Text: "ERRORE! @hamcha controlla la console!",
+ ReplyID: &message.MessageID,
+ })
+ return
+ }
+
+ img, _, err := image.Decode(bytes.NewReader(byt))
+ if err != nil {
+ log.Printf("[memegen] Image decode error: %s\n", err.Error())
+ m.client.SendTextMessage(tg.ClientTextMessageData{
+ ChatID: message.Chat.ChatID,
+ Text: "ERRORE! Non riesco a leggere l'immagine",
+ ReplyID: &message.MessageID,
+ })
+ return
+ }
+
+ m.client.SendChatAction(tg.ClientChatActionData{
+ ChatID: message.Chat.ChatID,
+ Action: tg.ActionUploadingPhoto,
+ })
+
+ //TODO Clean up this mess
+
+ // Create target image
+ bounds := img.Bounds()
+ iwidth := float64(bounds.Size().X)
+ iheight := float64(bounds.Size().Y)
+
+ timg := image.NewRGBA(bounds)
+ gc := draw2dimg.NewGraphicContext(timg)
+ gc.Emojis = m.emojis
+ gc.SetStrokeColor(image.Black)
+ gc.SetFillColor(image.White)
+ gc.SetFontData(memeFontData)
+ gc.DrawImage(img)
+
+ write := func(text string, istop bool) {
+ text = strings.ToUpper(strings.TrimSpace(text))
+ gc.Restore()
+ gc.Save()
+
+ // Detect appropriate font size
+ scale := iheight / iwidth * (iwidth / 10)
+ gc.SetFontSize(scale)
+ gc.SetLineWidth(scale / 15)
+
+ // Get NEW bounds
+ left, top, right, bottom := gc.GetStringBounds(text)
+
+ width := right - left
+ texts := []string{text}
+ if width > iwidth {
+ // Split text
+ texts = utils.SplitCenter(text)
+
+ // Get longest line
+ longer := float64(0)
+ longid := 0
+ widths := make([]float64, len(texts))
+ for id := range texts {
+ tleft, _, tright, _ := gc.GetStringBounds(texts[id])
+ widths[id] = tright - tleft
+ if width > longer {
+ longer = widths[id]
+ longid = id
+ }
+ }
+
+ // Still too big? Decrease font size again
+ iter := 0
+ for width > iwidth && iter < 10 {
+ log.Println("Warning, resizing!")
+ gc.SetFontSize(scale * (0.8 - 0.1*float64(iter)))
+ left, top, right, bottom = gc.GetStringBounds(texts[longid])
+ width = right - left
+ iter++
+ }
+ }
+
+ height := bottom - top
+ margin := float64(height / 50)
+ lines := float64(len(texts) - 1)
+
+ gc.Save()
+ for id, txt := range texts {
+ gc.Save()
+ left, _, right, _ = gc.GetStringBounds(txt)
+ width = right - left
+
+ y := float64(0)
+ if istop {
+ y = (height+margin)*float64(id+1) + margin*5
+ } else {
+ y = iheight - (height * lines) + (height * float64(id)) - margin*5
+ }
+
+ gc.Translate((iwidth-width)/2, y)
+ gc.StrokeString(txt)
+ gc.FillString(txt)
+ gc.Restore()
+ }
+ }
+ write(txtup, true)
+ write(txtdw, false)
+
+ buf := new(bytes.Buffer)
+ err = jpeg.Encode(buf, timg, &(jpeg.Options{Quality: 80}))
+ if err != nil {
+ log.Printf("[memegen] Image encode error: %s\n", err.Error())
+ m.client.SendTextMessage(tg.ClientTextMessageData{
+ ChatID: message.Chat.ChatID,
+ Text: "ERRORE! @hamcha controlla la console!",
+ ReplyID: &message.MessageID,
+ })
+ return
+ }
+ m.client.SendPhoto(tg.ClientPhotoData{
+ ChatID: message.Chat.ChatID,
+ Bytes: buf.Bytes(),
+ Filename: "meme.jpg",
+ ReplyID: &message.MessageID,
+ })
+ }
+ }
+}
diff --git a/modules/snapchat/mod.go b/modules/snapchat/mod.go
new file mode 100644
index 0000000..223d6da
--- /dev/null
+++ b/modules/snapchat/mod.go
@@ -0,0 +1,206 @@
+package snapchat
+
+import (
+ "bytes"
+ "image"
+ "image/color"
+ _ "image/gif"
+ "image/jpeg"
+ _ "image/png"
+ "log"
+ "math/rand"
+ "os"
+ "strings"
+ "time"
+
+ "git.fromouter.space/crunchy-rocks/clessy-ng/modules"
+ "git.fromouter.space/crunchy-rocks/clessy-ng/utils"
+
+ "git.fromouter.space/crunchy-rocks/draw2d"
+ "git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
+ "git.fromouter.space/crunchy-rocks/emoji"
+ "git.fromouter.space/crunchy-rocks/freetype"
+ "git.fromouter.space/hamcha/tg"
+)
+
+var snapFontData draw2d.FontData
+
+type Module struct {
+ client *tg.Telegram
+ name string
+ emojis emoji.Table
+}
+
+func (m *Module) Initialize(options modules.ModuleOptions) error {
+ m.client = options.API
+ m.name = options.Name
+ m.emojis = options.Emojis
+
+ rand.Seed(time.Now().Unix())
+
+ fontfile := utils.RequireEnv("CLESSY_SNAPCHAT_FONT")
+ bytes, err := os.ReadFile(fontfile)
+ if err != nil {
+ return err
+ }
+
+ font, err := freetype.ParseFont(bytes)
+ if err != nil {
+ return err
+ }
+
+ snapFontData = draw2d.FontData{
+ Name: "sourcesans",
+ Family: draw2d.FontFamilySans,
+ Style: draw2d.FontStyleBold,
+ }
+ draw2d.RegisterFont(snapFontData, font)
+
+ log.Println("[snapchat] Loaded!")
+
+ return nil
+}
+
+func (m *Module) OnUpdate(update tg.APIUpdate) {
+ // Not a message? Ignore
+ if update.Message == nil {
+ return
+ }
+ message := *update.Message
+
+ // Make replies work
+ if message.ReplyTo != nil && message.Text != nil && message.ReplyTo.Photo != nil {
+ message.Photo = message.ReplyTo.Photo
+ message.Caption = message.Text
+ }
+
+ if message.Photo != nil && message.Caption != nil {
+ caption := *(message.Caption)
+ if strings.HasPrefix(caption, "/snap ") && len(caption) > 6 {
+ txt := strings.TrimSpace(caption[6:])
+
+ maxsz := 0
+ photo := tg.APIPhotoSize{}
+ for _, curphoto := range message.Photo {
+ if curphoto.Width > maxsz {
+ maxsz = curphoto.Width
+ photo = curphoto
+ }
+ }
+
+ byt, err := m.client.GetFile(tg.FileRequestData{
+ FileID: photo.FileID,
+ })
+ if err != nil {
+ log.Printf("[snapchat] Received error: %s\n", err.Error())
+ m.client.SendTextMessage(tg.ClientTextMessageData{
+ ChatID: message.Chat.ChatID,
+ Text: "ERRORE! @hamcha controlla la console!",
+ ReplyID: &message.MessageID,
+ })
+ return
+ }
+
+ img, _, err := image.Decode(bytes.NewReader(byt))
+ if err != nil {
+ log.Printf("[snapchat] Image decode error: %s\n", err.Error())
+ m.client.SendTextMessage(tg.ClientTextMessageData{
+ ChatID: message.Chat.ChatID,
+ Text: "ERRORE! Non riesco a leggere l'immagine",
+ ReplyID: &message.MessageID,
+ })
+ return
+ }
+
+ m.client.SendChatAction(tg.ClientChatActionData{
+ ChatID: message.Chat.ChatID,
+ Action: tg.ActionUploadingPhoto,
+ })
+
+ // Create target image
+ bounds := img.Bounds()
+ iwidth := float64(bounds.Size().Y) / 1.6
+ iheight := float64(bounds.Size().Y)
+
+ repos := iwidth < float64(bounds.Size().X)
+ if !repos {
+ iwidth = float64(bounds.Size().X)
+ }
+
+ timg := image.NewRGBA(image.Rect(0, 0, int(iwidth), int(iheight)))
+ gc := draw2dimg.NewGraphicContext(timg)
+ gc.Emojis = m.emojis
+ gc.SetFontData(snapFontData)
+
+ gc.Save()
+ if repos {
+ gc.Translate(-(float64(bounds.Size().X)-iwidth)/2, 0)
+ }
+ gc.DrawImage(img)
+ gc.Restore()
+
+ scale := iwidth / 25
+ gc.SetFontSize(scale)
+
+ lineMargin := scale / 3
+ boxMargin := lineMargin
+ topMargin := lineMargin / 6
+ write := func(text string, startHeight float64) {
+ texts := utils.WordWrap(gc, strings.TrimSpace(text), iwidth*0.9)
+ totalHeight := startHeight
+ firstLine := 0.
+ for _, txt := range texts {
+ _, top, _, bottom := gc.GetStringBounds(txt)
+ height := (bottom - top)
+ if firstLine == 0 {
+ firstLine = height
+ }
+ totalHeight += lineMargin + height
+ }
+
+ // Draw background
+ starty := startHeight - boxMargin - topMargin - firstLine
+ endy := totalHeight + boxMargin - firstLine
+ gc.Save()
+ gc.SetFillColor(color.RGBA{0, 0, 0, 160})
+ gc.BeginPath()
+ gc.MoveTo(0, starty)
+ gc.LineTo(iwidth, starty)
+ gc.LineTo(iwidth, endy)
+ gc.LineTo(0, endy)
+ gc.Close()
+ gc.Fill()
+ gc.Restore()
+
+ // Write lines
+ gc.SetFillColor(image.White)
+ height := startHeight
+ for _, txt := range texts {
+ left, top, right, bottom := gc.GetStringBounds(txt)
+ width := right - left
+ gc.FillStringAt(txt, (iwidth-width)/2, height)
+ height += lineMargin + (bottom - top)
+ }
+ }
+ write(txt, (rand.Float64()*0.4+0.3)*iheight)
+
+ buf := new(bytes.Buffer)
+ err = jpeg.Encode(buf, timg, &(jpeg.Options{Quality: 80}))
+ if err != nil {
+ log.Printf("[snapchat] Image encode error: %s\n", err.Error())
+ m.client.SendTextMessage(tg.ClientTextMessageData{
+ ChatID: message.Chat.ChatID,
+ Text: "ERRORE! @hamcha controlla la console!",
+ ReplyID: &message.MessageID,
+ })
+ return
+ }
+ m.client.SendPhoto(tg.ClientPhotoData{
+ ChatID: message.Chat.ChatID,
+ Bytes: buf.Bytes(),
+ Filename: "meme.jpg",
+ ReplyID: &message.MessageID,
+ })
+ }
+ }
+}
diff --git a/modules/unsplash/mod.go b/modules/unsplash/mod.go
index 4be278c..9d64c81 100644
--- a/modules/unsplash/mod.go
+++ b/modules/unsplash/mod.go
@@ -2,7 +2,6 @@ package unsplash
import (
"bytes"
- "encoding/base64"
"image"
_ "image/gif"
"image/jpeg"
@@ -255,7 +254,7 @@ func (m *Module) OnUpdate(update tg.APIUpdate) {
}
m.client.SendPhoto(tg.ClientPhotoData{
ChatID: message.Chat.ChatID,
- Bytes: base64.StdEncoding.EncodeToString(buf.Bytes()),
+ Bytes: buf.Bytes(),
Filename: "quote.jpg",
ReplyID: &message.MessageID,
})
diff --git a/run.ps1 b/run.ps1
index 40dc47b..d5b0747 100644
--- a/run.ps1
+++ b/run.ps1
@@ -6,5 +6,6 @@ $env:CLESSY_EMOJI_PATH = "_data"
$env:CLESSY_UNSPLASH_FONT = "_data/gill.ttf"
$env:CLESSY_UNSPLASH_BG_PATH = "_data/pics"
$env:CLESSY_MEME_FONT = "_data/impact.ttf"
+$env:CLESSY_SNAPCHAT_FONT = "_data/source.ttf"
mkdir -force _data/pics
go run .
\ No newline at end of file
diff --git a/run.sh b/run.sh
index f493b65..f61beac 100644
--- a/run.sh
+++ b/run.sh
@@ -5,5 +5,6 @@ export CLESSY_DB_DIR=_data/db
export CLESSY_EMOJI_PATH=_data
export CLESSY_UNSPLASH_FONT=_data/gill.ttf
export CLESSY_MEME_FONT=_data/impact.ttf
+export CLESSY_SNAPCHAT_FONT=_data/source.ttf
mkdir -p _data/pics
go run .
\ No newline at end of file
diff --git a/utils/text.go b/utils/text.go
index b3f0af1..b44f94d 100644
--- a/utils/text.go
+++ b/utils/text.go
@@ -1,6 +1,11 @@
package utils
-import "strings"
+import (
+ "strings"
+ "unicode"
+
+ "git.fromouter.space/crunchy-rocks/draw2d"
+)
func abs(i int) int {
if i < 0 {
@@ -30,3 +35,56 @@ func SplitCenter(text string) []string {
}
return []string{strings.TrimSpace(text[:whitespaceBackIndex]), strings.TrimSpace(text[whitespaceBackIndex:])}
}
+
+// Word wrapping code from https://github.com/fogleman/gg
+// Copyright (C) 2016 Michael Fogleman
+// Licensed under MIT (https://github.com/fogleman/gg/blob/master/LICENSE.md)
+
+func splitOnSpace(x string) []string {
+ var result []string
+ pi := 0
+ ps := false
+ for i, c := range x {
+ s := unicode.IsSpace(c)
+ if s != ps && i > 0 {
+ result = append(result, x[pi:i])
+ pi = i
+ }
+ ps = s
+ }
+ result = append(result, x[pi:])
+ return result
+}
+
+func WordWrap(gc draw2d.GraphicContext, s string, width float64) []string {
+ var result []string
+ for _, line := range strings.Split(s, "\n") {
+ fields := splitOnSpace(line)
+ if len(fields)%2 == 1 {
+ fields = append(fields, "")
+ }
+ x := ""
+ for i := 0; i < len(fields); i += 2 {
+ left, _, right, _ := gc.GetStringBounds(x + fields[i])
+ w := right - left
+ if w > width {
+ if x == "" {
+ result = append(result, fields[i])
+ x = ""
+ continue
+ } else {
+ result = append(result, x)
+ x = ""
+ }
+ }
+ x += fields[i] + fields[i+1]
+ }
+ if x != "" {
+ result = append(result, x)
+ }
+ }
+ for i, line := range result {
+ result[i] = strings.TrimSpace(line)
+ }
+ return result
+}