Passed
Push — master ( 42afef...1f4723 )
by Apocist
50s
created

nodeVBulletinAPI.js ➔ ... ➔ ???   A

Complexity

Conditions 2
Paths 7

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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