steam-client 0.0.1.dev1__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-0.0.1.dev1/LICENSE +21 -0
- steam-client-0.0.1.dev1/PKG-INFO +12 -0
- steam-client-0.0.1.dev1/pyproject.toml +53 -0
- steam-client-0.0.1.dev1/setup.cfg +4 -0
- steam-client-0.0.1.dev1/setup.py +6 -0
- steam-client-0.0.1.dev1/steam_client/__init__.py +0 -0
- steam-client-0.0.1.dev1/steam_client/app.py +45 -0
- steam-client-0.0.1.dev1/steam_client/commands.py +49 -0
- steam-client-0.0.1.dev1/steam_client/game.py +87 -0
- steam-client-0.0.1.dev1/steam_client/library.py +79 -0
- steam-client-0.0.1.dev1/steam_client/library_directory.py +21 -0
- steam-client-0.0.1.dev1/steam_client/login_users.py +100 -0
- steam-client-0.0.1.dev1/steam_client/shortcut.py +70 -0
- steam-client-0.0.1.dev1/steam_client/steam.py +41 -0
- steam-client-0.0.1.dev1/steam_client/utils.py +13 -0
- steam-client-0.0.1.dev1/steam_client/web_api.py +24 -0
- steam-client-0.0.1.dev1/steam_client.egg-info/PKG-INFO +12 -0
- steam-client-0.0.1.dev1/steam_client.egg-info/SOURCES.txt +18 -0
- steam-client-0.0.1.dev1/steam_client.egg-info/dependency_links.txt +1 -0
- steam-client-0.0.1.dev1/steam_client.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Garulf
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: steam-client
|
|
3
|
+
Version: 0.0.1.dev1
|
|
4
|
+
Summary: Steam client library for Python
|
|
5
|
+
Author-email: William McAllister <dev.garulf@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
|
|
4
|
+
[project]
|
|
5
|
+
name = "steam-client"
|
|
6
|
+
authors = [
|
|
7
|
+
{name = "William McAllister", email = "dev.garulf@gmail.com"}
|
|
8
|
+
]
|
|
9
|
+
version = '0.0.1-dev.1'
|
|
10
|
+
description = "Steam client library for Python"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
classifiers = [
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
packages = ["steam_client"]
|
|
22
|
+
|
|
23
|
+
[tool.bumpversion]
|
|
24
|
+
current_version = "0.0.1-dev"
|
|
25
|
+
parse = """(?x)
|
|
26
|
+
(?P<major>[0-9]+)
|
|
27
|
+
\\.(?P<minor>[0-9]+)
|
|
28
|
+
\\.(?P<patch>[0-9]+)
|
|
29
|
+
(?:
|
|
30
|
+
-(?P<label>dev|rc|release)
|
|
31
|
+
(?:.(?P<label_n>[0-9]+))?
|
|
32
|
+
)?
|
|
33
|
+
"""
|
|
34
|
+
serialize = [
|
|
35
|
+
"{major}.{minor}.{patch}-{label}.{label_n}",
|
|
36
|
+
"{major}.{minor}.{patch}",
|
|
37
|
+
]
|
|
38
|
+
search = "version = '{current_version}'"
|
|
39
|
+
replace = "version = '{new_version}'"
|
|
40
|
+
tag = true
|
|
41
|
+
allow_dirty = false
|
|
42
|
+
commit = true
|
|
43
|
+
|
|
44
|
+
[tool.bumpversion.parts.label]
|
|
45
|
+
optional_value = "release"
|
|
46
|
+
values =[
|
|
47
|
+
"dev",
|
|
48
|
+
"rc",
|
|
49
|
+
"release",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[[tool.bumpversion.files]]
|
|
53
|
+
filename = "pyproject.toml"
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .steam import Steam
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class App(ABC):
|
|
11
|
+
"""Abstract base class for all Steam apps."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, steam: Steam):
|
|
14
|
+
self._steam = steam
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def appid(self) -> str:
|
|
19
|
+
"""Returns the game's appid."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def icon(self) -> Path:
|
|
25
|
+
"""Returns the path to the icon image."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def header(self) -> Path:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def grid(self) -> Path:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def hero(self) -> Path:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def run(self):
|
|
44
|
+
"""Launches app with the specified app ID in the Steam client."""
|
|
45
|
+
self._steam.commands.run_game_id(self.appid)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
SteamWindow = Literal[
|
|
6
|
+
'main',
|
|
7
|
+
'games',
|
|
8
|
+
'games/details',
|
|
9
|
+
'games/grid',
|
|
10
|
+
'games/list',
|
|
11
|
+
'friends',
|
|
12
|
+
'chat',
|
|
13
|
+
'bigpicture',
|
|
14
|
+
'news',
|
|
15
|
+
'settings',
|
|
16
|
+
'tools',
|
|
17
|
+
'console'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Commands:
|
|
22
|
+
|
|
23
|
+
def run_game_id(self, app_id: str) -> None:
|
|
24
|
+
"""Launches game with the specified ID in the Steam client."""
|
|
25
|
+
os.startfile(f'steam://rungameid/{app_id}')
|
|
26
|
+
|
|
27
|
+
def store(self, app_id: str) -> None:
|
|
28
|
+
"""Opens the game's store page in the Steam client."""
|
|
29
|
+
os.startfile(f'steam://store/{app_id}')
|
|
30
|
+
|
|
31
|
+
def install(self, app_id: str) -> None:
|
|
32
|
+
"""Opens the game's install prompt in the Steam client."""
|
|
33
|
+
os.startfile(f'steam://install/{app_id}')
|
|
34
|
+
|
|
35
|
+
def uninstall(self, app_id: str) -> None:
|
|
36
|
+
"""Opens the game's uninstall prompt in the Steam client."""
|
|
37
|
+
os.startfile(f'steam://uninstall/{app_id}')
|
|
38
|
+
|
|
39
|
+
def update_news(self, app_id: str) -> None:
|
|
40
|
+
"""Opens the game's update news in the Steam client."""
|
|
41
|
+
os.startfile(f'steam://updatenews/{app_id}')
|
|
42
|
+
|
|
43
|
+
def open(self, window: SteamWindow) -> None:
|
|
44
|
+
"""Opens the specified window in the Steam client."""
|
|
45
|
+
os.startfile(f'steam://open/{window}')
|
|
46
|
+
|
|
47
|
+
def open_url(self, url: str) -> None:
|
|
48
|
+
"""Opens the specified URL in the Steam client."""
|
|
49
|
+
os.startfile(f'steam://openurl/{url}')
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import functools
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import vdf
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .steam import Steam
|
|
10
|
+
|
|
11
|
+
from .app import App
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
UNKNOWN_GAME_NAME = 'UNKNOWN'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SteamGame(App):
|
|
18
|
+
"""Represents a Steam game."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, steam: Steam, library_path: str, appid: str):
|
|
21
|
+
self.library_path = library_path
|
|
22
|
+
self._appid = appid
|
|
23
|
+
super().__init__(steam)
|
|
24
|
+
|
|
25
|
+
def __repr__(self) -> str:
|
|
26
|
+
return f'Game(steam={self._steam.__repr__()}, library_path={self.library_path.__repr__()}, appid={self.appid.__repr__()})'
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def icon(self) -> Path:
|
|
30
|
+
"""Returns the path to the icon image."""
|
|
31
|
+
return Path(self._steam.library_cache).joinpath(f'{self.appid}_icon.jpg')
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def header(self) -> Path:
|
|
35
|
+
"""Returns the path to the header image."""
|
|
36
|
+
return Path(self._steam.library_cache).joinpath(f'{self.appid}_header.jpg')
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def grid(self) -> Path:
|
|
40
|
+
"""Returns the path to the 600x900 grid image."""
|
|
41
|
+
return Path(self._steam.library_cache).joinpath(f'{self.appid}_library_600x900.jpg')
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def hero(self) -> Path:
|
|
45
|
+
"""Returns the path to the hero image."""
|
|
46
|
+
return Path(self._steam.library_cache).joinpath(f'{self.appid}_library_hero.jpg')
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def hero_blur(self) -> Path:
|
|
50
|
+
"""Returns the path to the blurred hero image."""
|
|
51
|
+
return Path(self._steam.library_cache).joinpath(f'{self.appid}_library_hero_blur.jpg')
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def manifest_path(self) -> Path:
|
|
55
|
+
"""Returns the path to the game's appmanifest."""
|
|
56
|
+
return Path(self.library_path).joinpath('steamapps', f'appmanifest_{self.appid}.acf')
|
|
57
|
+
|
|
58
|
+
@functools.cached_property
|
|
59
|
+
def _manifest(self) -> dict:
|
|
60
|
+
"""Returns the data from the game's appmanifest."""
|
|
61
|
+
try:
|
|
62
|
+
with open(self.manifest_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
63
|
+
manifest = vdf.load(f)
|
|
64
|
+
return manifest
|
|
65
|
+
except FileNotFoundError:
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
@functools.cached_property
|
|
69
|
+
def name(self) -> str:
|
|
70
|
+
"""Returns the name of the game."""
|
|
71
|
+
try:
|
|
72
|
+
return self._manifest['AppState']['name']
|
|
73
|
+
except KeyError:
|
|
74
|
+
return UNKNOWN_GAME_NAME
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def appid(self) -> str:
|
|
78
|
+
"""Returns the app ID."""
|
|
79
|
+
return self._appid
|
|
80
|
+
|
|
81
|
+
def open_store_page(self):
|
|
82
|
+
"""Opens the game's store page in the Steam client."""
|
|
83
|
+
self._steam.commands.store(self.appid)
|
|
84
|
+
|
|
85
|
+
def uninstall(self):
|
|
86
|
+
"""Uninstalls the game."""
|
|
87
|
+
self._steam.commands.uninstall(self.appid)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import hashlib
|
|
4
|
+
from typing import TYPE_CHECKING, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
import vdf
|
|
7
|
+
|
|
8
|
+
from steam_client.shortcut import Shortcut
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .steam import Steam
|
|
12
|
+
from .library_directory import LibraryDirectory
|
|
13
|
+
from .game import SteamGame
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Library:
|
|
17
|
+
"""Represents the Steam library."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, steam: 'Steam'):
|
|
20
|
+
self._steam = steam
|
|
21
|
+
self._libraries_hash: Optional[str] = None
|
|
22
|
+
self._libraries: List[LibraryDirectory] = []
|
|
23
|
+
|
|
24
|
+
def _hash_steam_libraries(self) -> str:
|
|
25
|
+
"""Returns the MD5 hash of the Steam library folders file."""
|
|
26
|
+
with open(str(self._steam.library_folders), 'rb') as f:
|
|
27
|
+
return hashlib.md5(f.read()).hexdigest()
|
|
28
|
+
|
|
29
|
+
def _is_updated(self) -> bool:
|
|
30
|
+
"""Returns whether the Steam library folders have been updated."""
|
|
31
|
+
return self._libraries_hash != self._hash_steam_libraries()
|
|
32
|
+
|
|
33
|
+
def _update_libraries(self):
|
|
34
|
+
"""Updates the Steam library folders."""
|
|
35
|
+
with open(self._steam.library_folders, 'r', encoding='utf-8', errors='ignore') as f:
|
|
36
|
+
libraries = vdf.load(f)
|
|
37
|
+
# The is not always formatted the same way, so we grab the first key
|
|
38
|
+
folder_key = list(libraries.keys())[0]
|
|
39
|
+
self._libraries = [LibraryDirectory(self._steam, libraries[folder_key][item]["path"],
|
|
40
|
+
libraries[folder_key][item]["apps"]) for item in libraries[folder_key]]
|
|
41
|
+
|
|
42
|
+
def libraries(self) -> List[LibraryDirectory]:
|
|
43
|
+
"""Returns the Steam library folders."""
|
|
44
|
+
if self._is_updated():
|
|
45
|
+
self._libraries_hash = self._hash_steam_libraries()
|
|
46
|
+
self._update_libraries()
|
|
47
|
+
return self._libraries
|
|
48
|
+
|
|
49
|
+
def games(self) -> List[SteamGame]:
|
|
50
|
+
"""Returns the games from the Steam library."""
|
|
51
|
+
games = []
|
|
52
|
+
for library in self.libraries():
|
|
53
|
+
games.extend(library.get_games())
|
|
54
|
+
return games
|
|
55
|
+
|
|
56
|
+
def game_by_id(self, appid: str) -> Optional[SteamGame]:
|
|
57
|
+
"""Returns the game with the specified ID."""
|
|
58
|
+
for game in self.games():
|
|
59
|
+
if game.appid == appid:
|
|
60
|
+
return game
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def game_by_name(self, name: str) -> Optional[SteamGame]:
|
|
64
|
+
"""Returns the game with the specified name."""
|
|
65
|
+
for game in self.games():
|
|
66
|
+
if game.name.casefold() == name.casefold():
|
|
67
|
+
return game
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def shortcuts(self) -> List[Shortcut]:
|
|
71
|
+
"""Returns the Non-Steam shortcuts from the Steam library."""
|
|
72
|
+
shortcuts = []
|
|
73
|
+
for user in self._steam.login_users.users():
|
|
74
|
+
shortcuts.extend(user.shortcuts())
|
|
75
|
+
return shortcuts
|
|
76
|
+
|
|
77
|
+
def all(self) -> List[Union[SteamGame, Shortcut]]:
|
|
78
|
+
"""Returns all the games and shortcuts from the Steam library."""
|
|
79
|
+
return self.games() + self.shortcuts()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import TYPE_CHECKING, List
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import vdf
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .steam import Steam
|
|
10
|
+
from .game import SteamGame
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class LibraryDirectory:
|
|
15
|
+
"""Represents a Steam library folder."""
|
|
16
|
+
_steam: Steam
|
|
17
|
+
path: str
|
|
18
|
+
apps: List[str]
|
|
19
|
+
|
|
20
|
+
def get_games(self) -> List[SteamGame]:
|
|
21
|
+
return [SteamGame(self._steam, self.path, appid) for appid in self.apps]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TypedDict
|
|
7
|
+
|
|
8
|
+
import vdf
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from steam_client.steam import Steam
|
|
12
|
+
|
|
13
|
+
from .shortcut import Shortcut
|
|
14
|
+
|
|
15
|
+
STEAM64_OFFSET = 76561197960265728
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UserData(TypedDict):
|
|
19
|
+
AccountName: str
|
|
20
|
+
PersonaName: str
|
|
21
|
+
RememberPassword: str
|
|
22
|
+
WantsOfflineMode: str
|
|
23
|
+
SkipOfflineModeWarning: str
|
|
24
|
+
AllowAutoLogin: str
|
|
25
|
+
MostRecent: str
|
|
26
|
+
Timestamp: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
UserLoginsFile = Dict[str, Dict[str, UserData]]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class User:
|
|
33
|
+
"""Represents a current or previous logged in Steam user."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, steam: Steam, steam_id64: str, user_data: UserData):
|
|
36
|
+
self._steam = steam
|
|
37
|
+
self.steam_id64 = steam_id64
|
|
38
|
+
self._user_data = user_data
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return f'User(steam={self._steam.__repr__()}, steam_id64={self.steam_id64.__repr__()}, user_data={self._user_data.__repr__()})'
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def is_most_recent(self) -> bool:
|
|
45
|
+
"""Returns whether the user is the most recent user."""
|
|
46
|
+
return self._user_data['MostRecent'] == '1'
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def steam_id3(self) -> int:
|
|
50
|
+
"""Returns the last portion of the user's SteamID3."""
|
|
51
|
+
steamidacct = (int(self.steam_id64) - STEAM64_OFFSET)
|
|
52
|
+
return steamidacct
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def user_data_dir(self) -> Path:
|
|
56
|
+
"""Returns the path to the userdata folder."""
|
|
57
|
+
return Path(self._steam.base_path).joinpath('userdata', str(self.steam_id3))
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def config(self) -> Path:
|
|
61
|
+
"""Returns the path to the config folder."""
|
|
62
|
+
return Path(self.user_data_dir).joinpath('config')
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def shortcuts_file(self) -> Path:
|
|
66
|
+
"""Returns the path to the shortcuts.vdf file."""
|
|
67
|
+
return Path(self.config).joinpath('shortcuts.vdf')
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def grid_path(self) -> Path:
|
|
71
|
+
"""Returns the path to the user's shortcut grid images."""
|
|
72
|
+
return Path(self.user_data_dir).joinpath('config', 'grid')
|
|
73
|
+
|
|
74
|
+
def shortcuts(self) -> List[Shortcut]:
|
|
75
|
+
"""Returns the data from the shortcuts.vdf file."""
|
|
76
|
+
with open(self.shortcuts_file, 'rb') as f:
|
|
77
|
+
shortcuts = vdf.binary_load(f)
|
|
78
|
+
return [Shortcut(self._steam, self, shortcuts['shortcuts'][shortcut_idx]) for shortcut_idx in shortcuts['shortcuts']]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class LoginUsers:
|
|
82
|
+
"""Represents the loginusers.vdf file."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, steam: Steam):
|
|
85
|
+
self._steam = steam
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def _path(self) -> Path:
|
|
89
|
+
"""Returns the path to the loginusers.vdf file."""
|
|
90
|
+
return Path(self._steam.base_path).joinpath('config', 'loginusers.vdf')
|
|
91
|
+
|
|
92
|
+
def users(self) -> List[User]:
|
|
93
|
+
"""Returns the users from the loginusers.vdf file."""
|
|
94
|
+
with open(self._path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
95
|
+
login_users: UserLoginsFile = vdf.load(f)
|
|
96
|
+
return [User(self._steam, steam_id64, user_data) for steam_id64, user_data in login_users['users'].items()]
|
|
97
|
+
|
|
98
|
+
def most_recent_user(self) -> Optional[User]:
|
|
99
|
+
"""Returns the most recent user from the loginusers.vdf file."""
|
|
100
|
+
return next((user for user in self.users() if user.is_most_recent), None)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import TYPE_CHECKING, Dict
|
|
4
|
+
from typing import TypedDict
|
|
5
|
+
|
|
6
|
+
import pycrc.algorithms as crc
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .steam import Steam
|
|
10
|
+
from .login_users import User
|
|
11
|
+
|
|
12
|
+
from .app import App
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ShortcutEntry(TypedDict):
|
|
16
|
+
appid: int
|
|
17
|
+
appname: str
|
|
18
|
+
exe: str
|
|
19
|
+
StartDir: str
|
|
20
|
+
LaunchOptions: str
|
|
21
|
+
icon: str
|
|
22
|
+
tags: Dict[str, str]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Shortcut(App):
|
|
26
|
+
"""Represents a Non-Steam Game shortcut."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, steam: Steam, user: User, data: ShortcutEntry):
|
|
29
|
+
self._data = data
|
|
30
|
+
self._user = user
|
|
31
|
+
super().__init__(steam)
|
|
32
|
+
|
|
33
|
+
def __repr__(self) -> str:
|
|
34
|
+
return f'Shortcut(steam={self._steam.__repr__()}, data={self._data.__repr__()})'
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
return self._data["appname"]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def appid(self) -> str:
|
|
42
|
+
algorithm = crc.Crc(width=32, poly=0x04C11DB7, reflect_in=True,
|
|
43
|
+
xor_in=0xffffffff, reflect_out=True, xor_out=0xffffffff)
|
|
44
|
+
input_string = ''.join([self._data["exe"], self._data["appname"]])
|
|
45
|
+
top_32 = algorithm.bit_by_bit(input_string) | 0x80000000
|
|
46
|
+
full_64 = (top_32 << 32) | 0x02000000
|
|
47
|
+
return str(full_64)
|
|
48
|
+
|
|
49
|
+
def _short_id(self) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Return Steam shortened App ID.
|
|
52
|
+
This is primarily used for shortcuts in the grid.
|
|
53
|
+
"""
|
|
54
|
+
return str(int(self.appid) >> int(32))
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def icon(self) -> str:
|
|
58
|
+
return self._data["icon"] or str(self._user.grid_path.joinpath(f"{self._short_id()}_icon.png"))
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def header(self) -> str:
|
|
62
|
+
return str(self._user.grid_path.joinpath(f"{self._short_id()}.png"))
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def grid(self) -> str:
|
|
66
|
+
return str(self._user.grid_path.joinpath(f"{self._short_id()}p.png"))
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def hero(self) -> str:
|
|
70
|
+
return str(self._user.grid_path.joinpath(f"{self._short_id()}_hero.png"))
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from .library import Library
|
|
5
|
+
from .commands import Commands
|
|
6
|
+
from .login_users import LoginUsers
|
|
7
|
+
|
|
8
|
+
DEFAULT_WIN_STEAM_PATH = r"c:\Program Files (x86)\Steam"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Steam:
|
|
12
|
+
"""Represents the Steam client."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, base_path: str = DEFAULT_WIN_STEAM_PATH):
|
|
15
|
+
self.base_path: str = base_path
|
|
16
|
+
self.login_users = LoginUsers(self)
|
|
17
|
+
self.commands = Commands()
|
|
18
|
+
self.library = Library(self)
|
|
19
|
+
|
|
20
|
+
def __repr__(self) -> str:
|
|
21
|
+
return f'Steam(base_path={self.base_path.__repr__()})'
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def app_cache(self) -> Path:
|
|
25
|
+
"""Returns the path to the appcache folder."""
|
|
26
|
+
return Path(self.base_path).joinpath('appcache')
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def user_data(self) -> Path:
|
|
30
|
+
"""Returns the path to the userdata folder."""
|
|
31
|
+
return Path(self.base_path).joinpath('userdata')
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def library_folders(self) -> Path:
|
|
35
|
+
"""Returns the path to the libraryfolders.vdf file."""
|
|
36
|
+
return Path(self.base_path).joinpath('config', 'libraryfolders.vdf')
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def library_cache(self) -> Path:
|
|
40
|
+
"""Returns the path to the librarycache folder."""
|
|
41
|
+
return Path(self.base_path).joinpath('appcache', 'librarycache')
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import winreg as reg
|
|
2
|
+
from winreg import HKEY_LOCAL_MACHINE
|
|
3
|
+
|
|
4
|
+
from .steam import Steam
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
STEAM_SUB_KEY = r'SOFTWARE\WOW6432Node\Valve\Steam'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def steam_from_registry() -> Steam:
|
|
11
|
+
"""Returns a Steam instance from the Windows registry."""
|
|
12
|
+
with reg.OpenKey(HKEY_LOCAL_MACHINE, STEAM_SUB_KEY) as hkey:
|
|
13
|
+
return Steam(reg.QueryValueEx(hkey, "InstallPath")[0])
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
import requests # type: ignore
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
BASE_URL = "https://api.steampowered.com"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WebAPI:
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self._session = requests.Session()
|
|
13
|
+
|
|
14
|
+
def _request(self, method: str, endpoint: str, base_url: str = BASE_URL, **kwargs) -> requests.Response:
|
|
15
|
+
"""Makes a request to the Steam API."""
|
|
16
|
+
url = f'{base_url}/{endpoint}'
|
|
17
|
+
r = self._session.request(method, url, **kwargs)
|
|
18
|
+
r.raise_for_status()
|
|
19
|
+
return r
|
|
20
|
+
|
|
21
|
+
def get_app_list(self) -> Dict[Any, Any]:
|
|
22
|
+
"""Returns a list of all Steam apps."""
|
|
23
|
+
r = self._request('GET', 'ISteamApps/GetAppList/v2')
|
|
24
|
+
return r.json()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: steam-client
|
|
3
|
+
Version: 0.0.1.dev1
|
|
4
|
+
Summary: Steam client library for Python
|
|
5
|
+
Author-email: William McAllister <dev.garulf@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
steam_client/__init__.py
|
|
5
|
+
steam_client/app.py
|
|
6
|
+
steam_client/commands.py
|
|
7
|
+
steam_client/game.py
|
|
8
|
+
steam_client/library.py
|
|
9
|
+
steam_client/library_directory.py
|
|
10
|
+
steam_client/login_users.py
|
|
11
|
+
steam_client/shortcut.py
|
|
12
|
+
steam_client/steam.py
|
|
13
|
+
steam_client/utils.py
|
|
14
|
+
steam_client/web_api.py
|
|
15
|
+
steam_client.egg-info/PKG-INFO
|
|
16
|
+
steam_client.egg-info/SOURCES.txt
|
|
17
|
+
steam_client.egg-info/dependency_links.txt
|
|
18
|
+
steam_client.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
steam_client
|