forked from flanchan/doushio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
5.4 KiB
251 lines
5.4 KiB
6 years ago
|
/* Dumps the given thread to JSON.
|
||
|
* Not production-ready.
|
||
|
*/
|
||
|
|
||
|
var _ = require('../lib/underscore'),
|
||
|
async = require('async'),
|
||
|
caps = require('../server/caps'),
|
||
|
db = require('../db'),
|
||
|
etc = require('../etc'),
|
||
|
fs = require('fs'),
|
||
|
joinPath = require('path').join,
|
||
|
render = require('../server/render'),
|
||
|
util = require('util');
|
||
|
|
||
|
var DUMP_DIR = 'www/archive';
|
||
|
var AUTH_DUMP_DIR = 'authdump';
|
||
|
|
||
|
var DUMP_IDENT = {ip: '127.0.0.1', auth: 'dump'};
|
||
|
|
||
|
require('../server/amusement'); // load dice hook
|
||
|
|
||
|
function Dumper(reader, out) {
|
||
|
this.out = out;
|
||
|
this.reader = reader;
|
||
|
_.bindAll(this);
|
||
|
reader.on('thread', this.on_thread);
|
||
|
reader.on('post', this.on_post);
|
||
|
reader.on('endthread', this.on_endthread);
|
||
|
}
|
||
|
var D = Dumper.prototype;
|
||
|
|
||
|
D.on_thread = function (op_post) {
|
||
|
if (this.needComma) {
|
||
|
this.out.write(',\n');
|
||
|
this.needComma = false;
|
||
|
}
|
||
|
this.op = op_post.num;
|
||
|
this.out.write('[\n' + JSON.stringify(tweak_post(op_post)));
|
||
|
};
|
||
|
|
||
|
D.on_post = function (post) {
|
||
|
this.out.write(',\n' + JSON.stringify(tweak_post(post, this.op)));
|
||
|
};
|
||
|
|
||
|
D.on_endthread = function () {
|
||
|
this.out.write('\n]');
|
||
|
this.needComma = true;
|
||
|
this.op = null;
|
||
|
};
|
||
|
|
||
|
D.destroy = function () {
|
||
|
this.reader.removeListener('thread', this.on_thread);
|
||
|
this.reader.removeListener('post', this.on_post);
|
||
|
this.reader.removeListener('endthread', this.on_endthread);
|
||
|
this.reader = null;
|
||
|
this.out = null;
|
||
|
};
|
||
|
|
||
|
|
||
|
function AuthDumper(reader, out) {
|
||
|
Dumper.call(this, reader, out);
|
||
|
}
|
||
|
util.inherits(AuthDumper, Dumper);
|
||
|
var AD = AuthDumper.prototype;
|
||
|
|
||
|
AD.on_thread = function (post) {
|
||
|
this.out.write('{"ips":{');
|
||
|
|
||
|
if (post.num && post.ip) {
|
||
|
this.out.write('"'+post.num+'":'+JSON.stringify(post.ip));
|
||
|
this.needComma = true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AD.on_post = function (post) {
|
||
|
if (post.num && post.ip) {
|
||
|
if (this.needComma)
|
||
|
this.out.write(',');
|
||
|
else
|
||
|
this.needComma = true;
|
||
|
this.out.write('"'+post.num+'":'+JSON.stringify(post.ip));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
AD.on_endthread = function () {
|
||
|
this.out.write('}}');
|
||
|
this.needComma = false;
|
||
|
};
|
||
|
|
||
|
function tweak_post(post, known_op) {
|
||
|
post = _.clone(post);
|
||
|
|
||
|
/* thread-only */
|
||
|
if (typeof post.tags == 'string')
|
||
|
post.tags = db.parse_tags(post.tags);
|
||
|
if (typeof post.origTags == 'string')
|
||
|
post.origTags = db.parse_tags(post.origTags);
|
||
|
if (typeof post.hctr == 'string')
|
||
|
post.hctr = parseInt(post.hctr, 10);
|
||
|
if (typeof post.imgctr == 'string')
|
||
|
post.imgctr = parseInt(post.imgctr, 10);
|
||
|
|
||
|
/* post-only */
|
||
|
if (known_op == post.op)
|
||
|
delete post.op;
|
||
|
|
||
|
if (post.hideimg) {
|
||
|
delete post.image;
|
||
|
delete post.hideimg;
|
||
|
}
|
||
|
if (post.body == '')
|
||
|
delete post.body;
|
||
|
|
||
|
/* blacklisting is bad... */
|
||
|
delete post.ip;
|
||
|
|
||
|
return post;
|
||
|
}
|
||
|
|
||
|
function dump_thread(op, board, ident, outputs, cb) {
|
||
|
if (!caps.can_access_board(ident, board))
|
||
|
return cb(404);
|
||
|
if (!caps.can_access_thread(ident, op))
|
||
|
return cb(404);
|
||
|
|
||
|
var yaku = new db.Yakusoku(board, ident);
|
||
|
var reader = new db.Reader(yaku);
|
||
|
reader.get_thread(board, op, {});
|
||
|
reader.once('nomatch', function () {
|
||
|
cb(404);
|
||
|
yaku.disconnect();
|
||
|
});
|
||
|
reader.once('redirect', function (op) {
|
||
|
cb('redirect', op);
|
||
|
yaku.disconnect();
|
||
|
});
|
||
|
reader.once('begin', function (preThread) {
|
||
|
var dumper = new Dumper(reader, outputs.json);
|
||
|
var authDumper = new AuthDumper(reader, outputs.auth);
|
||
|
|
||
|
var out = outputs.html;
|
||
|
render.write_thread_head(out, '', board, op, {
|
||
|
subject: preThread.subject,
|
||
|
});
|
||
|
|
||
|
var fakeReq = {ident: ident, headers: {}};
|
||
|
var opts = {fullPosts: true, board: board};
|
||
|
render.write_thread_html(reader, fakeReq, out, opts);
|
||
|
|
||
|
reader.once('end', function () {
|
||
|
outputs.json.write('\n');
|
||
|
outputs.auth.write('\n');
|
||
|
render.write_page_end(out, ident, true);
|
||
|
yaku.disconnect();
|
||
|
cb(null);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
function on_err(err) {
|
||
|
yaku.disconnect();
|
||
|
cb(err);
|
||
|
}
|
||
|
reader.once('error', on_err);
|
||
|
yaku.once('error', on_err);
|
||
|
}
|
||
|
|
||
|
function close_stream(stream, cb) {
|
||
|
if (!stream.writable)
|
||
|
return cb(null);
|
||
|
if (stream.write(''))
|
||
|
close();
|
||
|
else
|
||
|
stream.once('drain', close);
|
||
|
|
||
|
function close() {
|
||
|
// deal with process.stdout not being closable
|
||
|
try {
|
||
|
stream.destroySoon(function (err) {
|
||
|
if (cb)
|
||
|
cb(err);
|
||
|
cb = null;
|
||
|
});
|
||
|
}
|
||
|
catch (e) {
|
||
|
if (cb)
|
||
|
cb(null);
|
||
|
cb = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function load_state(cb) {
|
||
|
async.series([
|
||
|
etc.checked_mkdir.bind(null, DUMP_DIR),
|
||
|
etc.checked_mkdir.bind(null, AUTH_DUMP_DIR),
|
||
|
require('../server/state').reload_hot_resources,
|
||
|
db.track_OPs,
|
||
|
], cb);
|
||
|
}
|
||
|
|
||
|
if (require.main === module) (function () {
|
||
|
var op = parseInt(process.argv[2], 10), board = process.argv[3];
|
||
|
if (!op) {
|
||
|
console.error('Usage: node upkeep/dump.js <thread>');
|
||
|
process.exit(-1);
|
||
|
}
|
||
|
|
||
|
console.log('Loading state...');
|
||
|
load_state(function (err) {
|
||
|
if (err)
|
||
|
throw err;
|
||
|
|
||
|
if (!board)
|
||
|
board = db.first_tag_of(op);
|
||
|
if (!board) {
|
||
|
console.error(op + ' has no tags.');
|
||
|
process.exit(-1);
|
||
|
}
|
||
|
|
||
|
console.log('Dumping thread...');
|
||
|
|
||
|
var base = joinPath(DUMP_DIR, op.toString());
|
||
|
var authBase = joinPath(AUTH_DUMP_DIR, op.toString());
|
||
|
var outputs = {
|
||
|
auth: fs.createWriteStream(authBase + '.json'),
|
||
|
json: fs.createWriteStream(base + '.json'),
|
||
|
html: fs.createWriteStream(base + '.html'),
|
||
|
};
|
||
|
|
||
|
dump_thread(op, board, DUMP_IDENT, outputs, function (err) {
|
||
|
if (err)
|
||
|
throw err;
|
||
|
|
||
|
var streams = [];
|
||
|
for (var k in outputs)
|
||
|
streams.push(outputs[k]);
|
||
|
async.each(streams, close_stream, quit);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
function quit() {
|
||
|
// crappy flush for stdout (can't close it)
|
||
|
if (process.stdout.write(''))
|
||
|
process.exit(0);
|
||
|
else
|
||
|
process.stdout.on('drain', function () {
|
||
|
process.exit(0);
|
||
|
});
|
||
|
}
|
||
|
})();
|