rbxjs-plus
Version:
A system allowing access to roblox functions.
211 lines (203 loc) • 6.71 kB
JavaScript
// Dependencies
var parser = require('cheerio');
var entities = require('entities');
var Promise = require('bluebird');
// Includes
var http = require('../util/http.js').func;
var getForumError = require('../util/getForumError.js').func;
var getDate = require('../util/getDate.js').func;
// Args
exports.required = ['postId'];
exports.optional = ['page'];
// Define
var debug = false;
var maxPageSize = 25;
function doPage (thread, postId, pageNumber, target, tries) {
if (debug) {
console.log('Do page ' + pageNumber + ' of ' + postId + ' for ' + JSON.stringify(target) + ' out of ' + (thread && thread.totalPages));
}
var httpOpt = {
url: '//forum.roblox.com/Forum/ShowPost.aspx?PostID=' + postId + '&PageIndex=' + pageNumber,
options: {
resolveWithFullResponse: true,
followRedirect: false
}
};
return http(httpOpt)
.then(function (res) {
if (res.statusCode === 302) {
throw getForumError({location: res.headers.location, append: 'Could not get forum post page ' + pageNumber});
} else {
var body = res.body;
var $ = parser.load(body);
var posts = $('.forum-post');
var total = thread && thread.totalPages;
var exists = total && total > 0;
var last;
if (thread && !thread.subject) {
thread.subject = $('#ctl00_cphRoblox_PostView1_ctl00_PostTitle').text();
}
if (!thread || !exists) {
var totalPages = parseInt($('#ctl00_cphRoblox_PostView1_ctl00_Pager').find('.normalTextSmallBold').eq(0).text().match(/\d+$/)[0], 10);
if (!thread) {
return totalPages;
} else {
thread.totalPages = totalPages;
}
}
for (var i = 0; i < posts.length; i++) {
var post = posts.eq(i);
var meta = post.find('.normalTextSmaller').eq(3);
var id = parseInt(meta.find('a').attr('name'), 10);
last = id;
if (pageNumber === 1 && i === 0 && id !== postId) {
target = {
min: 1,
max: 0,
id: postId
};
postId = id;
thread.totalPages = -1;
} else if (!target || id === target.id) {
var content = entities.decodeHTML(post.find('.normalTextSmall').html()).replace(/<br>/g, '\n').replace(/<.*?>/g, ''); // Replace breaks with newlines and then remove remaining html tags (because URLs in particular are left inside)
var info = post.find('table').eq(0);
var span = info.find('span');
var join = getDate({time: span.eq(0).text().substring(8), timezone: 'CT'});
var postCount = parseInt(span.eq(1).text().substring(13), 10);
var user = post.find('.normalTextSmallBold');
var userId = parseInt(user.attr('href').match(/\d+/)[0], 10);
var date = getDate({time: meta.text(), timezone: 'CT'}); // Source dates are in CT, when viewed in a browser they are converted to local time: this was kind of confusing
var img = post.find('img[alt]');
var online = img.eq(0).attr('src') === '/Forum/skins/default/images/user_IsOnline.gif';
var tag = img.length > 1 ? img.eq(1).attr('alt') : null;
thread.posts.push({
author: {
id: userId,
name: user.text(),
online: online,
tag: tag,
joinDate: join,
postCount: postCount
},
postId: id,
date: date,
content: content,
parent: {id: postId, page: pageNumber}
});
}
}
if (target && thread.posts.length === 0) {
var min = target.min;
var max = target.max;
if (max === -1) {
max = thread.totalPages;
}
if (min === max) {
throw new Error('Could not find reply in thread'); // This should never happen, but who knows with ROBLOX
}
if (thread.totalPages < 0) {
target.max = -1;
return doPage(thread, postId, 1, target);
}
if (last < target.id) {
min = pageNumber + 1;
} else {
max = pageNumber - 1;
}
target.min = min;
target.max = max;
if (thread.totalPages) {
return doPage(thread, postId, Math.floor((min + max) / 2), target);
}
}
if (target) {
thread.resolved = postId;
}
}
})
.catch(function (err) {
console.error(err.stack);
if (!tries || tries < 3) {
return getPage(thread, postId, pageNumber, target, tries ? tries + 1 : 0);
}
});
}
function getPage (thread, postId, pageNumber, target) {
return doPage(thread, postId, pageNumber, target);
}
function getPages (thread, postId, pages, totalPages) {
var jobs = [];
var total = totalPages;
var start, end;
var isArray = pages instanceof Array;
if (isArray) {
start = 0;
end = pages.length;
} else {
start = 2;
end = pages + 1;
}
for (var i = start; i < end; i++) {
var page = isArray ? pages[i] : i;
if (page < 0) {
page = total + 1 + page;
}
if (!totalPages || (page <= totalPages && page > 0)) {
jobs.push(getPage(thread, postId, page));
}
}
return Promise.all(jobs)
.then(function () {
thread.posts = thread.posts.sort(function (a, b) {
return a.postId - b.postId;
});
return thread;
});
}
exports.func = function (args) {
var page = args.page;
var postId = args.postId;
var isArray = page instanceof Array;
var thread = {
posts: []
};
var promise;
if (page) {
if (isArray) {
page = page.sort(function (a, b) {
return a - b;
});
} else {
page = [page];
}
var low = page[0];
if (low < 0) {
// All this logic is to minimize the number of requests and get page number from a request that already has to be made, if possible
var high = page[page.length - 1];
var replace = high > 0;
promise = getPage(replace ? thread : null, postId, replace ? high : 1)
.then(function (total) {
if (replace) {
page.pop();
total = thread.totalPages;
}
return getPages(thread, postId, page, total);
});
} else {
promise = getPages(thread, postId, page);
}
} else {
promise = getPage(thread, postId, 1)
.then(function () {
if (thread.resolved) {
return thread;
}
var total = thread.totalPages;
return getPages(thread, postId, total, total);
});
}
promise.getStatus = function () {
return Math.round((thread.totalPages ? (thread.posts.length / maxPageSize) / thread.totalPages : 0) * 10000) / 100;
};
return promise;
};