-
Notifications
You must be signed in to change notification settings - Fork 0
SteamClient
steam.client.SteamClient is the gevent-based client for Steam's connection-manager (CM) protocol. It handles the encrypted handshake, login, message framing, and dispatch — with a rich event model on top.
SteamClient composes several mixins into one class:
- Apps — PICS product info, licenses, access tokens, depot keys, ticket flows.
- User — persona state, friends' presence, games-played, chat.
-
Web — bootstraps an authenticated
requests.Sessionfrom the CM login. - UnifiedMessages — service-method (UM) call / response wiring.
- Leaderboards — read leaderboard entries as list-like objects.
- Friends — friend list synchronization + add / remove / block.
- GameServers — game-server listing through Steam.
The mixins are combined via steam.client.builtins.BuiltinBase, which SteamClient inherits from alongside CMClient. You don't instantiate the mixins directly — you use SteamClient and everything is there.
Needs the client extra:
poetry install --extras clientThat pulls in gevent, protobuf, and gevent-eventemitter.
from steam.client import SteamClient
client = SteamClient()SteamClient() takes no arguments. Instantiation is cheap — no network, no threads.
Connection flows are the three login methods:
result = client.anonymous_login() # -> EResult
assert result == client.EResult.OKSignature: anonymous_login()
No credentials, no 2FA. Enough for public PICS lookups and general CM messages that don't require licenses. Cannot download depot files — see FAQ.
result = client.cli_login() # prompts for username, password, 2FA
result = client.cli_login('bob') # prompts only for password + 2FASignature: cli_login(username='', password='')
Best for scripts you run yourself. Handles email codes, 2FA codes, invalid passwords, and Steam-is-down retries with the same prompt loop.
result = client.login(
username='bob',
password='hunter2',
two_factor_code='K6VKF',
)Signature: login(username, password='', login_key=None, auth_code=None, two_factor_code=None, login_id=None)
The EVENT_AUTH_CODE_REQUIRED event fires when Steam wants a 2FA / email code — subscribe to it to build your own prompt UI:
@client.on(client.EVENT_AUTH_CODE_REQUIRED)
def on_auth_needed(is_2fa, code_mismatch):
if is_2fa:
code = input('Enter 2FA code: ')
client.login(username, password, two_factor_code=code)
else:
code = input('Enter email code: ')
client.login(username, password, auth_code=code)After a successful password login, Steam returns a login_key on the EVENT_NEW_LOGIN_KEY event. Persist that key and use relogin() next time — no password, no 2FA prompt:
if client.relogin_available:
client.relogin()
else:
client.cli_login()Set client.set_credential_location('/path/to/dir') if you also want sentry files persisted; the client uses that directory to remember CM lists too.
client.logout() # send ClientLogOff, wait for the CM to close
client.disconnect() # close the socket (idempotent)
client.reconnect() # exponential-backoff reconnectreconnect(maxdelay=30, retry=0) waits with exponential backoff up to maxdelay seconds. The backoff resets on a successful login.
SteamClient extends gevent_eventemitter.EventEmitter, so events are consumed via .on() decorators or .wait_event() / .wait_msg() blocking calls.
-
client.EVENT_LOGGED_ON— fires once when login succeeds. -
client.EVENT_AUTH_CODE_REQUIRED— fires with(is_2fa: bool, code_mismatch: bool)when Steam wants a code. -
client.EVENT_NEW_LOGIN_KEY— fires when Steam issues a fresh login key. Saveclient.login_keyfrom here. -
client.EVENT_DISCONNECTED— fires when the socket closes. -
client.EVENT_RECONNECT— fires just before a reconnect attempt. -
client.EVENT_CHANNEL_SECURED— CM handshake finished, safe tosend().
Messages themselves also emit events, keyed by their EMsg enum:
from steam.enums.emsg import EMsg
@client.on(EMsg.ClientPersonaState)
def on_persona(msg):
for friend in msg.body.friends:
print(friend.player_name, friend.persona_state)Service methods emit under their target_job_name (e.g. "Player.GetGameBadgeLevels#1").
Block until an event fires:
resp = client.wait_event(EMsg.ClientPersonaState, timeout=5)Signature: wait_event(event, timeout=None, raises=False) (inherited from EventEmitter).
wait_msg is wait_event for messages that return exactly one payload — it unwraps the tuple:
msg = client.wait_msg(EMsg.ClientPersonaState, timeout=5)
if msg:
print(msg.body)Signature: wait_msg(event, timeout=None, raises: bool = False)
SteamClient exposes three send flavours: fire-and-forget, job-tracked, and job-tracked-then-blocking.
send(message, body_params=None)Fire-and-forget. message is a Msg or MsgProto. Optional body_params dict is merged into message.body for MsgProto instances.
Job-flagged messages get a jobid in their header — Steam echoes it in the reply, and the client fires an event under job_<id> when the reply arrives. Use send_job() for a fire-and-listen pattern:
from steam.core.msg import MsgProto
from steam.enums.emsg import EMsg
jobid = client.send_job(MsgProto(EMsg.ClientRequestFriendData))
resp = client.wait_event(jobid, timeout=15)
if resp:
(msg,) = resp
print(msg.body)Signature: send_job(message, body_params=None) -> str
For the common round-trip case, use send_job_and_wait:
resp_body = client.send_job_and_wait(
MsgProto(EMsg.ClientGetNumberOfCurrentPlayersDP),
{'appid': 570},
timeout=10,
)
print(resp_body.player_count)Signature: send_job_and_wait(message, body_params=None, timeout=None, raises=False)
Returns msg.body directly, or None on timeout.
Service methods (also called "unified messages") are Steam's newer JSON-shaped RPC layer. Fire one with:
resp = client.send_um_and_wait('Player.GetGameBadgeLevels#1', {
'playerid': 123456,
})
if resp.header.eresult == client.EResult.OK:
print(resp.body)
else:
print('Error:', resp.header.error_message)Signature: send_um_and_wait(method_name, params=None, timeout=10, raises=False)
The #1 suffix is the method version. Look up available service methods in steam/protobufs/*_pb2.py — every service X { rpc Y(...) returns (...); } becomes callable.
Full page: PICS — including the comparison table for when to use PICS vs. store HTTP vs. Web API.
The most-used method on SteamClient (via the Apps mixin):
get_product_info(apps=[], packages=[], meta_data_only=False, raw=False, auto_access_tokens=True, timeout=15)Basic use:
resp = client.get_product_info(apps=[570, 730], packages=[123])
print(resp['apps'][570]['common']['name']) # 'Dota 2'
print(resp['apps'][730]['common']['name']) # 'Counter-Strike 2'
print(resp['packages'][123])Return shape:
{
'apps': {570: {'common': {...}, 'extended': {...}, 'depots': {...}, ...},
730: {...}},
'packages': {123: {...}},
}Each entry also carries _missing_token, _change_number, _sha, _size bookkeeping fields.
-
apps=[...]— list of app ids (ints) or dicts like{'appid': 570, 'access_token': ...}for licensed content. -
packages=[...]— same, but withpackageidkeys. -
meta_data_only=True— response omits the VDF payload. Fast when you only need the change number / sha. -
raw=True— buffers returned as raw bytes under_buffer, no VDF parsing. -
auto_access_tokens=True(default) — callget_access_tokens()first and fill them in. Turn off to bring your own tokens. -
timeout=15— seconds to wait per response chunk. Largeappslists arrive as multiple chunks; timeout is per-chunk.
If an app is licensed but the request lacked its access token, the response entry has _missing_token=True. See the docstring example inside steam/client/builtins/apps.py for the retry-with-token pattern.
The full end-to-end flow from the README:
from steam.client import SteamClient
client = SteamClient()
assert client.anonymous_login()
resp = client.get_product_info(apps=[553850], timeout=15) # Helldivers 2
print(resp['apps'][553850]['common']['name'])
client.logout()
client.disconnect()Run against live Steam any time you need to confirm the whole CM handshake / anonymous-login / PICS-fetch path is intact.
- Downloading actual game files? CDNClient wraps a logged-in
SteamClientfor depot access. - Need cookies for the web storefront from an authenticated
SteamClient? Useclient.get_web_session()from the Web mixin — or, if you don't have a CM login already, stand up a WebAuth directly. - Curious what all the events look like? Grep
steam/enums/emsg.pyfor the fullEMsgcatalogue.
H47R15/steam — maintained fork of ValvePython/steam. MIT licensed.