'use strict';
const assert = require('assert');
var winston = require('winston');
var channelsModule = require('./channels.js');
var rolesModule = require('./roles.js');
var groupsEndpoints = require('../endpoints/groups-v1.js');
var Events = require('./events.js').Events;
var conversationsEndpoint = require('../endpoints/conversations-v1.js');
var conversationsModule = require('./conversations.js');
var usersModule = require('./users.js');
/**
* @class Server
* @description A Server is a large Curse group root, to not mix up with groups.
* It is the responsability of the caller to check if the server doesn't exist already
* @property {Client} client [Client]{@link Client} object used to create this [Server]{@link Server} instance.
* @property {string} ID Curse UUID of the server.
* @property {string} name Name of the server.
* @property {Map} roles Regroup all the [Roles]{@link Role} of the current [Server]{@link Server}.
* The keys are the roles IDs and the values are instances of the [Role]{@link Role} class.
* @property {array} channelList Regroup all the [Channels]{@link Channel} of the current [Server]{@link Server}.
*/
class Server {
constructor(serverID, client){
//We need the server to not exist already
assert(client.servers.has(serverID) == false);
client.servers.set(serverID, this);
this._ready = false;
this.client = client;
this.ID = serverID;
this.name;
this.roles = new Map();
this.channelList = [];
var self = this;
this._updateInformations(function(errors){
if(errors === null){
self._ready = true;
self.client._ready();
}
else {
winston.log('error', 'Server.constructor', 'Cannot initialize server instance, deleting instance.');
self.client.servers.delete(groupNotification.GroupID);
}
});
}
/**
* Kick a User from the server
* @param {User} user Specified user to kick
* @param {Function} callback Facultative arg, callback: (errors) => {}.
This function can take an argument errors that is null or undefined when function ends correctly.
*/
kickUser(user, callback){
//Define an empty void if no callback specified
if(callback === undefined){
callback = _ => {};
}
var self = this;
groupsEndpoints.deleteGroupMember(this.ID, user.ID, this.client.token, function(errors){
if(errors != undefined){
winston.log('error', 'Server.kickUser', 'Cannot kick user', user.ID);
callback(errors);
}
else {
callback(null);
}
});
}
/**
* Kick a User from the server
* @param {User} user Specified user to ban
* @param {string} reason Reason of the ban
* @param {boolean} ipBan If true, ban the user with his ip.
* @param {Function} callback Facultative arg, callback: (errors) => {}.
This function can take an argument errors that is null or undefined when function ends correctly.
*/
banUser(user, reason, ipBan, callback){
//Define an empty void if no callback specified
if(callback === undefined){
callback = _ => {};
}
if(reason === undefined){
reason = "";
}
var self = this;
groupsEndpoints.serverBanUser(this.ID, user.ID, reason, ipBan, this.client.token, function(errors){
if(errors != undefined){
winston.log('error', 'Server.kickUser', 'Cannot kick user', user.ID);
callback(errors);
}
else {
callback(null);
}
});
}
/**
* @description Iterate through all the members of the server.
* @param {Function} callback callback: (errors, user, done) => {}.
* * **errors** is **null** or **undefined** when everything happens correctly.
* * **user** is a [User]{@link User} object of the next user.
* * **done** is a **boolean**, **true** when there is no more user to iterate over.
* * To stop the iteration return false inside the callback
*
* @example
* myServer.everyServerMembers(function(errors, user, done){
* if(errors == null && !done){
*
* //Work with the user object
*
* //Example to stop iterating:
* //This will stop the iteration if the user with curse ID 1 is found
* if(user.ID === 1){
* return false;
* }
* }
* });
*/
everyServerMembers(callback){
var self = this;
var page = 1;
var userArray = [];
var continueLoop = true;
function requestCallback(){
groupsEndpoints.getGroupMembers(self.ID, false, page, 50, self.client.token, function(errors, answer){
if(errors === null){
userArray = answer.content;
//What if endpoints return empty array ?
if(userArray.length == 0){
continueLoop = false;
callback(null, undefined, true);
} else {
page = page + 1;
//While locale
while(userArray.length > 0 && continueLoop){
var tempUser = userArray.shift();
var user = new usersModule.User(tempUser.UserID, self.client);
user._username = tempUser.Username;
//Stop loop when user return false
continueLoop = callback(null, user, false) != false;
}
if(userArray.length == 0 && continueLoop){
requestCallback();
}
}
}
else{
callback(errors, undefined, true);
}
});
}
requestCallback();
}
/*
* Fetch group informations about the current server and send it to the update function
* @param {Function} callback Callback when list of channel is updated
*/
_updateInformations(callback){
if(callback === undefined){
callback = _ => {};
}
//Update channels, update roles, member count..
var self = this;
groupsEndpoints.getGroup(this.ID, false, this.client.token, function(errors, data){
if(errors === null){
self._updateFromInformations(data.content, callback);
//Callback with no errors
callback(null);
} else {
winston.log('error', 'Server._updateInformations', 'Cannot fetch server informations, no update made');
winston.log('debug', 'Server._updateInformations', groupNotification.GroupID, groupNotification.GroupTitle);
winston.log('debug', 'Server._updateInformations');
callback(errors);
}
});
}
/*
* Update the server, it's channels, it's roles.. from a group notification
*/
_updateFromInformations(groupNotif){
//To use only internally
this._groupNotification = groupNotif;
//Set server name
this.name = this._groupNotification.GroupTitle;
// Generate or update channelList:
for (let channelItem of groupNotif.Channels){
//Checking that channel doesn't exit
if(this.client.channels.has(channelItem.GroupID) == false){
var channel = new channelsModule.Channel(channelItem, this, this.client);
this.channelList.push(channel);
}
// If channel already exists, then we update it and check that it's in channelList
else {
var channel = this.client.channels.get(channelItem.GroupID);
channel._update(channelItem);
if(this.channelList.indexOf(channel) === -1){
this.channelList.push(channel);
}
}
}
// Generate or update server roles
for (let roleItem of groupNotif.Roles){
//Checking that the role is not a duplicate
if(this.roles.has(roleItem["RoleID"]) === false){
var role = new rolesModule.Role(
roleItem["RoleID"],
roleItem["Name"],
roleItem["Rank"],
roleItem["IsDefault"],
this,
this.client
);
this.roles.set(roleItem["RoleID"], role);
}
//If the role already exist, we just update it
else {
this.roles.get(roleItem["RoleID"])._update(
roleItem["Name"],
roleItem["Rank"],
roleItem["IsDefault"]);
}
}
}
/**
* Send a private message to a user inside a server
* @param {User} user User to send the message
* @param {string} messageContent Content of the message
* @param {Function} callback Facultative arg, callback: (errors) => {}.
This function can take an argument errors that is null or undefined when function ends correctly.
*/
sendPrivateConversationMessage(user, messageContent, callback){
// Create conversation string "serverID:smallID:biggerID"
if(this.client.clientID > user.ID){
var conversationID = this.ID + ':' + user.ID + ':' + this.client.clientID;
} else {
var conversationID = this.ID + ':' + this.client.clientID + ':' + user.ID;
}
// Check if conversation exist, if not create a conversation
if(this.client.conversations.has(conversationID)){
var conversation = this.client.conversations.get(conversationID);
} else {
var conversation = new conversationsModule.Conversation(conversationID,
conversationsEndpoint.ConversationType.GroupPrivateConversation, this.client);
}
// Use sendmessage from conversation
conversation.sendMessage(messageContent, callback);
}
//Arguments must be filled with at least null, callback must be here
/**
* Search for bans in the server. Arguments can be set to **undefined**.
* @param {string} query String to search the ban (Curse API don't give much detail on it).
* @param {number} pagesize Maximum size for the page of results.
* @param {number} page First page is 1.
* @param {Function} callback Callback: (errors, bans) => {}.
* * **errors** is null or undefined when function ends correctly.
* * **bans** is an **array** of [ServerBan]{@link ServerBan}.
*/
getBans(query, pagesize, page, callback){
var self = this;
groupsEndpoints.getBanList(this.ID, query, pagesize, page, this.client.token, function(errors, answer){
if(errors == null){
var bans = [];
for(let banItem of answer.content){
bans.push(new ServerBan(banItem, self));
}
callback(null, bans);
} else {
callback(errors, undefined);
}
});
}
/**
* @description Unban a specified user from the current server
* @param {User} user User to unban
* @param {Function} callback Facultative arg, callback: (errors) => {}.
This function can take an argument errors that is null or undefined when function ends correctly.
*/
unban(user, callback){
//Define empty void if no callback
if(callback === undefined){
callback = _ => {};
}
groupsEndpoints.serverUnbanUser(this.ID, user.ID, this.client.token, function(errors, content){
if(errors === undefined){
callback();
} else {
callback(errors);
}
});
}
/**
* @description Leave this server to not receive notifications from it anymore.
* @param {Function} callback Facultative arg, callback: (errors) => {}.
This function can take an argument errors that is null or undefined when function ends correctly.
*/
leave(callback){
//Define an empty void if no callback specified
if(callback === undefined){
callback = _ => {};
}
groupsEndpoints.leaveServer(this.ID, this.client.token, function(errors, content){
if(callback === undefined){
callback();
} else {
callback(errors);
}
});
}
/**
* Search for a particular user, the callback returns an array of ServerMemberResult objects
* All parameters are facultative, but use everyServerMembers to check the server memberlist
* @param {string} Username Username to search for
* @param {number} RoleID Role ID to look into
* @param {number} ResultSize Size of the answer, API limits mosts of the arrays to 50 or 100 items
* @param {number} ResultPage First page is 1
* @param {number} SortType Follows groups-v1.GroupMemberSearchSortType definition.
* * Default: 0
* * Role: 1
* * Username: 2
* * DateJoined: 3
* * DateLastActive: 4
* @param {boolean} SortAscending Ascending is **true**, descending is **false**.
* @param {Function} callback Callback: (errors, results) => {}.
* * **errors** is null or undefined when function ends correctly.
* * **results** is an **array** of [ServerMemberResult]{@link ServerMemberResult}.
*/
searchUser(Username, RoleID, ResultSize, ResultPage, SortType, SortAscending, callback){
var self = this;
groupsEndpoints.searchGroupMember(this.ID, Username, RoleID, ResultSize, ResultPage, SortType, SortAscending,
this.client.token, function(errors, answer){
if(errors === null){
var data = answer.content;
var results = [];
for (let item of data){
var servermember = new ServerMemberResult(item, self);
//Add researchResult to result array
results.push(servermember);
}
callback(null, results);
}
else{
callback(errors, null);
}
}
);
}
/**
* Get user informations from the server.
* @param {User} user User to get informations about
* @param {Function} callback Callback: (errors, results) => {}.
* * **errors** is null or undefined when function ends correctly.
* * **results** is a [ServerMemberResult]{@link ServerMemberResult}.
*/
getUserInfo(user, callback){
var self = this;
groupsEndpoints.getGroupMemberInfo(user.ID, this.ID, this.client.token, function(errors, answer){
if(errors === null){
var data = answer.content;
var result = new ServerMemberResult(data, self);
callback(null, result);
}
else {
callback(errors, null);
}
});
}
/**
* @description Iterate through all the bans of the server.
* @param {Function} callback callback: (errors, ban, done) => {}.
* * **errors** is **null** or **undefined** when everything happens correctly.
* * **ban** is a [ServerBan]{@link ServerBan} object of the next ban.
* * **done** is a **boolean**, **true** when there is no more bans to iterate over.
* * To stop the iteration return false inside the callback
*
* @example
* myServer.everyBans(function(errors, ban, done){
* if(errors == null && !done){
*
* //Work with the ServerBan object
*
* //Example to stop iterating:
* //This will stop the iteration if the user banned has curse ID 1
* if(ban.user.ID === 1){
* return false;
* }
* }
* });
*/
everyBans(callback){
var self = this;
var page = 1;
var bansArray = [];
var continueLoop = true;
function requestCallback(){
groupsEndpoints.getBanList(self.ID, null, 50, page, self.client.token, function(errors, answer){
if(errors === null){
bansArray = answer.content;
//What if endpoints return empty array ?
if(bansArray.length == 0){
continueLoop = false;
callback(null, undefined, true);
} else {
page = page + 1;
while(bansArray.length > 0 && continueLoop){
var tempBan = bansArray.shift();
var ban = new ServerBan(tempBan, self);
//Stop loop when user return false
continueLoop = callback(null, ban, false) != false;
}
if(bansArray.length == 0 && continueLoop){
requestCallback();
}
}
}
else{
callback(errors, undefined, true);
}
});
}
requestCallback();
}
}
/**
* @class ServerMemberResult
* @description ServerMemberResult is defining the structure of informations returned with a user when searching into the members of this server.
* @property {Server} server Server where the information about the user is coming from.
* @property {User} user Corresponding [User]{@link User} object.
* @property {string} username Curse username.
* @property {string} nickname Nickname of the user on this server, if set.
* @property {Role} bestRole [Role]{@link Role} corresponding to the best role of the user on this server.
* @property {array} roles Array of [Role]{@link Role} to which the user is member of on this server.
* @property {number} dateJoined Server timestamp when the user joined this server.
* @property {number} connectionStatus
* @property {number} dateLastSeen Server timestamp when the user was last seen (connected).
* @property {number} dateLastActive Server timestamp when the user was last active.
* @property {number} dateRemoved
* @property {boolean} isActive True if the user is active.
* @property {number} currentGameID ID of the current played game by the user.
* @property {boolean} isVoiceMuted
* @property {boolean} isVoiceDeafened
* @property {number} avatarTimestamp
* @property {boolean} isVerified
* @property {boolean} isBanned
*/
class ServerMemberResult {
constructor(groupMemberContract, server){
this.server = server;
this.user = server.client.getUser(groupMemberContract.UserID);
this.username = groupMemberContract.Username;
this.nickname = groupMemberContract.Nickname;
this.bestRole = server.roles.get(groupMemberContract.BestRole);
this.roles = [];
this.dateJoined = groupMemberContract.DateJoined;
this.connectionStatus = groupMemberContract.ConnectionStatus;
this.dateLastSeen = groupMemberContract.DateLastSeen;
this.dateLastActive = groupMemberContract.DateLastActive;
this.dateRemoved = groupMemberContract.DateRemoved;
this.isActive = groupMemberContract.IsActive;
this.currentGameID = groupMemberContract.currentGameID;
this.isVoiceMuted = groupMemberContract.IsVoiceMuted;
this.isVoiceDeafened = groupMemberContract.IsVoiceDeafened;
this.avatarTimestamp = groupMemberContract.AvatarTimestamp;
this.isVerified = groupMemberContract.IsVerified;
this.isBanned = groupMemberContract.IsBanned;
for(let role of groupMemberContract.Roles){
this.roles.push(server.roles.get(role));
}
//Help our local username cache here:
this.user._username = groupMemberContract.Username;
}
}
/**
* @class ServerBan
* @description ServerBan is defining the structure of informations returned when looking for server bans.
* @property {Server} server Server of the ban.
* @property {User} user Banned user.
* @property {string} username Nickname used by the user when he was banned.
* @property {User} requestorUser Ban requestor user.
* @property {string} requestorUsername Nickname used by the ban requestor when the banned occured.
* @property {number} statusTimestamp Timestamp of the ban.
* @property {string} reason Reason of the ban, set by the requestor.
* @property {boolean} isIpBan True if this is an ip ban.
* @property {string} maskedIPAddress Partially hidden IP Address of the banned user if it's an IP Ban.
*/
class ServerBan{
constructor(GroupBannedUserContract, server){
this.server = server,
this.user = server.client.getUser(GroupBannedUserContract.UserID);
this.username = GroupBannedUserContract.Username;
this.requestorUser = server.client.getUser(GroupBannedUserContract.RequestorUserID);
this.requestorUsername = GroupBannedUserContract.RequestorUsername;
this.statusTimestamp = GroupBannedUserContract.StatusTimestamp;
this.reason = GroupBannedUserContract.Reason;
this.isIpBan = (GroupBannedUserContract.MaskedIPAddress != null);
this.maskedIPAddress = GroupBannedUserContract.MaskedIPAddress;
}
}
exports.Server = Server;
exports.ServerMemberResult = ServerMemberResult;