let EventEmitter = require('events'),
Constants = require('./Constants'),
SocketConnection = require('./helpers/SocketConnection'),
RequestHandler = require('./helpers/RequestHandler'),
Collection = require('./helpers/Collection'),
Message = require('./structures/Message'),
User = require('./structures/User'),
ExtendedUser = require('./structures/ExtendedUser'),
Room = require('./structures/Room'),
Media = require('./structures/Media'),
Booth = require('./structures/Booth'),
Queue = require('./structures/Queue');
/**
* The main Client object
* @property {Boolean} ready Indicates if the client is ready for rest calls.
* @property {Room} room The current room
* @property {String} socketStatus The current status of the socket connection
* @property {ExtendedUser} self The logged-in user
*/
class Client extends EventEmitter {
/**
* Create a new Client
* @param {String} email The Email to use for login
* @param {String} password the password to use
* @param {Object} [options] An object containing additional settings
* @param {Boolean} [options.useFriends=false] Whether the bot should distinguish between friends or not
* @param {Boolean} [options.autoConnect=false] If the bot should automatically establish a socket connection
* @param {Boolean} [options.autoReconnect=false] If the bot should automatically reopen an errored or closed socket connection
* @param {Number} [options.requestFreeze=1000] The time all requests are freezed when a ratelimit warning is received. Can not be lower than 1
* @param {Number} [options.chatFreeze=1000] The time all chatmessages are freezed when receiving a "floodChat" event
* @param {Boolean} [options.ignoreRateLimits=false] Whether to respect plug.dj's rate limits or not. It's not recommended to use this option except when you are having your own handling for rate limits.
* @param {Boolean} [options.updateNotification=false] Whether you want to notified about (possible) updates.
*/
constructor(email, password, options = {}) {
super();
if (!email)throw new Error("Email has to be provided.");
if (!password)throw new Error("Password has to be provided");
options.requestFreeze = options.requestFreeze || 1000;
options.chatFreeze = options.chatFreeze || 1000;
this._options = options;
this.ready = false;
this.room = null;
this.messages = new Collection(Message);
this.deletedMessages = new Collection(Message);
this.users = new Collection(User);
this.offlineUsers = new Collection(User);
this.history = [];
this.playback = null;
this._ws = new SocketConnection(this);
this._connect = false;
this._connectPromise = {resolve: undefined, reject: undefined};
this._requestAgent = new RequestHandler(this);
this._mediaCache = new Collection(Media);
this._getCsrf().then(token => {
this._csrf = token;
this._login(email, password).then(() => {
this._requestAgent.request('get', Constants.endpoints.self).then((user) => {
this.user = new ExtendedUser(user, this);
this.ready = true;
/**
* Emitted when the client is ready to make rest calls
* @event Client#ready
*/
this.emit('ready');
if (this._connect || options.autoConnect) this.connect().then(this._connectPromise.resolve).catch(this._connectPromise.reject);
}).catch(err => {
throw new Error('Failed to get self', err);
});
}).catch(err => {
throw new Error('Failed to login, please check your credentials', err);
});
}).catch(err => {
throw new Error(`Unable to get CSRF token, Error: ${err.stack}`);
});
}
get socketStatus() {
return this._ws.status;
}
get self() {
return this.user;
}
_getCsrf() {
return new Promise((resolve, reject) => {
this._requestAgent.request('get', Constants.endpoints.csrf).then(body => resolve(body.data[0].c)).catch(reject);
});
}
_login(email, password) {
return new Promise((resolve, reject) => {
this._requestAgent.request('post', Constants.endpoints.login, {
csrf: this._csrf,
email,
password
}).then(res => {
if (res.status === 'ok') resolve(res);
}).catch(reject);
});
}
/**
* Establishes the Websocket Conncetion to plug.dj
* @return {Promise}
*/
connect() {
if (this.ready)return this._ws.connect();
return new Promise((resolve, reject) => {
this._connectPromise = {resolve, reject};
this._connect = true;
});
}
/**
* Joins a room (community)
* @param {String} slug the slug to join
* @return {Promise}
*/
joinRoom(slug) {
return new Promise((resolve, reject) => {
if (!this.ready)return reject(new Error('Client is not ready yet.'));
if (!slug && Constants.invalidRooms.includes(slug))return reject(new Error('Room is invalid'));
return this._requestAgent.request('post', Constants.endpoints.joinRoom, {slug}).then(() => {
return this._requestAgent.request('get', Constants.endpoints.roomState).then(body => {
console.log(body);
if (!body.data[0])return reject(new Error('Room could not be fetched.'));
for (let u of body.data[0].users) this.users.add(new User(u, this));
this.room = new Room(body.data[0].meta, this);
this.room.setBooth(new Booth(body.data[0].booth, this));
//noinspection JSUnresolvedVariable
this.room.setQueue(new Queue(body.data[0].booth.waitingDJs, this));
/**
* Emitted when a room was joined and the caches were filled.
* @event Client#joinedRoom
*/
this.emit('joinedRoom', slug);
});
}).catch(reject);
});
}
/**
* Sends a message in chat
* @param {String} content The message content
* @return {Promise}
*/
//todo add delete timeout
sendChat(content) {
return new Promise((resolve, reject) => {
if (!this.ready)return reject(new Error('Client is not ready yet.'));
if (!content)return reject(new Error('message must be provided'));
if (content.length > 200)return reject(new Error('Message is longer than 200'));
this._ws.sendChat({content, resolve, reject});
});
}
/**
* Bans an user from the room.
* @param {Number} userID The id of the user
* @param {String} [time='d'] The ban duration, defaults to one day ('h' for one hour, 'd' for a day, 'f' for forever)
* @param {Number} [reason=1] The ban reason, defaults to 'violating community rules'
* @return {Promise}
*/
banUser(userID, time = Constants.banDurations.day, reason = Constants.banReasons.violatingCommunityRules) {
return new Promise((resolve, reject) => {
if (!this.ready)return reject(new Error('Client is not ready yet.'));
//todo let it return a ban object
this._requestAgent.request('post', Constants.endpoints.addBan, {
userID,
reason,
duration: time
}).then(resolve, reject);
});
}
/**
* Removes a ban for a user
* @param {Number} userID The id of the user
* @return {Promise}
*/
unbanUser(userID) {
return new Promise((resolve, reject) => {
if (!this.ready)return reject(new Error('Client is not ready yet.'));
this._requestAgent.request('post', Constants.endpoints.bans + userID).then(resolve, reject);
});
}
/**
* Skips the current playback. All fields are automatically filled in, however it is recommended to provide at least the userID to prevent wrong skips
* @param {Number} [userID] The id of the current dj
* @param {String} [historyID] The id of the current playback
* @returns {Promise}
*/
skipSong(userID = this.playback.user, historyID = this.playback.historyID) {
return new Promise((resolve, reject) => {
if (!this.ready)return reject(new Error('Client is not ready yet.'));
if (!this.playback)return reject(new Error('No one is playing'));
this._requestAgent.request('post', Constants.endpoints.skip, {
historyID: historyID || this.playback.historyID,
userID
}).then(resolve, reject);
});
}
/**
* Deletes a chat message
* @param {String} chatID Id of the message to be deleted
* @returns {Promise}
*/
deleteMessage(chatID) {
return new Promise((resolve, reject) => {
if (!this.ready)return reject(new Error('Client is not ready yet.'));
this._requestAgent.request('delete', Constants.endpoints.chat + chatID).then(resolve, reject);
});
}
/**
* Sets an user as staff
* @param {Number} userID
* @param {Number} role the role, 0 for grey, 1 for res dj, 2 for bouncer, 3 for manager, 4 for co-host, 5 for host
* @return {Promise}
*/
setRole(userID, role) {
return new Promise((resolve, reject) => {
if (!this.ready)return reject(new Error('Client is not ready yet.'));
if (0 > role || role > 5)return reject(new Error('Role out of bounds'));
if (role === 0) this._requestAgent.request('delete', Constants.endpoints.staff + `/${userID}`).then(() => resolve()).catch(reject);
else this._requestAgent.request('post', Constants.endpoints.staff + `/update`, {
userID,
roleID: role
}).then(() => resolve()).catch(reject);
});
}
/**
* Shorthand for {Client}.setRole(userID, 0)
* @param {Number} userID
* @return {Promise}
*/
removeRole(userID) {
return this.setRole(userID, 0);
}
}
module.exports = Client;