diff options
| author | Christine Dodrill <me@christine.website> | 2017-05-31 22:45:56 -0700 |
|---|---|---|
| committer | Christine Dodrill <me@christine.website> | 2017-05-31 22:45:56 -0700 |
| commit | f86e39c7bd447720b385d2da901694a30cfbdbe5 (patch) | |
| tree | 375e82e8fac3750e43f5d5be3531e8f00ba13b9d | |
| parent | 495864f18470b056a644da37f68f9cc447e74d1b (diff) | |
| download | x-f86e39c7bd447720b385d2da901694a30cfbdbe5.tar.xz x-f86e39c7bd447720b385d2da901694a30cfbdbe5.zip | |
pbot: new primitive gif filter bot
91 files changed, 30340 insertions, 0 deletions
diff --git a/tg/pbot/main.go b/tg/pbot/main.go new file mode 100644 index 0000000..00a342d --- /dev/null +++ b/tg/pbot/main.go @@ -0,0 +1,139 @@ +package main + +import ( + "fmt" + "image" + "log" + "net/http" + "os" + "runtime" + "time" + + "github.com/Xe/uuid" + "github.com/fogleman/primitive/primitive" + _ "github.com/joho/godotenv/autoload" + "gopkg.in/telegram-bot-api.v4" + + // image formats + _ "image/jpeg" + _ "image/png" + + _ "github.com/hullerob/go.farbfeld" + _ "golang.org/x/image/bmp" + _ "golang.org/x/image/tiff" + _ "golang.org/x/image/webp" +) + +func main() { + bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_TOKEN")) + if err != nil { + log.Panic(err) + } + + ps, ok := puushLogin(os.Getenv("PUUSH_KEY")) + if !ok { + log.Fatal("puush login failed") + } + + u := tgbotapi.NewUpdate(0) + u.Timeout = 60 + + updates, err := bot.GetUpdatesChan(u) + + for update := range updates { + if update.Message == nil { + continue + } + + err := renderImg(bot, ps, update) + if err != nil { + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "error: "+err.Error()) + log.Printf("error in processing message from %s: %v", update.Message.From.String(), err) + bot.Send(msg) + } + } +} + +func stepImg(img image.Image, count int) image.Image { + bg := primitive.MakeColor(primitive.AverageImageColor(img)) + model := primitive.NewModel(img, bg, 512, runtime.NumCPU()) + + for range make([]struct{}, count) { + model.Step(primitive.ShapeTypeTriangle, 128, 0) + } + + return model.Context.Image() +} + +func renderImg(bot *tgbotapi.BotAPI, ps string, update tgbotapi.Update) error { + msg := update.Message + + // ignore chats without photos + if len(*msg.Photo) == 0 { + return nil + } + + p := *msg.Photo + pho := p[len(p)-1] + fu, err := bot.GetFileDirectURL(pho.FileID) + if err != nil { + return err + } + + resp, err := http.Get(fu) + if err != nil { + return err + } + defer resp.Body.Close() + + img, ifmt, err := image.Decode(resp.Body) + if err != nil { + return err + } + + log.Printf("%s: image id %s loaded (%s)", msg.From, pho.FileID, ifmt) + umsg := tgbotapi.NewMessage(update.Message.Chat.ID, "rendering... (may take a while)") + bot.Send(umsg) + + before := time.Now() + imgs := []image.Image{} + + for i := range make([]struct{}, 10) { + log.Printf("%s: starting frame render", msg.From) + imgs = append(imgs, stepImg(img, 150)) + log.Printf("%s: frame rendered", msg.From) + + umsg = tgbotapi.NewMessage(update.Message.Chat.ID, fmt.Sprintf("frame %d/10 rendered", i+1)) + bot.Send(umsg) + } + + gpath := "./var/" + update.Message.From.String() + ".gif" + err = primitive.SaveGIFImageMagick(gpath, imgs, 15, 15) + if err != nil { + return err + } + + after := time.Now().Sub(before) + + buf, err := os.Open(gpath) + if err != nil { + return err + } + defer os.Remove(gpath) + + umsg = tgbotapi.NewMessage(update.Message.Chat.ID, "uploading (took "+after.String()+" to render)") + bot.Send(umsg) + + furl, err := puush(ps, uuid.New()+".gif", buf) + if err != nil { + return err + } + + omsg := tgbotapi.NewMessage(update.Message.Chat.ID, furl.String()) + _, err = bot.Send(omsg) + if err != nil { + return err + } + + return nil +} diff --git a/tg/pbot/puush.go b/tg/pbot/puush.go new file mode 100644 index 0000000..259cb56 --- /dev/null +++ b/tg/pbot/puush.go @@ -0,0 +1,93 @@ +package main + +import ( + "bytes" + "crypto/md5" + "errors" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/url" + "strings" +) + +// Puush constants +const ( + PuushBase = "https://puush.me/api/" + PuushAuthURL = "https://puush.me/api/auth/" + PuushUploadURL = "https://puush.me/api/up/" +) + +func puushLogin(key string) (string, bool) { + r, err := http.PostForm(PuushAuthURL, url.Values{"k": {key}}) + if err != nil { + fmt.Println(err) + return "", false + } + body, _ := ioutil.ReadAll(r.Body) + r.Body.Close() + info := strings.Split(string(body), ",") + if info[0] == "-1" { + return "", false + } + + session := info[1] + return session, true +} + +func puush(session, fname string, fin io.Reader) (*url.URL, error) { + buf := new(bytes.Buffer) + w := multipart.NewWriter(buf) + kwriter, err := w.CreateFormField("k") + if err != nil { + return nil, err + } + + io.WriteString(kwriter, session) + + file, _ := ioutil.ReadAll(fin) + + h := md5.New() + h.Write(file) + + cwriter, err := w.CreateFormField("c") + if err != nil { + return nil, err + } + io.WriteString(cwriter, fmt.Sprintf("%x", h.Sum(nil))) + + zwriter, err := w.CreateFormField("z") + if err != nil { + return nil, err + } + io.WriteString(zwriter, "poop") // They must think their protocol is shit + + fwriter, err := w.CreateFormFile("f", fname) + if err != nil { + return nil, err + } + fwriter.Write(file) + + w.Close() + + req, err := http.NewRequest("POST", "http://puush.me/api/up", buf) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", w.FormDataContentType()) + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + return nil, err + } + body, _ := ioutil.ReadAll(res.Body) + res.Body.Close() + info := strings.Split(string(body), ",") + if info[0] == "0" { + return url.Parse(info[1]) + } + + return nil, errors.New("upload failed") +} diff --git a/tg/pbot/vendor-log b/tg/pbot/vendor-log new file mode 100644 index 0000000..1fe2451 --- /dev/null +++ b/tg/pbot/vendor-log @@ -0,0 +1,22 @@ +62b230097e9c9534ca2074782b25d738c4b68964 (dirty) github.com/Xe/uuid +ee8994ff90057955c428a5a949da5d064bf3ce6b github.com/fogleman/gg +80f39ceaa8f4c66acb28aba6abe6b15128c06113 github.com/fogleman/primitive/primitive +bcfeb16b74e8aea9e2fe043406f2ef74b1cb0759 github.com/golang/freetype/raster +bcfeb16b74e8aea9e2fe043406f2ef74b1cb0759 github.com/golang/freetype/truetype +b572f0728b691aae4256edb2e408279146eafe52 github.com/hullerob/go.farbfeld +325433c502d409f3c3dc820098fb0cfe38d98dc7 github.com/joho/godotenv +325433c502d409f3c3dc820098fb0cfe38d98dc7 github.com/joho/godotenv/autoload +a90a01d73ae432e2611d178c18367fbaa13e0154 github.com/technoweenie/multipartstreamer +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/bmp +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/draw +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/font +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/font/basicfont +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/math/f64 +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/math/fixed +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/riff +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/tiff +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/tiff/lzw +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/vp8 +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/vp8l +426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d golang.org/x/image/webp +0a57807db79efce7f6719fbb2c0e0f83fda79aec (dirty) gopkg.in/telegram-bot-api.v4 diff --git a/tg/pbot/vendor/github.com/Xe/uuid/dce.go b/tg/pbot/vendor/github.com/Xe/uuid/dce.go new file mode 100644 index 0000000..50a0f2d --- /dev/null +++ b/tg/pbot/vendor/github.com/Xe/uuid/dce.go @@ -0,0 +1,84 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) UUID { + uuid := NewUUID() + if uuid != nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCEPerson(Person, uint32(os.Getuid())) +func NewDCEPerson() UUID { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCEGroup(Group, uint32(os.Getgid())) +func NewDCEGroup() UUID { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID or false. +func (uuid UUID) Domain() (Domain, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return Domain(uuid[9]), true +} + +// Id returns the id for a Version 2 UUID or false. +func (uuid UUID) Id() (uint32, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return binary.BigEndian.Uint32(uuid[0:4]), true +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/tg/pbot/vendor/github.com/Xe/uuid/doc.go b/tg/pbot/vendor/github.com/Xe/uuid/doc.go new file mode 100644 index 0000000..d8bd013 --- /dev/null +++ b/tg/pbot/vendor/github.com/Xe/uuid/doc.go |
