steam-cloud-backup 0.1.1__tar.gz → 0.1.2__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_cloud_backup-0.1.1/steam_cloud_backup.egg-info → steam_cloud_backup-0.1.2}/PKG-INFO +20 -7
- {steam_cloud_backup-0.1.1 → steam_cloud_backup-0.1.2}/README.md +14 -0
- {steam_cloud_backup-0.1.1 → steam_cloud_backup-0.1.2}/pyproject.toml +13 -2
- steam_cloud_backup-0.1.2/source/__init__.py +0 -0
- steam_cloud_backup-0.1.2/source/cli.py +128 -0
- steam_cloud_backup-0.1.2/source/errors.py +23 -0
- steam_cloud_backup-0.1.2/source/lib.py +135 -0
- steam_cloud_backup-0.1.2/source/tools.py +73 -0
- steam_cloud_backup-0.1.1/PKG-INFO +0 -116
- steam_cloud_backup-0.1.1/setup.cfg +0 -4
- steam_cloud_backup-0.1.1/steam_cloud_backup.egg-info/SOURCES.txt +0 -8
- steam_cloud_backup-0.1.1/steam_cloud_backup.egg-info/dependency_links.txt +0 -1
- steam_cloud_backup-0.1.1/steam_cloud_backup.egg-info/requires.txt +0 -2
- steam_cloud_backup-0.1.1/steam_cloud_backup.egg-info/top_level.txt +0 -1
- {steam_cloud_backup-0.1.1 → steam_cloud_backup-0.1.2}/LICENSE +0 -0
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: steam-cloud-backup
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: micro-script for downloading all save files from steam cloud
|
|
5
5
|
Author: q-deltaftactal
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Repository, https://github.com/q-deltafractal/steam-cloud-backup.git
|
|
8
|
-
Project-URL: Issues, https://github.com/q-deltafractal/steam-cloud-backup/issues
|
|
9
|
-
Classifier: Topic :: Utilities
|
|
10
|
-
Requires-Python: >=3.14
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
7
|
License-File: LICENSE
|
|
8
|
+
Classifier: Topic :: Utilities
|
|
13
9
|
Requires-Dist: aiofiles>=25.1.0
|
|
14
10
|
Requires-Dist: aiohttp[speedups]>=3.14.1
|
|
15
|
-
|
|
11
|
+
Requires-Python: >=3.14
|
|
12
|
+
Project-URL: Repository, https://github.com/q-deltafractal/steam-cloud-backup.git
|
|
13
|
+
Project-URL: Issues, https://github.com/q-deltafractal/steam-cloud-backup/issues
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
16
15
|
|
|
17
16
|
# steam-cloud-backup
|
|
18
17
|
|
|
@@ -20,6 +19,20 @@ micro-script for downloading all save files from [steam cloud](https://store.ste
|
|
|
20
19
|
|
|
21
20
|
<br>
|
|
22
21
|
|
|
22
|
+
## installation
|
|
23
|
+
|
|
24
|
+
from (PyPI)[https://pypi.org/project/steam-cloud-backup/]:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# with pip
|
|
28
|
+
pip install steam-cloud-backup
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# or pipx
|
|
33
|
+
pipx install steam-cloud-backup
|
|
34
|
+
```
|
|
35
|
+
|
|
23
36
|
## backup process
|
|
24
37
|
|
|
25
38
|
### requirements
|
|
@@ -4,6 +4,20 @@ micro-script for downloading all save files from [steam cloud](https://store.ste
|
|
|
4
4
|
|
|
5
5
|
<br>
|
|
6
6
|
|
|
7
|
+
## installation
|
|
8
|
+
|
|
9
|
+
from (PyPI)[https://pypi.org/project/steam-cloud-backup/]:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# with pip
|
|
13
|
+
pip install steam-cloud-backup
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# or pipx
|
|
18
|
+
pipx install steam-cloud-backup
|
|
19
|
+
```
|
|
20
|
+
|
|
7
21
|
## backup process
|
|
8
22
|
|
|
9
23
|
### requirements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "steam-cloud-backup"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="q-deltaftactal" }
|
|
6
6
|
]
|
|
@@ -21,8 +21,19 @@ dependencies = [
|
|
|
21
21
|
Repository = "https://github.com/q-deltafractal/steam-cloud-backup.git"
|
|
22
22
|
Issues = "https://github.com/q-deltafractal/steam-cloud-backup/issues"
|
|
23
23
|
|
|
24
|
+
[project.scripts]
|
|
25
|
+
scb-cli = 'steam-cloud-backup.cli:main'
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["uv_build>=0.11.28,<0.12"]
|
|
29
|
+
build-backend = "uv_build"
|
|
30
|
+
|
|
31
|
+
[tool.uv.build-backend]
|
|
32
|
+
module-name = "source"
|
|
33
|
+
module-root = ""
|
|
34
|
+
|
|
24
35
|
[tool.setuptools]
|
|
25
|
-
|
|
36
|
+
packages = ['source']
|
|
26
37
|
|
|
27
38
|
[tool.ruff.format]
|
|
28
39
|
quote-style = "single"
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
__all__ = ()
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import asyncio
|
|
7
|
+
import argparse
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from source.errors import BadSessionException, ZeroAnswerException
|
|
11
|
+
from source.lib import SESSION_ID, STEAM_LOGIN_SECURE, parse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# connection
|
|
15
|
+
DEFAULT_USER_AGENT = (
|
|
16
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
|
|
17
|
+
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
|
18
|
+
'Chrome/150.0.0.0 Safari/537.36'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _ArgumentParser(argparse.ArgumentParser):
|
|
23
|
+
def _print_message(self, message: str, file=None) -> None:
|
|
24
|
+
super()._print_message(f'\n{message}\n', file=file)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> None:
|
|
28
|
+
parser = _ArgumentParser(
|
|
29
|
+
prog='steam-cloud-backup',
|
|
30
|
+
description='micro-script for downloading all save files from steam cloud',
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# session group
|
|
34
|
+
group = parser.add_argument_group('session')
|
|
35
|
+
group.add_argument(
|
|
36
|
+
'-i',
|
|
37
|
+
f'--{SESSION_ID}',
|
|
38
|
+
type=str,
|
|
39
|
+
)
|
|
40
|
+
group.add_argument(
|
|
41
|
+
'-l',
|
|
42
|
+
f'--{STEAM_LOGIN_SECURE}',
|
|
43
|
+
type=str,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# connection group
|
|
47
|
+
group = parser.add_argument_group('connection')
|
|
48
|
+
group.add_argument(
|
|
49
|
+
'-j',
|
|
50
|
+
'--max-concurrent-connections',
|
|
51
|
+
default=16,
|
|
52
|
+
help='default: 16',
|
|
53
|
+
type=int,
|
|
54
|
+
)
|
|
55
|
+
group.add_argument(
|
|
56
|
+
'--connect-timeout',
|
|
57
|
+
default=60,
|
|
58
|
+
help='default: 60 (s)',
|
|
59
|
+
type=int,
|
|
60
|
+
)
|
|
61
|
+
group.add_argument(
|
|
62
|
+
'--useragent',
|
|
63
|
+
default=DEFAULT_USER_AGENT,
|
|
64
|
+
type=str,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# out group
|
|
68
|
+
group = parser.add_argument_group('out')
|
|
69
|
+
group.add_argument('folder', type=Path)
|
|
70
|
+
group.add_argument(
|
|
71
|
+
'-v',
|
|
72
|
+
'--verbose',
|
|
73
|
+
choices=(0, 1, 2),
|
|
74
|
+
default=1,
|
|
75
|
+
help="""logging type:
|
|
76
|
+
0 - off;
|
|
77
|
+
1 (default) - info;
|
|
78
|
+
2 - debug""",
|
|
79
|
+
type=int,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
args = parser.parse_args()
|
|
83
|
+
del group
|
|
84
|
+
|
|
85
|
+
# data
|
|
86
|
+
try:
|
|
87
|
+
for k in (STEAM_LOGIN_SECURE,):
|
|
88
|
+
attr = getattr(args, k)
|
|
89
|
+
if not attr:
|
|
90
|
+
if not (value := input(f'{k}: ')):
|
|
91
|
+
raise
|
|
92
|
+
setattr(args, k, value)
|
|
93
|
+
except Exception, KeyboardInterrupt:
|
|
94
|
+
parser.exit(1, '\nnot enough data\n')
|
|
95
|
+
|
|
96
|
+
logging_level: int | None
|
|
97
|
+
match args.verbose:
|
|
98
|
+
case 0:
|
|
99
|
+
logging_level = None
|
|
100
|
+
case 1:
|
|
101
|
+
logging_level = logging.INFO
|
|
102
|
+
case 2:
|
|
103
|
+
logging_level = logging.DEBUG
|
|
104
|
+
|
|
105
|
+
logging.basicConfig(level=logging_level)
|
|
106
|
+
|
|
107
|
+
# parser
|
|
108
|
+
try:
|
|
109
|
+
asyncio.run(
|
|
110
|
+
parse(
|
|
111
|
+
c_session_id=getattr(args, SESSION_ID),
|
|
112
|
+
c_steam_login_secure=getattr(args, STEAM_LOGIN_SECURE),
|
|
113
|
+
concurrent_connections=args.max_concurrent_connections,
|
|
114
|
+
connect_timeout=args.connect_timeout,
|
|
115
|
+
path_to_folder=args.folder,
|
|
116
|
+
useragent=args.useragent,
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
except BadSessionException:
|
|
120
|
+
parser.exit(2, 'bad cookie session\n')
|
|
121
|
+
except ZeroAnswerException:
|
|
122
|
+
parser.exit(3, 'steam return zero page\n')
|
|
123
|
+
except KeyboardInterrupt:
|
|
124
|
+
parser.exit(4, 'user kill task')
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == '__main__':
|
|
128
|
+
main()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
'ParserException',
|
|
3
|
+
'BadSessionException',
|
|
4
|
+
'ZeroAnswerException',
|
|
5
|
+
'DownloadException',
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ParserException(Exception): ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BadSessionException(ParserException): ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ZeroAnswerException(ParserException): ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DownloadException(ParserException):
|
|
21
|
+
def __init__(self, path: Path) -> None:
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.path = path
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
__all__ = ('parse',)
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import random
|
|
5
|
+
import string
|
|
6
|
+
import logging
|
|
7
|
+
import asyncio
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import aiofiles
|
|
11
|
+
import aiohttp
|
|
12
|
+
from aiohttp import TCPConnector
|
|
13
|
+
|
|
14
|
+
from source.tools import TableParser
|
|
15
|
+
from source.errors import BadSessionException, DownloadException, ZeroAnswerException
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# datalink
|
|
19
|
+
REMOTE_STORAGE_URL = 'https://store.steampowered.com/account/remotestorage'
|
|
20
|
+
|
|
21
|
+
# cookie key names
|
|
22
|
+
SESSION_ID = 'sessionId'
|
|
23
|
+
STEAM_LOGIN_SECURE = 'steamLoginSecure'
|
|
24
|
+
|
|
25
|
+
# max rows per page
|
|
26
|
+
STEAM_PER_PAGE = 50
|
|
27
|
+
|
|
28
|
+
# logging
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def parse(
|
|
33
|
+
c_steam_login_secure: str,
|
|
34
|
+
path_to_folder: Path,
|
|
35
|
+
useragent: str,
|
|
36
|
+
concurrent_connections: int,
|
|
37
|
+
connect_timeout: int,
|
|
38
|
+
c_session_id: str | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
if c_session_id is None:
|
|
41
|
+
# steam default `sessionId` scheme
|
|
42
|
+
c_session_id = ''.join(
|
|
43
|
+
random.choices(string.ascii_lowercase + string.digits, k=24)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
async with aiohttp.ClientSession(
|
|
47
|
+
connector=TCPConnector(limit=concurrent_connections, ttl_dns_cache=300),
|
|
48
|
+
timeout=aiohttp.ClientTimeout(total=connect_timeout),
|
|
49
|
+
headers={
|
|
50
|
+
'User-Agent': useragent,
|
|
51
|
+
},
|
|
52
|
+
cookies={
|
|
53
|
+
SESSION_ID: c_session_id,
|
|
54
|
+
STEAM_LOGIN_SECURE: c_steam_login_secure,
|
|
55
|
+
},
|
|
56
|
+
cookie_jar=aiohttp.CookieJar(),
|
|
57
|
+
) as session:
|
|
58
|
+
text: str
|
|
59
|
+
async with session.get(url=REMOTE_STORAGE_URL) as r:
|
|
60
|
+
logger.debug('%s', str(r))
|
|
61
|
+
|
|
62
|
+
if not r.ok:
|
|
63
|
+
raise BadSessionException
|
|
64
|
+
text = await r.text()
|
|
65
|
+
|
|
66
|
+
if 'g_AccountID = 0;' in text:
|
|
67
|
+
raise BadSessionException
|
|
68
|
+
|
|
69
|
+
g_parser = TableParser()
|
|
70
|
+
g_parser.feed(text)
|
|
71
|
+
|
|
72
|
+
path_to_folder.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
|
|
74
|
+
if not (games_table := g_parser.rows):
|
|
75
|
+
raise ZeroAnswerException
|
|
76
|
+
# / size
|
|
77
|
+
for name, rows_c, _, g_url in games_table:
|
|
78
|
+
logger.info('iter game: %s; files count: %s', name, rows_c)
|
|
79
|
+
|
|
80
|
+
game_path = path_to_folder / name
|
|
81
|
+
|
|
82
|
+
for page in range(math.ceil(int(rows_c) / STEAM_PER_PAGE)):
|
|
83
|
+
params = {'index': page * STEAM_PER_PAGE} if page else None
|
|
84
|
+
|
|
85
|
+
first_iter = True
|
|
86
|
+
bad_files: set[Path] = set()
|
|
87
|
+
while first_iter or bad_files:
|
|
88
|
+
logger.debug('%s', str(bad_files))
|
|
89
|
+
try:
|
|
90
|
+
l_parser: TableParser
|
|
91
|
+
async with session.get(url=g_url, params=params) as dr:
|
|
92
|
+
l_parser = TableParser()
|
|
93
|
+
l_parser.feed(await dr.text())
|
|
94
|
+
|
|
95
|
+
async with asyncio.TaskGroup() as task_g:
|
|
96
|
+
# / date written
|
|
97
|
+
for folder, file_name, size, _, l_url in l_parser.rows:
|
|
98
|
+
file_path = game_path / folder / file_name
|
|
99
|
+
if first_iter or (
|
|
100
|
+
file_path in bad_files
|
|
101
|
+
and bad_files.remove(file_path)
|
|
102
|
+
is None # deleting file_name in bad_files if find
|
|
103
|
+
):
|
|
104
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
task_g.create_task(
|
|
106
|
+
download_file(session, l_url, file_path, size)
|
|
107
|
+
)
|
|
108
|
+
except* DownloadException as errs:
|
|
109
|
+
for e in errs.exceptions:
|
|
110
|
+
bad_files.add(e.path)
|
|
111
|
+
except* Exception:
|
|
112
|
+
await asyncio.sleep(1)
|
|
113
|
+
|
|
114
|
+
first_iter = False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def download_file(
|
|
118
|
+
session: aiohttp.ClientSession,
|
|
119
|
+
url: str,
|
|
120
|
+
path: Path,
|
|
121
|
+
size: str,
|
|
122
|
+
chunk_size: int = 8_192,
|
|
123
|
+
) -> None:
|
|
124
|
+
try:
|
|
125
|
+
async with session.get(url=url) as r:
|
|
126
|
+
r.raise_for_status()
|
|
127
|
+
|
|
128
|
+
async with aiofiles.open(path, 'wb') as f:
|
|
129
|
+
logger.info('download file %s; size: %s', path, size)
|
|
130
|
+
async for chunk in r.content.iter_chunked(chunk_size):
|
|
131
|
+
await f.write(chunk)
|
|
132
|
+
|
|
133
|
+
except Exception as err:
|
|
134
|
+
logger.debug('%s: %s', type(err), str(err))
|
|
135
|
+
raise DownloadException(path)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
__all__ = ('TableParser',)
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
from html.parser import HTMLParser
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _CellIO(io.StringIO):
|
|
8
|
+
"""override io.StringIO with write blocking"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
11
|
+
super().__init__(*args, **kwargs)
|
|
12
|
+
|
|
13
|
+
self.shadow_close = False
|
|
14
|
+
|
|
15
|
+
def write(self, *args, **kwargs) -> int:
|
|
16
|
+
if not self.shadow_close:
|
|
17
|
+
return super().write(*args, **kwargs)
|
|
18
|
+
return -1
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TableParser(HTMLParser):
|
|
22
|
+
"""parse steam backend table in py objects"""
|
|
23
|
+
|
|
24
|
+
_current_row: list[str]
|
|
25
|
+
_cell_buf: _CellIO
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
super().__init__()
|
|
29
|
+
|
|
30
|
+
self._in_tr = False
|
|
31
|
+
self._in_td = False
|
|
32
|
+
|
|
33
|
+
self.rows: list[tuple[str, ...]] = []
|
|
34
|
+
|
|
35
|
+
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
|
36
|
+
tag = tag.lower()
|
|
37
|
+
|
|
38
|
+
if tag == 'tr':
|
|
39
|
+
self._in_tr = True
|
|
40
|
+
self._current_row = []
|
|
41
|
+
elif self._in_tr and tag == 'td':
|
|
42
|
+
self._in_td = True
|
|
43
|
+
self._cell_buf = _CellIO()
|
|
44
|
+
elif tag == 'a':
|
|
45
|
+
# `href=` attr
|
|
46
|
+
self._cell_buf.write(attrs[0][1])
|
|
47
|
+
self._cell_buf.shadow_close = True
|
|
48
|
+
|
|
49
|
+
def handle_endtag(self, tag: str) -> None:
|
|
50
|
+
tag = tag.lower()
|
|
51
|
+
|
|
52
|
+
if tag == 'tr':
|
|
53
|
+
if self._current_row:
|
|
54
|
+
self.rows.append(tuple(self._current_row))
|
|
55
|
+
self._in_tr = False
|
|
56
|
+
if self._in_tr and tag == 'td':
|
|
57
|
+
self._cell_buf.seek(0)
|
|
58
|
+
cell = self._cell_buf.read().strip()
|
|
59
|
+
# void is not a correct folder name
|
|
60
|
+
self._current_row.append(cell if cell else '_')
|
|
61
|
+
|
|
62
|
+
self._in_td = False
|
|
63
|
+
|
|
64
|
+
def handle_data(self, data: str) -> None:
|
|
65
|
+
if self._in_tr and self._in_td:
|
|
66
|
+
self._cell_buf.write(data)
|
|
67
|
+
|
|
68
|
+
def feed(self, html: str, *args, **kwargs) -> None:
|
|
69
|
+
super().feed(
|
|
70
|
+
html[html.index('<tbody>') + 7 : html.rindex('</tbody>')].strip(),
|
|
71
|
+
*args,
|
|
72
|
+
**kwargs,
|
|
73
|
+
)
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: steam-cloud-backup
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: micro-script for downloading all save files from steam cloud
|
|
5
|
-
Author: q-deltaftactal
|
|
6
|
-
License-Expression: MIT
|
|
7
|
-
Project-URL: Repository, https://github.com/q-deltafractal/steam-cloud-backup.git
|
|
8
|
-
Project-URL: Issues, https://github.com/q-deltafractal/steam-cloud-backup/issues
|
|
9
|
-
Classifier: Topic :: Utilities
|
|
10
|
-
Requires-Python: >=3.14
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
|
-
Requires-Dist: aiofiles>=25.1.0
|
|
14
|
-
Requires-Dist: aiohttp[speedups]>=3.14.1
|
|
15
|
-
Dynamic: license-file
|
|
16
|
-
|
|
17
|
-
# steam-cloud-backup
|
|
18
|
-
|
|
19
|
-
micro-script for downloading all save files from [steam cloud](https://store.steampowered.com/account/remotestorage)
|
|
20
|
-
|
|
21
|
-
<br>
|
|
22
|
-
|
|
23
|
-
## backup process
|
|
24
|
-
|
|
25
|
-
### requirements
|
|
26
|
-
|
|
27
|
-
- [ ] [uv](https://docs.astral.sh/uv/) python package manager
|
|
28
|
-
- [ ] [git](https://git-scm.com/install/) version control
|
|
29
|
-
|
|
30
|
-
_latest versions_
|
|
31
|
-
|
|
32
|
-
### preparation
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# clone repo
|
|
36
|
-
git clone https://github.com/q-deltafractal/steam-cloud-backup.git
|
|
37
|
-
cd steam-cloud-backup
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# setup virtual env
|
|
42
|
-
uv sync
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### backup
|
|
46
|
-
|
|
47
|
-
```console
|
|
48
|
-
$ uv run main.py backup-folder/
|
|
49
|
-
steamLoginSecure: <input>
|
|
50
|
-
INFO:__main__:iter game: Steam Client; files count: 2
|
|
51
|
-
...
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
<br>
|
|
55
|
-
|
|
56
|
-
To identify login to your account, steam uses `steamLoginSecure` cookie, therefore the script requires it to work.
|
|
57
|
-
Method of obtaining this cookie:
|
|
58
|
-
|
|
59
|
-
- [ ] log in to https://store.steampowered.com/login/
|
|
60
|
-
- [ ] open Dev Tools via `ctrl + shift + i`
|
|
61
|
-
- [ ] go to the application _(or storage)_ tab
|
|
62
|
-
- [ ] in the cookies section, select `https://store.steampowered.com`
|
|
63
|
-
- [ ] copy the entire contents of the `steamLoginSecure` column
|
|
64
|
-
|
|
65
|
-
<br>
|
|
66
|
-
|
|
67
|
-
## usage
|
|
68
|
-
|
|
69
|
-
```console block=api
|
|
70
|
-
usage: steam-cloud-backup [-h] [-i SESSIONID] [-l STEAMLOGINSECURE]
|
|
71
|
-
[-j MAX_CONCURRENT_CONNECTIONS]
|
|
72
|
-
[--connect-timeout CONNECT_TIMEOUT]
|
|
73
|
-
[--useragent USERAGENT] [-v (0, 1, 2)]
|
|
74
|
-
folder
|
|
75
|
-
|
|
76
|
-
micro-script for downloading all save files from steam cloud
|
|
77
|
-
|
|
78
|
-
options:
|
|
79
|
-
-h, --help show this help message and exit
|
|
80
|
-
|
|
81
|
-
session:
|
|
82
|
-
-i, --sessionId SESSIONID
|
|
83
|
-
-l, --steamLoginSecure STEAMLOGINSECURE
|
|
84
|
-
|
|
85
|
-
connection:
|
|
86
|
-
-j, --max-concurrent-connections MAX_CONCURRENT_CONNECTIONS
|
|
87
|
-
default: 16
|
|
88
|
-
--connect-timeout CONNECT_TIMEOUT
|
|
89
|
-
default: 60 (s)
|
|
90
|
-
--useragent USERAGENT
|
|
91
|
-
|
|
92
|
-
out:
|
|
93
|
-
folder
|
|
94
|
-
-v, --verbose (0, 1, 2)
|
|
95
|
-
logging type: 0 - off; 1 (default) - info; 2 - debug
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
<br>
|
|
99
|
-
|
|
100
|
-
## license
|
|
101
|
-
|
|
102
|
-
project is licensed under:
|
|
103
|
-
|
|
104
|
-
<table>
|
|
105
|
-
<tr>
|
|
106
|
-
<td>
|
|
107
|
-
MIT
|
|
108
|
-
</td>
|
|
109
|
-
<td>
|
|
110
|
-
<a href="LICENSE">LICENSE</a>
|
|
111
|
-
</td>
|
|
112
|
-
<td>
|
|
113
|
-
https://mit-license.org/
|
|
114
|
-
</td>
|
|
115
|
-
</tr>
|
|
116
|
-
</table>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
source
|
|
File without changes
|