From f9d5fb46f180ced7e07f5e7825e1187c63441559 Mon Sep 17 00:00:00 2001 From: Hamcha Date: Wed, 15 Feb 2017 16:59:49 +0100 Subject: [PATCH] Add snapchat module --- mods/main.go | 60 +++++++++++-- mods/memegen.go | 8 +- mods/snapchat.go | 217 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 13 deletions(-) create mode 100644 mods/snapchat.go diff --git a/mods/main.go b/mods/main.go index 87b17ae..f6075f6 100644 --- a/mods/main.go +++ b/mods/main.go @@ -2,24 +2,58 @@ package main import ( "flag" + "log" "strings" "github.com/hamcha/clessy/tg" ) +type Mod struct { + OnInit func() + OnMessage func(*tg.Broker, tg.APIMessage) +} + +var mods = map[string]Mod{ + "metafora": { + OnMessage: metafora, + }, + "viaggi": { + OnInit: initviaggi, + OnMessage: viaggi, + }, + "meme": { + OnInit: initmeme, + OnMessage: memegen, + }, + "unsplash": { + OnInit: initunsplash, + OnMessage: unsplash, + }, + "macro": { + OnInit: initmacro, + OnMessage: macro, + }, + "snapchat": { + OnInit: initsnapchat, + OnMessage: snapchat, + }, +} + func initmods() { - initviaggi() - initmeme() - initunsplash() - initmacro() + for name, mod := range mods { + log.Printf("Initializing %s..", name) + if mod.OnInit != nil { + mod.OnInit() + } + } } func dispatch(broker *tg.Broker, update tg.APIMessage) { - metafora(broker, update) - viaggi(broker, update) - memegen(broker, update) - unsplash(broker, update) - macro(broker, update) + for _, mod := range mods { + if mod.OnMessage != nil { + mod.OnMessage(broker, update) + } + } } func isCommand(update tg.APIMessage, cmdname string) bool { @@ -34,15 +68,23 @@ func isCommand(update tg.APIMessage, cmdname string) bool { var botname *string var impact *string var gillmt *string +var sourcesans *string func main() { brokerAddr := flag.String("broker", "localhost:7314", "Broker address:port") botname = flag.String("botname", "maudbot", "Bot name for /targetet@commands") impact = flag.String("impact", "impact.ttf", "Path to impact.ttf (Impact font)") gillmt = flag.String("gillmt", "gill.ttf", "Path to gill.ttf (Gill Sans MT font)") + sourcesans = flag.String("sourcesans", "source.ttf", "Path to source.ttf (Source Sans Pro font)") macropath = flag.String("macropath", "macros.json", "Path to macros db (JSON)") + disable := flag.String("disable", "", "Disable mods (separated by comma)") flag.Parse() + for _, modname := range strings.Split(*disable, ",") { + modname = strings.TrimSpace(modname) + delete(mods, modname) + } + initmods() err := tg.CreateBrokerClient(*brokerAddr, dispatch) diff --git a/mods/memegen.go b/mods/memegen.go index d8ca8bb..69b7bab 100644 --- a/mods/memegen.go +++ b/mods/memegen.go @@ -65,21 +65,21 @@ func memegen(broker *tg.Broker, update tg.APIMessage) { broker.GetFile(photo.FileID, func(broker *tg.Broker, data tg.BrokerUpdate) { if data.Type == tg.BError { - log.Println("[memegen] Received error from broker: %s\n", *data.Error) + log.Printf("[memegen] Received error from broker: %s\n", *data.Error) broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) return } pbytes, err := base64.StdEncoding.DecodeString(*data.Bytes) if err != nil { - log.Println("[memegen] Base64 decode error: %s\n", err.Error()) + log.Printf("[memegen] Base64 decode error: %s\n", err.Error()) broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) return } img, _, err := image.Decode(bytes.NewReader(pbytes)) if err != nil { - log.Println("[memegen] Image decode error: %s\n", err.Error()) + log.Printf("[memegen] Image decode error: %s\n", err.Error()) broker.SendTextMessage(update.Chat, "ERROR: Non riesco a leggere l'immagine", &update.MessageID) return } @@ -170,7 +170,7 @@ func memegen(broker *tg.Broker, update tg.APIMessage) { buf := new(bytes.Buffer) err = jpeg.Encode(buf, timg, &(jpeg.Options{Quality: 80})) if err != nil { - log.Println("[memegen] Image encode error: %s\n", err.Error()) + log.Printf("[memegen] Image encode error: %s\n", err.Error()) broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) return } diff --git a/mods/snapchat.go b/mods/snapchat.go new file mode 100644 index 0000000..f720d70 --- /dev/null +++ b/mods/snapchat.go @@ -0,0 +1,217 @@ +package main + +import ( + "bytes" + "encoding/base64" + "image" + "image/color" + _ "image/gif" + "image/jpeg" + _ "image/png" + "io/ioutil" + "log" + "math/rand" + "os" + "strings" + "unicode" + + "time" + + "github.com/golang/freetype" + "github.com/hamcha/clessy/tg" + "github.com/llgcode/draw2d" + "github.com/llgcode/draw2d/draw2dimg" +) + +var snapFontData draw2d.FontData + +func initsnapchat() { + rand.Seed(time.Now().Unix()) + + fontfile, err := os.Open(*sourcesans) + assert(err) + defer fontfile.Close() + + bytes, err := ioutil.ReadAll(fontfile) + assert(err) + + font, err := freetype.ParseFont(bytes) + assert(err) + + snapFontData = draw2d.FontData{"sourcesans", draw2d.FontFamilySans, draw2d.FontStyleBold} + draw2d.RegisterFont(snapFontData, font) +} + +func snapchat(broker *tg.Broker, update tg.APIMessage) { + // Make replies work + if update.ReplyTo != nil && update.Text != nil && update.ReplyTo.Photo != nil { + update.Photo = update.ReplyTo.Photo + update.Caption = update.Text + } + + if update.Photo != nil && update.Caption != nil { + caption := *(update.Caption) + if strings.HasPrefix(caption, "/snap ") && len(caption) > 6 { + txt := strings.TrimSpace(caption[6:]) + + maxsz := 0 + photo := tg.APIPhotoSize{} + for _, curphoto := range update.Photo { + if curphoto.Width > maxsz { + maxsz = curphoto.Width + photo = curphoto + } + } + + broker.GetFile(photo.FileID, func(broker *tg.Broker, data tg.BrokerUpdate) { + if data.Type == tg.BError { + log.Printf("[snapchat] Received error from broker: %s\n", *data.Error) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + + pbytes, err := base64.StdEncoding.DecodeString(*data.Bytes) + if err != nil { + log.Printf("[snapchat] Base64 decode error: %s\n", err.Error()) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + + img, _, err := image.Decode(bytes.NewReader(pbytes)) + if err != nil { + log.Printf("[snapchat] Image decode error: %s\n", err.Error()) + broker.SendTextMessage(update.Chat, "ERROR: Non riesco a leggere l'immagine", &update.MessageID) + return + } + + // 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.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 := 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()) + broker.SendTextMessage(update.Chat, "ERRORE! @hamcha controlla la console!", &update.MessageID) + return + } + broker.SendPhoto(update.Chat, buf.Bytes(), "meme.jpg", "", &update.MessageID) + }) + } + } +} + +// 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 +}