Created
April 3, 2017 17:20
-
-
Save anonymous/1a260c5eaa4298af3b8c4a1afd2b7fe8 to your computer and use it in GitHub Desktop.
Reddit /r/places bot
This file contains 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
// 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