Created
November 23, 2014 10:50
-
-
Save sshirokov/9c2ee1be07c393f2871d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Generated by CoffeeScript 1.7.1 | |
| (function() { | |
| var AVATAR_BASE, AYPStrip, AYP_AWS_BUCKET, AYP_AWS_KEY, AYP_AWS_SECRET, AYP_BUBBLE_MAX_WIDTH, AYP_BUBBLE_PADDING_HORIZONTAL, AYP_BUBBLE_PADDING_VERTICAL, AYP_FONT_FILE, AYP_FONT_SIZE, AYP_PANEL_HEIGHT, AYP_PANEL_PADDING, AYP_PANEL_WIDTH, AYP_TEXT_PADDING, BG_BASE, FONT_PATH, GD, IMG_BASE, PantsBuffer, ROOT, Redis, S3, Url, filterName, filterText, fs, path, s3, | |
| __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, | |
| __slice = [].slice; | |
| fs = require('fs'); | |
| path = require('path'); | |
| Url = require('url'); | |
| Redis = require('redis'); | |
| GD = require('node-gd'); | |
| S3 = require('node-s3'); | |
| AYP_AWS_KEY = process.env.AYP_AWS_KEY; | |
| AYP_AWS_SECRET = process.env.AYP_AWS_SECRET; | |
| AYP_AWS_BUCKET = process.env.AYP_AWS_BUCKET; | |
| AYP_PANEL_WIDTH = 348; | |
| AYP_PANEL_HEIGHT = 348; | |
| AYP_PANEL_PADDING = 6; | |
| AYP_BUBBLE_PADDING_VERTICAL = 3; | |
| AYP_BUBBLE_PADDING_HORIZONTAL = 3; | |
| AYP_BUBBLE_MAX_WIDTH = AYP_PANEL_WIDTH - 18; | |
| AYP_TEXT_PADDING = 2; | |
| AYP_FONT_SIZE = 12; | |
| AYP_FONT_FILE = "arial.ttf"; | |
| ROOT = path.resolve(__dirname, '..'); | |
| IMG_BASE = path.resolve(ROOT, 'ayp-template-images'); | |
| BG_BASE = path.resolve(IMG_BASE, 'bg'); | |
| AVATAR_BASE = path.resolve(IMG_BASE, 'avatars'); | |
| FONT_PATH = path.resolve(IMG_BASE, AYP_FONT_FILE); | |
| s3 = S3("s3://" + AYP_AWS_KEY + ":" + AYP_AWS_SECRET + "@" + AYP_AWS_BUCKET + ".s3.amazonaws.com/"); | |
| filterName = function(name) { | |
| if (/dusya/.test(name)) { | |
| name = 'dusya'; | |
| } | |
| return name; | |
| }; | |
| filterText = function(text) { | |
| var limit, suffix; | |
| text = text.replace(/(https?:\/\/[^\s]+)/, "[redacted]"); | |
| limit = 140; | |
| suffix = '...'; | |
| if (text.length > limit) { | |
| text = text.slice(0, limit - suffix.length); | |
| text += suffix; | |
| } | |
| return text; | |
| }; | |
| module.exports = function(robot) { | |
| var buffer; | |
| buffer = new PantsBuffer(); | |
| robot.catchAll(function(msg) { | |
| if (!msg.message.text) { | |
| return; | |
| } | |
| return buffer.store(msg.message.user.name.trim(), msg.message.text.trim()); | |
| }); | |
| return robot.respond(/ayp(\s+(me)?)?\s*$/i, function(msg) { | |
| return buffer.get(6, function(err, lines) { | |
| return new AYPStrip(lines, function(err, image) { | |
| var name, outPath; | |
| if (err) { | |
| return msg.reply("SOMETHING TERRIBLE HAPPENED: " + err); | |
| } | |
| name = "ayp-" + (Date.now()) + ".jpg"; | |
| outPath = path.resolve("/tmp", name); | |
| return image.saveJpeg(outPath, 95, function(err) { | |
| if (err) { | |
| return console.error("Failed to write result:", err); | |
| } | |
| return fs.readFile(outPath, function(err, data) { | |
| var info; | |
| fs.unlink(outPath, function() {}); | |
| if (err) { | |
| return msg.reply("I somehow lost the file I just put down at " + outpath + ". Like a moron :("); | |
| } | |
| if (![AYP_AWS_KEY, AYP_AWS_SECRET, AYP_AWS_BUCKET].every(function(p) { | |
| return p != null ? p.length : void 0; | |
| })) { | |
| return msg.reply("You hve no S3 creds bub"); | |
| } | |
| info = { | |
| headers: { | |
| 'Content-Type': 'image/jpeg' | |
| }, | |
| body: data | |
| }; | |
| return s3.put(name, info, function(err) { | |
| if (err) { | |
| return msg.reply("Woooops! Failed to upload: " + err); | |
| } | |
| return msg.reply("I made a thing: http://s3.amazonaws.com/" + AYP_AWS_BUCKET + "/" + name); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }); | |
| }; | |
| AYPStrip = (function() { | |
| function AYPStrip(script, ready) { | |
| this.script = script; | |
| this.ready = ready; | |
| this.formatForTextBubble = __bind(this.formatForTextBubble, this); | |
| this.loadAvatars = __bind(this.loadAvatars, this); | |
| this.textBubble = __bind(this.textBubble, this); | |
| this.drawPanelText = __bind(this.drawPanelText, this); | |
| this.buildPanel = __bind(this.buildPanel, this); | |
| this.buildPanels = __bind(this.buildPanels, this); | |
| this.buildComic = __bind(this.buildComic, this); | |
| this.buildComic(); | |
| } | |
| AYPStrip.prototype.buildComic = function() { | |
| return this.buildPanels((function(_this) { | |
| return function(err, panels) { | |
| var loaders; | |
| if (err) { | |
| return _this.ready(err, null); | |
| } | |
| loaders = { | |
| png: GD.openPng, | |
| jpg: GD.openJpeg, | |
| jpeg: GD.openJpeg | |
| }; | |
| return fs.readdir(BG_BASE, function(err, files) { | |
| var ext, loader, selected; | |
| if (err) { | |
| _this.ready(err, null); | |
| } | |
| files = files.filter(function(f) { | |
| return f[0] !== '.'; | |
| }); | |
| selected = files[Math.round(Math.random() * (files.length - 1))]; | |
| ext = selected.slice(-3).toLowerCase(); | |
| loader = loaders[ext]; | |
| if (!loader) { | |
| _this.ready("Can't find loader for " + selected, null); | |
| } | |
| return loader(path.resolve(BG_BASE, selected), function(err, bg) { | |
| var left, panel, top, totalPadding, _fn, _i, _len; | |
| if (err) { | |
| return _this.ready(err, bg); | |
| } | |
| totalPadding = AYP_PANEL_PADDING * 2; | |
| left = 0; | |
| top = Math.round(totalPadding / 2); | |
| _fn = function(panel) { | |
| _this.compositeImage(bg, panel, Math.round(left += totalPadding / 2), top); | |
| left += panel.width; | |
| return left += Math.round(totalPadding / 2); | |
| }; | |
| for (_i = 0, _len = panels.length; _i < _len; _i++) { | |
| panel = panels[_i]; | |
| _fn(panel); | |
| } | |
| return _this.ready(false, bg); | |
| }); | |
| }); | |
| }; | |
| })(this)); | |
| }; | |
| AYPStrip.prototype.buildPanels = function(cb) { | |
| var fail, failed, finishPanel, panels; | |
| failed = false; | |
| panels = [null, null, null]; | |
| fail = function(err) { | |
| cb(err, []); | |
| return failed = true; | |
| }; | |
| finishPanel = function(err, n, panel) { | |
| if (failed) { | |
| return; | |
| } | |
| if (err) { | |
| return fail(err); | |
| } | |
| panels[n] = panel; | |
| if (panels.every(function(p) { | |
| return p; | |
| })) { | |
| return cb(false, panels); | |
| } | |
| }; | |
| this.buildPanel(this.script.slice(0, 2), function(err, panel) { | |
| return finishPanel(err, 0, panel); | |
| }); | |
| this.buildPanel(this.script.slice(2, 4), function(err, panel) { | |
| return finishPanel(err, 1, panel); | |
| }); | |
| return this.buildPanel(this.script.slice(4, 6), function(err, panel) { | |
| return finishPanel(err, 2, panel); | |
| }); | |
| }; | |
| AYPStrip.prototype.buildPanel = function(lines, cb) { | |
| var clear, frame, l, names; | |
| frame = GD.createTrueColor(AYP_PANEL_WIDTH, AYP_PANEL_WIDTH); | |
| frame.saveAlpha(1); | |
| clear = frame.colorAllocateAlpha(0, 0, 0, 127); | |
| frame.fill(0, 0, clear); | |
| if (!(lines.length > 0)) { | |
| return cb(false, frame); | |
| } | |
| names = ((function() { | |
| var _i, _len, _results; | |
| _results = []; | |
| for (_i = 0, _len = lines.length; _i < _len; _i++) { | |
| l = lines[_i]; | |
| _results.push(l[0]); | |
| } | |
| return _results; | |
| })()).filter(function(v, i, a) { | |
| return a.indexOf(v) === i; | |
| }); | |
| return this.loadAvatars(names, (function(_this) { | |
| return function(err, avatars) { | |
| var char, first, left, line, top, what, who, _i, _len; | |
| if (err) { | |
| return cb(err, null); | |
| } | |
| if (names.length === 1) { | |
| char = avatars[names[0]]; | |
| left = (frame.width / 2) - (char.width / 2); | |
| top = frame.height - char.height; | |
| _this.compositeImage(frame, char, left, top); | |
| } else { | |
| first = true; | |
| for (_i = 0, _len = lines.length; _i < _len; _i++) { | |
| line = lines[_i]; | |
| who = line[0], what = line[1]; | |
| char = avatars[who]; | |
| top = frame.height - char.height; | |
| if (first) { | |
| left = 0; | |
| first = false; | |
| } else { | |
| left = frame.width - char.width; | |
| } | |
| _this.compositeImage(frame, char, left, top); | |
| } | |
| } | |
| _this.drawPanelText(frame, lines); | |
| return cb(false, frame); | |
| }; | |
| })(this)); | |
| }; | |
| AYPStrip.prototype.drawPanelText = function(frame, lines) { | |
| var bubble, first, left, line, top, topPad, what, who, _i, _len, _results; | |
| first = true; | |
| top = 0; | |
| left = AYP_BUBBLE_PADDING_HORIZONTAL; | |
| topPad = AYP_BUBBLE_PADDING_VERTICAL; | |
| _results = []; | |
| for (_i = 0, _len = lines.length; _i < _len; _i++) { | |
| line = lines[_i]; | |
| who = line[0], what = line[1]; | |
| bubble = this.textBubble(what); | |
| if (!first) { | |
| left = frame.width - bubble.width - AYP_BUBBLE_PADDING_HORIZONTAL; | |
| } else { | |
| first = false; | |
| } | |
| this.compositeImage(frame, bubble, left, top); | |
| _results.push(top += bubble.height + topPad); | |
| } | |
| return _results; | |
| }; | |
| AYPStrip.prototype.textBubble = function(msg, font, size, max) { | |
| var black, frame, h, w, white, _ref; | |
| if (font == null) { | |
| font = FONT_PATH; | |
| } | |
| if (size == null) { | |
| size = AYP_FONT_SIZE; | |
| } | |
| if (max == null) { | |
| max = AYP_BUBBLE_MAX_WIDTH; | |
| } | |
| msg = this.formatForTextBubble(msg, font, size, max); | |
| _ref = this.textSize(msg, font, size), w = _ref[0], h = _ref[1]; | |
| frame = GD.createTrueColor(w + (AYP_TEXT_PADDING * 2), h + (AYP_TEXT_PADDING * 2)); | |
| frame.saveAlpha(1); | |
| white = frame.colorAllocate(0xff, 0xff, 0xff); | |
| black = frame.colorAllocate(0x00, 0x00, 0x00); | |
| frame.fill(0, 0, white); | |
| frame.stringFT(black, font, size, 0, AYP_TEXT_PADDING, AYP_TEXT_PADDING + size + 1, msg); | |
| return frame; | |
| }; | |
| AYPStrip.prototype.loadAvatars = function(names, cb) { | |
| var charPathForNick, fail, failed, nameObj, _i, _len, _results; | |
| names = names.map(function(n) { | |
| return { | |
| name: n, | |
| img: null, | |
| err: null | |
| }; | |
| }); | |
| failed = false; | |
| fail = function(err) { | |
| var faliled; | |
| cb(err, null); | |
| return faliled = true; | |
| }; | |
| charPathForNick = function(nick) { | |
| var potential; | |
| potential = path.resolve(AVATAR_BASE, "" + (nick.toLowerCase()) + ".png"); | |
| if (((function() { | |
| try { | |
| return fs.statSync(potential); | |
| } catch (_error) {} | |
| })())) { | |
| return potential; | |
| } | |
| return path.resolve(AVATAR_BASE, "default.png"); | |
| }; | |
| _results = []; | |
| for (_i = 0, _len = names.length; _i < _len; _i++) { | |
| nameObj = names[_i]; | |
| _results.push((function(nameObj) { | |
| return GD.openPng(charPathForNick(nameObj.name), function(err, img) { | |
| var avatars, obj, _j, _len1; | |
| if (failed) { | |
| return; | |
| } | |
| if (err) { | |
| return fail(err); | |
| } | |
| if (!err) { | |
| nameObj.img = img; | |
| } | |
| if (names.every(function(o) { | |
| return o.img; | |
| })) { | |
| avatars = {}; | |
| for (_j = 0, _len1 = names.length; _j < _len1; _j++) { | |
| obj = names[_j]; | |
| avatars[obj.name] = obj.img; | |
| } | |
| return cb(null, avatars); | |
| } | |
| }); | |
| })(nameObj)); | |
| } | |
| return _results; | |
| }; | |
| AYPStrip.prototype.textSize = function(msg, font, size) { | |
| var bb, black, img; | |
| img = GD.create(1, 1); | |
| black = img.colorAllocate(0, 0, 0); | |
| bb = img.stringFTBBox(black, font, size, 0, 0, 0, msg); | |
| return [bb[2] - bb[0], bb[1] - bb[7]]; | |
| }; | |
| AYPStrip.prototype.chunkInputInto = function(msg, splits) { | |
| var chunkLen, chunks, n, stepSize, _i; | |
| stepSize = Math.round(msg.length / splits); | |
| chunks = []; | |
| for (n = _i = 0; 0 <= splits ? _i < splits : _i > splits; n = 0 <= splits ? ++_i : --_i) { | |
| chunks[n] = msg.slice(0, stepSize); | |
| msg = msg.slice(stepSize); | |
| while (!/\s$/.test(chunks[n])) { | |
| chunkLen = chunks[n].length; | |
| msg = "" + chunks[n][chunkLen - 1] + msg; | |
| chunks[n] = chunks[n].slice(0, -1); | |
| } | |
| } | |
| if (msg.length) { | |
| chunks[chunks.length - 1] += msg; | |
| } | |
| return chunks.map(function(c) { | |
| return c.trim(); | |
| }); | |
| }; | |
| AYPStrip.prototype.formatForTextBubble = function(msg, font, size, max) { | |
| var h, splits, w, _ref; | |
| msg = msg.trim(); | |
| _ref = this.textSize(msg, font, size), w = _ref[0], h = _ref[1]; | |
| if (w > max) { | |
| splits = Math.round(w / max); | |
| if (w % max) { | |
| splits += 1; | |
| } | |
| return msg = this.chunkInputInto(msg, splits).join("\n"); | |
| } else { | |
| return msg; | |
| } | |
| }; | |
| AYPStrip.prototype.compositeImage = function(dst, sprite, left, top) { | |
| var dim; | |
| dim = [sprite.width, sprite.height]; | |
| left = Math.round(left); | |
| top = Math.round(top); | |
| sprite.copyResampled.apply(sprite, [dst, left, top, 0, 0].concat(__slice.call(dim), __slice.call(dim))); | |
| return dst; | |
| }; | |
| return AYPStrip; | |
| })(); | |
| PantsBuffer = (function() { | |
| PantsBuffer.prototype.key = function() { | |
| return "" + this.options.prefix + ":buffer"; | |
| }; | |
| PantsBuffer.prototype.get = function(n, callback) { | |
| var resultWrapper; | |
| resultWrapper = function(err, res) { | |
| if (err) { | |
| return callback(err, res); | |
| } | |
| res = res.map(function(line) { | |
| var what, who, _, _ref; | |
| _ref = line.match(/^([^\s]+): (.+)$/), _ = _ref[0], who = _ref[1], what = _ref[2]; | |
| return [filterName(who), filterText(what)]; | |
| }); | |
| return callback(err, res); | |
| }; | |
| return this.storage.zcard(this.key(), (function(_this) { | |
| return function(err, count) { | |
| var start; | |
| if (err) { | |
| return console.error("Failed zcard of " + (_this.key()) + ": " + err); | |
| } | |
| if (count <= n) { | |
| return _this.storage.zrange(_this.key(), 0, -1, resultWrapper); | |
| } | |
| start = Math.round(Math.random() * (count - n)); | |
| return _this.storage.zrange(_this.key(), start, start + (n - 1), resultWrapper); | |
| }; | |
| })(this)); | |
| }; | |
| PantsBuffer.prototype.store = function(who, what) { | |
| var body, stamp; | |
| this.trim(); | |
| stamp = Date.now(); | |
| body = "" + who + ": " + what; | |
| return this.storage.zadd([this.key(), stamp, body], (function(_this) { | |
| return function(err, response) { | |
| if (err) { | |
| return console.error("WARNING: Failed to store: '" + who + ": " + what + "' @ " + stmp + ": " + err); | |
| } | |
| }; | |
| })(this)); | |
| }; | |
| PantsBuffer.prototype.trim = function() { | |
| var days, end; | |
| days = this.options.days; | |
| end = Date.now() - (days * 24 * 60 * 60 * 1000); | |
| return this.storage.zremrangebyscore(this.key(), 0, end, (function(_this) { | |
| return function(err, response) { | |
| if (err) { | |
| return console.error("WARNING: Failed to trim '" + (_this.key()) + "': " + err); | |
| } | |
| }; | |
| })(this)); | |
| }; | |
| function PantsBuffer(options) { | |
| var info, _base, _base1; | |
| this.options = options != null ? options : {}; | |
| info = Url.parse(process.env.REDISTOGO_URL || process.env.REDISCLOUD_URL || process.env.BOXEN_REDIS_URL || 'redis://localhost:6379'); | |
| this.storage = Redis.createClient(info.port, info.hostname); | |
| if (info.auth) { | |
| this.storage.auth(info.auth.split(":")[1]); | |
| } | |
| if ((_base = this.options).prefix == null) { | |
| _base.prefix = "ayp:"; | |
| } | |
| if ((_base1 = this.options).days == null) { | |
| _base1.days = 3; | |
| } | |
| } | |
| return PantsBuffer; | |
| })(); | |
| }).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment