SockDrawer/SockBot

View on GitHub
providers/nodebb/topic.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';
/**
* NodeBB provider module Topic class
* @module sockbot.providers.nodebb.Topic
* @author Accalia
* @license MIT
*/
const utils = require('../../lib/utils');
 
/**
* Create a Topic class and bind it to a forum instance
*
* @param {Provider} forum A forum instance to bind to constructed Topic class
* @returns {User} A Topic class bound to the provided `forum` instance
*/
Function `bindTopic` has 173 lines of code (exceeds 25 allowed). Consider refactoring.
exports.bindTopic = function bindTopic(forum) {
/**
* Topic Class
*
* Represends a forum topic
*
* @public
*
*/
class Topic {
/**
* Construct a topic object from a provided payload.
*
* This constructor is intended for private use only, if you need top construct a topic from payload data use
* `Topic.parse()` instead.
*
* @public
* @class
*
* @param {*} payload Payload to construct the User object out of
*/
constructor(payload) {
payload = utils.parseJSON(payload);
const values = {
authorId: payload.uid,
title: payload.title,
url: payload.slug,
posted: new Date(payload.timestamp),
lastPosted: new Date(payload.lastposttime),
id: payload.tid,
mainPostId: payload.mainPid,
postCount: payload.postcount,
categoryId: payload.cid
};
utils.mapSet(this, values);
}
 
/**
* Forum specific ID for topic category
*
* @public
*
* @type {!number}
*/
get categoryId() {
return utils.mapGet(this, 'categoryId');
}
/**
* Forum specific ID for topic author
*
* @public
*
* @type {!number}
*/
get authorId() {
return utils.mapGet(this, 'authorId');
}
 
/**
* Topic title
*
* @public
*
* @type {!string}
*/
get title() {
return utils.mapGet(this, 'title');
}
 
/**
* DateTime that the topic was created
*
* @public
*
* @type {!Date}
*/
get posted() {
return utils.mapGet(this, 'posted');
}
 
/**
* DateTime that the topic was last replied to
*
* @public
*
* @type {!Date}
*/
get lastPosted() {
return utils.mapGet(this, 'lastPosted');
}
 
/**
* Forum Specific Topic Id
*
* @public
*
* @type {!number}
*/
get id() {
return utils.mapGet(this, 'id');
}
 
/**
* Forum id of the opening post
*
* @public
*
* @type {!number}
*/
get mainPostId() {
return utils.mapGet(this, 'mainPostId');
}
 
/**
* Count of posts in topic
*
* @public
*
* @type {number}
*/
get postCount() {
return utils.mapGet(this, 'postCount');
}
 
/**
* Retrieve the web URL for the topic
*
* @public
*
* @returns {Promise<string>} Resolves to the web URL for this topic
*
* @promise
* @fulfill {string} The Web URL for this topic
* @reject {Error} An Error that occured while retrieving the post URL
*/
url() {
return Promise.resolve(`${forum.url}/${utils.mapGet(this, 'url')}`);
}
 
/**
* Reply to this topic with the given content
*
* @public
*
* @param {string} content Post Content
* @returns {Promise<Post>} Resolves to the newly created Post
*
* @promise
* @fulfill {Post} The newly created Post
* @reject {Error} An Error that occured while posting
*/
reply(content) {
const payload = {
tid: this.id,
content: content
};
return forum._emitWithRetry(10000, 'posts.reply', payload)
.then((result) => forum.Post.parse(result));
}
 
/**
* Proccess Post
*
* @typedef {PostProcessor}
* @function
*
* @param {Post} post Post to process
* @param {User} user User who posted `post`
* @param {Topic} topic Topic `post` is posted to
* @returns {Promise} A promise that fulfills when processing is complete
*/
 
/**
* Apply a Promised based function to retrieved post data
*
* @private
*
* @param {string} data Post Content
* @param {PostProcessor} eachPost A function to process each post
* @returns {Promise} Resolves on completion
*
* @promise
* @reject {Error} An Error that occured while posting
*/
_forEachPost(data, eachPost) {
const post = forum.Post.parse(data);
const user = forum.User.parse(data.user);
return eachPost(post, user, this);
}
 
/**
* Retrieve all posts from this topic, passing each off to a provided iterator function.
*
* @public
*
* @param {PostProcessor} eachPost A function to process retrieved posts.
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
getAllPosts(eachPost) {
return new Promise((resolve, reject) => {
let idx = 0;
const iterate = () => forum._emit('topics.loadMore', {
tid: this.id,
after: idx,
direction: 1
}).then((results) => {
if (!results.posts || !results.posts.length) {
return resolve(this);
}
idx += results.posts.length;
return utils.iterate(results.posts, (data) => this._forEachPost(data, eachPost))
.then(iterate).catch(reject);
}).catch((err) => reject(err));
iterate();
});
}
 
/**
* Retrieve most posts from this topic, passing each off to a provided iterator function.
*
* @public
*
* @param {PostProcessor} eachPost A function to process retrieved posts.
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
getLatestPosts(eachPost) {
return forum._emit('topics.loadMore', {
tid: this.id,
after: this.postCount,
direction: -1
}).then((results) => utils.iterate(results.posts, (data) => this._forEachPost(data, eachPost)));
}
 
/**
* Mark the topic read up to a point
*
* @public
*
* @param {number} [postNumber] Last read post. Omit to mark the entire topic read
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
markRead(postNumber) {
if (postNumber) {
const payload = {
tid: this.id,
index: postNumber
};
return forum._emit('topics.bookmark', payload)
.then(() => this);
}
return forum._emit('topics.markAsRead', [this.id])
.then(() => this);
}
 
/**
* Watch the topic for new replies
*
* @public
*
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
watch() {
return forum._emit('topics.follow', this.id)
.then(() => this);
}
 
/**
* Stop watching the tipic for new replies
*
* @public
*
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
unwatch() {
return forum._emit('topics.toggleFollow', this.id)
.then((following) => {
if (following) {
return forum._emit('topics.toggleFollow', this.id);
}
return Promise.resolve();
})
.then(() => this);
}
 
/**
* Mute the topic to suppress notifications
*
* @public
*
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
mute() {
return Promise.resolve(this);
}
 
/**
* Unmute the topic, allowing notifications to be generated again.
*
* @public
*
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
unmute() {
return Promise.resolve(this);
}
/**
* Locks the topic. Will reject if you're not a moderator.
*
* @public
*
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
Similar blocks of code found in 2 locations. Consider refactoring.
lock() {
return forum._emit('topics.lock', {
tids: [this.id],
cid: this.categoryId
})
.then(() => this);
}
/**
* Unlocks the topic. Will reject if you're not a moderator.
*
* @public
*
* @returns {Promise<Topic>} Resolves to self on completion
*
* @promise
* @fulfill {Topic} Source Topic
* @reject {Error} An Error that occured while posting
*/
Similar blocks of code found in 2 locations. Consider refactoring.
unlock() {
return forum._emit('topics.unlock', {
tids: [this.id],
cid: this.categoryId
})
.then(() => this);
}
 
/**
* Retrieve a topic by topic id
*
* @static
* @public
*
* @param {!number} topicId Id of topic to retrieve
* @returns {Promise<Topic>} Retrieved topic
*
* @promise
* @fulfill {Topic} Retrieved Topic
* @reject {Error} An Error that occured while posting
*/
static get(topicId) {
return forum._emit('topics.getTopic', topicId)
.then((topic) => Topic.parse(topic));
}
 
/**
* Parse a topic from retrieved data
*
* @public
*
* @param {*} payload Payload to parse into a topic
* @returns {Topic} Parsed topic
*/
static parse(payload) {
if (!payload) {
throw new Error('E_TOPIC_NOT_FOUND');
}
return new Topic(payload);
}
 
/**
* @typedef {TopicExtended}
* @prop {Topic} topic Topic data
* @prop {User} user User data
* @prop {Category} category Category data
*/
 
/**
* Parse a topic with embedded user and category information into respective objects
*
* @public
*
* @param {*} data Data to parse into a topic
* @returns {Promise<TopicExtended>} Parsed Results
*
* @promise
* @fulfill {TopicExtended} Parsed topic data
*/
static parseExtended(data) {
const topic = forum.Topic.parse(data);
const user = forum.User.parse(data.user);
const category = forum.Category.parse(data.category);
return Promise.resolve({
topic: topic,
user: user,
category: category
});
}
 
/**
* Proccess a Topic
*
* @typedef {TopicProcessor}
* @function
*
* @param {Topic} topic Topic to process
* @param {User} user User who started `topic`
* @param {Category} category Category `topic` is contained in
* @returns {Promise} A promise that fulfills when processing is complete
*/
 
/**
* Retrieve, parse, and handle many topics
*
* @private
*
* @param {string} room The type of topic list to retrieve
* @param {object} query Additional query parameters to filter list on
* @param {TopicProcessor} eachTopic a function to process each retrieved topic
* @returns {Promise} A promise that resolves when processing is complete
*/
static _getMany(room, query, eachTopic) {
return new Promise((resolve, reject) => {
query.after = 0;
const iterate = () => forum._emit(room, utils.cloneData(query)).then((results) => {
if (!results.topics || !results.topics.length) {
return resolve(this);
}
query.after += results.topics.length;
const each = (data) => Topic.parseExtended(data)
.then((parsed) => eachTopic(parsed.topic, parsed.user, parsed.category));
return utils.iterate(results.topics, each)
.then(iterate).catch(reject);
}).catch(reject);
iterate();
});
}
 
/**
* Get All Unread Topics
*
* @public
*
* @param {TopicProcessor} eachTopic A function to process each retrieved topic
* @returns {Promise} A promise that resolves when all topics have been processed
*/
static getUnreadTopics(eachTopic) {
return Topic._getMany('topics.loadMoreUnreadTopics', {}, eachTopic);
}
 
/**
* Get All Topics in order of most recent activity
*
* @public
*
* @param {TopicProcessor} eachTopic A function to process each retrieved topic
* @returns {Promise} A promise that resolves when all topics have been processed
*/
static getRecentTopics(eachTopic) {
return Topic._getMany('topics.loadMoreFromSet', {
set: 'topics:recent'
}, eachTopic);
}
}
return Topic;
};