Skip to content

Instantly share code, notes, and snippets.

@smcv
Created May 3, 2022 17:41
Show Gist options
  • Save smcv/8430646f3cf5e502711ddf3ea620eaea to your computer and use it in GitHub Desktop.
Save smcv/8430646f3cf5e502711ddf3ea620eaea to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# Copyright © 2020-2022 Collabora Ltd.
#
# SPDX-License-Identifier: MIT
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import argparse
import datetime
import json
import logging
import os
import sys
import typing
import steam
import steam.client
import steam.client.cdn
from steam.enums.common import EResult
logger = logging.getLogger('describe-depots.py')
APP_IDS = [1070560, 1391110, 1628350]
class Main:
def __init__(
self,
beta_password_env: typing.Sequence[str] = (),
credential_env: typing.Optional[str] = None,
interactive: bool = False,
**kwargs: typing.Dict[str, typing.Any]
) -> None:
self.beta_password_env = beta_password_env
self.interactive = interactive
self.credential_env = credential_env
@staticmethod
def _check_eresult(result: EResult) -> None:
if result == EResult.OK:
return
else:
raise RuntimeError(repr(result))
def run(self) -> None:
client = steam.client.SteamClient()
if self.interactive:
self._check_eresult(client.cli_login())
elif self.credential_env is not None:
username, password = os.environ[self.credential_env].split(':', 1)
self._check_eresult(client.login(username, password))
else:
self._check_eresult(client.anonymous_login())
cdn = steam.client.cdn.CDNClient(client)
cdn.load_licenses()
unlocked_beta = set() # type: typing.Set[int]
for env in self.beta_password_env:
appid_str, password = os.environ[env].split(':', 1)
appid = int(appid_str)
try:
self._check_eresult(cdn.check_beta_password(appid, password))
except RuntimeError:
logger.warning('Incorrect beta password for %d', appid)
else:
logger.info('Unlocked a beta for %d', appid)
unlocked_beta.add(appid)
tokens = client.get_access_tokens(APP_IDS)
inputs = [] # type: typing.List[typing.Dict[str, typing.Any]]
for appid in APP_IDS:
if appid in tokens['apps']:
inputs.append(
dict(appid=appid, access_token=tokens['apps'][appid]))
else:
logger.warning('Unable to get an access token for %d', appid)
if not inputs:
logger.error('No access tokens available')
raise SystemExit(1)
output = client.get_product_info(inputs)
json.dump(output, sys.stdout, indent=4)
print()
for appid, app_info in output['apps'].items():
print(
'App ID: {} ({})'.format(appid, app_info['common']['name'])
)
branches = set() # type: typing.Set[str]
branch_build_ids = {} # type: typing.Dict[str, str]
for branch, branch_info in app_info['depots']['branches'].items():
branch_build_ids[branch] = branch_info['buildid']
for branch, branch_info in app_info['depots']['branches'].items():
print('\tBranch: {}'.format(branch))
print('\t\tBuild ID: {}'.format(branch_info['buildid']))
updated = datetime.datetime.fromtimestamp(
int(branch_info['timeupdated']),
datetime.timezone.utc,
)
print('\t\tUpdated: {}'.format(updated.isoformat()))
branches.add(branch)
use_branch = branch
for depot, depot_info in app_info['depots'].items():
if not depot.isdigit():
continue
print('\t\tDepot: {}'.format(depot))
mid = depot_info['manifests'].get(branch)
if mid is None:
for other, build_id in branch_build_ids.items():
if build_id == branch_build_ids[branch]:
mid = depot_info['manifests'].get(other)
if mid is not None:
use_branch = other
break
if mid is None:
print('\t\t\t(Private)')
use_branch = ''
else:
print('\t\t\tManifest: {}'.format(mid))
if use_branch:
for manifest in cdn.get_manifests(
appid, branch=use_branch,
):
for f in manifest.iter_files('VERSIONS.txt'):
for line in f.read().splitlines():
print(
'\t\t\t\t{}'.format(line.decode('utf-8')))
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
'--credential-env', default=None,
help='Environment variable to be evaluated for Steam login:password',
)
parser.add_argument(
'--beta-password-env', action='append', default=[],
help='Environment variable to be evaluated for appid:password',
)
parser.add_argument(
'--interactive', action='store_true', default=False,
help='Log in to Steam interactively',
)
args = parser.parse_args()
assert args
Main(**vars(args)).run()
if __name__ == '__main__':
if sys.stderr.isatty():
try:
import colorlog
except ImportError:
logging.basicConfig()
else:
formatter = colorlog.ColoredFormatter(
'%(log_color)s%(levelname)s:%(name)s:%(reset)s %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logging.getLogger().addHandler(handler)
else:
logging.basicConfig()
logger.setLevel(logging.DEBUG)
try:
main()
except KeyboardInterrupt:
raise SystemExit(130)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment