/**
* @module dotaBot
* @description Dota bot functions
*/
/**
* A Long class for representing a 64 bit two's-complement integer value
* derived from the Closure Library for stand-alone use and extended with unsigned support.
* @external Long
* @category Other
* @see {@link https://www.npmjs.com/package/long|long} npm package
*/
/**
* The Steam for Node JS package, allowing interaction with Steam.
* @external steam
* @category node-steam
* @see {@link https://www.npmjs.com/package/steam|steam} npm package
*/
/**
* External steam SteamClient class.
* @class SteamClient
* @category node-steam
* @memberof external:steam
* @see {@link https://github.com/seishun/node-steam#steamclient}
*/
/**
* External steam SteamUser class.
* @class SteamUser
* @category node-steam
* @memberof external:steam
* @see {@link https://github.com/seishun/node-steam/tree/master/lib/handlers/user}
*/
/**
* External steam SteamFriends class.
* @class SteamFriends
* @category node-steam
* @memberof external:steam
* @see {@link https://github.com/seishun/node-steam/tree/master/lib/handlers/friends}
*/
/**
* External namespace for Dota2 classes.
* @external Dota2
* @category node-dota2
* @see https://github.com/Arcana/node-dota2
*/
/**
* External Dota2 Dota2Client class.
* @class Dota2Client
* @category node-dota2
* @memberof external:Dota2
* @see {@link https://github.com/Arcana/node-dota2#module_Dota2.Dota2Client}
*/
const convertor = require('steam-id-convertor');
const steam = require('steam');
const util = require('util');
const fs = require('fs');
const Long = require('long');
const crypto = require('crypto');
const Dota2 = require('dota2');
const Promise = require('bluebird');
const { EventEmitter } = require('events');
const path = require('path');
const Queue = require('./util/queue');
const Fp = require('./util/fp');
const CONSTANTS = require('./constants');
const logger = require('./logger');
const Db = require('./db');
steam.servers = JSON.parse(fs.readFileSync(path.join(__dirname, '../servers')));
/**
* Converts a Dota team slot value to a faction value.
* @function
* @param {number} slot - The Dota team slot.
* @returns {number} An inhouse lobby faction.
*/
const slotToTeam = (slot) => {
switch (slot) {
case Dota2.schema.DOTA_GC_TEAM.DOTA_GC_TEAM_GOOD_GUYS:
return 1;
case Dota2.schema.DOTA_GC_TEAM.DOTA_GC_TEAM_BAD_GUYS:
return 2;
default:
return null;
}
};
/**
* Updates a player to team mapping object.
* Used to track which players have joined team slots in lobby.
* A null slot value will remove the player from the mapping meaning they are not in a team slot.
* @function
* @param {string} steamId64 - The player's steamid64.
* @param {?number} slot - The lobby team slot the player joined.
* @param {Object} playerState - A player to team mapping.
* @returns {Object} A player to team mapping.
*/
const updatePlayerState = (steamId64, slot, playerState) => {
const _playerState = { ...playerState };
if (slot == null) {
delete _playerState[steamId64];
}
else {
const team = slotToTeam(slot);
if (team == null) {
delete _playerState[steamId64];
}
else {
_playerState[steamId64] = team;
}
}
logger.silly(`DotaBot updatePlayerState ${steamId64} ${slot} ${util.inspect(playerState)} -> ${util.inspect(_playerState)}`);
return _playerState;
};
/**
* Checks if all entries in a player to team mapping match the player to team state mapping.
* @function
* @param {Object} teamCache - The intended player to team state.
* @param {Object} playerState - The current state of players to teams.
* @returns {boolean} Whether the player state matches the team cache.
*/
const isDotaLobbyReady = (teamCache, playerState) => {
for (const [steamId64, team] of Object.entries(teamCache)) {
if (playerState[steamId64] !== team) return false;
}
return true;
};
const connectToSteam = async steamClient => new Promise((resolve, reject) => {
try {
steamClient.once('connected', () => resolve(steamClient));
steamClient.connect();
}
catch (e) {
logger.error(e);
reject(e);
}
});
const logOnToSteam = logOnDetails => steamClient => async steamUser => new Promise((resolve, reject) => {
const onError = (error) => {
// eslint-disable-next-line no-use-before-define
steamClient.removeListener('logOnResponse', onLoggedOn);
// eslint-disable-next-line no-use-before-define
steamClient.removeListener('loggedOff', onLoggedOff);
reject(error);
};
const onLoggedOff = () => {
// eslint-disable-next-line no-use-before-define
steamClient.removeListener('logOnResponse', onLoggedOn);
steamClient.removeListener('error', onError);
reject();
};
const onLoggedOn = (logonResp) => {
steamClient.removeListener('loggedOff', onLoggedOff);
steamClient.removeListener('error', onError);
if (logonResp.eresult === steam.EResult.OK) {
resolve(steamClient);
}
else {
reject(logonResp);
}
};
steamClient.once('logOnResponse', onLoggedOn);
steamClient.once('error', onError);
steamClient.once('loggedOff', onLoggedOff);
steamUser.logOn(logOnDetails);
});
const connectToDota = async dotaClient => new Promise((resolve, reject) => {
try {
dotaClient.once('ready', () => resolve(dotaClient));
dotaClient.launch();
}
catch (e) {
logger.error(e);
reject(e);
}
});
const updateServers = (servers) => {
logger.silly('DotaBot Received servers.');
if (servers && servers.length) {
fs.writeFileSync('servers', JSON.stringify(servers));
}
return servers;
};
const updateMachineAuth = sentryPath => (sentry, callback) => {
fs.writeFileSync(sentryPath, sentry.bytes);
logger.silly('DotaBot sentryfile saved');
callback({ sha_file: crypto.createHash('sha1').update(sentry.bytes).digest() });
};
const defaultLobbyOptions = {
game_name: Date.now().toString(),
server_region: Dota2.ServerRegion.USEAST,
game_mode: Dota2.schema.DOTA_GameMode.DOTA_GAMEMODE_CM,
series_type: 2,
game_version: 1,
allow_cheats: false,
fill_with_bots: false,
allow_spectating: true,
pass_key: 'password',
radiant_series_wins: 0,
dire_series_wins: 0,
allchat: true,
visibility: Dota2.schema.DOTALobbyVisibility.DOTALobbyVisibility_Public,
cm_pick: Dota2.schema.DOTA_CM_PICK.DOTA_CM_RANDOM,
};
const createSteamClient = () => new steam.SteamClient();
const createSteamUser = steamClient => new steam.SteamUser(steamClient);
const createSteamFriends = steamClient => new steam.SteamFriends(steamClient);
const createDotaClient = (steamClient, debug, debugMore) => new Dota2.Dota2Client(steamClient, debug, debugMore);
const diffMembers = (membersA = [], membersB = []) => membersA.filter(memberA => !membersB.find(memberB => memberA.id.compare(memberB.id) === 0));
const intersectMembers = (membersA = [], membersB = []) => membersA.filter(memberA => membersB.find(memberB => memberA.id.compare(memberB.id) === 0));
const membersToPlayerState = (members) => {
const playerState = {};
for (const member of members) {
playerState[member.id.toString()] = slotToTeam(member.team);
}
return playerState;
};
const processMembers = (oldMembers = [], newMembers = []) => {
const members = {
left: diffMembers(oldMembers, newMembers),
joined: diffMembers(newMembers, oldMembers),
changedSlot: [],
};
for (const oldMember of oldMembers) {
const newMember = newMembers.find(member => oldMember.id.compare(member.id) === 0);
if (newMember && (oldMember.team !== newMember.team || oldMember.slot !== newMember.slot)) {
members.changedSlot.push({
previous: oldMember,
current: newMember,
});
}
}
return members;
};
const invitePlayer = dotaBot => async user => dotaBot.inviteToLobby(user.steamId64);
const kickPlayer = dotaBot => async user => dotaBot.practiceLobbyKick(parseInt(convertor.to32(user.steamId64)));
const disconnectDotaBot = async (dotaBot) => {
logger.silly(`DotaBot disconnectDotaBot ${dotaBot.steamId64}`);
await dotaBot.disconnect();
await Db.updateBotStatusBySteamId(CONSTANTS.BOT_OFFLINE)(dotaBot.steamId64);
return dotaBot;
};
const connectDotaBot = async (dotaBot) => {
logger.silly(`DotaBot connectDotaBot ${dotaBot.steamId64}`);
await dotaBot.connect();
await Db.updateBotStatusBySteamId(CONSTANTS.BOT_ONLINE)(dotaBot.steamId64);
return dotaBot;
};
const createDotaBotLobby = ({ lobbyName, password, leagueid, gameMode, firstPick, radiantFaction }) => async (dotaBot) => {
const cmPick = radiantFaction === firstPick ? Dota2.schema.DOTA_CM_PICK.DOTA_CM_GOOD_GUYS : Dota2.schema.DOTA_CM_PICK.DOTA_CM_BAD_GUYS;
const gameModeValue = Dota2.schema.DOTA_GameMode[gameMode];
logger.silly(`DotaBot createDotaBotLobby ${lobbyName} ${password} ${leagueid} ${gameMode} ${gameModeValue} ${cmPick} ${dotaBot.steamId64}`);
const result = await dotaBot.createPracticeLobby({ game_name: lobbyName, pass_key: password, leagueid, game_mode: gameModeValue, cm_pick: cmPick });
if (result === steam.EResult.OK) {
logger.silly('DotaBot createDotaBotLobby practice lobby created');
await Db.updateBotStatusBySteamId(CONSTANTS.BOT_IN_LOBBY)(dotaBot.steamId64);
logger.silly('DotaBot createDotaBotLobby bot status updated');
await dotaBot.practiceLobbyKickFromTeam(dotaBot.accountId);
await dotaBot.joinLobbyChat();
return true;
}
logger.silly('DotaBot createDotaBotLobby practice lobby failed');
await dotaBot.leavePracticeLobby();
await dotaBot.leaveLobbyChat();
return false;
};
const joinDotaBotLobby = ({ dotaLobbyId, lobbyName, password, leagueid, gameMode, firstPick, radiantFaction }) => async (dotaBot) => {
const cmPick = radiantFaction === firstPick ? Dota2.schema.DOTA_CM_PICK.DOTA_CM_GOOD_GUYS : Dota2.schema.DOTA_CM_PICK.DOTA_CM_BAD_GUYS;
const gameModeValue = Dota2.schema.DOTA_GameMode[gameMode];
logger.silly(`DotaBot joinDotaBotLobby ${lobbyName} ${password} ${leagueid} ${gameMode} ${gameModeValue} ${cmPick}`);
const options = { game_name: lobbyName, pass_key: password, leagueid, game_mode: gameModeValue, cm_pick: cmPick };
await dotaBot.joinPracticeLobby(dotaLobbyId, options);
await Db.updateBotStatusBySteamId(CONSTANTS.BOT_IN_LOBBY)(dotaBot.steamId64);
await dotaBot.configPracticeLobby(options);
await dotaBot.joinLobbyChat();
return true;
};
/**
* Start a dota lobby and return the match id.
* @async
* @param {module:dotaBot.DotaBot} dotaBot - The dota bot.
* @returns {string} The match id.
*/
const startDotaLobby = async (dotaBot) => {
const lobbyData = await dotaBot.launchPracticeLobby();
await dotaBot.leaveLobbyChat();
return lobbyData.match_id.toString();
};
const loadDotaBotTickets = async (dotaBot) => {
const leagueInfos = await dotaBot.requestLeagueInfoListAdmins();
logger.silly(`loadDotaBotTickets ${util.inspect(leagueInfos)}`);
const tickets = await Fp.mapPromise(Db.upsertTicket)(leagueInfos);
const bot = await Db.findBotBySteamId64(dotaBot.steamId64);
await Db.setTicketsOf(bot)(tickets);
return tickets;
};
class DotaBot extends EventEmitter {
/**
* Constructor of the DotaBot. This prepares an object for connecting to
* Steam and the Dota2 Game Coordinator.
* @classdesc Class representing a Dota bot.
* Handles all in game functions required to host an inhouse lobby.
* @extends external:EventEmitter
* @param {external:steam.SteamClient} steamClient - A SteamClient instance.
* @param {external:steam.SteamUser} steamUser - A SteamUser instance.
* @param {external:steam.SteamFriends} steamFriends - A SteamFriends instance.
* @param {external:Dota2.Dota2Client} dotaClient - A Dota2Client instance.
* @param {module:db.Bot} config - Bot configuration object.
* */
constructor(steamClient, steamUser, steamFriends, dotaClient, config) {
super();
this.lobbyOptions = {};
this._teamCache = {};
this._queue = new Queue(null, null, true);
this.config = config;
this.steamClient = steamClient;
this.steamUser = steamUser;
this.steamFriends = steamFriends;
this.Dota2 = dotaClient;
this.logOnDetails = {
account_name: config.accountName,
password: config.password,
};
if (config.steam_guard_code) this.logOnDetails.auth_code = config.steam_guard_code;
if (config.two_factor_code) this.logOnDetails.two_factor_code = config.two_factor_code;
try {
const sentry = fs.readFileSync(`sentry/${config.steamId64}`);
if (sentry.length) this.logOnDetails.sha_sentryfile = sentry;
}
catch (beef) {
logger.silly(`DotaBot Cannot load the sentry. ${beef}`);
}
// Block queue until GC is ready
this.block();
this.Dota2.on('ready', () => this.onDotaReady());
this.Dota2.on('unready', () => this.onDotaUnready());
this.Dota2.on('unhandled', kMsg => logger.silly(`DotaBot UNHANDLED MESSAGE ${kMsg}`));
this.Dota2.on('practiceLobbyUpdate', (lobby) => {
logger.silly('DotaBot practiceLobbyUpdate');
if (this.lobby) this.processLobbyUpdate(this.lobby, lobby);
if (lobby.match_outcome === Dota2.schema.EMatchOutcome.k_EMatchOutcome_RadVictory
|| lobby.match_outcome === Dota2.schema.EMatchOutcome.k_EMatchOutcome_DireVictory) {
this.emit(CONSTANTS.EVENT_MATCH_OUTCOME, lobby.lobby_id, lobby.match_outcome, lobby.members);
}
});
this.Dota2.on('practiceLobbyCleared', () => this.emit(CONSTANTS.EVENT_BOT_LOBBY_LEFT));
this.Dota2.on('matchSignedOut', matchId => this.emit(CONSTANTS.EVENT_MATCH_SIGNEDOUT, matchId));
this.Dota2.on('practiceLobbyResponse', (result, body) => {
logger.silly(`DotaBot practiceLobbyResponse ${util.inspect(body)}`);
});
this.Dota2.on('chatMessage', (channel, senderName, message, chatData) => {
logger.silly(`DotaBot chatMessage ${channel} ${senderName} {$message} ${util.inspect(chatData)}`);
if (channel === `Lobby_${this.dotaLobbyId}`) {
this.emit(CONSTANTS.MSG_CHAT_MESSAGE, channel, senderName, message, chatData);
}
});
this.steamClient.on('loggedOff', async eresult => this.onSteamClientLoggedOff(eresult));
this.steamClient.on('error', async error => this.onSteamClientError(error));
this.steamClient.on('servers', () => {
steam.servers = updateServers();
});
this.steamUser.on('updateMachineAuth', updateMachineAuth(`sentry/${this.steamId64}`));
}
get lobbyOptions() {
return this._lobbyOptions;
}
set lobbyOptions(options) {
this._lobbyOptions = { ...defaultLobbyOptions, ...options };
}
/**
* Get the player to team mapping object
* @return {object} The player to team mapping object.
* */
get teamCache() {
return this._teamCache;
}
/**
* Set the player to team mapping object
* @param {object} newCache - The new player to team mapping object.
* */
set teamCache(newCache) {
this._teamCache = newCache;
}
/**
* Get bot steamId64
* @return {string} The bot steam 64 id.
* */
get steamId64() {
return this.config.steamId64;
}
/**
* Get the dota lobby object
* @return {object} The current lobby state
* */
get lobby() {
return this.Dota2.Lobby;
}
/**
* Get the dota lobby id
* @return {external:Long} The id of the current lobby.
* */
get dotaLobbyId() {
return this.Dota2.Lobby ? this.Dota2.Lobby.lobby_id : Long.ZERO;
}
/**
* Get the dota lobby player state
* @return {object} The current lobby player state
* */
get playerState() {
return this.Dota2.Lobby ? membersToPlayerState(this.Dota2.Lobby.members) : {};
}
/**
* Get the dota lobby channel name
* @return {string} The channel name of the current lobby.
* */
get lobbyChannelName() {
return `Lobby_${this.dotaLobbyId}`;
}
/**
* Get the bot account id
* @return {number} The account id.
* */
get accountId() {
return this.Dota2.ToAccountID(this.Dota2._client.steamID);
}
/**
* Get the current state of the queue
* @return {string} The current state of the queue.
* */
get state() {
return this._queue.state;
}
/**
* Get the current rate limit factor
* @return {number} The current queue rate limit factor in milliseconds.
* */
get rateLimit() {
return this._queue.rateLimit;
}
/**
* Set the rate limiting factor
* @param {number} rateLimit - Milliseconds to wait between requests.
* */
set rateLimit(rateLimit) {
this._queue.rateLimit = rateLimit;
}
/**
* Get the current backoff time of the queue
* @return {number} The current queue backoff time in milliseconds.
* */
get backoff() {
return this._queue.backoff;
}
/**
* Set the backoff time of the queue
* @param {number} backoff - Exponential backoff time in milliseconds.
* */
set backoff(backoff) {
this._queue.backoff = backoff;
}
/**
* Schedule a function for execution. This function will be executed as soon
* as the GC is available.
* */
schedule(fn) {
this._queue.schedule(fn);
}
/**
* Block the queue
*/
block() {
this._queue.block();
}
/**
* Unblock the queue
*/
release() {
this._queue.release();
}
/**
* Clear the queue
*/
clear() {
this._queue.clear();
}
/**
* Steam client logged off handler.
* Attempts to log on to steam and connect to Dota
*/
async onSteamClientLoggedOff(eresult) {
logger.silly(`DotaBot logged off from Steam. Trying reconnect. ${eresult}`);
// Block queue while there's no access to Steam
this.block();
await this.logOnToSteam();
await this.connectToDota();
}
/**
* Steam client error handler.
* Attempts to connect to steam and connect to Dota
*/
async onSteamClientError(error) {
logger.silly('DotaBot connection closed by server. Trying reconnect');
logger.error(error);
// Block queue while there's no access to Steam
this.block();
await this.connect();
}
/**
* Dota ready handler.
* Unblocks the queue.
*/
onDotaReady() {
// Activate queue when GC is ready
logger.silly('DotaBot node-dota2 ready.');
this.release();
}
/**
* Dota unready handler.
* Blocks the queue.
*/
onDotaUnready() {
logger.silly('DotaBot node-dota2 unready.');
// Block queue when GC is not ready
this.block();
}
/**
* Initiates the connection to Steam and the Dota2 Game Coordinator.
* */
async connect() {
logger.silly('DotaBot steamClient connecting...');
await connectToSteam(this.steamClient);
await this.logOnToSteam();
await this.connectToDota();
}
/**
* Log on to steam. Set online state and display name.
*/
async logOnToSteam() {
logger.silly('DotaBot logOnToSteam logging on steamUser...');
await logOnToSteam(this.logOnDetails)(this.steamClient)(this.steamUser);
this.steamFriends.setPersonaState(steam.EPersonaState.Online);
this.steamFriends.setPersonaName(this.config.personaName);
logger.silly('DotaBot set steam persona state and name.');
}
/**
* Connect to dota and unblock queue.
*/
async connectToDota() {
await connectToDota(this.Dota2);
// Activate queue when GC is ready
logger.silly('DotaBot node-dota2 ready.');
this.release();
}
/**
* Disconnect from the Game Coordinator. This will also cancel all queued
* operations!
* */
async disconnect() {
return Promise.try(() => {
this.clear();
this.Dota2.exit();
this.steamClient.disconnect();
logger.silly('DotaBot Logged off.');
});
}
processLobbyUpdate(oldLobby, newLobby) {
const members = processMembers(oldLobby.members, newLobby.members);
for (const member of members.left) {
logger.silly(`DotaBot processLobbyUpdate member left ${member.id}`);
this.emit(CONSTANTS.MSG_LOBBY_PLAYER_LEFT, member);
}
for (const member of members.joined) {
logger.silly(`DotaBot processLobbyUpdate member joined ${member.id}`);
this.emit(CONSTANTS.MSG_LOBBY_PLAYER_JOINED, member);
}
for (const memberState of members.changedSlot) {
logger.silly(`DotaBot processLobbyUpdate member slot changed ${util.inspect(memberState.previous)} => ${util.inspect(memberState.current)}`);
this.emit(CONSTANTS.MSG_LOBBY_PLAYER_CHANGED_SLOT, memberState);
const steamId64 = memberState.current.id.toString();
logger.silly(`DotaBot processLobbyUpdate steamId64 ${steamId64} teamCache ${this.teamCache[steamId64]} slotToTeam ${slotToTeam(memberState.current.team)}`);
if (Object.prototype.hasOwnProperty.call(this.teamCache, steamId64)) {
if (this.teamCache[steamId64] !== slotToTeam(memberState.current.team)) {
const accountId = parseInt(convertor.to32(steamId64));
logger.silly(`DotaBot processLobbyUpdate slot change mismatch. kicking ${accountId} ${typeof accountId}`);
this.practiceLobbyKickFromTeam(accountId).catch(e => logger.error(e));
}
}
}
if (isDotaLobbyReady(this.teamCache, membersToPlayerState(newLobby.members))) {
logger.silly('DotaBot processLobbyUpdate lobby ready');
this.emit(CONSTANTS.EVENT_LOBBY_READY);
}
}
async sendMessage(message) {
return new Promise((resolve, reject) => {
logger.silly(`DotaBot sendMessage ${message}`);
this.schedule(() => {
if (this.lobby) {
this.Dota2.sendMessage(message, this.lobbyChannelName, Dota2.schema.DOTAChatChannelType_t.DOTAChannelType_Lobby);
resolve(true);
}
else {
logger.error('DotaBot sendMessage missing lobby');
resolve(false);
}
});
});
}
/**
* Invites the given steam id to the Dota lobby.
* @async
* @param {string} steamId64 - A steam id.
*/
async inviteToLobby(steamId64) {
return new Promise((resolve, reject) => {
this.schedule(() => {
if (this.lobby) {
this.Dota2.once('inviteCreated', () => resolve(true));
logger.silly(`DotaBot inviteToLobby ${steamId64}`);
this.Dota2.inviteToLobby(Long.fromString(steamId64));
}
else {
logger.error('DotaBot inviteToLobby missing lobby');
resolve(false);
}
});
});
}
async configPracticeLobby(options) {
return new Promise((resolve, reject) => {
this.schedule(() => {
if (this.lobby) {
this.lobbyOptions = options;
logger.silly(`DotaBot configPracticeLobby ${util.inspect(this.lobbyOptions)}`);
this.Dota2.configPracticeLobby(this.dotaLobbyId, this.lobbyOptions, (err, body) => {
logger.silly(`DotaBot configPracticeLobby response ${err} ${body}`);
if (!err) {
resolve(body);
}
else {
logger.error(err);
reject(err);
}
});
}
else {
logger.error('DotaBot configPracticeLobby missing lobby');
resolve(false);
}
});
});
}
async flipLobbyTeams() {
return new Promise((resolve, reject) => {
this.schedule(() => {
if (this.lobby) {
logger.silly('DotaBot flipLobbyTeams');
for (const [steamId64, team] of Object.entries(this.teamCache)) {
this.teamCache[steamId64] = 3 - team;
}
// flipLobbyTeams callback does not fire
this.Dota2.flipLobbyTeams();
Promise.delay(1000).then(() => resolve(true));
}
else {
logger.error('DotaBot flipLobbyTeams missing lobby');
resolve(false);
}
});
});
}
async launchPracticeLobby() {
return new Promise((resolve, reject) => {
this.schedule(() => {
logger.silly('DotaBot launchPracticeLobby');
this.Dota2.launchPracticeLobby((err) => {
logger.silly(`DotaBot launchPracticeLobby response ${err}`);
if (!err) {
resolve(this.lobby);
}
else {
logger.error(err);
reject(err);
}
});
});
});
}
/**
* Kick the given account id from the lobby team slots.
* @async
* @param {number} accountId - An account id.
*/
async practiceLobbyKickFromTeam(accountId) {
return new Promise((resolve, reject) => {
this.schedule(() => {
if (this.lobby) {
logger.silly(`DotaBot practiceLobbyKickFromTeam ${accountId}`);
this.Dota2.practiceLobbyKickFromTeam(accountId, (err, body) => {
logger.silly(`DotaBot practiceLobbyKickFromTeam response ${err} ${body}`);
if (!err) {
resolve(body);
}
else {
logger.error(err);
reject(err);
}
});
}
else {
logger.error('DotaBot practiceLobbyKickFromTeam missing lobby');
resolve(false);
}
});
});
}
/**
* Kick the given account id from the lobby.
* @async
* @param {number} accountId - An account id.
*/
async practiceLobbyKick(accountId) {
return new Promise((resolve, reject) => {
this.schedule(() => {
if (this.lobby) {
logger.silly(`DotaBot practiceLobbyKick ${accountId}`);
this.Dota2.practiceLobbyKick(accountId, (err, body) => {
logger.silly(`DotaBot practiceLobbyKick response ${err} ${body}`);
if (!err) {
resolve(body);
}
else {
logger.error(err);
reject(err);
}
});
}
else {
logger.error('DotaBot practiceLobbyKick missing lobby');
resolve(false);
}
});
});
}
async leavePracticeLobby() {
return new Promise((resolve, reject) => {
this.schedule(() => {
logger.silly('DotaBot leavePracticeLobby');
this.Dota2.leavePracticeLobby((err, body) => {
logger.silly(`DotaBot leavePracticeLobby response ${err} ${body}`);
if (!err) {
resolve(body);
}
else {
logger.error(err);
reject(err);
}
});
});
});
}
async abandonCurrentGame() {
return new Promise((resolve, reject) => {
this.schedule(() => {
try {
logger.silly('DotaBot abandonCurrentGame');
// abandonCurrentGame does not callback
this.Dota2.abandonCurrentGame();
resolve();
}
catch (e) {
logger.error(e);
reject(e);
}
});
});
}
async destroyLobby() {
return new Promise((resolve, reject) => {
this.schedule(() => {
logger.silly('DotaBot destroyLobby');
this.Dota2.destroyLobby((err, body) => {
logger.silly(`DotaBot destroyLobby response ${err} ${body}`);
if (!err) {
this.lobbyOptions = null;
resolve(body);
}
else {
logger.error(err);
reject(err);
}
});
});
});
}
async joinChat(channelName, channelType) {
return new Promise((resolve, reject) => {
this.schedule(() => {
try {
logger.silly(`DotaBot joinChat ${channelName}`);
this.Dota2.joinChat(channelName, channelType);
resolve();
}
catch (e) {
logger.error(e);
reject(e);
}
});
});
}
async joinLobbyChat() {
return new Promise((resolve, reject) => {
if (this.lobby) {
resolve(this.joinChat(this.lobbyChannelName, Dota2.schema.DOTAChatChannelType_t.DOTAChannelType_Lobby));
}
else {
logger.error('DotaBot joinLobbyChat missing lobby');
resolve(false);
}
});
}
async leaveChat(channelName, channelType) {
return new Promise((resolve, reject) => {
this.schedule(() => {
try {
logger.silly(`DotaBot leaveChat ${channelName}`);
this.Dota2.leaveChat(channelName, channelType);
resolve();
}
catch (e) {
logger.error(e);
reject(e);
}
});
});
}
async leaveLobbyChat() {
return new Promise((resolve, reject) => {
if (this.lobby) {
resolve(this.leaveChat(this.lobbyChannelName, Dota2.schema.DOTAChatChannelType_t.DOTAChannelType_Lobby));
}
else {
logger.error('DotaBot leaveLobbyChat missing lobby');
resolve(false);
}
});
}
/**
* Join the lobby by dota lobby id.
* @async
* @param {string} dotaLobbyId - A dota lobby id.
*/
async joinPracticeLobby(dotaLobbyId, options) {
return new Promise((resolve, reject) => {
this.schedule(() => {
this.lobbyOptions = options;
logger.silly(`DotaBot joinPracticeLobby ${dotaLobbyId}`);
this.Dota2.joinPracticeLobby(Long.fromString(dotaLobbyId), this.lobbyOptions.pass_key, (err, body) => {
if (!err) {
if (body.result === Dota2.schema.DOTAJoinLobbyResult.DOTA_JOIN_RESULT_SUCCESS) {
resolve(body);
}
else {
logger.error(body.eresult);
reject(body.result);
}
}
else {
logger.error(err);
reject(err);
}
});
});
});
}
async createPracticeLobby(options) {
return new Promise((resolve, reject) => {
this.schedule(() => {
this.lobbyOptions = options;
logger.silly(`DotaBot createPracticeLobby ${util.inspect(this.lobbyOptions)}`);
this.Dota2.createPracticeLobby(this.lobbyOptions, (err, body) => {
if (!err) {
if (body.eresult === steam.EResult.OK || body.eresult === steam.EResult.Fail) {
resolve(body.eresult);
}
else {
logger.error(body.eresult);
reject(body.eresult);
}
}
else {
logger.error(err);
reject(err);
}
});
});
});
}
async requestLeagueInfoListAdmins() {
return new Promise((resolve, reject) => {
this.schedule(() => {
logger.silly('DotaBot requestLeagueInfoListAdmins');
this.Dota2.requestLeagueInfoListAdmins((err, body) => {
if (!err) {
logger.silly(`DotaBot requestLeagueInfoListAdmins ${util.inspect(body)}`);
resolve(body.map(ticket => ({
leagueid: ticket.league_id,
name: ticket.name,
mostRecentActivity: ticket.most_recent_activity,
startTimestamp: ticket.start_timestamp,
endTimestamp: ticket.end_timestamp,
})));
}
else {
logger.error(err);
reject(err);
}
});
});
});
}
}
const createDotaBot = (config) => {
const steamClient = createSteamClient();
const steamUser = createSteamUser(steamClient);
const steamFriends = createSteamFriends(steamClient);
const dotaClient = createDotaClient(steamClient, true, true);
return new DotaBot(steamClient, steamUser, steamFriends, dotaClient, config);
};
module.exports = {
slotToTeam,
updatePlayerState,
isDotaLobbyReady,
connectToSteam,
logOnToSteam,
connectToDota,
updateServers,
updateMachineAuth,
defaultLobbyOptions,
createSteamClient,
createSteamUser,
createSteamFriends,
createDotaClient,
diffMembers,
intersectMembers,
membersToPlayerState,
processMembers,
invitePlayer,
kickPlayer,
disconnectDotaBot,
connectDotaBot,
createDotaBotLobby,
joinDotaBotLobby,
startDotaLobby,
loadDotaBotTickets,
DotaBot,
createDotaBot,
};
Source