Giter Site home page Giter Site logo

xdimgg / node-steamapi Goto Github PK

View Code? Open in Web Editor NEW
171.0 10.0 42.0 229 KB

An object-oriented Steam API wrapper for Node.js developers.

Home Page: https://www.npmjs.com/package/steamapi

JavaScript 5.91% TypeScript 94.09%
node nodejs api-wrapper steam-api object-oriented promise

node-steamapi's Introduction

SteamAPI

NPM Discord

Documentation

A list of all the methods SteamAPI provides is available here.

Breaking changes from 2.x to 3.x

  • CommonJS Modules -> ES Modules
  • Import using import statement instead of require()
  • SteamAPI constructor now takes false as the first parameter if you don't want to supply a key
  • Options for constructor have changes from { enabled, expires, disableWarnings } to { language, currency, headers, baseAPI, baseStore, baseActions, inMemoryCacheEnabled, gameDetailCacheEnabled, gameDetailCacheTTL, userResolveCacheEnabled, userResolveCacheTTL }
  • Custom caching may be enabled by setting inMemoryCacheEnabled: false and setting <SteamAPI>.gameDetailCache/<SteamAPI>.userResolveCache. Must implement CacheMap<K, V> interface in src/Cache.ts
  • getFeaturedGames() returns object instead of array
  • Server#game -> Server#gameDir
  • App IDs are passed as numbers not strings (although a string will probably still work)
  • Any other changes to the API can be found in https://github.com/xDimGG/node-steamapi/blob/master/PORT.md

Setup

Installation

npm i steamapi

Getting an API Key

Once signed into Steam, head over to http://steamcommunity.com/dev/apikey to generate an API key.

Usage

First, we start by making a SteamAPI "user".

import SteamAPI from 'steamapi';

const steam = new SteamAPI('steam token');

Now, we can call methods on the steam object.

For example, let's retrieve the SteamID64 of a user. SteamAPI provides a resolve method, which accepts URLs and IDs.

steam.resolve('https://steamcommunity.com/id/DimGG').then(id => {
	console.log(id); // 76561198146931523
});

Now let's take that ID, and fetch the user's profile.

steam.getUserSummary('76561198146931523').then(summary => {
	console.log(summary);
	/**
	UserSummary {
		steamID: '76561198146931523',
		avatar: {
			small: 'https://avatars.steamstatic.com/7875e33529232d95cad28ea1054897618907fa03.jpg',
			medium: 'https://avatars.steamstatic.com/7875e33529232d95cad28ea1054897618907fa03_medium.jpg',
			large: 'https://avatars.steamstatic.com/7875e33529232d95cad28ea1054897618907fa03_full.jpg',
			hash: '7875e33529232d95cad28ea1054897618907fa03'
		},
		url: 'https://steamcommunity.com/id/DimGG/',
		visible: true,
		personaState: 0,
		personaStateFlags: 0,
		allowsComments: true,
		nickname: 'dim',
		lastLogOffTimestamp: 1704553877,
		createdTimestamp: 1406393110,
		realName: undefined,
		primaryGroupID: '103582791457347196',
		gameID: undefined,
		gameName: undefined,
		gameServerIP: undefined,
		gameServerID: undefined,
		countryCode: 'US',
		stateCode: undefined,
		cityID: undefined
	}
	*/
});

node-steamapi's People

Contributors

4aiman avatar aquilasagitta avatar bwbellairs avatar chescos avatar cosmoledo avatar falsejoey avatar hollarves avatar jankcat avatar lloti avatar patkub avatar scui1 avatar xdimgg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-steamapi's Issues

Don't Promise.reject causing server error 500. console.error instead

Hi! ๐Ÿ‘‹

Firstly, thanks for your work on this project! ๐Ÿ™‚

Today I used patch-package to patch [email protected] for the project I'm working on.

Here is the diff that solved my problem:

diff --git a/node_modules/steamapi/src/SteamAPI.js b/node_modules/steamapi/src/SteamAPI.js
index d479a8b..f291dc4 100644
--- a/node_modules/steamapi/src/SteamAPI.js
+++ b/node_modules/steamapi/src/SteamAPI.js
@@ -291,7 +291,7 @@ class SteamAPI {
 	 */
 	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'));
+		if ((arr && id.some(i => !reID.test(i))) || (!arr && !reID.test(id))) return console.error('SteamAPI: Invalid/no id provided');
 
 		return this
 			.get(`/ISteamUser/GetPlayerBans/v1?steamids=${id}`)
@@ -299,7 +299,7 @@ class SteamAPI {
 				? arr
 					? json.players.map(player => new PlayerBans(player))
 					: new PlayerBans(json.players[0])
-				: Promise.reject(new Error('No players found'))
+				: console.error('SteamAPI: No players found')
 			);
 	}
 
@@ -351,11 +351,11 @@ class SteamAPI {
 	 * @returns {Promise<Game[]>} Owned games
 	 */
 	getUserOwnedGames(id) {
-		if (!reID.test(id)) return Promise.reject(new TypeError('Invalid/no id provided'));
+		if (!reID.test(id)) return console.error('SteamAPI: 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')));
+			.then(json => json.response.games ? json.response.games.map(game => new Game(game)) : console.error('SteamAPI: No games found'));
 	}
 
 	/**

This issue body was partially generated by patch-package.

TypeError: arr is not iterable

I can not seem to get around to the getUserStats()

I receive TypeError: arr is not iterable when I do the following:

this.pubgStats = await steamAPI.getUserStats(this.steamID, 578080));

Idea: Add Game Leaderboard checks

Currently it looks like you're only able to check leaderboard stats on a user-to-user basis, but being able to pull information directly from a leaderboard as a object list of players including nickname and score values would be nice.

Example for the game Rivals of Aether:

appID = 383980;
leaderboardID = 2222327;

the code for checking a leaderboard would probably be something like:

steam.getGameLeaderboards(appID).then(lb => {
console.log(lb); //object list of leaderboard ID's for game
});

steam.getGameLeaderboard(leaderboardID, appID).then(lb => {
/* lb would be an object of players in the top 200 list on the leaderboard */
console.log(lb.player[0].nickname, lb.player[0].score); //expected output: "No Love Deep Weeb, 2386"
});

The leaderboardID corresponds to "Ranked Season 3 Leaderboard" for the game "Rivals of Aether".
Under user stats, a player's score on this leaderboard is listed as player.stats.ranked3_score for that player. Link to leaderboard here: https://steamcommunity.com/stats/383980/leaderboards/2222327

TypeError: Cannot read property 'map' of undefined

TypeError: Cannot read property 'map' of undefined
at get.then.json (D:\Apps\coop\functions\node_modules\steamapi\src\SteamAPI.js:326:38)
at process._tickCallback (internal/process/next_tick.js:68:7)

This was working earlier. Suddenly broke?

Broken promises!

Hey,
In your resolve function, if the ID is already in the resolvecache, it will break on a page refresh as it's not resolving the promise.
if (this.resolveCache.has(id)) return this.resolveCache.get(id);

steamClient.resolve profile ids that shouldn't exist into profiles

        const SteamAPI = require('steamapi');
        const steamClient = new SteamAPI("steamapikey");

        const steam64IDFromSteamURL = await steamClient.resolve("steamcommunity.com/profiles/fluted");
        console.log(steam64IDFromSteamURL);

Result: 76561197981967565 --> random guy "( ยค Konzer ยค )"

steamcommunity.com/profiles links should always follow with numbers afaik

TypeError: Cannot read property 'disableWarnings' of undefined

/DraftBot/node_modules/steamapi/src/SteamAPI.js:51
            if (this.options.disableWarnings) return;
                             ^

/DraftBot/node_modules/steamapi/src/SteamAPI.js:51
TypeError: Cannot read property 'disableWarnings' of undefined
at SteamAPI._warn (/home/Alpha/DraftBot/node_modules/steamapi/src/SteamAPI.js:51:20)
at new SteamAPI (/home/Alpha/DraftBot/node_modules/steamapi/src/SteamAPI.js:34:38)
at Object. (/home/Alpha/DraftBot/commands/leaderboards/brawlhalla.js:8:15)
at Module._compile (internal/modules/cjs/loader.js:734:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:745:10)
at Module.load (internal/modules/cjs/loader.js:626:32)
at tryModuleLoad (internal/modules/cjs/loader.js:566:12)
at Function.Module._load (internal/modules/cjs/loader.js:558:3)
at Module.require (internal/modules/cjs/loader.js:663:17)
at require (internal/modules/cjs/helpers.js:20:18)

nodejs -v
v11.9.0
npm -v
6.5.0

Debian 9

Count parameter on GetRecentlyPlayedGames

Looks like GetRecentlyPlayedGames supports a optional count parameter which is not implemented

From Steam docs:

GetRecentlyPlayedGames returns a list of games a player has played in the last two weeks, if the profile is publicly visible. Private, friends-only, and other privacy settings are not supported unless you are asking for your own personal details (ie the WebAPI key you are using is linked to the steamid you are requesting).
    steamid
        The SteamID of the account.
    count
        Optionally limit to a certain number of games (the number of games a person has played in the last 2 weeks is typically very small)

Network errors crash node.js and cannot be caught

Sometimes when using node-steamapi, a network error will crash the entire node.js process:

events.js:292
      throw er; // Unhandled 'error' event
      ^

Error: getaddrinfo ENOTFOUND api.steampowered.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:66:26)
Emitted 'error' event on ClientRequest instance at:
    at TLSSocket.socketErrorListener (_http_client.js:432:9)
    at TLSSocket.emit (events.js:315:20)
    at emitErrorNT (internal/streams/destroy.js:84:8)
    at processTicksAndRejections (internal/process/task_queues.js:84:21) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'api.steampowered.com'
}

We have the following Express route which uses node-steamapi. It takes a valid steam id as a query parameter, calls steam.resolve, then uses steam.getUserSummary and replies back with json containing the summary. Wrapping the code in a try...catch block doesn't help.
Example request: http://localhost:3000/summary?id=76561197989862681

router.get('/summary', function(req, res) {
    // get steam id as query parameters
    const pId = steam.resolve(req.query.id)
    pId.then((id) => {
        // get user summary
        const pSummary = steam.getUserSummary(id)
        pSummary.then((summary) => {
            // return summary data
            return res.status(200).json({
                status: "success",
                data: summary,
            })
        })
        .catch((reason) => {
            // error getting user summary
            return res.status(404).json({
                status: "error",
                message: reason.message,
            })
        })
    })
    .catch((reason) => {
        // error getting user summary
        return res.status(404).json({
            status: "error",
            message: reason.message,
        })
    })
});

cc: @ww2497 @ZacharyCChang0828 @mss6522

[RegExps] Regular Expressions permit invalid strings

I'm happy to write up PRs for this and any issue I add here, just let me know and I'll get to work

Specifically, reProfileURL and reProfileID.

I've created a test for the reProfileURL regex, as well as some potential alternatives.

The processed results are as follows;

{
  "reProfileURL": {
    "falsePositives": 27,
    "falseNegatives": 0
  },
  "minimalist_same_behavior": {
    "falsePositives": 27,
    "falseNegatives": 0
  },
  "minimalist_fixed_id_length": {
    "falsePositives": 9,
    "falseNegatives": 0
  },
  "strict_fixed": {
    "falsePositives": 0,
    "falseNegatives": 0
  }
}

Checking for false negatives here ensures that the regular expressions don't over-restrict what they will and will not match (this in fact was crucial in creating the fixed id length and strict RegExps).

The test includes 18 permutations which ought to pass, and 29 which ought to fail.

Expected passes;

  • 6x urls missing segments from left
  • 6x urls missing segments from left with paths
  • 6x urls missing segments from left with url params

Expected fails;

  • 6x urls with invalid segments
  • 5x urls with invalid segments, all segments prior to invalid removed
  • 6x urls missing segments from left, id too long
  • 6x urls missing segments from left with paths, id too long
  • 6x urls missing segments from left with url params, id too long

The strict_fixed regex with no false flags is as follows;

/^(?:(?:(?:(?:https?)?:\/\/)?(?:www\.)?steamcommunity\.com)?)?\/?profiles\/(\d{17})(?:(?:\?|\/).*)?$/i;

Issue with regex for resolving vanity URLs

When using the .resolve method and trying to resolve say DimGG's profile instead of getting his SteamID (76561198146931523) I get 76561197961425533. Which happened to be a profile named https. I've found that the regex is removing everything behind the first set of characters (https) instead of everything in front of the users profile name. I've been able to fix it by changing the regex but I'm not 100% certain that this is how the code is intended to work.

I replaced this

async resolve(info) {
	if (!info) throw new Error('no info provided');
	let steamID, steamURL;
	if (/^(?:\/?profiles\/)?\d{17}.*$/.test(info)) {
		steamID = info.replace(/^(?:\/?profiles\/)?(\d{17}).*$/, '$1');
	} else if (/^(?:\/?id\/)?\w{2,32}.*$/.test(info)) {
		steamURL = info.replace(/^(?:\/?id\/)?(\w{2,32}).*$/, '$1');
	} else {

With this, in SteamAPI.js

async resolve(info) {
	if (!info) throw new Error('no info provided');
	let steamID, steamURL;
	if (/(\/profiles\/)/.test(info)) {
		steamID = info.replace(/.*?(\/profiles\/)/, '');
	} else if (/(\/id\/)/.test(info)) {
		steamURL = info.replace(/.*?(\/id\/)/, '');
	} else {

Error: Internal Server Error

I receive this error specifically when I try and use getUserStats method. The others seem to work fine.

Code

const SteamAPI = require('steamapi');
const steam = new SteamAPI('APIKEY IS  HERE');

steam.getUserStats('76561198259915581', '730').then((data) => {
    console.log(data);
});

Error

(node:24892) UnhandledPromiseRejectionWarning: Error: Internal Server Error
    at IncomingMessage.<anonymous> (C:\Users\Zack\Documents\Scripting Projects\Steam-API\node_modules\steamapi\src\utils\fetch.js:28:47)
    at Object.onceWrapper (events.js:420:28)
    at IncomingMessage.emit (events.js:326:22)
    at endReadableNT (_stream_readable.js:1226:12)
    at processTicksAndRejections (internal/process/task_queues.js:80:21)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:24892) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, 
or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:24892) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Cross-Origin Request Blocked when resolving usernames via React.

I was trying to use this library in a React application to query the games shared by a list of usernames but I seem to be getting blocked by CORS when resolving the usernames into IDs. The full error is as follows:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.steampowered.com/ISteamUser/ResolveVanityURL/v1?vanityurl=<username>&key=<apikey>. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

I tried force-enabling CORS with this addon and the application worked as expected.

Here's the code I'm running:

import SteamAPI from "steamapi";

let steam;

const getUserGames = async (username) => {
  const userID = await steam.resolve(
    `https://steamcommunity.com/id/${username}/`,
  );
  return (await steam.getUserOwnedGames(userID)).map((game) => game.name);
};

export const getIntersect = async (apiKey, usernames) => {
  steam = new SteamAPI(apiKey);
  
  const games = await Promise.all(usernames.map(getUserGames));

  return games.reduce((a, b) => a.filter((game) => b.includes(game))).sort();
};

[semantics][non-issue(?)] GetUserRecentGames: edge case on count

Relevant: 6447177

By checking count for truthiness, count=0 is excluded.

I realise that a user passing in count=0 is probably making a mistake rather than requesting no response, though semantically this check ought to be a null check rather than a truthy one. The effects are the same, as it appears Steam servers are also performing a truthy check (...&count=0 has the same response as an unspecified count); this is purely semantic.

const countInterpolation = count === null || count === undefined ? "" : `count=${count}`;
// ... interpolate the above constant / add the above expression as interpolation

or

const shouldInterpolateCount = (count ?? undefined) === undefined;
// ...
shouldInterpolateCount ? `count=${count}` : ""

Incorrect example for `objectify.js`

Relevant:

* * { a: { value: 'b', other: 'c' }, b: { value: 'c', other: 'd' } }

The example in the JSDoc states the following as an example

[{ name: 'a', value: 'b', other: 'c' }, { name: 'b', value: 'c', other: 'd' }]
// To
{ a: { value: 'b', other: 'c' }, b: { value: 'c', other: 'd' } }

Using a copy-paste of the function to more easily dump this into the Node repl;

Welcome to Node.js v18.16.0.
Type ".help" for more information.
> const objectify = (arr, val = 'value', key = 'name', boolean = false) => {
...     const object = {};
...     for (const obj of arr)
...             object[obj[key]] = boolean ? Boolean(obj[val]) : obj[val];
... 
...     return object;
... };
undefined
> 
> objectify([{ name: 'a', value: 'b', other: 'c' }, { name: 'b', value: 'c', other: 'd' }]);
{ a: 'b', b: 'c' }

The stated output is

{ a: { value: 'b', other: 'c' }, b: { value: 'c', other: 'd' } }

The actual output is

{ a: 'b', b: 'c' }

Is the doccomment example incorrect, or is the function incorrectly implemented? Judging by the args I'd guess the example is incorrect, but it's difficult to tell without documentation.

CORS modification support

I've been trying to get around this error:

No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

It would be cool if we were able to modify the headers of the request.

Update imports in fetch.js to use Webpack 5 compatiable polyfills

Webpack 5 no longer automatically adds polyfills for the following imports in fetch.js

const { createDeflate, createGunzip } = require('zlib');
const { parse } = require('url');
const https = require('https');
const http = require('http');

This makes life very difficult for angular 13+

I think all that is needed is to run:

npm install browserify-zlib
npm install stream-http
npm install https-browserify
npm install url

and then update the imports in fetch.js to:

const { createDeflate, createGunzip } = require('browserify-zlib');
const { parse } = require('url');
const https = require('https-browserify');
const http = require('stream-http');

ETIMEDOUT not handeled error

In fetch.js file I think there is a mistype:
op.once('errror', reject); should be op.once('error', reject); (There is an extra 'r' in 'error'). But thats not the main issue I want to address here.

After performing few thousand of request occasionally I get:

Error: connect ETIMEDOUT 92.122.156.90:443 at Object._errnoException (util.js:992:11) at _exceptionWithHostPort (util.js:1014:20) at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1186:14)
I have fixed this by catching the error after fetch in fetch.js

return new Promise((resolve, reject) => {
		fetch(options, res => {
			const ce = res.headers['content-encoding'];
			let data = '';
			let op = res;
			if (ce === 'deflate') res.pipe(op = createDeflate());
			if (ce === 'gzip') res.pipe(op = createGunzip());

			op.on('data', chunk => data += chunk);
			op.on('error', reject);
			op.once('end', () => {
				if (res.statusCode === 500) return reject(new Error('Internal server error'));
				if (res.statusCode === 403) return reject(new Error('Invalid key'));
				if (res.statusCode === 400) return reject(new Error(reg.test(data) ? data.match(reg)[1] : data));
				if (res.statusCode !== 200) return reject(new Error(res.statusMessage));
				try {
					resolve(JSON.parse(data));
				} catch (_) {
					reject(new Error(data));
				}
			});
		}).on("error", function(error){
			console.log(error);
		});
	});

Question: Is this lib still mantained?

I sent a couple of PRs a week ago, but no response so far :(

Since I'm using this lib for a project I wouldn't mind helping maintain it.

What do you guys say?

Incorrect Header Check on Apps List

I tried to fetch my apps list and got this error. The request itself returned 200 OK and the data I needed.

Uncaught (in promise) Error: incorrect header check
at Zlib._handle.onerror (index.js:361)
at Zlib.push../node_modules/browserify-zlib/lib/binding.js.Zlib._error (binding.js:290)
at Zlib.push../node_modules/browserify-zlib/lib/binding.js.Zlib._checkError (binding.js:261)
at Zlib.push../node_modules/browserify-zlib/lib/binding.js.Zlib._after (binding.js:270)
at binding.js:124
at Item.push../node_modules/node-libs-browser/node_modules/process/browser.js.Item.run (browser.js:167)
at drainQueue (browser.js:131)

TypeError: No match

C:\bot\node_modules\steamapi\src\SteamAPI.js:117
: Promise.reject(new TypeError(json.response.message))
^

TypeError: No match
at C:\bot\node_modules\steamapi\src\SteamAPI.js:117:23
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Node.js v19.1.0
NPM v9.2.0

i think it's from this code:

s[1] = SteamName

steam.resolve(id/${s[1]}/).then(id => {
console.log(id: ${id})

Any ideas on what's is up ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.