13

I'm trying to make a Twitter bot that generates a random rgb colour, creates a picture of this colour and tweets it. I've created a piece of JS that can generate and tweet a random rgb value and a piece of javascript that can generate a picture of a random colour but I'm not sure how to combine the two.

My problem is that I can't generate the PNG image without having a document. If I run the script on a server using Node.JS there isn't a document to create the canvas in. Is there any other way to create a png picture (maybe by temporarily saving it to the server) and attaching it to a tweet?

Thanks for your help!

This is the code I have for tweeting a random value:

var Twit = require('twit')

var T = new Twit({
  consumer_key:         '###', 
  consumer_secret:      '###',
  access_token:         '###',
  access_token_secret:  '###'
})

function tweet() {
  //Generate a random colour
  var r = Math.floor((Math.random() * 256));
  var g = Math.floor((Math.random() * 256));
  var b = Math.floor((Math.random() * 256));
  var color = "rgb("+r+","+g+","+b+")";

  // tweet that colour
  T.post('statuses/update', { status: color }); 
}

setTimeout(tweet, 30000);

And this is a JS script that generates a PNG file of a random colour on a web page:

var r = Math.floor((Math.random() * 256));
var g = Math.floor((Math.random() * 256));
var b = Math.floor((Math.random() * 256));
var color = "rgb("+r+","+g+","+b+")";

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

// draw box
context.beginPath();
context.moveTo(0, 00);
context.lineTo(0, 800);
context.lineTo(800, 800);
context.lineTo(800, 0);
context.closePath();
context.lineWidth = 5;
context.fillStyle = color;
context.fill();

// save canvas image as data url (png format by default)
var dataURL = canvas.toDataURL();

// set canvasImg image src to dataURL
// so it can be saved as an image
document.getElementById('canvasImg').src = dataURL;
thanksd
  • 54,176
  • 22
  • 157
  • 150
Wouter van Dijke
  • 662
  • 1
  • 7
  • 17

1 Answers1

7

Updated for 2020:

As pointed by @Mike'Pomax'Kamermans in the comments, NodeJS now (since v7) contains the same URL object as the browser, which can be used for putting the canvas data URI into and then saved to disk similarly to this answer: https://stackoverflow.com/a/11335500/2138943

Old answer (for Node versions < 7)

You can use an Node <canvas> implementation backed with a JavaScript DOM, such as node-canvas with jsDom. For example with canvas, your code should look something like this:

    var Canvas = require('canvas');
    var Image = Canvas.Image;
    var canvas = new Canvas(800, 800);
    var context = canvas.getContext('2d');
    var r = Math.floor((Math.random() * 256));
    var g = Math.floor((Math.random() * 256));
    var b = Math.floor((Math.random() * 256));
    var color = "rgb("+r+","+g+","+b+")";


    // draw box
    context.beginPath();
    context.moveTo(0, 00);
    context.lineTo(0, 800);
    context.lineTo(800, 800);
    context.lineTo(800, 0);
    context.closePath();
    context.lineWidth = 5;
    context.fillStyle = color;
    context.fill();

    // save canvas image as data url (png format by default)
    var dataURL = canvas.toDataURL();

    // set canvasImg image src to dataURL
    // so it can be saved as an image
    document.getElementById('canvasImg').src = dataURL;

If you want to get rid of the jsDom requirement you can use canvas.pngStream() like @josh3736 suggests in the comments (if you aren't doing anything else with the document itself ie. you only need the canvas)

Canvas#pngStream()

To create a PNGStream simply call canvas.pngStream(), and the stream will start to emit data events, finally emitting end when finished. If an exception occurs the error event is emitted.

var fs = require('fs'),
  ,  out = fs.createWriteStream(__dirname + '/text.png')   
  , stream = canvas.pngStream();
stream.on('data', function(chunk){out.write(chunk); });

stream.on('end', function(){console.log('saved png'); }); 

Currently only sync streaming is supported, however we plan on supporting async streaming as well (of course :) ). Until then the Canvas#toBuffer(callback) alternative is async utilizing eio_custom().

Pete TNT
  • 8,293
  • 4
  • 36
  • 45
  • 2
    This is good, although since we're on the server there's really no reason to use a data URL -- just save/serve the result as a regular PNG file (`canvas.pngStream()`). – josh3736 Feb 01 '16 at 19:00
  • Even easier -- pipe to the write stream. `canvas.pngStream().pipe(out)` – josh3736 Feb 01 '16 at 19:10
  • Thanks @PeteTNT! I think I understand what the canvas implementation does, but I'm not sure how to use it. To be exact I'm not sure what I can pass into `T.post('media/upload', { media_data: **here** }`. It says the parameter is invalid when I use `dataUrl`. I tried `var b64content = fs.readFileSync('dataUrl', { encoding: 'base64' })` but that gave a No such file-error. How can I tell the T.post where the image/canvas went? – Wouter van Dijke Feb 01 '16 at 19:25
  • @josh3736 what happens when I pipe to the write stream? I'm trying to get the file to tweet like this but it doesn't work: `var fs = require('fs') , out = fs.createWriteStream('/img' + r + g + b + '.png') //__dirname + , stream = canvas.pngStream(); var dataUrl = canvas.pngStream().pipe(out); var b64content = fs.readFileSync('/img' + r + g + b + '.png', { encoding: 'base64' }) // first we must post the media to Twitter T.post('media/upload', { media_data: b64content }, function (err, data, response)` – Wouter van Dijke Feb 01 '16 at 20:04
  • @WouterVanDijke: If you're just sending the image directly to twitter, don't even bother with an on-disk file. Normally, I'd recommend streaming from your source (canvas) to the server, but the twit module doesn't appear to support that. Since your images are small, it's probably safe to just get the entire buffer and pass it to twit. `T.post('/media/upload', { media_data: canvas.toBuffer() })` – josh3736 Feb 01 '16 at 20:35
  • ...also, [don't use *`Sync` methods](http://stackoverflow.com/a/15007987/201952). – josh3736 Feb 01 '16 at 20:36
  • @josh3736 thanks. I now have this `var fs = require('fs') , out = fs.createWriteStream(__dirname + '/text.png') , stream = canvas.pngStream(); var dataUrl = canvas.pngStream().pipe(out); var b64content = canvas.toBuffer(); // first we must post the media to Twitter T.post('media/upload', { media_data: canvas.toBuffer() }, function (err, data, response)` I get an error `code: 44, message: 'media_ids parameter is invalid.'` which is apparently coming from the Twitter API (at least I think so). Any idea why the parameter is invalid and how else I could input it? – Wouter van Dijke Feb 01 '16 at 22:29
  • A bit late to this answer, but: there was/is no need for `jsdom` in any of this, just put the canvas data url in an actual [URL object](https://nodejs.org/api/url.html#url_new_url_input_base) (which is part of the standard library), and then [save it to file](https://stackoverflow.com/a/11335500/740553). – Mike 'Pomax' Kamermans Aug 04 '20 at 20:11
  • @Mike'Pomax'Kamermans yeah. the URL object did not exist at all when this answer was written (it was introduced in Node 7 and backported to 6, 8 months after this answer). Fell free to update the answer – Pete TNT Aug 04 '20 at 23:33
  • 1
    I'll leave that to you, adding in a "2020 edit: just do X" would benefit the community, but it's your answer, not mine. If I were to edit it, I should be writing a new answer instead and (if the original poster is still around) would get your answer unaccepted. – Mike 'Pomax' Kamermans Aug 05 '20 at 00:31