steam-client 3.0.0.dev0__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- steam_client-4.0.0/PKG-INFO +147 -0
- steam_client-4.0.0/README.md +130 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/pyproject.toml +2 -2
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/app.py +1 -7
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/commands.py +12 -18
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/game.py +14 -13
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/library.py +22 -20
- steam_client-4.0.0/steam_client/library_folder.py +17 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/login_users.py +11 -12
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/shortcut.py +3 -4
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/steam.py +1 -1
- steam_client-4.0.0/steam_client.egg-info/PKG-INFO +147 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client.egg-info/SOURCES.txt +3 -1
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/tests/test_commands.py +5 -5
- steam_client-4.0.0/tests/test_game.py +103 -0
- steam_client-4.0.0/tests/test_library.py +220 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/tests/test_login_users.py +5 -5
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/tests/test_shortcut.py +2 -2
- steam_client-3.0.0.dev0/PKG-INFO +0 -16
- steam_client-3.0.0.dev0/steam_client/library_directory.py +0 -18
- steam_client-3.0.0.dev0/steam_client.egg-info/PKG-INFO +0 -16
- steam_client-3.0.0.dev0/tests/test_game.py +0 -61
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/LICENSE +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/setup.cfg +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/setup.py +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/__init__.py +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/py.typed +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/utils.py +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client/web_api.py +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client.egg-info/dependency_links.txt +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client.egg-info/requires.txt +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/steam_client.egg-info/top_level.txt +0 -0
- {steam_client-3.0.0.dev0 → steam_client-4.0.0}/tests/test_steam.py +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steam-client
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: Steam client library for Python
|
|
5
|
+
Author-email: William McAllister <dev.garulf@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/garulf/steam-client
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: vdf>=3.4
|
|
15
|
+
Requires-Dist: pycrc>=0.10.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# Steam Client
|
|
19
|
+
|
|
20
|
+
A Python library for interacting with a locally installed Steam client — enumerate your game library/non-steam games, read game metadata and artwork paths, send Steam URI commands, and query the Steam Web API.
|
|
21
|
+
|
|
22
|
+
[](https://www.buymeacoffee.com/garulf)
|
|
23
|
+
[](https://ko-fi.com/garulf)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Requires Python 3.8+.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install steam-client
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install from source:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install -e .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from steam_client.steam import Steam
|
|
47
|
+
|
|
48
|
+
steam = Steam() # Uses default path: C:\Program Files (x86)\Steam
|
|
49
|
+
|
|
50
|
+
# On Windows, auto-detect path from registry:
|
|
51
|
+
from steam_client.utils import steam_from_registry
|
|
52
|
+
steam = steam_from_registry()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Common Operations
|
|
58
|
+
|
|
59
|
+
### List all installed games
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
for game in steam.library.games():
|
|
63
|
+
print(game.appid, game.name)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Find a game
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
game = steam.library.game_by_id("440") # by app ID
|
|
70
|
+
game = steam.library.game_by_name("Portal 2") # by name (case-insensitive)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Artwork paths
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
game.header # Path to header image (460×215 jpg)
|
|
77
|
+
game.grid # Path to grid image (600×900 jpg)
|
|
78
|
+
game.hero # Path to hero image
|
|
79
|
+
game.icon # Path to icon file, or None
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Launch and manage games via Steam URI commands
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from steam_client.commands import Commands, SteamWindows
|
|
86
|
+
|
|
87
|
+
commands = Commands()
|
|
88
|
+
commands.run_game_id("440") # Launch a game
|
|
89
|
+
commands.store("440") # Open store page
|
|
90
|
+
commands.install("440") # Prompt install
|
|
91
|
+
commands.uninstall("440") # Prompt uninstall
|
|
92
|
+
commands.open(SteamWindows.FRIENDS) # Open a Steam window
|
|
93
|
+
commands.open_url("https://store.steampowered.com")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or launch a game directly from a `SteamGame` object:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
game.run()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Users and shortcuts
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
for user in steam.users:
|
|
106
|
+
print(user.user.data.PersonaName)
|
|
107
|
+
|
|
108
|
+
for shortcut in steam.library.shortcuts():
|
|
109
|
+
print(shortcut.appname)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Steam Web API
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from steam_client.web_api import WebAPI
|
|
116
|
+
|
|
117
|
+
api = WebAPI()
|
|
118
|
+
apps = api.get_app_list()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Platform Notes
|
|
124
|
+
|
|
125
|
+
| Platform | Default path | Registry helper |
|
|
126
|
+
|----------|--------------------------------|-------------------------|
|
|
127
|
+
| Windows | `C:\Program Files (x86)\Steam` | `steam_from_registry()` |
|
|
128
|
+
| Linux | `~/.local/share/steam` | Not available |
|
|
129
|
+
|
|
130
|
+
Pass a custom path to override the default:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
steam = Steam("/custom/steam/path")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Requires read access to local Steam config files (`libraryfolders.vdf`, `shortcuts.vdf`, appmanifest files).
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pytest # Run tests
|
|
144
|
+
flake8 steam_client # Lint
|
|
145
|
+
mypy steam_client # Type check
|
|
146
|
+
tox # Run all environments (lint, type, py311–314)
|
|
147
|
+
```
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Steam Client
|
|
2
|
+
|
|
3
|
+
A Python library for interacting with a locally installed Steam client — enumerate your game library/non-steam games, read game metadata and artwork paths, send Steam URI commands, and query the Steam Web API.
|
|
4
|
+
|
|
5
|
+
[](https://www.buymeacoffee.com/garulf)
|
|
6
|
+
[](https://ko-fi.com/garulf)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Requires Python 3.8+.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install steam-client
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install from source:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from steam_client.steam import Steam
|
|
30
|
+
|
|
31
|
+
steam = Steam() # Uses default path: C:\Program Files (x86)\Steam
|
|
32
|
+
|
|
33
|
+
# On Windows, auto-detect path from registry:
|
|
34
|
+
from steam_client.utils import steam_from_registry
|
|
35
|
+
steam = steam_from_registry()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Common Operations
|
|
41
|
+
|
|
42
|
+
### List all installed games
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
for game in steam.library.games():
|
|
46
|
+
print(game.appid, game.name)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Find a game
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
game = steam.library.game_by_id("440") # by app ID
|
|
53
|
+
game = steam.library.game_by_name("Portal 2") # by name (case-insensitive)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Artwork paths
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
game.header # Path to header image (460×215 jpg)
|
|
60
|
+
game.grid # Path to grid image (600×900 jpg)
|
|
61
|
+
game.hero # Path to hero image
|
|
62
|
+
game.icon # Path to icon file, or None
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Launch and manage games via Steam URI commands
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from steam_client.commands import Commands, SteamWindows
|
|
69
|
+
|
|
70
|
+
commands = Commands()
|
|
71
|
+
commands.run_game_id("440") # Launch a game
|
|
72
|
+
commands.store("440") # Open store page
|
|
73
|
+
commands.install("440") # Prompt install
|
|
74
|
+
commands.uninstall("440") # Prompt uninstall
|
|
75
|
+
commands.open(SteamWindows.FRIENDS) # Open a Steam window
|
|
76
|
+
commands.open_url("https://store.steampowered.com")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or launch a game directly from a `SteamGame` object:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
game.run()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Users and shortcuts
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
for user in steam.users:
|
|
89
|
+
print(user.user.data.PersonaName)
|
|
90
|
+
|
|
91
|
+
for shortcut in steam.library.shortcuts():
|
|
92
|
+
print(shortcut.appname)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Steam Web API
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from steam_client.web_api import WebAPI
|
|
99
|
+
|
|
100
|
+
api = WebAPI()
|
|
101
|
+
apps = api.get_app_list()
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Platform Notes
|
|
107
|
+
|
|
108
|
+
| Platform | Default path | Registry helper |
|
|
109
|
+
|----------|--------------------------------|-------------------------|
|
|
110
|
+
| Windows | `C:\Program Files (x86)\Steam` | `steam_from_registry()` |
|
|
111
|
+
| Linux | `~/.local/share/steam` | Not available |
|
|
112
|
+
|
|
113
|
+
Pass a custom path to override the default:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
steam = Steam("/custom/steam/path")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Requires read access to local Steam config files (`libraryfolders.vdf`, `shortcuts.vdf`, appmanifest files).
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pytest # Run tests
|
|
127
|
+
flake8 steam_client # Lint
|
|
128
|
+
mypy steam_client # Type check
|
|
129
|
+
tox # Run all environments (lint, type, py311–314)
|
|
130
|
+
```
|
|
@@ -6,7 +6,7 @@ name = "steam-client"
|
|
|
6
6
|
authors = [
|
|
7
7
|
{name = "William McAllister", email = "dev.garulf@gmail.com"}
|
|
8
8
|
]
|
|
9
|
-
version = "
|
|
9
|
+
version = "4.0.0"
|
|
10
10
|
description = "Steam client library for Python"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.8"
|
|
@@ -31,7 +31,7 @@ packages = ["steam_client"]
|
|
|
31
31
|
"steam_client" = ["py.typed"]
|
|
32
32
|
|
|
33
33
|
[tool.bumpversion]
|
|
34
|
-
current_version = "
|
|
34
|
+
current_version = "4.0.0"
|
|
35
35
|
search = "version = \"{current_version}\""
|
|
36
36
|
replace = "version = \"{new_version}\""
|
|
37
37
|
parse = """(?x)
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
from .commands import Commands
|
|
8
6
|
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from .steam import Steam
|
|
11
|
-
|
|
12
7
|
|
|
13
8
|
class App(ABC):
|
|
14
9
|
"""Abstract base class for all Steam apps."""
|
|
15
10
|
|
|
16
|
-
def __init__(self
|
|
17
|
-
self._steam = steam
|
|
11
|
+
def __init__(self):
|
|
18
12
|
self._commands = Commands()
|
|
19
13
|
|
|
20
14
|
@property
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
from typing import List
|
|
2
1
|
import webbrowser
|
|
3
2
|
from enum import StrEnum
|
|
4
3
|
|
|
5
|
-
SCHEME = 'steam'
|
|
6
|
-
|
|
7
4
|
|
|
8
5
|
class SteamWindows(StrEnum):
|
|
9
6
|
"""Enumeration of Steam client windows."""
|
|
@@ -23,47 +20,44 @@ class SteamWindows(StrEnum):
|
|
|
23
20
|
|
|
24
21
|
class Command():
|
|
25
22
|
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _create_uri(self, path: List[str], endpoint: str) -> str:
|
|
30
|
-
return f'{self._scheme}://{"/".join(path)}/{endpoint}'
|
|
23
|
+
def _create_uri(self, *segments: str) -> str:
|
|
24
|
+
return f'steam://{"/".join(segments)}'
|
|
31
25
|
|
|
32
|
-
def
|
|
26
|
+
def open(self, *segments: str) -> None:
|
|
33
27
|
"""Executes the command with the specified endpoint."""
|
|
34
|
-
webbrowser.open(self._create_uri(
|
|
28
|
+
webbrowser.open(self._create_uri(*segments))
|
|
35
29
|
|
|
36
30
|
|
|
37
31
|
class Commands:
|
|
38
32
|
"""A collection of commands for the Steam client."""
|
|
39
33
|
|
|
40
34
|
def __init__(self):
|
|
41
|
-
self._command = Command(
|
|
35
|
+
self._command = Command()
|
|
42
36
|
|
|
43
37
|
def run_game_id(self, app_id: str) -> None:
|
|
44
38
|
"""Launches game with the specified ID in the Steam client."""
|
|
45
|
-
self._command(
|
|
39
|
+
self._command.open('rungameid', app_id)
|
|
46
40
|
|
|
47
41
|
def store(self, app_id: str) -> None:
|
|
48
42
|
"""Opens the game's store page in the Steam client."""
|
|
49
|
-
self._command(
|
|
43
|
+
self._command.open('store', app_id)
|
|
50
44
|
|
|
51
45
|
def install(self, app_id: str) -> None:
|
|
52
46
|
"""Opens the game's install prompt in the Steam client."""
|
|
53
|
-
self._command(
|
|
47
|
+
self._command.open('install', app_id)
|
|
54
48
|
|
|
55
49
|
def uninstall(self, app_id: str) -> None:
|
|
56
50
|
"""Opens the game's uninstall prompt in the Steam client."""
|
|
57
|
-
self._command(
|
|
51
|
+
self._command.open('uninstall', app_id)
|
|
58
52
|
|
|
59
53
|
def update_news(self, app_id: str) -> None:
|
|
60
54
|
"""Opens the game's update news in the Steam client."""
|
|
61
|
-
self._command(
|
|
55
|
+
self._command.open('updatenews', app_id)
|
|
62
56
|
|
|
63
57
|
def open(self, window: SteamWindows) -> None:
|
|
64
58
|
"""Opens the specified window in the Steam client."""
|
|
65
|
-
self._command(
|
|
59
|
+
self._command.open('open', window)
|
|
66
60
|
|
|
67
61
|
def open_url(self, url: str) -> None:
|
|
68
62
|
"""Opens the specified URL in the Steam client."""
|
|
69
|
-
self._command(
|
|
63
|
+
self._command.open('openurl', url)
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import functools
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
6
5
|
import vdf # type: ignore
|
|
7
6
|
|
|
8
|
-
if TYPE_CHECKING:
|
|
9
|
-
from .steam import Steam
|
|
10
|
-
|
|
11
7
|
from .app import App
|
|
12
8
|
|
|
13
9
|
|
|
@@ -22,19 +18,20 @@ ASSETS = frozenset({
|
|
|
22
18
|
})
|
|
23
19
|
|
|
24
20
|
|
|
25
|
-
class
|
|
21
|
+
class Game(App):
|
|
26
22
|
"""Represents a Steam game."""
|
|
27
23
|
|
|
28
|
-
def __init__(self,
|
|
24
|
+
def __init__(self, library_cache_path: Path, library_path: str, appid: str):
|
|
25
|
+
self._library_cache_path = Path(library_cache_path)
|
|
29
26
|
self.library_path = library_path
|
|
30
27
|
self._appid = appid
|
|
31
28
|
self._icon: Path | None = None
|
|
32
29
|
self._name: str | None = None
|
|
33
|
-
super().__init__(
|
|
30
|
+
super().__init__()
|
|
34
31
|
|
|
35
32
|
def __repr__(self) -> str:
|
|
36
33
|
return (
|
|
37
|
-
f'Game(
|
|
34
|
+
f'Game(library_cache_path={self._library_cache_path.__repr__()}, '
|
|
38
35
|
f'library_path={self.library_path.__repr__()}, '
|
|
39
36
|
f'appid={self.appid.__repr__()})'
|
|
40
37
|
)
|
|
@@ -42,7 +39,7 @@ class SteamGame(App):
|
|
|
42
39
|
@property
|
|
43
40
|
def asset_dir(self) -> Path:
|
|
44
41
|
"""Returns the path to the game's asset directory."""
|
|
45
|
-
return
|
|
42
|
+
return self._library_cache_path.joinpath(self.appid)
|
|
46
43
|
|
|
47
44
|
@property
|
|
48
45
|
def icon(self) -> Path | None:
|
|
@@ -61,22 +58,22 @@ class SteamGame(App):
|
|
|
61
58
|
@property
|
|
62
59
|
def header(self) -> Path:
|
|
63
60
|
"""Returns the path to the header image."""
|
|
64
|
-
return
|
|
61
|
+
return self._library_cache_path.joinpath(f'{self.appid}_header.jpg')
|
|
65
62
|
|
|
66
63
|
@property
|
|
67
64
|
def grid(self) -> Path:
|
|
68
65
|
"""Returns the path to the 600x900 grid image."""
|
|
69
|
-
return
|
|
66
|
+
return self._library_cache_path.joinpath(f'{self.appid}_library_600x900.jpg')
|
|
70
67
|
|
|
71
68
|
@property
|
|
72
69
|
def hero(self) -> Path:
|
|
73
70
|
"""Returns the path to the hero image."""
|
|
74
|
-
return
|
|
71
|
+
return self._library_cache_path.joinpath(f'{self.appid}_library_hero.jpg')
|
|
75
72
|
|
|
76
73
|
@property
|
|
77
74
|
def hero_blur(self) -> Path:
|
|
78
75
|
"""Returns the path to the blurred hero image."""
|
|
79
|
-
return
|
|
76
|
+
return self._library_cache_path.joinpath(f'{self.appid}_library_hero_blur.jpg')
|
|
80
77
|
|
|
81
78
|
@property
|
|
82
79
|
def manifest_path(self) -> Path:
|
|
@@ -115,6 +112,10 @@ class SteamGame(App):
|
|
|
115
112
|
"""Opens the game's store page in the Steam client."""
|
|
116
113
|
self._commands.store(self.appid)
|
|
117
114
|
|
|
115
|
+
def install(self):
|
|
116
|
+
"""Installs the game."""
|
|
117
|
+
self._commands.install(self.appid)
|
|
118
|
+
|
|
118
119
|
def uninstall(self):
|
|
119
120
|
"""Uninstalls the game."""
|
|
120
121
|
self._commands.uninstall(self.appid)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import hashlib
|
|
3
|
-
from typing import TYPE_CHECKING, List, Optional, Union
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional, Union, Generator
|
|
4
4
|
|
|
5
5
|
import vdf # type: ignore
|
|
6
6
|
|
|
@@ -8,8 +8,8 @@ from steam_client.shortcut import Shortcut
|
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from .steam import Steam
|
|
11
|
-
from .
|
|
12
|
-
from .game import
|
|
11
|
+
from .library_folder import LibraryFolder
|
|
12
|
+
from .game import Game
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Library:
|
|
@@ -18,7 +18,7 @@ class Library:
|
|
|
18
18
|
def __init__(self, steam: 'Steam'):
|
|
19
19
|
self._steam = steam
|
|
20
20
|
self._libraries_hash: Optional[str] = None
|
|
21
|
-
self._libraries: List[
|
|
21
|
+
self._libraries: List[LibraryFolder] = []
|
|
22
22
|
|
|
23
23
|
def _hash_steam_libraries(self) -> str:
|
|
24
24
|
"""Returns the MD5 hash of the Steam library folders file."""
|
|
@@ -35,44 +35,46 @@ class Library:
|
|
|
35
35
|
libraries = vdf.load(f)
|
|
36
36
|
# The is not always formatted the same way, so we grab the first key
|
|
37
37
|
folder_key = list(libraries.keys())[0]
|
|
38
|
-
self._libraries = [
|
|
39
|
-
|
|
38
|
+
self._libraries = [LibraryFolder(self._steam.library_cache, libraries[folder_key][item]["path"],
|
|
39
|
+
libraries[folder_key][item]["apps"]) for item in libraries[folder_key]]
|
|
40
40
|
|
|
41
|
-
def libraries(self) ->
|
|
41
|
+
def libraries(self) -> Generator[LibraryFolder, None, None]:
|
|
42
42
|
"""Returns the Steam library folders."""
|
|
43
43
|
if self._is_updated():
|
|
44
44
|
self._libraries_hash = self._hash_steam_libraries()
|
|
45
45
|
self._update_libraries()
|
|
46
|
-
|
|
46
|
+
for library in self._libraries:
|
|
47
|
+
yield library
|
|
47
48
|
|
|
48
|
-
def games(self) ->
|
|
49
|
+
def games(self) -> Generator[Game, None, None]:
|
|
49
50
|
"""Returns the games from the Steam library."""
|
|
50
|
-
games = []
|
|
51
51
|
for library in self.libraries():
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
for game in library.get_games():
|
|
53
|
+
yield game
|
|
54
54
|
|
|
55
|
-
def game_by_id(self, appid: str) -> Optional[
|
|
55
|
+
def game_by_id(self, appid: str) -> Optional[Game]:
|
|
56
56
|
"""Returns the game with the specified ID."""
|
|
57
57
|
for game in self.games():
|
|
58
58
|
if game.appid == appid:
|
|
59
59
|
return game
|
|
60
60
|
return None
|
|
61
61
|
|
|
62
|
-
def game_by_name(self, name: str) -> Optional[
|
|
62
|
+
def game_by_name(self, name: str) -> Optional[Game]:
|
|
63
63
|
"""Returns the game with the specified name."""
|
|
64
64
|
for game in self.games():
|
|
65
65
|
if game.name.casefold() == name.casefold():
|
|
66
66
|
return game
|
|
67
67
|
return None
|
|
68
68
|
|
|
69
|
-
def shortcuts(self) ->
|
|
69
|
+
def shortcuts(self) -> Generator[Shortcut, None, None]:
|
|
70
70
|
"""Returns the Non-Steam shortcuts from the Steam library."""
|
|
71
|
-
shortcuts = []
|
|
72
71
|
for user in self._steam.users:
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
for shortcut in user.shortcuts():
|
|
73
|
+
yield shortcut
|
|
75
74
|
|
|
76
|
-
def all(self) ->
|
|
75
|
+
def all(self) -> Generator[Union[Game, Shortcut], None, None]:
|
|
77
76
|
"""Returns all the games and shortcuts from the Steam library."""
|
|
78
|
-
|
|
77
|
+
for game in self.games():
|
|
78
|
+
yield game
|
|
79
|
+
for shortcut in self.shortcuts():
|
|
80
|
+
yield shortcut
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from .game import Game
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class LibraryFolder:
|
|
11
|
+
"""Represents a Steam library folder."""
|
|
12
|
+
library_cache_path: Path
|
|
13
|
+
path: str
|
|
14
|
+
apps: List[str]
|
|
15
|
+
|
|
16
|
+
def get_games(self) -> List[Game]:
|
|
17
|
+
return [Game(self.library_cache_path, self.path, appid) for appid in self.apps]
|
|
@@ -2,13 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import List, Optional
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
5
|
|
|
7
6
|
import vdf # type: ignore
|
|
8
7
|
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from steam_client.steam import Steam
|
|
11
|
-
|
|
12
8
|
from .shortcut import Shortcut
|
|
13
9
|
|
|
14
10
|
STEAM64_OFFSET = 76561197960265728
|
|
@@ -35,8 +31,8 @@ class UserData:
|
|
|
35
31
|
class LoginUser:
|
|
36
32
|
"""Represents a current or previous logged in Steam user."""
|
|
37
33
|
|
|
38
|
-
def __init__(self,
|
|
39
|
-
self.
|
|
34
|
+
def __init__(self, base_path: str, user: User):
|
|
35
|
+
self._base_path = Path(base_path)
|
|
40
36
|
self.user = user
|
|
41
37
|
|
|
42
38
|
@property
|
|
@@ -53,7 +49,7 @@ class LoginUser:
|
|
|
53
49
|
@property
|
|
54
50
|
def user_data_dir(self) -> Path:
|
|
55
51
|
"""Returns the path to the userdata folder."""
|
|
56
|
-
return
|
|
52
|
+
return self._base_path.joinpath('userdata', str(self.steam_id3))
|
|
57
53
|
|
|
58
54
|
@property
|
|
59
55
|
def config(self) -> Path:
|
|
@@ -74,25 +70,28 @@ class LoginUser:
|
|
|
74
70
|
"""Returns the data from the shortcuts.vdf file."""
|
|
75
71
|
with open(self.shortcuts_file, 'rb') as f:
|
|
76
72
|
shortcuts = vdf.binary_load(f)
|
|
77
|
-
return [Shortcut(self
|
|
73
|
+
return [Shortcut(self, shortcuts['shortcuts'][shortcut_idx]) for shortcut_idx in shortcuts['shortcuts']]
|
|
78
74
|
|
|
79
75
|
|
|
80
76
|
class LoginUsers:
|
|
81
77
|
"""Represents the loginusers.vdf file."""
|
|
82
78
|
|
|
83
|
-
def __init__(self,
|
|
84
|
-
self.
|
|
79
|
+
def __init__(self, base_path: str):
|
|
80
|
+
self._base_path = Path(base_path)
|
|
85
81
|
|
|
86
82
|
@property
|
|
87
83
|
def _path(self) -> Path:
|
|
88
84
|
"""Returns the path to the loginusers.vdf file."""
|
|
89
|
-
return
|
|
85
|
+
return self._base_path.joinpath('config', 'loginusers.vdf')
|
|
90
86
|
|
|
91
87
|
def users(self) -> List[LoginUser]:
|
|
92
88
|
"""Returns the users from the loginusers.vdf file."""
|
|
93
89
|
with open(self._path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
94
90
|
login_users = vdf.load(f)
|
|
95
|
-
return [
|
|
91
|
+
return [
|
|
92
|
+
LoginUser(str(self._base_path), User(int(user_id), user_data))
|
|
93
|
+
for user_id, user_data in login_users['users'].items()
|
|
94
|
+
]
|
|
96
95
|
|
|
97
96
|
def most_recent_user(self) -> Optional[LoginUser]:
|
|
98
97
|
"""Returns the most recent user from the loginusers.vdf file."""
|
|
@@ -6,7 +6,6 @@ from typing import TypedDict
|
|
|
6
6
|
import pycrc.algorithms as crc # type: ignore
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from .steam import Steam
|
|
10
9
|
from .login_users import LoginUser
|
|
11
10
|
|
|
12
11
|
from .app import App
|
|
@@ -25,13 +24,13 @@ class ShortcutEntry(TypedDict):
|
|
|
25
24
|
class Shortcut(App):
|
|
26
25
|
"""Represents a Non-Steam Game shortcut."""
|
|
27
26
|
|
|
28
|
-
def __init__(self,
|
|
27
|
+
def __init__(self, user: LoginUser, data: ShortcutEntry):
|
|
29
28
|
self._data = data
|
|
30
29
|
self._user = user
|
|
31
|
-
super().__init__(
|
|
30
|
+
super().__init__()
|
|
32
31
|
|
|
33
32
|
def __repr__(self) -> str:
|
|
34
|
-
return f'Shortcut(
|
|
33
|
+
return f'Shortcut(data={self._data.__repr__()})'
|
|
35
34
|
|
|
36
35
|
@property
|
|
37
36
|
def name(self) -> str:
|