Skip to content

Commit 272992a

Browse files
committed
Initial commit
0 parents  commit 272992a

File tree

8 files changed

+1927
-0
lines changed

8 files changed

+1927
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# EditorConfig is awesome: https://EditorConfig.org
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
[*]
7+
indent_style = tab
8+
indent_size = 4
9+
end_of_line = lf
10+
charset = utf-8
11+
trim_trailing_whitespace = true
12+
insert_final_newline = true

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
SteamData
3+
*.tgz

.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"semi": true
3+
}

LICENSE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# The MIT License (MIT)
2+
3+
Copyright © `2022` `tacheometry`
4+
5+
Permission is hereby granted, free of charge, to any person
6+
obtaining a copy of this software and associated documentation
7+
files (the “Software”), to deal in the Software without
8+
restriction, including without limitation the rights to use,
9+
copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the
11+
Software is furnished to do so, subject to the following
12+
conditions:
13+
14+
The above copyright notice and this permission notice shall be
15+
included in all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
18+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24+
OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Simple Steam hour farmer
2+
3+
_`steam-hour-farmer` is a program that emulates you playing a game on Steam, with the purpose of effortlessly getting hours played on certain games on your profile._
4+
5+
- Can be deployed on a VPS to run 24/7.
6+
- Only requires the games to be in your Steam library - they don't need to be installed.
7+
- When you start playing games on your main computer, it pauses automatically.
8+
- After you're finished playing, the bot resumes automatically (provided it's able to log in again).
9+
- Doesn't require multiple accounts.
10+
- Inspired by [@Gunthersuper/steam-idle-bot](https://github.com/Gunthersuper/steam-idle-bot).
11+
12+
## How to use
13+
14+
1. Have Node.js configured (minimum version 16). This can be done through [nvm](https://github.com/nvm-sh/nvm) on a VPS or through the [official website](https://nodejs.org/).
15+
2. Install this package:
16+
```
17+
npm install -g steam-hour-farmer
18+
```
19+
3. Make a directory somewhere. This is where your Steam data will be stored, and where you can configure the bot.
20+
4. Find your Steam game ids. In each game, go to Properties -> Updates -> Copy the App ID.
21+
5. In this directory, make an `.env` file with the content:
22+
23+
```sh
24+
USERNAME="your_steam_username",
25+
PASSWORD="your_steam_password",
26+
GAMES="440,4000"
27+
```
28+
29+
This will start playing Team Fortress 2 and Garry's Mod for example.
30+
31+
The `GAMES` part of the file describes what games you'd like the bot to play, separated by a comma.
32+
33+
Additionally, a `PERSONA` value may be supplied to set the online status of the user. Can be Online (1), Busy (2), Away (3), Snooze (4). For example,
34+
35+
```sh
36+
PERSONA="1"
37+
```
38+
39+
to be Online. Do not write this value if you don't want the user's presence to change.
40+
41+
If you have access to your Steam Shared Secret, you can input it into a `SHARED_SECRET` variable. This will prevent you from needing to input your Steam Guard code at all.
42+
43+
All of this configuration can be passed via environment variables too - they don't need to be in this `.env` file.
44+
45+
6. Run the program in the same directory:
46+
47+
```
48+
steam-hour-farmer
49+
```
50+
51+
When the bot starts, it will request a Steam Guard code via email or the mobile application. When you start playing on another machine, the bot will be kicked from its session, requiring a re-login, with a new Steam Guard code.
52+
53+
This can be remedied by using the Steam Shared Secret, or disabling Steam Guard.
54+
55+
Note that the playtime might seem to be the same when looking from another client - it can take a couple hours for Steam to refresh it sometimes.

index.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env node
2+
"use strict";
3+
4+
const readline = require("readline");
5+
const util = require("util");
6+
const Steam = require("steam-user");
7+
const TOTP = require("steam-totp");
8+
9+
console.log(`Documentation: https://github.com/tacheometry/steam-hour-farmer`);
10+
11+
require("dotenv").config();
12+
let { USERNAME, PASSWORD, PERSONA, GAMES, SHARED_SECRET } = process.env;
13+
{
14+
PERSONA = parseInt(PERSONA);
15+
const shouldExist = (name) => {
16+
if (!process.env[name]) {
17+
console.error(
18+
`Environment variable "${name}" should be provided, but it is undefined.`
19+
);
20+
process.exit(1);
21+
}
22+
};
23+
24+
shouldExist("USERNAME");
25+
shouldExist("PASSWORD");
26+
shouldExist("GAMES");
27+
}
28+
29+
const SHOULD_PLAY = GAMES.split(",").map((n) => parseInt(n));
30+
if (SHOULD_PLAY.length === 0)
31+
console.warn("Could not find any games to play. Maybe this is a mistake?");
32+
33+
const readlineInterface = readline.createInterface({
34+
input: process.stdin,
35+
output: process.stdout,
36+
});
37+
const consoleQuestion = util
38+
.promisify(readlineInterface.question)
39+
.bind(readlineInterface);
40+
41+
const getTOTP = () => TOTP.generateAuthCode(SHARED_SECRET);
42+
43+
const User = new Steam({
44+
machineIdType: Steam.EMachineIDType.PersistentRandom,
45+
dataDirectory: "SteamData",
46+
});
47+
48+
const logOn = () => {
49+
User.logOn({
50+
accountName: USERNAME,
51+
password: PASSWORD,
52+
rememberPassword: true,
53+
clientOS: Steam.EOSType.Windows10,
54+
twoFactorCode: SHARED_SECRET
55+
? TOTP.generateAuthCode(SHARED_SECRET)
56+
: undefined,
57+
autoRelogin: true,
58+
});
59+
};
60+
61+
const panic = (message = "Exiting...") => {
62+
console.error(message);
63+
process.exit(1);
64+
};
65+
66+
let playingOnOtherSession = false;
67+
let currentNotification;
68+
const refreshGames = () => {
69+
let notification;
70+
if (playingOnOtherSession) {
71+
notification = "Farming is paused.";
72+
} else {
73+
User.gamesPlayed(SHOULD_PLAY);
74+
notification = "Farming...";
75+
}
76+
if (currentNotification !== notification) {
77+
currentNotification = notification;
78+
console.log(notification);
79+
}
80+
};
81+
82+
User.on("steamGuard", async (domain, callback) => {
83+
if (SHARED_SECRET) return callback(getTOTP());
84+
const manualCode = await consoleQuestion(
85+
`Enter Steam Guard code` +
86+
(domain ? ` for email at ${domain}` : "") +
87+
": "
88+
);
89+
callback(manualCode);
90+
});
91+
92+
User.on("playingState", (blocked, app) => {
93+
playingOnOtherSession = blocked;
94+
refreshGames();
95+
});
96+
97+
User.on("loggedOn", () => {
98+
console.log(`Successfully logged in to Steam with ID ${User.steamID}`);
99+
if (PERSONA !== undefined) User.setPersona(PERSONA);
100+
101+
// Allow time to receive the playingState event to see if playing on another session if that's the case
102+
setTimeout(refreshGames, 5000);
103+
});
104+
105+
User.on("error", (e) => {
106+
if (e.eresult === Steam.EResult.LoggedInElsewhere) {
107+
console.log("Got kicked by other Steam session. Logging in again...");
108+
logOn();
109+
return;
110+
}
111+
panic(`Got an error from Steam: "${e.message}".`);
112+
});
113+
114+
logOn();
115+
116+
setInterval(refreshGames, 20 * 1000);

0 commit comments

Comments
 (0)