Skip to content

Instantly share code, notes, and snippets.

@sshirokov
Created November 23, 2014 10:50
Show Gist options
  • Select an option

  • Save sshirokov/9c2ee1be07c393f2871d to your computer and use it in GitHub Desktop.

Select an option

Save sshirokov/9c2ee1be07c393f2871d to your computer and use it in GitHub Desktop.
// 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