apocist /
nodeVBulletinAPI
| 1 | 'use strict'; |
||
| 2 | const md5 = require('js-md5'), |
||
| 3 | os = require('os'), |
||
| 4 | request = require('request'), |
||
| 5 | _ = require('underscore'), |
||
| 6 | Forum = require('./Forum'), |
||
| 7 | Member = require('./Member'), |
||
| 8 | //Post = require('./Post'), |
||
| 9 | Thread = require('./Thread'), |
||
| 10 | {version} = require('./package.json'); |
||
| 11 | |||
| 12 | /** |
||
| 13 | * |
||
| 14 | */ |
||
| 15 | class VBApi { |
||
| 16 | /** |
||
| 17 | * Initialize a vb api connection .This needs to be called for the first time |
||
| 18 | * @param {string} apiUrl |
||
| 19 | * @param {string} apiKey |
||
| 20 | * @param {string} platformName |
||
| 21 | * @param {string} platformVersion |
||
| 22 | * @param {object=} options - A fallback to the old style config |
||
| 23 | * @param {string=} options.apiUrl |
||
| 24 | * @param {string=} options.apiKey |
||
| 25 | * @param {string=} options.platformName |
||
| 26 | * @param {string=} options.platformVersion |
||
| 27 | */ |
||
| 28 | constructor(apiUrl, apiKey, platformName, platformVersion, options) { |
||
| 29 | this.defaultVars = { |
||
| 30 | baseUrl: '', //Needed for cookie related commands |
||
| 31 | apiUrl: '', |
||
| 32 | apiKey: '', |
||
| 33 | clientName: 'nodeVBulletinAPI', |
||
| 34 | clientVersion: version, |
||
| 35 | uniqueId: '' |
||
| 36 | }; |
||
| 37 | |||
| 38 | this.clientSessionVars = { |
||
| 39 | apiVersion: '', |
||
| 40 | apiAccessToken: '', |
||
| 41 | sessionHash: '', // Unused? |
||
| 42 | apiClientId: '', |
||
| 43 | secret: '', |
||
| 44 | inited: false, |
||
| 45 | error: null |
||
| 46 | }; |
||
| 47 | |||
| 48 | /** |
||
| 49 | * @typedef UserVars |
||
| 50 | * @property {string} dbsessionhash |
||
| 51 | * @property {number} userid |
||
| 52 | * @property {string} username |
||
| 53 | * @property {boolean} loggedIn |
||
| 54 | * @type {UserVars} |
||
| 55 | */ |
||
| 56 | this.userSessionVars = { |
||
| 57 | dbsessionhash: '', |
||
| 58 | username: '', |
||
| 59 | userid: 0, |
||
| 60 | loggedIn: false |
||
| 61 | }; |
||
| 62 | |||
| 63 | /** @private */ |
||
| 64 | this.__waitingForInitializationCallback = function () { |
||
| 65 | }; // A blank callback to be filled in |
||
| 66 | |||
| 67 | options = options || {}; |
||
| 68 | options.apiUrl = apiUrl || options.apiUrl || ''; |
||
| 69 | options.apiKey = apiKey || options.apiKey || ''; |
||
| 70 | options.platformName = platformName || options.platformName || ''; |
||
| 71 | options.platformVersion = platformVersion || options.platformVersion || ''; |
||
| 72 | |||
| 73 | if ( |
||
| 74 | options.apiUrl !== '' |
||
| 75 | && options.apiUrl !== '' |
||
| 76 | && options.platformName !== '' |
||
| 77 | && options.platformVersion !== '' |
||
| 78 | ) { |
||
| 79 | this.__initialize(options); |
||
| 80 | } else { |
||
| 81 | this.clientSessionVars.error = 'apiInit(): Initialization requires a `apiUrl`, `apiKey`, `platformName`, and `platformVersion`'; |
||
| 82 | this.__waitingForInitializationCallback(false); |
||
| 83 | } |
||
| 84 | } |
||
| 85 | |||
| 86 | /** |
||
| 87 | * Initialize a vb api connection. This needs to be called for the first time |
||
| 88 | * @param {object} options |
||
| 89 | * @param {string} options.apiUrl |
||
| 90 | * @param {string} options.apiKey |
||
| 91 | * @param {string} options.platformName |
||
| 92 | * @param {string} options.platformVersion |
||
| 93 | * @private |
||
| 94 | */ |
||
| 95 | __initialize(options) { |
||
| 96 | let that = this; |
||
| 97 | // Run itself as a self invoked promise that is awaited by nothing. callMethod shall wait until this is finished |
||
| 98 | (async function __initialize_self() { |
||
| 99 | let error = null; |
||
| 100 | let result = null; |
||
| 101 | let regex_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/; |
||
| 102 | let url_parts = regex_url.exec(options.apiUrl); |
||
| 103 | that.defaultVars.baseUrl = that.defaultVars.baseUrl || url_parts[1] + ':' + url_parts[2] + url_parts[3] + '/'; |
||
| 104 | that.defaultVars.apiUrl = that.defaultVars.apiUrl || options.apiUrl; |
||
| 105 | that.defaultVars.apiKey = that.defaultVars.apiKey || options.apiKey; |
||
| 106 | that.defaultVars.uniqueId = that.defaultVars.uniqueId || md5(that.defaultVars.clientName + that.defaultVars.clientVersion + options.platformName + options.platformVersion + that.constructor.getMacAddress() + new Date().getTime()); |
||
| 107 | |||
| 108 | try { |
||
| 109 | /** |
||
| 110 | * |
||
| 111 | * @type {{}} |
||
| 112 | * @property {string} apiversion |
||
| 113 | * @property {string} apiaccesstoken |
||
| 114 | * @property {string} sessionhash |
||
| 115 | * @property {string} apiclientid |
||
| 116 | * @property {string} secret |
||
| 117 | */ |
||
| 118 | let response = await that.callMethod({ |
||
| 119 | method: 'api_init', |
||
| 120 | params: { |
||
| 121 | clientname: that.defaultVars.clientName, |
||
| 122 | clientversion: that.defaultVars.clientVersion, |
||
| 123 | platformname: options.platformName, |
||
| 124 | platformversion: options.platformVersion, |
||
| 125 | uniqueid: that.defaultVars.uniqueId |
||
| 126 | } |
||
| 127 | }); |
||
| 128 | |||
| 129 | that.clientSessionVars.apiVersion = ''; |
||
| 130 | that.clientSessionVars.apiAccessToken = ''; |
||
| 131 | that.clientSessionVars.sessionHash = ''; |
||
| 132 | that.clientSessionVars.apiClientId = ''; |
||
| 133 | that.clientSessionVars.secret = ''; |
||
| 134 | that.clientSessionVars.inited = false; |
||
| 135 | if ( |
||
| 136 | response.apiversion |
||
| 137 | && response.apiaccesstoken |
||
| 138 | && response.sessionhash |
||
| 139 | && response.apiclientid |
||
| 140 | && response.secret |
||
| 141 | ) { |
||
| 142 | that.clientSessionVars.apiVersion = response.apiversion; |
||
| 143 | that.clientSessionVars.apiAccessToken = response.apiaccesstoken; |
||
| 144 | that.clientSessionVars.sessionHash = response.sessionhash; |
||
| 145 | that.clientSessionVars.apiClientId = response.apiclientid; |
||
| 146 | that.clientSessionVars.secret = response.secret; |
||
| 147 | that.clientSessionVars.inited = true; |
||
| 148 | that.__waitingForInitializationCallback(true); |
||
| 149 | result = that; |
||
| 150 | } |
||
| 151 | |||
| 152 | if (result === null) { |
||
| 153 | that.clientSessionVars.error = that.constructor.parseErrorMessage(response) || 'TODO ERROR (api connection did not return a session)'; |
||
| 154 | that.__waitingForInitializationCallback(false); |
||
| 155 | error = that.clientSessionVars.error; |
||
| 156 | } |
||
| 157 | } catch (e) { |
||
| 158 | that.clientSessionVars.error = e; |
||
| 159 | that.__waitingForInitializationCallback(false); |
||
| 160 | // reject(e); |
||
| 161 | error = e; |
||
| 162 | } |
||
| 163 | return error || result; |
||
| 164 | }()); |
||
| 165 | } |
||
| 166 | |||
| 167 | /** |
||
| 168 | * Will return after #initialize() is complete. Otherwise may reject() after 15 second timeout |
||
| 169 | * @param {number=15} waitTime |
||
| 170 | * @returns {Promise<void>} |
||
| 171 | * @fulfill {void} |
||
| 172 | * @reject {string} - Error Reason |
||
| 173 | */ |
||
| 174 | async waitForInitialization(waitTime) { |
||
| 175 | let that = this; |
||
| 176 | waitTime = waitTime || 15; |
||
| 177 | return new Promise(async function (resolve, reject) { |
||
| 178 | if (that.clientSessionVars.inited === true) { |
||
| 179 | resolve(); |
||
| 180 | } else if (that.clientSessionVars.error !== null) { |
||
| 181 | reject(that.clientSessionVars.error); |
||
| 182 | } |
||
| 183 | |||
| 184 | /** |
||
| 185 | * @type {number} |
||
| 186 | * @private |
||
| 187 | */ |
||
| 188 | that.__waitingForInitializationTimeout = setTimeout( |
||
| 189 | function () { |
||
| 190 | that.__waitingForInitializationCallback = function () { |
||
| 191 | }; // Set back to a blank function |
||
| 192 | if (that.clientSessionVars.inited === true) { |
||
| 193 | resolve(); |
||
| 194 | } else { |
||
| 195 | reject('Connection could not be achieved due to timed out', that.clientSessionVars.error); |
||
| 196 | } |
||
| 197 | |||
| 198 | }, |
||
| 199 | waitTime * 1000 // 1 minute timeout |
||
| 200 | ); |
||
| 201 | /** |
||
| 202 | * @param {boolean=true} success |
||
| 203 | * @private |
||
| 204 | */ |
||
| 205 | that.__waitingForInitializationCallback = function (success) { |
||
| 206 | if (that.__waitingForInitializationTimeout) { |
||
| 207 | clearTimeout(that.__waitingForInitializationTimeout); |
||
| 208 | } |
||
| 209 | if (success === false) { |
||
| 210 | reject(that.clientSessionVars.error); |
||
| 211 | } else { |
||
| 212 | resolve(); |
||
| 213 | } |
||
| 214 | }; |
||
| 215 | }) |
||
| 216 | } |
||
| 217 | |||
| 218 | /** |
||
| 219 | * |
||
| 220 | * @param {object} options |
||
| 221 | * @param {string} options.method - Required action to take |
||
| 222 | * @param {object<string,string>} [options.params={}] - Optional parameter variables |
||
| 223 | * @param {?object<string,string>} [options.cookies] - Optional cookie variables |
||
| 224 | * @returns {Promise<{}>} |
||
| 225 | * @fulfill {{}} |
||
| 226 | * @reject {string} - Error Reason |
||
| 227 | */ |
||
| 228 | async callMethod(options) { |
||
| 229 | let that = this; |
||
| 230 | let sign = true; |
||
| 231 | options = options || {}; |
||
| 232 | options.params = options.params || {}; |
||
| 233 | return new Promise(async function (resolve, reject) { |
||
| 234 | if (!options.method) { |
||
| 235 | reject('callMethod(): requires a supplied method'); |
||
| 236 | return; |
||
| 237 | } |
||
| 238 | |||
| 239 | // Sign all calls except for api_init |
||
| 240 | if (options.method === 'api_init') { |
||
| 241 | sign = false; |
||
| 242 | } |
||
| 243 | |||
| 244 | // await a valid session before continuing (skipping waiting on __initialize()) |
||
| 245 | if (sign === true) { |
||
| 246 | try { |
||
| 247 | await that.waitForInitialization(); |
||
| 248 | } catch (e) { |
||
|
0 ignored issues
–
show
Coding Style
Comprehensibility
Best Practice
introduced
by
Loading history...
|
|||
| 249 | // For some reason is waitForInitialization(), there's nothing to be done, ignore it |
||
| 250 | } |
||
| 251 | } |
||
| 252 | |||
| 253 | // Gather our sessions variables together |
||
| 254 | let reqParams = { |
||
| 255 | api_m: options.method, |
||
| 256 | api_c: that.clientSessionVars.apiClientId, //clientId |
||
| 257 | api_s: that.clientSessionVars.apiAccessToken, //apiAccessToken (may be empty) |
||
| 258 | api_v: that.clientSessionVars.apiVersion //api version |
||
| 259 | }; |
||
| 260 | _.extend(reqParams, options.params); // Combine the arrays |
||
| 261 | |||
| 262 | if (sign === true) { |
||
| 263 | // Generate a signature to validate that we are authenticated |
||
| 264 | if (that.clientSessionVars.inited) { |
||
| 265 | reqParams.api_sig = md5(that.clientSessionVars.apiAccessToken + that.clientSessionVars.apiClientId + that.clientSessionVars.secret + that.defaultVars.apiKey); |
||
| 266 | } else { |
||
| 267 | reject('callMethod(): requires initialization. Not initialized'); |
||
| 268 | return; |
||
| 269 | } |
||
| 270 | } |
||
| 271 | |||
| 272 | // Create a valid http Request |
||
| 273 | let reqOptions = { |
||
| 274 | url: that.defaultVars.apiUrl, |
||
| 275 | formData: reqParams, |
||
| 276 | headers: { |
||
| 277 | 'User-Agent': that.defaultVars.clientName |
||
| 278 | } |
||
| 279 | }; |
||
| 280 | |||
| 281 | // Some command require adding a cookie, we'll do that here |
||
| 282 | if (options.cookies) { |
||
| 283 | let j = request.jar(); |
||
| 284 | for (let variable in options.cookies) { |
||
| 285 | if (options.cookies.hasOwnProperty(variable)) { |
||
| 286 | let cookieString = variable + '=' + options.cookies[variable]; |
||
| 287 | let cookie = request.cookie(cookieString); |
||
| 288 | j.setCookie(cookie, that.defaultVars.baseUrl); |
||
| 289 | } |
||
| 290 | } |
||
| 291 | reqOptions.jar = j;// Adds cookies to the request |
||
| 292 | } |
||
| 293 | |||
| 294 | try { |
||
| 295 | request.post( |
||
| 296 | reqOptions, |
||
| 297 | function (error, response, body) { |
||
| 298 | if (!error && response.statusCode === 200) { |
||
| 299 | resolve(JSON.parse(body)); |
||
| 300 | } else { |
||
| 301 | //console.log('No response'); |
||
| 302 | reject('callMethod(): no response.'); |
||
| 303 | } |
||
| 304 | } |
||
| 305 | ); |
||
| 306 | } catch (e) { |
||
| 307 | reject(e); |
||
| 308 | } |
||
| 309 | }); |
||
| 310 | } |
||
| 311 | |||
| 312 | /** |
||
| 313 | * Attempts to log in a user. |
||
| 314 | * @param {string} username - Username |
||
| 315 | * @param {string} password - clear text password TODO need to secure this more? |
||
| 316 | * @param {object=} options |
||
| 317 | * @param {string=} options.username - Ignore, already required at username |
||
| 318 | * @param {string=} options.password - Ignore, already required at password |
||
| 319 | * @returns {Promise<UserVars>} |
||
| 320 | * @fulfill {UserVars} |
||
| 321 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 322 | */ |
||
| 323 | async login(username, password, options) { |
||
| 324 | options = options || {}; |
||
| 325 | options.username = username || options.username || ''; |
||
| 326 | options.password = md5(password || options.password || ''); |
||
| 327 | return await this.loginMD5('', '', options); |
||
| 328 | } |
||
| 329 | |||
| 330 | /** |
||
| 331 | * |
||
| 332 | * Attempts to log in a user. Requires the password to be pre md5 hashed. |
||
| 333 | * @param {string} username - Username |
||
| 334 | * @param {string} password - MD5 hashed password TODO need to secure this more? |
||
| 335 | * @param {object=} options |
||
| 336 | * @param {string=} options.username - Ignore, already required at username |
||
| 337 | * @param {string=} options.password - Ignore, already required at password |
||
| 338 | * @returns {Promise<UserVars>} |
||
| 339 | * @fulfill {UserVars} |
||
| 340 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 341 | */ |
||
| 342 | async loginMD5(username, password, options) { |
||
| 343 | let that = this; |
||
| 344 | options = options || {}; |
||
| 345 | options.username = username || options.username || {}; |
||
| 346 | options.password = password || options.password || {}; |
||
| 347 | return new Promise(async function (resolve, reject) { |
||
| 348 | try { |
||
| 349 | let response = await that.callMethod( |
||
| 350 | { |
||
| 351 | method: 'login_login', |
||
| 352 | params: { |
||
| 353 | vb_login_username: options.username || '', |
||
| 354 | vb_login_md5password: options.password || '' |
||
| 355 | } |
||
| 356 | } |
||
| 357 | ); |
||
| 358 | /** |
||
| 359 | redirect_login - (NOT A ERROR) Login successful |
||
| 360 | badlogin - Username or Password incorrect. Login failed. |
||
| 361 | badlogin_strikes - Username or Password incorrect. Login failed. You have used {X} out of 5 login attempts. After all 5 have been used, you will be unable to login for 15 minutes. |
||
| 362 | */ |
||
| 363 | let error = that.constructor.parseErrorMessage(response); |
||
| 364 | if (response.session) { |
||
| 365 | that.userSessionVars = response.session; |
||
| 366 | if (error === 'redirect_login') { |
||
| 367 | that.userSessionVars.username = options.username; |
||
| 368 | that.userSessionVars.loggedIn = true; |
||
| 369 | } |
||
| 370 | } |
||
| 371 | if (error === 'redirect_login') { |
||
| 372 | error = null; |
||
| 373 | } |
||
| 374 | if (error === null) { |
||
| 375 | resolve(that.userSessionVars); |
||
| 376 | } else { |
||
| 377 | reject(error); |
||
| 378 | } |
||
| 379 | |||
| 380 | } catch (e) { |
||
| 381 | reject(e); |
||
| 382 | } |
||
| 383 | }); |
||
| 384 | } |
||
| 385 | |||
| 386 | /** |
||
| 387 | * Attempts to log the user out. |
||
| 388 | * @returns {Promise<boolean>} - Returns true on success, otherwise error code is rejected |
||
| 389 | * @fulfill {boolean} |
||
| 390 | * @reject {string} - Error Reason |
||
| 391 | */ |
||
| 392 | async logout() { |
||
| 393 | let that = this; |
||
| 394 | return new Promise(async function (resolve, reject) { |
||
| 395 | let error; |
||
| 396 | try { |
||
| 397 | let response = await that.callMethod({ |
||
| 398 | method: 'login_logout' |
||
| 399 | }); |
||
| 400 | error = that.constructor.parseErrorMessage(response); |
||
| 401 | if (response.session) { |
||
| 402 | that.userSessionVars = response.session; |
||
| 403 | if (error === 'cookieclear') { |
||
| 404 | that.userSessionVars.username = ''; |
||
| 405 | that.userSessionVars.loggedIn = false; |
||
| 406 | } |
||
| 407 | } |
||
| 408 | if (error === 'cookieclear') { |
||
| 409 | error = null; |
||
| 410 | } |
||
| 411 | } catch (e) { |
||
| 412 | reject(e); |
||
| 413 | } |
||
| 414 | |||
| 415 | if (error) { |
||
| 416 | reject(error); |
||
| 417 | } else { |
||
| 418 | resolve(true) |
||
| 419 | } |
||
| 420 | }); |
||
| 421 | } |
||
| 422 | |||
| 423 | /** |
||
| 424 | * Return a Mac address of a network interface for machine identification |
||
| 425 | * @returns {string} macAddress |
||
| 426 | */ |
||
| 427 | static getMacAddress() { |
||
| 428 | let interfaces = os.networkInterfaces(); |
||
| 429 | let address = ''; |
||
| 430 | loop1: |
||
| 431 | for (let k in interfaces) { |
||
| 432 | if (interfaces.hasOwnProperty(k)) { |
||
| 433 | for (let k2 in interfaces[k]) { |
||
| 434 | if (interfaces[k].hasOwnProperty(k2)) { |
||
| 435 | let addressI = interfaces[k][k2]; |
||
| 436 | if ( |
||
| 437 | (addressI.family === 'IPv4' || addressI.family === 'IPv6') |
||
| 438 | && addressI.hasOwnProperty('internal') |
||
| 439 | && addressI.internal === false |
||
| 440 | && addressI.hasOwnProperty('mac') |
||
| 441 | && addressI.mac !== '00:00:00:00:00:00' |
||
| 442 | ) { |
||
| 443 | address = addressI.mac; |
||
| 444 | break loop1; |
||
| 445 | } |
||
| 446 | } |
||
| 447 | } |
||
| 448 | } |
||
| 449 | } |
||
| 450 | return address; |
||
| 451 | } |
||
| 452 | |||
| 453 | /** |
||
| 454 | * |
||
| 455 | * @param {object} response - Response object from callMethod() |
||
| 456 | * @returns {string} status - Error message |
||
| 457 | */ |
||
| 458 | static parseErrorMessage(response) { |
||
| 459 | let retur = ''; |
||
| 460 | if ( |
||
| 461 | response.hasOwnProperty('response') |
||
| 462 | && response.response.hasOwnProperty('errormessage') |
||
| 463 | ) { |
||
| 464 | if (_.isArray(response.response.errormessage)) { |
||
| 465 | retur = response.response.errormessage[0] |
||
| 466 | } else { |
||
| 467 | retur = response.response.errormessage; |
||
| 468 | } |
||
| 469 | } |
||
| 470 | return retur; |
||
| 471 | } |
||
| 472 | |||
| 473 | /** |
||
| 474 | * List every Forum and sub forum available to the user. |
||
| 475 | * @returns {Promise<Forum[]>} - Array of Forum objects |
||
| 476 | * @fulfill {Forum[]} |
||
| 477 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 478 | */ |
||
| 479 | async getForums() { |
||
| 480 | let that = this; |
||
| 481 | return new Promise(async function (resolve, reject) { |
||
| 482 | let forums = []; |
||
| 483 | try { |
||
| 484 | let response = await that.callMethod( |
||
| 485 | { |
||
| 486 | method: 'api_forumlist' |
||
| 487 | }); |
||
| 488 | |||
| 489 | if (response) { |
||
| 490 | for (let forum in response) { |
||
| 491 | if (response.hasOwnProperty(forum)) { |
||
| 492 | forums.push(new Forum(response[forum])); |
||
| 493 | } |
||
| 494 | } |
||
| 495 | } |
||
| 496 | } catch (e) { |
||
| 497 | reject(e); |
||
| 498 | } |
||
| 499 | resolve(forums); |
||
| 500 | }); |
||
| 501 | } |
||
| 502 | |||
| 503 | /** |
||
| 504 | * List detailed info about a forum and it's sub-forums and threads |
||
| 505 | * @param {number} forumId - Forum id |
||
| 506 | * @param {object=} options - Secondary Options |
||
| 507 | * @param {number=} options.forumid - Ignore, already required at forumId |
||
| 508 | * TODO note additional options |
||
| 509 | * @returns {Promise<Forum>} - Returns a Forum object |
||
| 510 | * @fulfill {Forum} |
||
| 511 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 512 | */ |
||
| 513 | async getForum(forumId, options) { |
||
| 514 | let that = this; |
||
| 515 | options = options || {}; |
||
| 516 | options.forumid = forumId || options.forumid || ''; //required |
||
| 517 | |||
| 518 | return new Promise(async function (resolve, reject) { |
||
| 519 | let forum = null; |
||
| 520 | try { |
||
| 521 | let response = await that.callMethod({ |
||
| 522 | method: 'forumdisplay', |
||
| 523 | params: options |
||
| 524 | }); |
||
| 525 | if ( |
||
| 526 | response |
||
| 527 | && response.hasOwnProperty('response') |
||
| 528 | ) { |
||
| 529 | forum = new Forum(response.response); |
||
| 530 | } |
||
| 531 | } catch (e) { |
||
| 532 | reject(e); |
||
| 533 | } |
||
| 534 | if (forum !== null) { |
||
| 535 | resolve(forum); |
||
| 536 | } else { |
||
| 537 | reject(); |
||
| 538 | } |
||
| 539 | }); |
||
| 540 | } |
||
| 541 | |||
| 542 | /** |
||
| 543 | * List detailed information about a Thread and it's Posts |
||
| 544 | * @param {number} threadId - Thread id |
||
| 545 | * @param {object=} options - Secondary Options |
||
| 546 | * @param {number=} options.threadid - Ignore, already required at threadId |
||
| 547 | * TODO note additional options |
||
| 548 | * @returns {Promise<Thread>} - Returns a Thread object |
||
| 549 | * @fulfill {Thread} |
||
| 550 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 551 | */ |
||
| 552 | async getThread(threadId, options) { |
||
| 553 | let that = this; |
||
| 554 | options = options || {}; |
||
| 555 | options.threadid = threadId || options.threadid || ''; //required |
||
| 556 | |||
| 557 | return new Promise(async function (resolve, reject) { |
||
| 558 | let thread = null; |
||
| 559 | try { |
||
| 560 | let response = await that.callMethod({ |
||
| 561 | method: 'showthread', |
||
| 562 | params: options |
||
| 563 | }); |
||
| 564 | if ( |
||
| 565 | response |
||
| 566 | && response.hasOwnProperty('response') |
||
| 567 | ) { |
||
| 568 | thread = new Thread(response.response); |
||
| 569 | } |
||
| 570 | } catch (e) { |
||
| 571 | reject(e); |
||
| 572 | } |
||
| 573 | if (thread !== null) { |
||
| 574 | resolve(thread); |
||
| 575 | } else { |
||
| 576 | reject(); |
||
| 577 | } |
||
| 578 | }); |
||
| 579 | } |
||
| 580 | |||
| 581 | /** |
||
| 582 | * Attempts to submit a new Post into a specified Thread |
||
| 583 | * @param {number} threadId - Thread id |
||
| 584 | * @param {string} message - Post Message |
||
| 585 | * @param {object=} options |
||
| 586 | * @param {boolean=} options.signature - Optionally append your signature |
||
| 587 | * @param {number=} options.threadid - Ignore, already required at threadId |
||
| 588 | * @param {string=} options.message - Ignore, already required at message |
||
| 589 | * TODO note additional options |
||
| 590 | * @returns {Promise<*>} - Returns a unhandled response currently |
||
| 591 | * @fulfill {*} |
||
| 592 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 593 | */ |
||
| 594 | async newPost(threadId, message, options) { |
||
| 595 | let that = this; |
||
| 596 | options = options || {}; |
||
| 597 | options.threadid = threadId || options.threadid || ''; //required |
||
| 598 | options.message = message || options.message || ''; //required |
||
| 599 | if (options.signature === true) { |
||
| 600 | //System only handle 1 or 0. defaults to 0 |
||
| 601 | options.signature = '1'; |
||
| 602 | } |
||
| 603 | |||
| 604 | return new Promise(async function (resolve, reject) { |
||
| 605 | try { |
||
| 606 | let response = await that.callMethod({ |
||
| 607 | method: 'newreply_postreply', |
||
| 608 | params: options |
||
| 609 | }); |
||
| 610 | let possibleError = that.constructor.parseErrorMessage(response); |
||
| 611 | //success is errormessgae 'redirect_postthanks' |
||
| 612 | //error 'threadclosed' if thread is closed. FIXME does not error |
||
| 613 | //reports threadid and postid |
||
| 614 | if ( |
||
| 615 | possibleError === 'redirect_postthanks' |
||
| 616 | && response.hasOwnProperty('show') |
||
| 617 | ) { |
||
| 618 | resolve(response.show); |
||
| 619 | } else { |
||
| 620 | reject(possibleError || response); |
||
| 621 | } |
||
| 622 | } catch (e) { |
||
| 623 | reject(e); |
||
| 624 | } |
||
| 625 | }); |
||
| 626 | } |
||
| 627 | |||
| 628 | /** |
||
| 629 | * Attempts to edit an existing Post |
||
| 630 | * @param {number} postId - Post id |
||
| 631 | * @param {string} message - Post Message |
||
| 632 | * @param {object=} options |
||
| 633 | * @param {string=} options.reason - Reason for editing |
||
| 634 | * @param {boolean=} options.signature - Optionally append your signature |
||
| 635 | * @param {number=} options.postid - Ignore, already required at postId |
||
| 636 | * @param {string=} options.message - Ignore, already required at message |
||
| 637 | * TODO note additional options |
||
| 638 | * @returns {Promise<*>} - Returns a unhandled response currently |
||
| 639 | * @fulfill {*} |
||
| 640 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 641 | */ |
||
| 642 | async editPost(postId, message, options) { |
||
| 643 | let that = this; |
||
| 644 | options = options || {}; |
||
| 645 | options.postid = postId || options.postid || ''; //required |
||
| 646 | options.message = message || options.message || ''; //required |
||
| 647 | if (options.signature === true) { |
||
| 648 | //System only handle 1 or 0. defaults to 0 |
||
| 649 | options.signature = '1'; |
||
| 650 | } |
||
| 651 | |||
| 652 | return new Promise(async function (resolve, reject) { |
||
| 653 | try { |
||
| 654 | let response = await that.callMethod({ |
||
| 655 | method: 'editpost_updatepost', |
||
| 656 | params: options |
||
| 657 | }); |
||
| 658 | let possibleError = that.constructor.parseErrorMessage(response); |
||
| 659 | //success is errormessgae 'redirect_editthanks' |
||
| 660 | if (possibleError === 'redirect_editthanks') { |
||
| 661 | resolve({postid: options.postid}); |
||
| 662 | } else { |
||
| 663 | reject(possibleError || response); |
||
| 664 | } |
||
| 665 | } catch (e) { |
||
| 666 | reject(e); |
||
| 667 | } |
||
| 668 | }); |
||
| 669 | } |
||
| 670 | |||
| 671 | /** |
||
| 672 | * Attempts to retrieve data about a specific user found by username |
||
| 673 | * @param {string} username - Username |
||
| 674 | * @param {object=} options - Secondary Options |
||
| 675 | * @param {string=} options.username - Ignore, already required at username |
||
| 676 | * @returns {Promise<Member>} - Returns a Member object |
||
| 677 | * @fulfill {Member} |
||
| 678 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 679 | */ |
||
| 680 | async getMember(username, options) { |
||
| 681 | let that = this; |
||
| 682 | options = options || {}; |
||
| 683 | options.username = username || options.username || ''; //required |
||
| 684 | |||
| 685 | return new Promise(async function (resolve, reject) { |
||
| 686 | let thread = null; |
||
| 687 | try { |
||
| 688 | let response = await that.callMethod({ |
||
| 689 | method: 'member', |
||
| 690 | params: options |
||
| 691 | }); |
||
| 692 | if ( |
||
| 693 | response |
||
| 694 | && response.hasOwnProperty('response') |
||
| 695 | ) { |
||
| 696 | thread = new Member(response.response); |
||
| 697 | } |
||
| 698 | } catch (e) { |
||
| 699 | reject(e); |
||
| 700 | } |
||
| 701 | if (thread !== null) { |
||
| 702 | resolve(thread); |
||
| 703 | } else { |
||
| 704 | reject(); |
||
| 705 | } |
||
| 706 | }); |
||
| 707 | } |
||
| 708 | |||
| 709 | /** |
||
| 710 | * TODO untested - does not seem to function yet |
||
| 711 | * Attempts to delete an existing Post |
||
| 712 | * @param {number} postId - Post id |
||
| 713 | * @param {number} threadId - Thread id |
||
| 714 | * @param {object=} options |
||
| 715 | * @param {string=} options.reason - Reason for deleting |
||
| 716 | * @param {number=} options.postid - Ignore, already required at postId |
||
| 717 | * @param {number=} options.threadid - Ignore, already required at threadId |
||
| 718 | * TODO note additional options |
||
| 719 | * @returns {Promise<*>} - Returns a unhandled response currently |
||
| 720 | * @fulfill {*} |
||
| 721 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 722 | */ |
||
| 723 | async deletePost(postId, threadId, options) { |
||
| 724 | let that = this; |
||
| 725 | options = options || {}; |
||
| 726 | options.postid = postId || options.postid || ''; //required |
||
| 727 | options.threadid = threadId || options.threadid || ''; // TODO required???? |
||
| 728 | |||
| 729 | return new Promise(async function (resolve, reject) { |
||
| 730 | try { |
||
| 731 | let response = await that.callMethod({ |
||
| 732 | method: 'editpost_deletepost', |
||
| 733 | params: options |
||
| 734 | }); |
||
| 735 | let possibleError = that.constructor.parseErrorMessage(response); |
||
| 736 | //unknown response |
||
| 737 | if ( |
||
| 738 | possibleError === 'redirect_deletepost' |
||
| 739 | && response.hasOwnProperty('show') |
||
| 740 | ) { |
||
| 741 | //console.log('response', response); |
||
| 742 | resolve(response.show); |
||
| 743 | } else { |
||
| 744 | reject(possibleError || response); |
||
| 745 | } |
||
| 746 | } catch (e) { |
||
| 747 | reject(e); |
||
| 748 | } |
||
| 749 | }); |
||
| 750 | } |
||
| 751 | |||
| 752 | /** |
||
| 753 | * Attempts to submit a new Thread into a specified Forum. This will also be considered the first Post |
||
| 754 | * @param {number} forumId - Forum Id |
||
| 755 | * @param {string} subject - Post/Thread Subject |
||
| 756 | * @param {string} message - Post Message |
||
| 757 | * @param {object=} options |
||
| 758 | * @param {boolean=} options.signature - Optionally append your signature |
||
| 759 | * @param {number=} options.forumid - Ignore, already required at postId |
||
| 760 | * @param {string=} options.subject - Ignore, already required at postId |
||
| 761 | * @param {string=} options.message - Ignore, already required at postId |
||
| 762 | * TODO note additional options |
||
| 763 | * @returns {Promise<*>} - Returns a unhandled response currently |
||
| 764 | * @fulfill {*} |
||
| 765 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 766 | */ |
||
| 767 | async newThread(forumId, subject, message, options) { |
||
| 768 | let that = this; |
||
| 769 | options = options || {}; |
||
| 770 | options.forumid = forumId || options.forumid || ''; //required |
||
| 771 | options.subject = subject || options.subject || ''; //required |
||
| 772 | options.message = message || options.message || ''; //required |
||
| 773 | |||
| 774 | if (options.signature === true) { |
||
| 775 | //System only handle 1 or 0. defaults to 0 |
||
| 776 | options.signature = '1'; // FIXME This didn't seem to work |
||
| 777 | } |
||
| 778 | |||
| 779 | return new Promise(async function (resolve, reject) { |
||
| 780 | try { |
||
| 781 | let response = await that.callMethod({ |
||
| 782 | method: 'newthread_postthread', |
||
| 783 | params: options |
||
| 784 | }); |
||
| 785 | let possibleError = that.constructor.parseErrorMessage(response); |
||
| 786 | //success is errormessgae 'redirect_postthanks' |
||
| 787 | //reports threadid and postid |
||
| 788 | if ( |
||
| 789 | possibleError === 'redirect_postthanks' |
||
| 790 | && response.hasOwnProperty('show') |
||
| 791 | ) { |
||
| 792 | resolve(response.show); |
||
| 793 | } else { |
||
| 794 | reject(possibleError || response); |
||
| 795 | } |
||
| 796 | } catch (e) { |
||
| 797 | reject(e); |
||
| 798 | } |
||
| 799 | }); |
||
| 800 | } |
||
| 801 | |||
| 802 | /** |
||
| 803 | * TODO incomplete - does not seem to function yet |
||
| 804 | * Attempts to close a specific Thread. Requires a user to have a 'inline mod' permissions |
||
| 805 | * @param {number} threadId - Id of Thread to close |
||
| 806 | * @returns {Promise<*>} - Returns a unhandled response currently |
||
| 807 | * @fulfill {*} |
||
| 808 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 809 | */ |
||
| 810 | async modCloseThread(threadId) { |
||
| 811 | let that = this; |
||
| 812 | let cookies = {}; |
||
| 813 | if (threadId) { |
||
| 814 | //TODO multiple ids are delimited with a '-'. eg: 123-345-456 |
||
| 815 | cookies.vbulletin_inlinethread = threadId; |
||
| 816 | } |
||
| 817 | return new Promise(async function (resolve, reject) { |
||
| 818 | try { |
||
| 819 | let response = await that.callMethod({ |
||
| 820 | method: 'inlinemod_close', |
||
| 821 | cookies: cookies || {} |
||
| 822 | }); |
||
| 823 | //let possibleError = that.constructor.parseErrorMessage(response); |
||
| 824 | //unknown responses |
||
| 825 | /*if ( |
||
| 826 | possibleError === 'redirect_postthanks' |
||
| 827 | && response.hasOwnProperty('show') |
||
| 828 | ) {*/ |
||
| 829 | resolve(response); |
||
| 830 | /*} else { |
||
| 831 | reject(possibleError || response); |
||
| 832 | }*/ |
||
| 833 | } catch (e) { |
||
| 834 | reject(e); |
||
| 835 | } |
||
| 836 | }); |
||
| 837 | } |
||
| 838 | |||
| 839 | /** |
||
| 840 | * TODO incomplete - does not seem to function yet |
||
| 841 | * Attempts to open a specific Thread. Requires a user to have a 'inline mod' permissions |
||
| 842 | * @param {number} threadId - Id of Thread to open |
||
| 843 | * @returns {Promise<*>} - Returns a unhandled response currently |
||
| 844 | * @fulfill {*} |
||
| 845 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 846 | */ |
||
| 847 | async modOpenThread(threadId) { |
||
| 848 | let that = this; |
||
| 849 | let cookies = {}; |
||
| 850 | if (threadId) { |
||
| 851 | //TODO multiple ids are delimited with a '-'. eg: 123-345-456 |
||
| 852 | cookies.vbulletin_inlinethread = threadId; |
||
| 853 | } |
||
| 854 | return new Promise(async function (resolve, reject) { |
||
| 855 | try { |
||
| 856 | let response = await that.callMethod({ |
||
| 857 | method: 'inlinemod_open', |
||
| 858 | cookies: cookies || {} |
||
| 859 | }); |
||
| 860 | //let possibleError = that.constructor.parseErrorMessage(response); |
||
| 861 | //unknown responses |
||
| 862 | /*if ( |
||
| 863 | possibleError === 'redirect_postthanks' |
||
| 864 | && response.hasOwnProperty('show') |
||
| 865 | ) {*/ |
||
| 866 | resolve(response); |
||
| 867 | /*} else { |
||
| 868 | reject(possibleError || response); |
||
| 869 | }*/ |
||
| 870 | } catch (e) { |
||
| 871 | reject(e); |
||
| 872 | } |
||
| 873 | }); |
||
| 874 | } |
||
| 875 | |||
| 876 | /** |
||
| 877 | * TODO incomplete - does not seem to function yet |
||
| 878 | * Attempts to delete a specific Thread. Requires a user to have a 'inline mod' permissions |
||
| 879 | * @param {number} threadId - Id of Thread to close |
||
| 880 | * @returns {Promise<*>} - Returns a unhandled response currently |
||
| 881 | * @fulfill {*} |
||
| 882 | * @reject {string} - Error Reason. Expects: (TODO list common errors here) |
||
| 883 | */ |
||
| 884 | async modDeleteThread(threadId) { |
||
| 885 | let that = this; |
||
| 886 | let cookies = {}; |
||
| 887 | if (threadId) { |
||
| 888 | //TODO multiple ids are delimited with a '-'. eg: 123-345-456 |
||
| 889 | cookies.vbulletin_inlinethread = threadId; |
||
| 890 | } |
||
| 891 | return new Promise(async function (resolve, reject) { |
||
| 892 | try { |
||
| 893 | let response = await that.callMethod({ |
||
| 894 | method: 'inlinemod_dodeletethreads', |
||
| 895 | cookies: cookies || {} |
||
| 896 | }); |
||
| 897 | //let possibleError = that.constructor.parseErrorMessage(response); |
||
| 898 | //unknown responses |
||
| 899 | /*if ( |
||
| 900 | possibleError === 'redirect_postthanks' |
||
| 901 | && response.hasOwnProperty('show') |
||
| 902 | ) {*/ |
||
| 903 | resolve(response); |
||
| 904 | /*} else { |
||
| 905 | reject(possibleError || response); |
||
| 906 | }*/ |
||
| 907 | } catch (e) { |
||
| 908 | reject(e); |
||
| 909 | } |
||
| 910 | }); |
||
| 911 | } |
||
| 912 | } |
||
| 913 | |||
| 914 | module.exports = VBApi; |
||
| 915 |