const EventEmitter = require("events").EventEmitter;
const Nodesu = require("nodesu");
const OutgoingBanchoMessage = require("./OutgoingBanchoMessage");
const BanchoWhoisReturn = require("./IrcCommands/whois/BanchoWhoisReturn");
const BanchoBotStatsCommand = require("./StatsCommand/BanchoBotStatsCommand");
/**
* Bancho User
* @description Optional properties are filled after fetching the API with fetchFromAPI().
*
* @property {BanchoClient} banchojs Bancho.js client this user was instancied by
* @property {string} ircUsername
* @property {number} [id]
* @property {string} [username]
* @property {Date} [joinDate]
* @property {number} [count300]
* @property {number} [count100]
* @property {number} [count50]
* @property {number} [playcount]
* @property {number} [rankedScore]
* @property {number} [totalScore]
* @property {number} [ppRank]
* @property {number} [level]
* @property {number} [ppRaw]
* @property {number} [accuracy]
* @property {number} [countRankSS]
* @property {number} [countRankS]
* @property {number} [countRankA]
* @property {number} [country]
* @property {number} [totalSecondsPlayed]
* @property {number} [ppCountryRank]
* @property {Array.<nodesu.UserEvent>} [events]
* @extends EventEmitter
*/
class BanchoUser extends EventEmitter {
/**
* Creates an instance of BanchoUser.
* @param {BanchoClient} banchojs Bancho.js client this user was instancied by
* @param {string} ircUsername
*/
constructor(banchojs, ircUsername) {
super();
this.banchojs = banchojs;
this.ircUsername = ircUsername;
this.whoisUserCallback = null;
this.whoisChannelsCallback = null;
this.whoisEndCallback = null;
this.whoisUserNotFoundCallback = null;
this.whoisPromise = null;
this.wherePromise = null;
this.statsPromise = null;
this.listenersInitialized = false;
}
on() {
if(!this.listenersInitialized) {
this.banchojs.on("PM", (msg) => {
if(msg.user == this)
/**
* Emitted when a message is received from a BanchoUser
* @event BanchoUser#message
* @type {PrivateMessage}
*/
this.emit("message", msg);
});
this.listenersInitialized = true;
}
super.on.apply(this, arguments);
}
/**
* Fetch the user from the osu! API if possible. Populates all the "optional" properties of BanchoUser.
*
* @async
* @throws {Error} osu! API/no API key error
* @returns Promise<nodesu.User>
*/
fetchFromAPI() {
if(this.banchojs.osuApi) {
return new Promise((resolve, reject) => {
this.banchojs.osuApi.user.get(this.ircUsername, this.banchojs.gamemode, null, Nodesu.LookupType.string).then((user) => {
this.updateFromAPI(user);
resolve(user);
}, reject);
});
}
else
throw new Error("osu! API key is missing!");
}
updateFromAPI(user) {
this.id = user.id;
this.username = user.username;
this.joinDate = user.joinDate;
this.count300 = user.count300;
this.count100 = user.count100;
this.count50 = user.count50;
this.playcount = user.playcount;
this.rankedScore = user.rankedScore;
this.totalScore = user.totalScore;
this.ppRank = user.ppRank;
this.level = user.level;
this.ppRaw = user.ppRaw;
this.accuracy = user.accuracy;
this.countRankSS = user.countRankSS;
this.countRankS = user.countRankS;
this.countRankA = user.countRankA;
this.country = user.country;
this.totalSecondsPlayed = user.totalSecondsPlayed;
this.ppCountryRank = user.ppCountryRank;
this.events = user.events;
}
/**
* Returns true if the user is the client
*
* @returns {boolean}
*/
isClient() {
return (this.banchojs.username.toLowerCase() == this.ircUsername.toLowerCase());
}
/**
* Sends a PM to this user.
*
* @async
* @throws {Error} If we're offline
* @param {string} message
* @returns {Promise<null>} Resolves when message is sent (rate-limiting)
*/
sendMessage(message) {
return (new OutgoingBanchoMessage(this.banchojs, this, message)).send();
}
/**
* Sends an ACTION message to this user.
*
* @async
* @throws {Error} If we're offline
* @param {string} message
* @returns {Promise<null>} Resolves when message is sent (rate-limiting)
*/
sendAction(message) {
return (new OutgoingBanchoMessage(this.banchojs, this, `\x01ACTION ${message}\x01`)).send();
}
/**
* Fires an IRC WHOIS request to the server about this user. Only works on online users.
* Throws if the user can't be found or is offline.
*
* @async
* @return {Promise<BanchoWhoisReturn>}
*/
whois() {
if(!this.whoisPromise)
this.whoisPromise = new Promise((resolve, reject) => {
const whoisReturn = new BanchoWhoisReturn(this.ircUsername);
this.whoisUserCallback = (userId) => whoisReturn.userId = userId;
this.whoisChannelsCallback = (channels) => whoisReturn.channels = channels;
const afterWhois = () => {
this.whoisUserCallback = null;
this.whoisChannelsCallback = null;
this.whoisEndCallback = null;
this.whoisUserNotFoundCallback = null;
this.whoisPromise = null;
};
this.whoisEndCallback = () => {
this.id = whoisReturn.userId;
this.username = whoisReturn.name;
resolve(whoisReturn);
afterWhois();
};
this.whoisUserNotFoundCallback = () => {
reject(new Error("User is offline"));
afterWhois();
};
this.banchojs.send("WHOIS "+this.ircUsername);
});
return this.whoisPromise;
}
/**
* Fires a !where command to BanchoBot about this user. Only works on online users.
* Throws if the user can't be found or is offline.
*
* @async
* @return {Promise<string>}
*/
where() {
if(!this.wherePromise)
this.wherePromise = new Promise((resolve, reject) => {
const answerRegex = /(.+) is in (.+)/;
const BanchoBot = this.banchojs.getUser("BanchoBot");
const afterAnswer = () => {
BanchoBot.removeListener("message", listener);
this.wherePromise = null;
};
const listener = (message) => {
if(message.message == "The user is currently not online.") {
reject(new Error("User is offline"));
return afterAnswer();
}
const m = answerRegex.exec(message.message);
if(m && m[1] == this.ircUsername) {
resolve(m[2]);
afterAnswer();
}
};
BanchoBot.on("message", listener);
BanchoBot.sendMessage("!where "+this.ircUsername);
});
return this.wherePromise;
}
/**
* Fires a !stats command to BanchoBot about this user. Works on online and offline users.
* Will update the username, id, rankedScore, ppRank and playcount properties of this user.
* Status, levels and accuracy are stored in the returned object, as they are inaccurate data (missing decimals).
* It is recommended to get levels and accuracy by polling the osu! API instead.
* Throws if the user can't be found.
*
* @async
* @return {Promise<BanchoBotStatsReturn>}
*/
stats() {
if(!this.statsPromise)
this.statsPromise = new Promise((resolve, reject) => {
const statsCommand = new BanchoBotStatsCommand(this);
statsCommand.run()
.then(resolve, reject)
.then(() => this.statsPromise = null);
});
return this.statsPromise;
}
get id() {
return this._id;
}
set id(newId) {
if(isNaN(newId))
throw new Error("id needs to be a number!");
if(this._id != null)
this.banchojs.usersById.delete(this._id);
this._id = newId;
this.banchojs.usersById.set(this._id, this);
}
}
module.exports = BanchoUser;