var http = require('http');
var qs = require('qs');
/**
* Creates a new Steam Web instance
* @param {object} settings
* @param {string} settings.apiKey The steam web API key, obtained from: http://steamcommunity.com/dev/apikey
* @param {string} [settings.format=json] The format for the response [json, xml, vdf] - default: json
* @constructor
*/
var steam = function(obj) {
var validFormats = ['json', 'xml', 'vdf'];
// Error checking
if (typeof obj != 'object') {
throw new Error('invalid options passed to constructor');
}
if (typeof obj.apiKey == 'undefined' || typeof obj.apiKey != 'string') {
throw new Error('invalid or missing API key');
}
if (obj.format) {
if (validFormats.indexOf(obj.format) > -1) {
this.format = obj.format;
} else {
throw new Error('invalid format specified');
}
}
// Instance vars
this.apiKey = obj.apiKey;
};
// Defaults
steam.prototype.format = 'json';
steam.prototype.apiKey = '';
// API methods
/**
* Gets the news for a specific app
* @param {object} obj
*/
steam.prototype.getNewsForApp = function(obj) {
if (!this.validate(obj, 'getNewsForApp')) {
return false;
}
obj.path = '/ISteamNews/GetNewsForApp/';
this.addVersion(obj, 'v0002');
this.makeRequest(obj);
};
/**
* Gets the global achievement percentages for a specific app
* @param {object} obj
*/
steam.prototype.getGlobalAchievementPercentagesForApp = function(input) {
var obj = this.normalizeAppGameId(input);
if (!this.validate(obj, 'getGlobalAchievementPercentagesForApp')) {
return false;
}
obj.path = '/ISteamUserStats/GetGlobalAchievementPercentagesForApp/';
this.addVersion(obj, 'v0002');
this.makeRequest(obj);
};
/**
* Gets the player summaries for a list of users
* @param {object} obj
*/
steam.prototype.getPlayerSummaries = function(obj) {
if (!this.validate(obj, 'getPlayerSummaries')) {
return false;
}
// Turn the array into a comma separated list
if (typeof obj.steamids == 'object') {
obj.steamids = obj.steamids.join('\,');
}
obj.path = '/ISteamUser/GetPlayerSummaries/';
this.addVersion(obj, 'v0002');
this.makeRequest(obj);
};
/**
* Gets a user's friend list
* @param {object} obj
*/
steam.prototype.getFriendList = function(obj) {
if (!this.validate(obj, 'getFriendList')) {
return false;
}
obj.path = '/ISteamUser/GetFriendList/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets a user's owned games list
* @param {object} obj
*/
steam.prototype.getOwnedGames = function(obj) {
if (!this.validate(obj, 'getOwnedGames')) {
return false;
}
obj.path = '/IPlayerService/GetOwnedGames/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets the item schema for a specific game
* @param {object} obj
*/
steam.prototype.getSchema = function(input) {
var obj = this.normalizeAppGameId(input);
if (!this.validate(obj, 'getSchema')) {
return false;
}
obj.path = '/IEconItems_' + obj.gameid + '/GetSchema/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets a player's items for a specific game
* <br />
* <br />
* This will work with TF2 and some other games but it no longer works with Counterstrike, valve has disabled the API
* See https://www.reddit.com/r/SteamBot/comments/5ztiv6/psa_api_method_shutdown_ieconitems/
* @param {object} obj
*/
steam.prototype.getPlayerItems = function(input) {
var obj = this.normalizeAppGameId(input);
if (!this.validate(obj, 'getPlayerItems')) {
return false;
}
obj.path = '/IEconItems_' + obj.gameid + '/GetPlayerItems/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets the prices for items in a game
* @param {object} obj
*/
steam.prototype.getAssetPrices = function(obj) {
obj = this.normalizeAppGameId(obj);
if (!this.validate(obj, 'getAssetPrices')) {
return false;
}
obj.path = '/ISteamEconomy/GetAssetPrices/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets class info for a specific game asset
* @param {object} obj
*/
steam.prototype.getAssetClassInfo = function(obj) {
obj = this.normalizeAppGameId(obj);
if (!this.validate(obj, 'getAssetClassInfo')) {
return false;
}
// Convenience allowing to just pass an array of classIds
if (obj.classIds && !obj.class_count) {
var i = 0;
obj.classIds.forEach(function(id) {
obj['classid' + i] = id;
i++;
});
obj.class_count = obj.classIds.length;
}
obj.path = '/ISteamEconomy/GetAssetClassInfo/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets a user's achievements for a specific game
* @param {object} obj
*/
steam.prototype.getPlayerAchievements = function(obj) {
obj = this.normalizeAppGameId(obj);
if (!this.validate(obj, 'getPlayerAchievements')) {
return false;
}
obj.path = '/ISteamUserStats/GetPlayerAchievements/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets a user's recently played games
* @param {object} obj
*/
steam.prototype.getRecentlyPlayedGames = function(obj) {
if (!this.validate(obj, 'getRecentlyPlayedGames')) {
return false;
}
obj.path = '/IPlayerService/GetRecentlyPlayedGames/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets a user's stats for a specific game
* @param {object} obj
*/
steam.prototype.getUserStatsForGame = function(obj) {
if (!this.validate(obj, 'getUserStatsForGame')) {
return false;
}
obj.path = '/ISteamUserStats/GetUserStatsForGame/';
this.addVersion(obj, 'v0002');
this.makeRequest(obj);
};
/**
* Gets the global stats for a specific game
* @param {object} obj
*/
steam.prototype.getGlobalStatsForGame = function(input) {
var obj = this.normalizeAppGameId(input);
if (typeof obj.name == 'string') {
obj.name = [obj.name];
}
if (!obj.count) {
obj.count = obj.name.length;
}
if (!this.validate(obj, 'getGlobalStatsForGame')) {
return false;
}
obj.path = '/ISteamUserStats/GetGlobalStatsForGame/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Check if a user is playing a shared game
* @param {object} obj
*/
steam.prototype.isPlayingSharedGame = function(obj) {
if (!this.validate(obj, 'isPlayingSharedGame')) {
return false;
}
obj.path = '/IPlayerService/IsPlayingSharedGame/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets a game's statistics schema
* @param {object} obj
*/
steam.prototype.getSchemaForGame = function(input) {
var obj = this.normalizeAppGameId(input);
if (!this.validate(obj, 'getSchemaForGame')) {
return false;
}
obj.path = '/ISteamUserStats/GetSchemaForGame/';
this.addVersion(obj, 'v2');
this.makeRequest(obj);
};
/**
* Gets a list of bans for a specific user
* @param {object} obj
*/
steam.prototype.getPlayerBans = function(obj) {
if (!this.validate(obj, 'getPlayerBans')) {
return false;
}
if (typeof obj.steamids == 'object' && obj.steamids != null) {
obj.steamids = obj.steamids.join(',');
}
obj.path = '/ISteamUser/GetPlayerBans/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Gets a list of apps and their appid
* @param {object} obj
*/
steam.prototype.getAppList = function(obj) {
obj.path = '/ISteamApps/GetAppList/';
this.addVersion(obj, 'v2');
this.makeRequest(obj);
};
/**
* Gets a list of servers at a specific address
* @param {object} obj
*/
steam.prototype.getServersAtAddress = function(obj) {
if (!this.validate(obj, 'getServersAtAddress')) {
return false;
}
obj.path = '/ISteamApps/GetServersAtAddress/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Check if a specific version is the latest version of that game
* @param {object} obj
*/
steam.prototype.upToDateCheck = function(obj) {
if (!this.validate(obj, 'upToDateCheck')) {
return false;
}
obj.path = '/ISteamApps/UpToDateCheck/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
// TODO: ISteamRemoteStorage
/**
* Gets a user's group memberships
* @param {object} obj
*/
steam.prototype.getUserGroupList = function(obj) {
if (!this.validate(obj, 'getUserGroupList')) {
return false;
}
obj.path = '/ISteamUser/GetUserGroupList/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Resolves a specific vanity URL (seems to no longer work)
* @param {object} obj
*/
steam.prototype.resolveVanityURL = function(obj) {
if (!this.validate(obj, 'resolveVanityURL')) {
return false;
}
obj.path = '/ISteamUser/ResolveVanityURL/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets the number of current players for a specific game
* @param {object} obj
*/
steam.prototype.getNumberOfCurrentPlayers = function(obj) {
if (!this.validate(obj, 'getNumberOfCurrentPlayers')) {
return false;
}
obj.path = '/ISteamUserStats/GetNumberOfCurrentPlayers/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Gets a user's Steam level
* @param {object} obj
*/
steam.prototype.getSteamLevel = function(obj) {
if (!this.validate(obj, 'getSteamLevel')) {
return false;
}
obj.path = '/IPlayerService/GetSteamLevel/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Gets a user's badges
* @param {object} obj
*/
steam.prototype.getBadges = function(obj) {
if (!this.validate(obj, 'getBadges')) {
return false;
}
obj.path = '/IPlayerService/GetBadges/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Gets a user's progress towards a specific badge
* @param {object} obj
*/
steam.prototype.getCommunityBadgeProgress = function(obj) {
if (!this.validate(obj, 'getCommunityBadgeProgress')) {
return false;
}
obj.path = '/IPlayerService/GetCommunityBadgeProgress/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Gets info on a specific server
* @param {object} obj
*/
steam.prototype.getServerInfo = function(obj) {
obj.path = '/ISteamWebAPIUtil/GetServerInfo/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets a list of supported API endpoints
* @param {object} obj
*/
steam.prototype.getSupportedAPIList = function(obj) {
obj.path = '/ISteamWebAPIUtil/GetSupportedAPIList/';
this.addVersion(obj, 'v0001');
this.makeRequest(obj);
};
/**
* Gets the schema URL for a specific game
* @param {object} obj
*/
steam.prototype.getSchemaURL = function(input) {
var obj = this.normalizeAppGameId(input);
if (!this.validate(obj, 'getSchemaURL')) {
return false;
}
obj.path = '/IEconItems_' + obj.gameid + '/GetSchemaURL/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Gets the store metadata for a specific game
* @param {object} obj
*/
steam.prototype.getStoreMetadata = function(input) {
var obj = this.normalizeAppGameId(input);
if (!this.validate(obj, 'getStoreMetadata')) {
return false;
}
obj.path = '/IEconItems_' + obj.gameid + '/GetStoreMetadata/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Gets the store status for a specific game (only TF2 currently supported)
* @param {object} obj
*/
steam.prototype.getStoreStatus = function(input) {
var obj = this.normalizeAppGameId(input);
if (!this.validate(obj, 'getStoreStatus')) {
return false;
}
obj.path = '/IEconItems_' + obj.gameid + '/GetStoreStatus/';
this.addVersion(obj, 'v1');
this.makeRequest(obj);
};
/**
* Used internally to validate an object before sending an API request. This
* could also be used by the user if they need to verify the validity of data
* submitted from an outside source. Callback receives two paramers: `err` and
* `data`.
* @private
* @param {object} obj
* @param {string} method
*/
steam.prototype.validate = function(obj, method) {
var error;
if (!obj) {
throw new Error('no arguments passed');
}
// If the user doesn't pass a callback, it makes no sense
if (typeof obj.callback != 'function') {
throw new Error('invalid callback');
}
switch (method) {
case 'getNewsForApp':
if (typeof obj.appid != 'string' && typeof obj.appid != 'number') {
error = 'invalid appid';
}
if (typeof obj.count != 'string' && typeof obj.count != 'number') {
error = 'invalid count';
}
if (typeof obj.maxlength != 'string' && typeof obj.maxlength != 'number') {
error = 'invalid maxlength';
}
break;
case 'getGlobalAchievementPercentagesForApp':
if (typeof obj.gameid != 'string' && typeof obj.gameid != 'number') {
error = 'invalid gameid';
}
if (!obj.gameid) {
error = 'invalid gameid';
}
break;
case 'getPlayerSummaries':
if (!obj.steamids) {
error = 'invalid steamids';
}
if (typeof obj.steamids == 'object' && !obj.steamids.length) {
error = 'getPlayerSummaries steamids only accepts a string or array of strings';
}
if (typeof obj.steamids == 'object' && obj.steamids.length > 100) {
error = 'too many steamids';
}
break;
case 'getFriendList':
if (!obj.steamid) {
error = 'invalid steamid';
}
break;
case 'getSchema':
if (!obj.gameid || (typeof obj.gameid != 'string' && typeof obj.gameid != 'number')) {
error = 'invalid gameid';
}
break;
case 'getPlayerItems':
if (!obj.gameid || (typeof obj.gameid != 'string' && typeof obj.gameid != 'number')) {
error = 'invalid gameid';
}
if (typeof obj.steamid != 'string') {
error = 'getPlayerItems steamid argument only accepts a string';
}
break;
case 'getOwnedGames':
if (!obj.steamid) {
error = 'invalid steamid';
}
break;
case 'getAssetPrices':
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid gameid';
}
break;
case 'getAssetClassInfo':
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid gameid';
}
if (obj.classIds && !obj.class_count && !obj.classIds.length) {
error = 'classIds convenience property must be array of numbers or strings';
}
break;
case 'getPlayerAchievements':
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid gameid';
}
if (!obj.steamid || (typeof obj.steamid != 'string' && typeof obj.steamid != 'number')) {
error = 'invalid steamid';
}
if (obj.l && typeof obj.l != 'string') {
error = 'invalid language';
}
break;
case 'getRecentlyPlayedGames':
if (!obj.steamid) {
error = 'invalid steamid';
}
break;
case 'getUserStatsForGame':
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid appid';
}
if (!obj.steamid || (typeof obj.steamid != 'string' && typeof obj.steamid != 'number')) {
error = 'invalid steamid';
}
break;
case 'getGlobalStatsForGame':
if (!obj.name || !Array.isArray(obj.name)) {
error = 'invalid name';
}
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid appid';
}
if (!obj.count || (typeof obj.count != 'string' && typeof obj.count != 'number') || obj.count != obj.name.length) {
// Could this be created internally if name isn't an array or something else weird?
error = 'invalid count on name argument';
}
break;
case 'isPlayingSharedGame':
if (!obj.steamid || (typeof obj.steamid != 'string' && typeof obj.steamid != 'number')) {
error = 'invalid steamid';
}
if (!obj.appid_playing || (typeof obj.appid_playing != 'string' && typeof obj.appid_playing != 'number')) {
error = 'invalid appid_playing';
}
break;
case 'getSchemaForGame':
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid appid';
}
break;
case 'getPlayerBans':
if (!obj.steamids) {
error = 'invalid steamids';
}
if (typeof obj.steamids == 'object' && !obj.steamids.length) {
error = 'getPlayerBans steamids only accepts a string or array of strings';
}
if (typeof obj.steamids == 'object' && obj.steamids.length > 100) {
error = 'too many steamids';
}
break;
case 'getServersAtAddress':
if (!obj.addr || typeof obj.addr != 'string' || !this.validateIPv4(obj.addr)) {
error = 'invalid addr';
}
break;
case 'upToDateCheck':
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid appid';
}
if (!obj.version || (typeof obj.version != 'string' && typeof obj.version != 'number')) {
error = 'invalid version';
}
break;
case 'getUserGroupList':
if (!obj.steamid || (typeof obj.steamid != 'string' && typeof obj.steamid != 'number')) {
error = 'invalid steamid';
}
break;
case 'resolveVanityURL':
if (!obj.vanityurl || typeof obj.vanityurl != 'string') {
error = 'invalid vanityurl';
}
break;
case 'getNumberOfCurrentPlayers':
if (!obj.appid || (typeof obj.appid != 'string' && typeof obj.appid != 'number')) {
error = 'invalid appid';
}
break;
case 'getSteamLevel':
if (!obj.steamid || (typeof obj.steamid != 'string' && typeof obj.steamid != 'number')) {
error = 'invalid steamid';
}
break;
case 'getBadges':
if (!obj.steamid || (typeof obj.steamid != 'string' && typeof obj.steamid != 'number')) {
error = 'invalid steamid';
}
break;
case 'getCommunityBadgeProgress':
if (!obj.steamid || (typeof obj.steamid != 'string' && typeof obj.steamid != 'number')) {
error = 'invalid steamid';
}
if (typeof obj.badgeid != 'string' && typeof obj.badgeid != 'number') {
error = 'invalid badgeid';
}
break;
case 'getSchemaURL':
if (!obj.gameid || (typeof obj.gameid != 'string' && typeof obj.gameid != 'number')) {
error = 'invalid gameid';
}
if (obj.language && typeof obj.language != 'string') {
error = 'invalid language';
}
break;
case 'getStoreMetadata':
if (!obj.gameid || (typeof obj.gameid != 'string' && typeof obj.gameid != 'number')) {
error = 'invalid gameid';
}
if (obj.language && typeof obj.language != 'string') {
error = 'invalid language';
}
break;
case 'getStoreStatus':
if (!obj.gameid || (typeof obj.gameid != 'string' && typeof obj.gameid != 'number')) {
error = 'invalid gameid';
}
break;
}
if (error) {
obj.callback(error);
return false;
}
return true;
};
/**
* This method is used internally to normalize gameids to appids or vice-versa
* @private
* @param {object} obj
* @return {object}
*/
steam.prototype.normalizeAppGameId = function(obj) {
if (obj.appid && !obj.gameid) {
obj.gameid = obj.appid;
} else if (obj.gameid && !obj.appid) {
obj.appid = obj.gameid;
}
return obj;
};
/**
* This method is used internally to validate IPv4 IP addresses
* @private
* @param {string} ip
* @return {boolean}
*/
steam.prototype.validateIPv4 = function(ip) {
var ipformat = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/g;
return ip.match(ipformat);
};
/**
* @private
* @param {object} config The configuration for the request (with an optional .apiVersion property)
* @param {string} defaultVersion The version that should be appened if a .apiVersion property does not exist
* @return {object} The new config object
* @description
* This method is used internally to append the version to a generated url.
* Will modify config.path by appending the correct version and '/?' to the url.
* If set, will then delete the .apiVersion property from the object so it doesnt get serialized and passed to the request
*/
steam.prototype.addVersion = function(config, defaultVersion) {
config.path += (config.apiVersion ? config.apiVersion : defaultVersion) + '/?';
if (config.apiVersion) {
delete config.apiVersion;
}
return config;
};
/**
* This method is used internally to make requests to the Steam API
* @private
* @param {string} obj
*/
steam.prototype.makeRequest = function(obj) {
var err;
var format = this.format;
// Clean up the object to get ready to send it to the API
var callback = obj.callback;
delete obj.callback;
var path = obj.path;
delete obj.path;
obj.key = this.apiKey;
obj.format = this.format;
// Generate the path
path += qs.stringify(obj);
var options = {
host: 'api.steampowered.com',
port: 80,
path: path
};
var req = http.get(options, function(res) {
var resData = '';
var statusCode = res.statusCode;
res.on('data', function(chunk) {
resData += chunk;
});
res.on('end', function() {
console.log(statusCode);
if (statusCode == 404) {
callback('404 Error was returned from steam API');
return;
} else if (statusCode == 403) {
callback('403 Error: Check your API key is correct');
return;
}
if (format == 'json') {
try {
resData = JSON.parse(resData);
} catch (e) {
callback('JSON response invalid, your API key is most likely wrong');
return;
}
}
if (statusCode >= 400 && statusCode < 500) {
callback(resData, undefined);
return;
}
if (typeof resData.result != 'undefined' &&
typeof resData.result.status != 'undefined' &&
resData.result.status != 1) {
callback(err, resData);
return;
}
callback(err, resData);
});
}).on('error', function(error) {
callback(error);
});
};
module.exports = steam;