steamapi-cloudflare-workers
Version:
A nice Steam API wrapper for Cloudflare Workers
429 lines (370 loc) • 14.4 kB
JavaScript
const SteamID = require('steamid');
const PlayerAchievements = require('./structures/PlayerAchievements');
const PlayerSummary = require('./structures/PlayerSummary');
const PlayerServers = require('./structures/PlayerServers');
const PlayerBadges = require('./structures/PlayerBadges');
const PlayerStats = require('./structures/PlayerStats');
const PlayerBans = require('./structures/PlayerBans');
const RecentGame = require('./structures/RecentGame');
const Friend = require('./structures/Friend');
const Server = require('./structures/Server');
const Game = require('./structures/Game');
const objectify = require('./utils/objectify');
const { name } = require('../package.json');
const reApp = /^\d{1,7}$/;
const reRegion = /^us|es|de|fr|ru|nz|au|uk$/i;
const reID = /^\d{17}$/;
const reProfileBase = String.raw`(?:(?:(?:(?:https?)?:\/\/)?(?:www\.)?steamcommunity\.com)?)?\/?`;
const reCommunityID = RegExp(String.raw`^(\d{17})$`, 'i');
const reSteamID2 = RegExp(String.raw`^(STEAM_\d+:\d+:\d+)$`, 'i');
const reSteamID3 = RegExp(String.raw`^(\[U:\d+:\d+\])$`, 'i');
const reProfileURL = RegExp(String.raw`${reProfileBase}profiles\/(\d{17})`, 'i');
const reProfileID = RegExp(String.raw`${reProfileBase}id\/([a-z0-9_-]{2,32})`, 'i');
const STATUS_SUCCESS = 1;
class SteamAPI {
/**
* Sets Steam key for future use.
* @param {string} key Steam key
* @param {Object} [options={}] Optional options for caching and warnings `getGameDetails()`
* @param {boolean} [options.enabled=true] Whether caching is enabled
* @param {number} [options.expires=86400000] How long cache should last for in ms (1 day by default)
* @param {boolean} [options.disableWarnings=false] Whether to suppress warnings
* @param {string} [options.baseURL=https://api.steampowered.com] You may need to proxy requests through a different base url in order to get around CORS.
* @param {string} [options.baseStoreURL=https://store.steampowered.com/api] You may need to proxy requests through a different base url in order to get around CORS.
* @param {Object} [options.headers={}] Custom headers JSON object
*/
constructor(key, { enabled = true, expires = 86400000, disableWarnings = false, baseURL = 'https://api.steampowered.com', baseStoreURL = 'https://store.steampowered.com/api', headers = {} } = {}) {
this.key = key;
this.baseAPI = baseURL;
this.baseStore = baseStoreURL;
this.headers = headers;
this.options = { enabled, expires, disableWarnings };
this.resolveCache = new Map();
if (enabled) this.cache = new Map();
if (!key) this._warn('no key provided, some methods won\'t work, go get one from https://goo.gl/DfNy5s');
}
/**
* Prints a warning
* @param {...any} args Message
* @returns {void}
* @private
*/
_warn(...args) {
if (this.options.disableWarnings) return;
console.warn('[SteamAPI]', ...args);
}
/**
* Get custom path that isn't in SteamAPI.
* @param {string} path Path to request e.g '/IPlayerService/GetOwnedGames/v1?steamid=76561198378422474'
* @param {string} [base=this.baseAPI] Base URL
* @param {string} [key=this.key] The key to use
* @returns {Promise<Object>} JSON Response
*/
get(path, base = this.baseAPI, key = this.key) {
return fetch(`${base}${path}${path.includes('?') ? '&' : '?'}key=${key}`, { headers: this.headers }).then((response) => {
if (!response.ok) {
throw response;
}
return response.json();
});
}
/**
* Resolve info based on id, profile, or url.
* Rejects promise if a profile couldn't be resolved.
* @param {string} info Something to resolve e.g 'https://steamcommunity.com/id/xDim'
* @returns {Promise<string>} Profile ID
*/
resolve(info) {
if (!info) return Promise.reject(new TypeError('Invalid/no app provided'));
// community id match, ex. 76561198378422474
const communityIDMatch = info.match(reCommunityID);
if (communityIDMatch !== null)
return Promise.resolve(communityIDMatch[1]);
// url, https://steamcommunity.com/profiles/76561198378422474
const urlMatch = info.match(reProfileURL);
if (urlMatch !== null)
return Promise.resolve(urlMatch[1]);
// Steam 2: STEAM_0:0:209078373
const steamID2Match = info.match(reSteamID2);
if (steamID2Match !== null) {
const sid = new SteamID(steamID2Match[1]);
return Promise.resolve(sid.getSteamID64());
}
// Steam 3: [U:1:418156746]
const steamID3Match = info.match(reSteamID3);
if (steamID3Match !== null) {
const sid = new SteamID(steamID3Match[1]);
return Promise.resolve(sid.getSteamID64());
}
// vanity id, https://steamcommunity.com/id/xDim
const idMatch = info.match(reProfileID);
if (idMatch !== null) {
const id = idMatch[1];
if (this.resolveCache.has(id)) return Promise.resolve(this.resolveCache.get(id));
return this
.get(`/ISteamUser/ResolveVanityURL/v1?vanityurl=${id}`)
.then(json => json.response.success === STATUS_SUCCESS
? this.resolveCache.set(id, json.response.steamid).get(id)
: Promise.reject(new TypeError(json.response.message))
);
}
return Promise.reject(new TypeError('Invalid format'));
}
/**
* @typedef {App}
* @property {number} appid The app's ID
* @property {string} name The app's name
*/
/**
* Get every single app on steam.
* @returns {Promise<App[]>} Array of apps
*/
getAppList() {
return this
.get('/ISteamApps/GetAppList/v2')
.then(json => json.applist.apps);
}
/**
* Get featured categories on the steam store.
* @returns {Promise<Object[]>} Featured categories
*/
getFeaturedCategories() {
return this
.get('/featuredcategories', this.baseStore)
.then(Object.values);
}
/**
* Get featured games on the steam store
* @returns {Promise<Object>} Featured games
*/
getFeaturedGames() {
return this.get('/featured', this.baseStore);
}
/**
* Get achievements for app id.
* @param {string} app App ID
* @returns {Promise<Object>} App achievements for ID
*/
getGameAchievements(app) {
if (!reApp.test(app)) return Promise.reject(new TypeError('Invalid/no app provided'));
return this
.get(`/ISteamUserStats/GetGlobalAchievementPercentagesForApp/v2?gameid=${app}`)
.then(json => objectify(json.achievementpercentages.achievements, 'percent'));
}
/**
* Get details for app id.
* <warn>Requests for this endpoint are limited to 200 every 5 minutes</warn>
* @param {string} app App ID
* @param {boolean} [force=false] Overwrite cache
* @param {string} [region=us] Store region
* @returns {Promise<Object>} App details for ID
*/
getGameDetails(app, force = false, region = 'us') {
if (!reApp.test(app)) return Promise.reject(TypeError('Invalid/no app provided'));
if (!reRegion.test(region)) return Promise.reject(TypeError('Invalid region provided'));
const request = () => this
.get(`/appdetails?appids=${app}&cc=${region}`, this.baseStore)
.then(json => json[app].success
? json[app].data
: Promise.reject(new Error('No app found'))
);
const key = `${app}-${region.toLowerCase()}`;
if (!force && this.options.enabled && this.cache.has(key) && this.cache.get(key)[0] > Date.now())
return Promise.resolve(this.cache.get(key)[1]);
if (this.options.enabled && (!this.cache.has(key) || this.cache.get(key)[0] <= Date.now()))
return request().then(json => this.cache.set(key, [Date.now() + this.options.expires, json]) && json);
return request();
}
/**
* Get news for app id.
* @param {string} app App ID
* @returns {Promise<Object[]>} App news for ID
*/
getGameNews(app) {
if (!reApp.test(app)) return Promise.reject(new TypeError('Invalid/no app provided'));
return this
.get(`/ISteamNews/GetNewsForApp/v2?appid=${app}`)
.then(json => json.appnews.count ? json.appnews.newsitems : Promise.reject(new Error('No news found')));
}
/**
* Get number of current players for app id.
* @param {string} app App ID
* @returns {Promise<number>} Number of players
*/
getGamePlayers(app) {
if (!reApp.test(app)) return Promise.reject(new TypeError('Invalid/no app provided'));
return this
.get(`/ISteamUserStats/GetNumberOfCurrentPlayers/v1?appid=${app}`)
.then(json => json.response.result === 1 ? json.response.player_count : Promise.reject(new Error('No app found')));
}
/**
* Get schema for app id.
* @param {string} app App ID
* @returns {Promise<Object>} Schema
*/
getGameSchema(app) {
if (!reApp.test(app)) return Promise.reject(new TypeError('Invalid/no app provided'));
return this
.get(`/ISteamUserStats/GetSchemaForGame/v2?appid=${app}`)
.then(json => json.game ? json.game : Promise.reject(new Error('No app found')));
// TODO: Schema Class
}
/**
* Get every server associated with host.
* @param {string} host Host to request
* @returns {Promise<Server[]>} Server info
*/
getServers(host) {
if (!host) return Promise.reject(new TypeError('No host provided'));
return this
.get(`/ISteamApps/GetServersAtAddress/v1?addr=${host}`)
.then(json => json.response.success
? json.response.servers.map(server => new Server(server))
: Promise.reject(new Error(json.response.message))
);
}
/**
* Get users achievements for app id.
* @param {string} id User ID
* @param {string} app App ID
* @returns {Promise<PlayerAchievements>} Achievements
*/
getUserAchievements(id, app) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
if (!reApp.test(app)) return Promise.reject(new TypeError('Invalid/no appid provided'));
return this
.get(`/ISteamUserStats/GetPlayerAchievements/v1?steamid=${id}&appid=${app}&l=english`)
.then(json => json.playerstats.success
? new PlayerAchievements(json.playerstats)
: Promise.reject(new Error(json.playerstats.message))
);
}
/**
* Get users badges.
* @param {string} id User ID
* @returns {Promise<PlayerBadges>} Badges
*/
getUserBadges(id) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/IPlayerService/GetBadges/v1?steamid=${id}`)
.then(json => new PlayerBadges(json.response));
}
/**
* Get users bans.
* @param {string|string[]} id User ID(s)
* @returns {Promise<PlayerBans|PlayerBans[]>} Ban info
*/
getUserBans(id) {
const arr = Array.isArray(id);
if ((arr && id.some(i => !reID.test(i))) || (!arr && !reID.test(id))) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/ISteamUser/GetPlayerBans/v1?steamids=${id}`)
.then(json => json.players.length
? arr
? json.players.map(player => new PlayerBans(player))
: new PlayerBans(json.players[0])
: Promise.reject(new Error('No players found'))
);
}
/**
* Get users friends.
* @param {string} id User ID
* @returns {Promise<Friend[]>} Friends
*/
getUserFriends(id) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/ISteamUser/GetFriendList/v1?steamid=${id}`)
.then(json => json.friendslist.friends.map(friend => new Friend(friend)));
}
/**
* Get users groups.
* @param {string} id User ID
* @returns {Promise<string[]>} Groups
*/
getUserGroups(id) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/ISteamUser/GetUserGroupList/v1?steamid=${id}`)
.then(json => json.response.success
? json.response.groups.map(group => group.gid)
: Promise.reject(new Error(json.response.message))
);
}
/**
* Get users level.
* @param {string} id User ID
* @returns {Promise<number>} Level
*/
getUserLevel(id) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/IPlayerService/GetSteamLevel/v1?steamid=${id}`)
.then(json => json.response.player_level);
}
/**
* Get users owned games.
* @param {string} id User ID
* @returns {Promise<Game[]>} Owned games
*/
getUserOwnedGames(id) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/IPlayerService/GetOwnedGames/v1?steamid=${id}&include_appinfo=1`)
.then(json => json.response.games ? json.response.games.map(game => new Game(game)) : Promise.reject(new Error('No games found')));
}
/**
* Get users recent games.
* @param {string} id User ID
* @returns {Promise<RecentGame[]>} Recent games
*/
getUserRecentGames(id) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/IPlayerService/GetRecentlyPlayedGames/v1?steamid=${id}`)
.then(json => json.response.total_count ? json.response.games.map(game => new RecentGame(game)) : []);
}
/**
* Gets servers on steamcommunity.com/dev/managegameservers using your key or provided key.
* @param {boolean} [hide=false] Hide deleted/expired servers
* @param {string} [key=this.key] Key
* @returns {Promise<PlayerServers>} Servers
*/
getUserServers(hide = false, key) {
return this
.get('/IGameServersService/GetAccountList/v1', this.baseAPI, key)
.then(json => new PlayerServers(json.response, hide));
}
/**
* Get users stats for app id.
* @param {string} id User ID
* @param {string} app App ID
* @returns {Promise<PlayerStats>} Stats for app id
*/
getUserStats(id, app) {
if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
if (!reApp.test(app)) return Promise.reject(new TypeError('Invalid/no app provided'));
return this
.get(`/ISteamUserStats/GetUserStatsForGame/v2?steamid=${id}&appid=${app}`)
.then(json => json.playerstats ? new PlayerStats(json.playerstats) : Promise.reject(new Error('No player found')));
}
/**
* Get users summary.
* @param {string} id User ID
* @returns {Promise<PlayerSummary>} Summary
*/
getUserSummary(id) {
const arr = Array.isArray(id);
if ((arr && id.some(i => !reID.test(i))) || (!arr && !reID.test(id))) return Promise.reject(new TypeError('Invalid/no id provided'));
return this
.get(`/ISteamUser/GetPlayerSummaries/v2?steamids=${id}`)
.then(json => json.response.players.length
? arr
? json.response.players.map(player => new PlayerSummary(player))
: new PlayerSummary(json.response.players[0])
: Promise.reject(new Error('No players found'))
);
}
}
module.exports = SteamAPI;