Skip to content

Commit bfc4433

Browse files
authored
Fixed tag fetching, now requires Steam login (#60)
1 parent 00e29db commit bfc4433

8 files changed

Lines changed: 645 additions & 448 deletions

File tree

README.md

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ The schema can be found in the `config.schema.json` file and used within your `c
4848

4949
*NOTE: The script will test your provided `config.json` against this schema, so make sure your configuration is valid.*
5050

51+
Note that *only if* you want to fetch tag data, Steam requires you to be authenticated with your Steam account.
52+
For this, provide your Steam account name and password in the `steamUser` property of the configuration file.
53+
The integration does not transmit this data over the internet, everything is done locally.
54+
If you have Steam Guard enabled, you will also need to provide the Steam Guard code when running the integration.
55+
56+
Once you have authenticated once, the integration will store a local refresh token for ~200 days before you need to log in again.
57+
You can then replace the `accountName` and `password` with the `useRefreshToken` property set to `true`, which will make the integration use the stored refresh token to authenticate you to the Steam API.
58+
5159
### Properties
5260

5361
The following is a list of all configuration items, their defaults and the values they can take.
@@ -114,6 +122,18 @@ If true, the integration will always update entries in the Notion database that
114122
| `boolean` | `false` | `true` or `false` | No |
115123
</details>
116124

125+
<details>
126+
<summary><code>steamUser</code></summary>
127+
128+
Login details to authenticate to your Steam account. This is required to be able to fetch tag data. If you have Steam Guard enabled, you will need to provide the Steam Guard code when running the app. Once logged in, the integration will store a local refresh token for ~200 days before you need to log in again. The integration does not transmit any of your login details anywhere, they are used internally to authenticate yourself to the Steam API. If you do not want to fetch tag data, you do not need to provide this property!
129+
130+
| Type | Default value | Possible values | Required |
131+
|---|---|---|---|
132+
| `useRefreshToken` | `true` | `true` only | Yes, if you want to use an already stored refresh token. This requires a previous login using `accountName` and `password`. |
133+
| `accountName` | `<steamAccountName>` | Your Steam account name | Yes, if you do not already have a stored refresh token and set `useRefreshToken` to `true`. |
134+
| `password` | `<steamAccountPassword>` | Your Steam account password | Yes, if you do not already have a stored refresh token and set `useRefreshToken` to `true`. |
135+
</details>
136+
117137
<details>
118138
<summary><code>gameProperties</code></summary>
119139

@@ -143,7 +163,8 @@ Which game properties should be fetched when a new Steam game is detected, and t
143163
},
144164
"tags": {
145165
"enabled": true,
146-
"notionProperty": "Tags"
166+
"notionProperty": "Tags",
167+
"tagLanguage": "english"
147168
}
148169
}
149170
```
@@ -347,7 +368,7 @@ The name of the Notion property to set the user review score in.
347368
<details>
348369
<summary><code>tags</code></summary>
349370

350-
The user-defined tags of the game as they can be seen on the store page. The database field in Notion must be of type `Multi-select`.
371+
Requires Steam login! Provide accountName and password in the top-level `steamUser` property! The user-defined tags of the game as they can be seen on the store page. The database field in Notion must be of type `Multi-select`.
351372

352373
| Type | Default value | Possible values | Required |
353374
|---|---|---|---|
@@ -357,7 +378,7 @@ The user-defined tags of the game as they can be seen on the store page. The dat
357378
"tags": {
358379
"enabled": true,
359380
"notionProperty": "Tags",
360-
"language": "english"
381+
"tagLanguage": "english"
361382
}
362383
```
363384

@@ -379,13 +400,13 @@ The name of the Notion property to set the tags in. This field must be of type `
379400
|---|---|---|---|
380401
| `string` | `"Tags"` | A valid Notion property name | Yes |
381402

382-
<h4><code>language</code></h4>
403+
<h4><code>tagLanguage</code></h4>
383404

384405
The language of the tags, e.g. "english" or "spanish".
385406

386407
| Type | Default value | Possible values | Required |
387408
|---|---|---|---|
388-
| `string` | `"english"` | Valid language names. Invalid names return an error from the Steam API. | Yes |
409+
| `string` | `"english"` | Valid full language names. Invalid names return an error from the Steam API. | Yes |
389410

390411
</details>
391412

config.default.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
"steamAppIdProperty": "Steam App ID",
77
"forceReset": false,
88
"alwaysUpdate": false,
9+
"steamUser": {
10+
"accountName": "<steamAccountName>",
11+
"password": "<steamPassword>"
12+
},
913
"gameProperties": {
1014
"gameName": {
1115
"enabled": true,

config.schema.json

Lines changed: 95 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,50 @@
4141
"type": "boolean",
4242
"default": false
4343
},
44+
"steamUser": {
45+
"description": "Login details to authenticate to your Steam account. This is required to be able to fetch tag data. If you have Steam Guard enabled, you will need to provide the Steam Guard code when running the app. Once logged in, the integration will store a local refresh token for ~200 days before you need to log in again. The integration does not transmit any of your login details anywhere, they are used internally to authenticate yourself to the Steam API. If you do not want to fetch tag data, you do not need to provide this property!",
46+
"type": "object",
47+
"default": {
48+
"accountName": "<steamAccountName>",
49+
"password": "<steamAccountPassword>"
50+
},
51+
"additionalProperties": false,
52+
"properties": {
53+
"useRefreshToken": {
54+
"description": "If true, the integration will try to use the refresh token stored in the local database to authenticate to your Steam account. You can only set this to \"true\" if you have previously logged in using \"accountName\" and \"password\", and the refresh token is still valid.",
55+
"type": "boolean",
56+
"default": false
57+
},
58+
"accountName": {
59+
"description": "Your Steam account name.",
60+
"type": "string",
61+
"default": "<steamAccountName>"
62+
},
63+
"password": {
64+
"description": "Your Steam account password.",
65+
"type": "string",
66+
"default": "<steamAccountPassword>"
67+
}
68+
},
69+
"oneOf": [
70+
{
71+
"required": [
72+
"accountName",
73+
"password"
74+
]
75+
},
76+
{
77+
"properties": {
78+
"useRefreshToken": {
79+
"const": true
80+
}
81+
},
82+
"required": [
83+
"useRefreshToken"
84+
]
85+
}
86+
]
87+
},
4488
"gameProperties": {
4589
"description": "Which game properties should be fetched when a new Steam game is detected, and the name of the corresponding field in the Notion database.",
4690
"type": "object",
@@ -69,7 +113,8 @@
69113
},
70114
"tags": {
71115
"enabled": true,
72-
"notionProperty": "Tags"
116+
"notionProperty": "Tags",
117+
"tagLanguage": "english"
73118
},
74119
"gameDescription": {
75120
"enabled": true,
@@ -227,30 +272,30 @@
227272
"type": "string",
228273
"default": "percentage",
229274
"oneOf": [
230-
{
231-
"const": "percentage",
232-
"title": "Notion database field type: \"Number\". A percentage value formatted as a float from 0.00-1.00."
233-
},
234-
{
235-
"const": "sentiment",
236-
"title": "Notion database field type: \"Select\". A sentiment value such as \"Overwhelmingly Positive\" or \"Mixed\"."
237-
},
238-
{
239-
"const": "total",
240-
"title": "Notion database field type: \"Number\". The total number of reviews submitted for the game, across all languages."
241-
},
242-
{
243-
"const": "positive",
244-
"title": "Notion database field type: \"Number\". The total number of positive reviews submitted for the game, across all languages."
245-
},
246-
{
247-
"const": "negative",
248-
"title": "Notion database field type: \"Number\". The total number of negative reviews submitted for the game, across all languages."
249-
},
250-
{
251-
"const": "positive/negative",
252-
"title": "Notion database field type: \"Text\". The total number of positive and negative reviews submitted for the game, across all languages, formatted as \"{numPositive} positive / {numNegative} negative\"."
253-
}
275+
{
276+
"const": "percentage",
277+
"title": "Notion database field type: \"Number\". A percentage value formatted as a float from 0.00-1.00."
278+
},
279+
{
280+
"const": "sentiment",
281+
"title": "Notion database field type: \"Select\". A sentiment value such as \"Overwhelmingly Positive\" or \"Mixed\"."
282+
},
283+
{
284+
"const": "total",
285+
"title": "Notion database field type: \"Number\". The total number of reviews submitted for the game, across all languages."
286+
},
287+
{
288+
"const": "positive",
289+
"title": "Notion database field type: \"Number\". The total number of positive reviews submitted for the game, across all languages."
290+
},
291+
{
292+
"const": "negative",
293+
"title": "Notion database field type: \"Number\". The total number of negative reviews submitted for the game, across all languages."
294+
},
295+
{
296+
"const": "positive/negative",
297+
"title": "Notion database field type: \"Text\". The total number of positive and negative reviews submitted for the game, across all languages, formatted as \"{numPositive} positive / {numNegative} negative\"."
298+
}
254299
]
255300
},
256301
"notionProperty": {
@@ -266,7 +311,7 @@
266311
]
267312
},
268313
"tags": {
269-
"description": "The user-defined tags of the game as they can be seen on the store page. The database field in Notion must be of type \"Multi-select\".",
314+
"description": "Requires Steam login! Provide \"accountName\" and \"password\" in the top-level \"steamUser\" property, \"refreshToken\" if you already logged in before! The user-defined tags of the game as they can be seen on the store page. The database field in Notion must be of type \"Multi-select\".",
270315
"type": "object",
271316
"default": {
272317
"enabled": true,
@@ -456,5 +501,29 @@
456501
"updateInterval",
457502
"steamAppIdProperty",
458503
"gameProperties"
504+
],
505+
"allOf": [
506+
{
507+
"if": {
508+
"properties": {
509+
"gameProperties": {
510+
"properties": {
511+
"tags": {
512+
"properties": {
513+
"enabled": {
514+
"const": true
515+
}
516+
}
517+
}
518+
}
519+
}
520+
}
521+
},
522+
"then": {
523+
"required": [
524+
"steamUser"
525+
]
526+
}
527+
}
459528
]
460529
}

index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ async function updateNotionDatabase() {
9393
// Get info about the game's review score from the reviews API, if required
9494
const appInfoReviews = reviewAPIRequired
9595
? await getSteamReviewScoreDirect(steamAppId)
96-
: null;
96+
: null;
9797

9898
let notionProperties = await getGameProperties(appInfoDirect, appInfoSteamUser[steamAppId], appInfoReviews, steamAppId);
9999

@@ -106,7 +106,7 @@ async function updateNotionDatabase() {
106106
}
107107
}
108108
}
109-
109+
110110
// Only update the last updated time if there were no errors during execution and we didn't hit the Steam API request limit
111111
// This makes sure that we can find the games that had errors or that we had to omit again the next time
112112
if (!hadError && !hitSteamAPILimit) {
@@ -124,4 +124,4 @@ async function updateNotionDatabase() {
124124
// Run this method again in `updateInterval` milliseconds
125125
setTimeout(main, updateInterval);
126126
}
127-
}
127+
}

js/steamAPI.js

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,40 @@
11
import SteamUser from 'steam-user';
2+
import { CONFIG, addRefreshTokenToLocalDatabase, getRefreshTokenFromLocalDatabase, steamUserLoginRequired } from './utils.js';
23

34
// ---------- Steam API ----------
45

5-
let steamClient = new SteamUser();
6-
steamClient.logOn();
6+
let steamUserConfig = {};
7+
8+
if (steamUserLoginRequired) {
9+
let refreshToken = await getRefreshTokenFromLocalDatabase();
10+
if (refreshToken) {
11+
steamUserConfig = {
12+
refreshToken: refreshToken,
13+
};
14+
} else
15+
if (CONFIG.steamUser.accountName && CONFIG.steamUser.password) {
16+
steamUserConfig = {
17+
accountName: CONFIG.steamUser.accountName,
18+
password: CONFIG.steamUser.password,
19+
}
20+
} else {
21+
console.error("\"steamUser.useRefreshToken\" was provided in the config, but no refresh token found in local database. \"steamUser.accountName\" and \"steamUser.password\" are required in the config, but they were not provided!");
22+
process.exit(1);
23+
}
24+
} else {
25+
steamUserConfig = {
26+
anonymous: true
27+
};
28+
}
29+
30+
let steamClient = new SteamUser({ renewRefreshTokens: true });
31+
32+
steamClient.on('refreshToken', async function (refreshToken) {
33+
await addRefreshTokenToLocalDatabase(refreshToken);
34+
});
35+
36+
console.log("Logging in to Steam", steamUserConfig.anonymous ? "anonymously..." : (steamUserConfig.accountName ? `as ${steamUserConfig.accountName}...` : "using a refresh token..."));
37+
steamClient.logOn(steamUserConfig);
738
await new Promise(resolve => steamClient.on('loggedOn', resolve));
839

940
// Gets app info directly from the Steam store API
@@ -54,13 +85,13 @@ export async function getSteamAppInfoSteamUser(appIds) {
5485
// Gets the current review score data for a game from the Steam reviews API
5586
export async function getSteamReviewScoreDirect(appId) {
5687
const result = await fetch(`https://store.steampowered.com/appreviews/${appId}?json=1&language=all`)
57-
.then(response => response.json())
58-
.then(data => {
59-
if (data?.success) {
60-
return data.query_summary;
61-
}
62-
return null;
63-
});
88+
.then(response => response.json())
89+
.then(data => {
90+
if (data?.success) {
91+
return data.query_summary;
92+
}
93+
return null;
94+
});
6495

6596
return result;
6697
}
@@ -77,10 +108,10 @@ export async function getSteamTagNames(storeTags, tagLanguage) {
77108
const result = Object.keys(response.tags).map(function (key) {
78109
return response.tags[key].name;
79110
});
80-
111+
81112
resolve(result);
82113
} catch (error) {
83-
console.log("Retrieving tag names failed. The most likely cause is that the language you provided is invalid and does not yield any results from the Steam API.");
114+
console.log("Retrieving tag names failed! The most likely cause is that you have not provided the \"steamUser\" property and are not authenticated, or the provided \"tagLanguage\" is invalid.");
84115
resolve(["Retrieving tags failed"]);
85116
}
86117
});

0 commit comments

Comments
 (0)