Passed
Pull Request — master (#5)
by Apocist
01:02
created

nodeVBulletinAPI.js (1 issue)

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
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
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