Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brunocassol/152708126672f355ecf71a392d927a36 to your computer and use it in GitHub Desktop.
Save brunocassol/152708126672f355ecf71a392d927a36 to your computer and use it in GitHub Desktop.
Reddit /r/places bot
// by: throw_away_stacy on reddit
// and yes I do birthday parties too
const WebSocket = require('websocket').w3cwebsocket;
const users = require('./users.json');
const Q = require('q');
const superagent = require('superagent');
const _ = require('highland');
const getPixels = require('get-pixels');
function getSocketUrl() {
return superagent
.get('https://www.reddit.com/place?webview')
.then(result => {
const ws = result.text.match(/"place_websocket_url": "(.*?)"/).pop();
if (!ws) {
return getSocketUrl();
}
return ws;
});
};
function updatesStream(stream) {
stream = stream || _();
getSocketUrl().then(ws => {
const connection = new WebSocket(ws);
connection.onopen = function () {
console.log('opened-socket');
};
connection.onerror = function (error) {
console.log('socket-error', error);
connection.close();
updatesStream(stream);
};
connection.onclose = function (error) {
console.log('socket-closed');
connection.close();
updatesStream(stream);
};
connection.onmessage = function (message) {
const result = JSON.parse(message.data);
if (result.type == 'place' && result.payload) {
stream.write(result.payload);
} else if (result.type == 'batch-place' && result.payload) {
console.log('batched');
result.payload.forEach(payload => {
stream.write(result.payload);
});
} else {
console.log('something weird', message.data);
}
};
});
return stream;
};
function getColorAtPixel(x, y) {
return superagent
.get('https://www.reddit.com/api/place/pixel.json')
.query({ x, y })
.then(status => {
return status.body.color;
});
}
function colorPixelIn(user, x, y, color) {
return superagent
.post('https://www.reddit.com/api/place/draw.json')
.type('form')
.send({ x, y, color })
.set('x-modhash', user.modhash)
.set('Cookie', [`reddit_session=${user.cookie.replace(/,/g, '%2C').replace(/:/g, '%3A')}`]);
}
function getImage(path) {
const defer = Q.defer();
getPixels(path, function(err, pixels) {
if(err) {
return defer.reject(err);
}
const shape = pixels.shape.slice();
const width = shape[0];
const height = shape[1];
const colors = {};
const data = pixels.data;
for (let i = 0; i < data.length; i += 4) {
const pixel = i / 4;
const x = pixel % width;
const y = Math.floor(pixel / width);
if (!colors[x]) colors[x] = {};
let color = 0;
if (data[i] == 255 && data[i+1] == 0 && data[i+2] == 0) {
color = 5;
}
colors[x][y] = color;
}
defer.resolve({
width,
height,
colors
});
});
return defer.promise;
}
const pixels = new Proxy({}, {
get: (target, name) => {
return (target[name] = target.hasOwnProperty(name) ? target[name] : {});
},
});
const handler = {
get: (target, name) => {
return (target[name] = target.hasOwnProperty(name) ? target[name] : new Date(0));
}
};
const pixelsUpdated = new Proxy({}, {
get: (target, name) => {
return (target[name] = target.hasOwnProperty(name) ? target[name] : new Proxy({}, handler));
},
});
const FIVE_MIN_MS = 21*60*1000;
function determinePixelToColor(image, dx, dy) {
const nextPixel = (x, y) => {
x++;
if (x >= image.width) {
y++;
x = 0;
}
if (y >= image.height) {
x = 0;
y = 0;
}
return { x, y };
};
return Q.Promise((resolve, reject) => {
const check = (x, y) => {
const desired = image.colors[x][y];
const actual = pixels[dx + x][dy + y];
const updated = pixelsUpdated[dx + x][dy + y];
const n = nextPixel(x, y);
if (Date.now() - updated.getTime() > FIVE_MIN_MS) {
// CASE: pixel at that position is stale
getColorAtPixel(dx + x, dy + y)
.then(color => {
console.log('updated color at', dx + x, dy + y, 'as', color);
pixels[dx + x][dy + y] = color;
pixelsUpdated[dx + x][dy + y] = new Date();
setTimeout(() => check(x, y), 0);
})
.catch(error => {
console.log('ERROR: updated color at', x, y, 'as', error, error.stack);
setTimeout(() => check(n.x, n.y), 0);
});
} else if (actual == desired) {
//CASE: pixel at that position matches
setTimeout(() => check(n.x, n.y), 0);
} else {
//CASE: found an offending pixel
resolve({
x: dx + x,
y: dy + y,
color: desired
});
}
};
check(0, 0);
});
}
function colorAllIn(image, offsetX, offsetY) {
const defer = Q.defer();
_(users)
.map(user => {
const next = _();
determinePixelToColor(image, offsetX, offsetY)
.then(pixel => {
return Q.all([
pixel,
colorPixelIn(user, pixel.x, pixel.y, pixel.color),
]);
})
.spread(pixel => {
console.log('COLORED:', JSON.stringify(pixel), 'BY', user.username, new Date());
pixels[pixel.x][pixel.y] = pixel.color;
pixelsUpdated[pixel.x][pixel.y] = new Date();
})
.catch(e => {
console.log('FAILED COLORING BY', user.name);
})
.finally(() => {
next.end();
});
return next;
})
.mergeWithLimit(1)
.done(() => {
defer.resolve();
});
return defer.promise;
};
updatesStream().each(record => {
pixels[record.x][record.y] = record.color;
pixelsUpdated[record.x][record.y] = new Date();
});
const offsetX = 0;
const offsetY = 0;
getImage('image_to_draw.png').then(image => {
if (image.colors[1][3] == 0) image.colors[1][3] = 5;
if (image.colors[14][3] == 0) image.colors[14][3] = 5;
const go = () => {
console.log('going..');
colorAllIn(image, offsetX, offsetY).finally(() => {
console.log('done and waiting');
setTimeout(go, 301*1000);
});
};
go();
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment