Skip to content
This repository has been archived by the owner on Jan 2, 2019. It is now read-only.

No More Forums #73

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 234 additions & 48 deletions examples/cleanWall.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,247 @@
// Delete posts from a group wall, including by post content and by author name.
var rbx = require('roblox-js');
var ProgressBar = require('progress');
var username = '';
var password = '';
var group = 0;

rbx.login(username, password)
.then(function () {
// This allows you to retrieve only a specific set of pages.
/* pages = [];
for (var i = 0; i <= 100; i++) {
pages.push(i);
} */
var wall = new ProgressBar('Getting wall [:bar] :current/:total = :percent :etas remaining ', {total: 10000});
var promise = rbx.getWall({
group: group,
// page: pages,
view: true
});
promise.then(function (wall) {
var posts = wall.posts;
// Remember these are reversed, it starts off with all the posts on the wall and you are REMOVING the ones you DON'T want to delete from the array
/* for (var i = posts.length - 1; i >= 0; i--) {
var post = posts[i];
if (post.author.name !== 'Bob') { // Delete all posts by Bob
posts.splice(i, 1);
var prompt = require('prompt');
var stream = require('stream');
var crypto = require('crypto');
var fs = require('fs');
var js = require('JSONStream');
var mainPath;

var maxThreads = 5;

prompt.message = '';
var schema = {
properties: {
group: {
description: 'Enter group ID',
required: true,
type: 'integer',
message: 'Group ID must be an integer'
},
username: {
description: 'Enter ROBLOX account username',
required: true
},
password: {
description: 'Enter ROBLOX account password',
hidden: true,
replace: '*',
required: true
},
find: {
description: 'Enter a string to find, this will only delete messages that have the specific string in them (optional)'
},
author: {
description: 'Enter author username to find. This will only delete messages made by this player (optional)'
},
startPage: {
description: 'Enter starting page (leave blank for all pages)',
type: 'integer',
message: 'Page must be an integer'
},
endPage: {
description: 'Enter ending page',
type: 'integer',
message: 'Page must be an integer',
ask: function () {
return prompt.history('startPage').value > 0;
}
if (!post.content.includes('Bob')) { // Delete all posts that contain "Bob"
posts.splice(i, 1);
}
}
};

function clean (path) {
console.log('Cleaning up...');
fs.unlinkSync(path);
}

function clearPage (group, page) {
var jobs = [];
var indices = page.indices;
for (var i = 0; i < indices.length; i++) {
var index = indices[i];
jobs.push(rbx.deleteWallPost({
group: group,
post: {
parent: {
index: index
},
view: page.view
}
} */
var deletion = new ProgressBar('Deleting posts [:bar] :current/:total = :percent :etas remaining ', {total: 10000});
console.time('Time: ');
var thread = rbx.threaded(function (i) {
var post = posts[i];
return rbx.deleteWallPost({
group: group,
post: {
parent: {
index: post.parent.index
},
view: wall.views[post.parent.page]
}
});
}, 0, posts.length);
var ivl = setInterval(function () {
deletion.update(thread.getStatus() / 100);
}, 1000);
thread.then(function () {
clearInterval(ivl);
console.timeEnd('Time: ');
});
}));
}
return Promise.all(jobs);
}

function processPage (group, page, author, find) {
var posts = page.posts;
var indices = [];
for (var i = 0; i < posts.length; i++) {
var post = posts[i];
if (!author || post.author.name === author) {
indices.push(i);
} else if (!find || post.content.includes(find)) {
indices.push(i);
}
}
return {
indices: indices,
view: page.view
};
}

function clear (group, path, total) {
var deletePosts = new ProgressBar('Deleting posts [:bar] :current/:total = :percent :etas remaining ', {total: total});

var clearStream = new stream.Writable({
objectMode: true,
highWaterMark: maxThreads
});
clearStream._write = function (chunk, encoding, done) {
clearPage(group, chunk)
.then(function () {
deletePosts.tick(chunk.indices.length);
})
.catch(function (err) {
console.error('Clear page error: ' + err.message);
})
.then(done);
};
clearStream.on('error', function (err) {
console.error('Delete post stream error: ' + err.message);
});

var read = fs.createReadStream(path);
var parse = js.parse('*');

console.time('Time: ');

var pipeline = read.pipe(parse).pipe(clearStream);

pipeline.on('finish', function () {
console.timeEnd('Time: ');
});
}

function get (group, find, author, startPage, endPage) {
var pages;
if (startPage && endPage) {
pages = [];
for (var i = startPage; i <= endPage; i++) {
pages.push(i);
}
}
var wall = new ProgressBar('Getting wall [:bar] :current/:total = :percent :etas remaining ', {total: 10000, clear: true});

var total = 0;
var first, last;
var low, high;

var processStream = new stream.Transform({
objectMode: true
});
processStream._transform = function (chunk, encoding, done) {
if (startPage ? chunk.page === startPage : (!first || chunk.page < low)) {
first = chunk.posts[0];
low = chunk.page;
} else if (endPage ? chunk.page === endPage : (!last || chunk.page > high)) {
last = chunk.posts[chunk.posts.length - 1];
high = chunk.page;
}
var response = processPage(group, chunk, author, find);
total += response.indices.length;
done(null, response);
chunk = null;
response = null;
};
processStream.on('error', function (err) {
console.error('Stream processing error: ' + err.message);
});

var path = './roblox-js-wall.' + crypto.randomBytes(20).toString('hex') + '.temp';
mainPath = path;
var write = fs.createWriteStream(path);
var stringify = js.stringify('[\n', ',\n', '\n]\n');
var pipeline = processStream.pipe(stringify).pipe(write);
var promise = rbx.getWall({
group: group,
page: pages,
view: true,
stream: processStream
});
var ivl = setInterval(function () {
wall.update(promise.getStatus() / 100);
}, 1000);
promise.then(function () {
clearInterval(ivl);
})
.catch(function (err) {
console.error('Get wall post failed: ' + err.message);
});
return new Promise(function (resolve, reject) {
pipeline.on('finish', function () {
resolve({
path: path,
total: total,
first: first,
last: last
});
});
});
}

function init (group, username, password, find, author, startPage, endPage) {
rbx.login(username, password)
.then(function () {
return get(group, find, author, startPage, endPage);
})
.then(function (response) {
if (response.total === 0) {
console.log('There are no wall posts to delete!');
return;
}
console.log('You are about to delete ' + response.total + ' wall posts selected from ' + (startPage && endPage ? ('page ' + startPage + ' to ' + endPage) : ('ALL pages')));
console.log('The list starts from the post "' + response.first.content.substring(20) + '..." and ends with the post "' + response.last.content.substring(20) + '..."');
prompt.get({
name: 'yesno',
message: 'Are you sure you want to do this? y/n',
validator: /^y|n$/,
required: true,
warning: 'You must respond with "y" or "n"'
}, function (err, result) {
if (err) {
console.error('Prompt error: ' + err.message);
return;
}
if (result.yesno === 'y') {
clear(group, response.path, response.total);
} else {
console.log('Aborted');
process.exit();
}
});
});
}

prompt.start();
prompt.get(schema, function (err, result) {
if (err) {
console.error('Prompt error: ' + err.message);
return;
}
init(result.group, result.username, result.password, result.find, result.author, result.startPage, result.endPage);
});

function shutdown (err) {
if (err && err.message) {
console.error('Fatal error: ' + err.message);
}
if (mainPath) {
clean(mainPath);
}
}

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('exit', shutdown);
2 changes: 1 addition & 1 deletion lib/forum/forumPost.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ exports.func = function (args) {
url: '//forum.roblox.com/Forum/AddPost.aspx' + (args.forumId ? ('?ForumID=' + args.forumId) : ('?PostID=' + args.postId)),
events: events,
http: {
url: '//forum.roblox.com/Forum/AddPost.aspx?ForumID=46' // If you get the verification token from the replying URL that token will not work with a new thread. The other way around, however, it works for both.
url: '//forum.roblox.com/Forum/AddPost.aspx?ForumID=65' // If you get the verification token from the replying URL that token will not work with a new thread. The other way around, however, it works for both.
}
})
.then(function (result) {
Expand Down
2 changes: 1 addition & 1 deletion lib/group/getWallPost.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function findPost (jar, group, id, page, view, resolve, reject, min, max) {
max = page - 1;
}
if (min > wall.totalPages || max <= 0) {
reject('Couldn\'t find post');
reject(new Error('Couldn\'t find post'));
return;
}
findPost(jar, group, id, Math.floor((min + max) / 2), view, resolve, reject, min, max);
Expand Down
4 changes: 3 additions & 1 deletion lib/util/shortPoll.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ exports.func = function (args) {
if (stop) {
return;
}
evt.emit('error', err);
if (err) {
evt.emit('error', err);
}
retries++;
if (retries > max) {
evt.emit('close', new Error('Max retries reached'));
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "roblox-js",
"version": "4.0.1",
"version": "4.0.2",
"description": "A node module that provides an interface for performing actions on ROBLOX, mostly for use with their HttpService feature.",
"main": "lib/index.js",
"scripts": {
Expand Down