diff --git a/components/chat.js b/components/chat.js new file mode 100644 index 0000000..3d3db92 --- /dev/null +++ b/components/chat.js @@ -0,0 +1,230 @@ +var SteamCommunity = require('../index.js'); +var SteamID = require('steamid'); + +SteamCommunity.ChatState = { + "Offline": 0, + "LoggingOn": 1, + "LogOnFailed": 2, + "LoggedOn": 3 +}; + +SteamCommunity.prototype.chatLogon = function(interval) { + if(this.chatState == SteamCommunity.ChatState.LoggingOn || this.chatState == SteamCommunity.ChatState.LoggedOn) { + return; + } + + interval = interval || 1000; + + this.emit('debug', 'Requesting chat WebAPI token'); + this.chatState = SteamCommunity.ChatState.LoggingOn; + + var self = this; + this.request("https://steamcommunity.com/chat", function(err, response, body) { + if(err || response.statusCode != 200) { + self.emit('debug', 'Error requesting chat WebAPI token: ' + (err ? err.message : "HTTP error " + response.statusCode)); + self.chatState = SteamCommunity.ChatState.LogOnFailed; + setTimeout(self.chatLogon.bind(self), 5000); + return; + } + + var match = body.match(/[0-9a-f]{32}/); + if(!match) { + self.emit('debug', 'Couldn\'t find a WebAPI chat token in the response.'); + self.chatState = SteamCommunity.ChatState.LogOnFailed; + setTimeout(self.chatLogon.bind(self), 5000); + return; + } + + self.request.post({ + "uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logon/v1", + "form": { + "ui_mode": "web", + "access_token": match[0] + }, + "json": true + }, function(err, response, body) { + if(err || response.statusCode != 200) { + self.emit('debug', 'Error logging into webchat: ' + (err ? err.message : "HTTP error " + response.statusCode)); + self.chatState = SteamCommunity.ChatState.LogOnFailed; + setTimeout(self.chatLogon.bind(self), 5000); + return; + } + + if(body.error != 'OK') { + self.emit('debug', 'Error logging into webchat: ' + body.error); + self.chatState = SteamCommunity.ChatState.LogOnFailed; + setTimeout(self.chatLogon.bind(self), 5000); + return; + } + + self._chat = { + "umqid": body.umqid, + "message": body.message, + "accessToken": match[0], + "interval": interval + }; + + self.chatFriends = {}; + + self.chatState = SteamCommunity.ChatState.LoggedOn; + self.emit('chatLoggedOn'); + self._chatPoll(); + }); + }); +}; + +SteamCommunity.prototype.chatMessage = function(recipient, text, type, callback) { + if(this.chatState != SteamCommunity.ChatState.LoggedOn) { + throw new Error("Chat must be logged on before messages can be sent"); + } + + if(typeof recipient === 'string') { + recipient = new SteamID(recipient); + } + + if(typeof type === 'function') { + callback = type; + type = 'saytext'; + } + + type = type || 'saytext'; + + this.request.post({ + "uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Message/v1", + "form": { + "access_token": this._chat.accessToken, + "steamid_dst": recipient.toString(), + "text": text, + "type": type, + "umqid": this._chat.umqid + }, + "json": true + }, function(err, response, body) { + if(!callback) { + return; + } + + if(err || response.statusCode != 200) { + callback(err ? err.message : "HTTP error " + response.statusCode); + } else if(body.error != 'OK') { + callback(body.error); + } else { + callback(); + } + }); +}; + +SteamCommunity.prototype.chatLogoff = function() { + var self = this; + this.request.post({ + "uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Logoff/v1", + "form": { + "access_token": this._chat.accessToken, + "umqid": this._chat.umqid + } + }, function(err, response, body) { + if(err || response.statusCode != 200) { + self.emit('debug', 'Error logging off of chat: ' + (err ? err.message : "HTTP error " + response.statusCode)); + setTimeout(self.chatLogoff.bind(self), 1000); + } else { + self.emit('chatLoggedOff'); + clearTimeout(self._chat.timer); + delete self._chat; + delete self.chatFriends; + self.chatState = SteamCommunity.ChatState.Offline; + } + }); +}; + +SteamCommunity.prototype._chatPoll = function() { + this.emit('debug', 'Doing chat poll'); + + var self = this; + this.request.post({ + "uri": "https://api.steampowered.com/ISteamWebUserPresenceOAuth/Poll/v1", + "form": { + "umqid": self._chat.umqid, + "message": self._chat.message, + "pollid": 1, + "sectimeout": 20, + "secidletime": 0, + "use_accountids": 1, + "access_token": self._chat.accessToken + }, + "json": true + }, function(err, response, body) { + if(self.chatState == SteamCommunity.ChatState.Offline) { + return; + } + + self._chat.timer = setTimeout(self._chatPoll.bind(self), self._chat.interval); + + if(err || response.statusCode != 200) { + self.emit('debug', 'Error in chat poll: ' + (err ? err.message : "HTTP error " + response.statusCode)); + return; + } + + if(body.error != 'OK') { + self.emit('debug', 'Error in chat poll: ' + body.error); + return; + } + + self._chat.message = body.messagelast; + + (body.messages || []).forEach(function(message) { + var sender = new SteamID(); + sender.universe = SteamID.Universe.PUBLIC; + sender.type = SteamID.Type.INDIVIDUAL; + sender.instance = SteamID.Instance.DESKTOP; + sender.accountid = message.accountid_from; + + switch(message.type) { + case 'personastate': + self._chatUpdatePersona(sender); + break; + + case 'saytext': + self.emit('chatMessage', sender, message.text, new Date(message.utc_timestamp * 1000)); + break; + + case 'typing': + self.emit('chatTyping', sender); + break; + + default: + self.emit('debug', 'Unhandled chat message type: ' + message.type); + } + }); + }); +}; + +SteamCommunity.prototype._chatUpdatePersona = function(steamID) { + this.emit('debug', 'Updating persona data for ' + steamID); + var self = this; + this.request({ + "uri": "https://steamcommunity.com/chat/friendstate/" + steamID.accountid, + "json": true + }, function(err, response, body) { + if(err || response.statusCode != 200) { + self.emit('debug', 'Chat update persona error: ' + (err ? err.message : "HTTP error " + response.statusCode)); + setTimeout(function() { + self._chatUpdatePersona(steamID); + }, 2000); + return; + } + + var persona = { + "steamID": steamID, + "personaName": body.m_strName, + "personaState": body.m_ePersonaState, + "personaStateFlags": body.m_nPersonaStateFlags, + "avatarHash": body.m_strAvatarHash, + "inGame": !!body.m_bInGame, + "inGameAppID": body.m_nInGameAppID ? parseInt(body.m_nInGameAppID, 10) : null, + "inGameName": body.m_strInGameName || null + }; + + self.emit('chatPersonaState', steamID, persona); + self.chatFriends[steamID.getSteamID64()] = persona; + }); +}; \ No newline at end of file diff --git a/index.js b/index.js index ff6ad23..e15cff2 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,8 @@ var RSA = require('node-bignumber').Key; var hex2b64 = require('node-bignumber').hex2b64; var SteamID = require('steamid'); +require('util').inherits(SteamCommunity, require('events').EventEmitter); + module.exports = SteamCommunity; SteamCommunity.SteamID = SteamID; @@ -10,6 +12,7 @@ SteamCommunity.SteamID = SteamID; function SteamCommunity() { this._jar = Request.jar(); this.request = Request.defaults({"jar": this._jar}); + this.chatState = SteamCommunity.ChatState.Offline; } SteamCommunity.prototype.login = function(details, callback) { @@ -263,3 +266,4 @@ SteamCommunity.prototype._myProfile = function(endpoint, form, callback) { require('./classes/CSteamGroup.js'); require('./classes/CSteamUser.js'); +require('./components/chat.js');