def send_um(self, method_name, params=None): """Send service method request :param method_name: method name (e.g. ``Player.GetGameBadgeLevels#1``) :type method_name: :class:`str` :param params: message parameters :type params: :class:`dict` :return: ``job_id`` identifier :rtype: :class:`str` Listen for ``jobid`` on this object to catch the response. """ proto = get_um(method_name) if proto is None: raise ValueError("Failed to find method named: %s" % method_name) message = MsgProto(EMsg.ServiceMethodCallFromClient) message.header.target_job_name = method_name message.body = proto() if params: proto_fill_from_dict(message.body, params) return self.send_job(message)
def get_web_session_cookies(self): """Get web authentication cookies via WebAPI's ``AuthenticateUser`` .. note:: A session is only valid during the current steam session. :return: dict with authentication cookies :rtype: :class:`dict`, :class:`None` """ if not self.logged_on: return None resp = self.send_job_and_wait(MsgProto(EMsg.ClientRequestWebAPIAuthenticateUserNonce), timeout=5) if resp is None: return None skey, ekey = generate_session_key() data = { 'steamid': self.steam_id, 'sessionkey': ekey, 'encrypted_loginkey': symmetric_encrypt(resp.webapi_authenticate_user_nonce.encode('ascii'), skey), } try: resp = webapi.post('ISteamUserAuth', 'AuthenticateUser', 1, params=data) except Exception as exp: self._logger.debug("get_web_session_cookies error: %s" % str(exp)) return None return { 'steamLogin': resp['authenticateuser']['token'], 'steamLoginSecure': resp['authenticateuser']['tokensecure'], }
def set_persona(self, state=None, name=None): """ Set persona state and/or name :param state: persona state flag :type state: :class:`steam.enums.common.EPersonaState` :param name: profile name :type name: :class:`str` """ if state is None and name is None: return message = MsgProto(EMsg.ClientChangeStatus) if state: if not isinstance(state, EPersonaState): raise ValueError( "Expected state to be instance of EPersonaState") message.body.persona_state = state if name: message.body.player_name = name self.send(message)
def get_leaderboard(self, app_id, name): """.. versionadded:: 0.8.2 Find a leaderboard :param app_id: application id :type app_id: :class:`int` :param name: leaderboard name :type name: :class:`str` :return: leaderboard instance :rtype: :class:`SteamLeaderboard` :raises: :class:`LookupError` on message timeout or error """ message = MsgProto(EMsg.ClientLBSFindOrCreateLB) message.header.routing_appid = app_id message.body.app_id = app_id message.body.leaderboard_name = name message.body.create_if_not_found = False resp = self.send_job_and_wait(message, timeout=15) if not resp: raise LookupError("Didn't receive response within 15seconds :(") if resp.eresult != EResult.OK: raise LookupError(EResult(resp.eresult)) return SteamLeaderboard(self, app_id, name, resp)
def get_entries(self, start=0, end=0, data_request=ELeaderboardDataRequest.Global): """Get leaderboard entries. :param start: start entry, not index (e.g. rank 1 is `start=1`) :type start: :class:`int` :param end: end entry, not index (e.g. only one entry then `start=1,end=1`) :type end: :class:`int` :param data_request: data being requested :type data_request: :class:`steam.enums.common.ELeaderboardDataRequest` :return: a list of entries, see `CMsgClientLBSGetLBEntriesResponse` :rtype: :class:`list` :raises: :class:`LookupError` on message timeout or error """ message = MsgProto(EMsg.ClientLBSGetLBEntries) message.body.app_id = self.app_id message.body.leaderboard_id = self.id message.body.range_start = start message.body.range_end = end message.body.leaderboard_data_request = data_request resp = self._steam.send_job_and_wait(message, timeout=15) if not resp: raise LookupError("Didn't receive response within 15seconds :(") if resp.eresult != EResult.OK: raise LookupError(EResult(resp.eresult)) return resp.entries
def change_email(self, password, email, code=''): """Change account's email address :param password: current account password :type password: :class:`str` :param email: new email address :type email: :class:`str` :param code: email code :type code: :class:`str` :return: result :rtype: :class:`.EResult` .. note:: To change email, first call without ``code`` and then with, to finalize the change """ message = MsgProto(EMsg.ClientEmailChange4) message.body.password = password message.body.email = email if code: message.body.code = code message.body.final = not not code message.body.newmethod = True message.body.client_supports_sms = True resp = self.send_job_and_wait(message, timeout=10) if resp is None: return EResult.Timeout else: return EResult(resp.eresult)
def send(self, message, params=None): """Send service method request :param message: proto message instance (use :meth:`SteamUnifiedMessages.get`) or method name (e.g. ``Player.GetGameBadgeLevels#1``) :type message: :class:`str`, proto message instance :param params: message parameters :type params: :class:`dict` :return: ``jobid`` event identifier :rtype: :class:`str` Listen for ``jobid`` on this object to catch the response. .. note:: If you listen for ``jobid`` on the client instance you will get the encapsulated message """ if isinstance(message, str): message = self.get(message) if message not in self._data: raise ValueError("Supplied message is invalid. Use 'get' method.") if params: proto_fill_from_dict(message, params) capsule = MsgProto(EMsg.ClientServiceMethod) capsule.body.method_name = self._data[message] capsule.body.serialized_method = message.SerializeToString() return self._steam.send_job(capsule)
def create_account(self, account_name, password, email=''): """Create a new Steam account :param account_name: desired account name :type account_name: :class:`str` :param password: desired password :type password: :class:`str` :param email: desired email :type email: :class:`str` :return: (EResult, SteamID) :rtype: :class:`tuple` """ message = MsgProto(EMsg.ClientCreateAccountProto) message.body.account_name = account_name message.body.password = password if email: message.body.email = email resp = self.send_job_and_wait(message, timeout=10) if resp is None: return EResult.Timeout, None else: return EResult( resp.eresult), SteamID(resp.steamid) if resp.steamid else None
def get_product_info(self, appids=[], packageids=[]): try: resp = self.steam.send_job_and_wait(MsgProto(EMsg.ClientPICSProductInfoRequest), { 'apps': map(lambda x: {'appid': x}, appids), 'packages': map(lambda x: {'packageid': x}, packageids), }, timeout=10 ) if not resp: return {} resp = proto_to_dict(resp) for app in resp.get('apps', []): app['appinfo'] = vdf.loads( app.pop('buffer')[:-1].decode('utf-8', 'replace'))['appinfo'] app['sha'] = hexlify(app['sha']).decode('utf-8') for pkg in resp.get('packages', []): pkg['appinfo'] = vdf.binary_loads(pkg.pop('buffer')[4:])[ str(pkg['packageid'])] pkg['sha'] = hexlify(pkg['sha']).decode('utf-8') return resp except Exception as e: print('get_product_info() exception: ' + str(e)) return {}
def check_beta_password(self, app_id, password): """Check branch beta password to unlock encrypted branches :param app_id: App ID :type app_id: int :param password: beta password :type password: str :returns: result :rtype: :class:`.EResult` """ resp = self.steam.send_job_and_wait( MsgProto(EMsg.ClientCheckAppBetaPassword), { 'app_id': app_id, 'betapassword': password }) if resp.eresult == EResult.OK: self._LOG.debug( "Unlocked following beta branches: %s", ', '.join(map(lambda x: x.betaname.lower(), resp.betapasswords))) for entry in resp.betapasswords: self.beta_passwords[(app_id, entry.betaname.lower())] = unhexlify( entry.betapassword) else: self._LOG.debug("App beta password check failed. %r" % EResult(resp.eresult)) return EResult(resp.eresult)
def _parse_message(self, message): emsg_id, = struct.unpack_from("<I", message) emsg = EMsg(clear_proto_bit(emsg_id)) if not self.connected and emsg != EMsg.ClientLogOnResponse: return if emsg in ( EMsg.ChannelEncryptRequest, EMsg.ChannelEncryptResponse, EMsg.ChannelEncryptResult, ): msg = Msg(emsg, message) else: try: if is_proto(emsg_id): msg = MsgProto(emsg, message) else: msg = Msg(emsg, message, extended=True) except Exception as e: self._LOG.fatal( "Failed to deserialize message: %s (is_proto: %s)", str(emsg), is_proto(emsg_id)) self._LOG.exception(e) if self.verbose_debug: self._LOG.debug("Incoming: %s\n%s" % (repr(msg), str(msg))) else: self._LOG.debug("Incoming: %s", repr(msg)) self.emit(emsg, msg)
def get_change_number(self, appids=None): if not appids: appids = [] else: appids = [appids] packageids = [] resp = self.steam.send_job_and_wait( MsgProto(EMsg.ClientPICSProductInfoRequest), { 'apps': map(lambda x: {'appid': x}, appids), 'packages': map(lambda x: {'packageid': x}, packageids), }, timeout=10) if not resp: return {} resp = proto_to_dict(resp) for app in resp.get('apps', []): app['appinfo'] = vdf.loads( app.pop('buffer').rstrip('\x00'))['appinfo'] app['sha'] = hexlify(app['sha']) for pkg in resp.get('packages', []): pkg['appinfo'] = vdf.binary_loads(pkg.pop('buffer')[4:])[str( pkg['packageid'])] pkg['sha'] = hexlify(pkg['sha']) return resp['apps'][0]['change_number']
def get_access_tokens(self, app_ids=[], package_ids=[]): """Get access tokens :param app_ids: list of app ids :type app_ids: :class:`list` :param package_ids: list of package ids :type package_ids: :class:`list` :return: dict with ``apps`` and ``packages`` containing their access tokens, see example below :rtype: :class:`dict`, :class:`None` .. code:: python {'apps': {123: 8888888886, ...}, 'packages': {456: 6666666666, ...} } """ if not app_ids and not package_ids: return resp = self.send_job_and_wait(MsgProto(EMsg.ClientPICSAccessTokenRequest), { 'appids': map(int, app_ids), 'packageids': map(int, package_ids), }, timeout=10 ) if resp: return {'apps': dict(map(lambda app: (app.appid, app.access_token), resp.app_access_tokens)), 'packages': dict(map(lambda pkg: (pkg.appid, pkg.access_token), resp.package_access_tokens)), }
def remove(self, steamid): """ Remove a friend :param steamid: their steamid :type steamid: :class:`int`, :class:`.SteamID`, :class:`.SteamUser` """ self._steam.send(MsgProto(EMsg.ClientRemoveFriend), {'friendid': steamid})
def query(self, filter_text, max_servers=10, timeout=30, **kw): r""" Query game servers https://developer.valvesoftware.com/wiki/Master_Server_Query_Protocol .. note:: When specifying ``filter_text`` use *raw strings* otherwise python won't treat backslashes as literal characters (e.g. ``query(r'\appid\730\white\1')``) :param filter_text: filter for servers :type filter_text: str :param max_servers: (optional) number of servers to return :type max_servers: int :param timeout: (optional) timeout for request in seconds :type timeout: int :param app_id: (optional) app id :type app_id: int :param geo_location_ip: (optional) ip (e.g. '1.2.3.4') :type geo_location_ip: str :returns: list of servers, see below. (``None`` is returned steam doesn't respond) :rtype: :class:`list`, :class:`None` Sample response: .. code:: python [{'auth_players': 0, 'server_ip': '1.2.3.4', 'query_port': 27015}, {'auth_players': 6, 'server_ip': '1:2:3:4::5', 'query_port': 27016}, ... ] """ if 'geo_location_ip' in kw: kw['geo_location_ip'] = ip4_to_int(kw['geo_location_ip']) kw['filter_text'] = filter_text kw['max_servers'] = max_servers resp = self._s.send_job_and_wait( MsgProto(EMsg.ClientGMSServerQuery), kw, timeout=timeout, ) if resp is None: return None resp = proto_to_dict(resp) for server in resp['servers']: server.pop('deprecated_server_ip', None) # no point returning this if 'v4' in server['server_ip']: server['server_ip'] = ip4_from_int(server['server_ip']['v4']) else: server['server_ip'] = ip6_from_bytes(server['server_ip']['v6']) return resp['servers']
def get_product_info(self, apps=[], packages=[], timeout=15): """Get product info for apps and packages :param apps: items in the list should be either just ``app_id``, or ``(app_id, access_token)`` :type apps: :class:`list` :param packages: items in the list should be either just ``package_id``, or ``(package_id, access_token)`` :type packages: :class:`list` :return: dict with ``apps`` and ``packages`` containing their info, see example below :rtype: :class:`dict`, :class:`None` .. code:: python {'apps': {570: {...}, ...}, 'packages': {123: {...}, ...} } """ if not apps and not packages: return message = MsgProto(EMsg.ClientPICSProductInfoRequest) for app in apps: app_info = message.body.apps.add() app_info.only_public = False if isinstance(app, tuple): app_info.appid, app_info.access_token = app else: app_info.appid = app for package in packages: package_info = message.body.packages.add() if isinstance(package, tuple): package_info.appid, package_info.access_token = package else: package_info.packageid = package message.body.meta_data_only = False job_id = self.send_job(message) data = dict(apps={}, packages={}) while True: chunk = self.wait_event(job_id, timeout=timeout) if chunk is None: return chunk = chunk[0].body for app in chunk.apps: data['apps'][app.appid] = vdf.loads(app.buffer[:-1].decode( 'utf-8', 'replace'))['appinfo'] for pkg in chunk.packages: data['packages'][pkg.packageid] = vdf.binary_loads( pkg.buffer[4:])[str(pkg.packageid)] if not chunk.response_pending: break return data
def change_status(self, persona_state, player_name): """Changes user's status according to passed value. Can also change profile name. """ sendmsg = MsgProto(EMsg.ClientChangeStatus) sendmsg.body.persona_state = persona_state sendmsg.body.player_name = player_name self.client.send(sendmsg)
def _handle_login_key(self, message): resp = MsgProto(EMsg.ClientNewLoginKeyAccepted) resp.body.unique_id = message.body.unique_id if self.logged_on: self.send(resp) self.idle() self.login_key = message.body.login_key self.emit(self.EVENT_NEW_LOGIN_KEY)
def get_product_changes(self, since_change_number): resp = self.steam.send_job_and_wait(MsgProto( EMsg.ClientPICSChangesSinceRequest), { 'since_change_number': since_change_number, 'send_app_info_changes': True, 'send_package_info_changes': True, }, timeout=10) return proto_to_dict(resp) or {}
def Send_Friend_Request(self, friend_steam_id): """ Send a friend request to a friend! friend_steam_id type: int friend_steam_id example: 77777777777777777 """ msg = MsgProto(EMsg.ClientAddFriend) msg.body.steamid_to_add = friend_steam_id self.client.send_message_and_wait(msg, None)
def send_msg(self, steamid, msg): """Sends a message to a steam user. """ sendmsg = MsgProto(EMsg.ClientFriendMsg) sendmsg.body.steamid = steamid sendmsg.body.chat_entry_type = 1 sendmsg.body.message = msg sendmsg.body.rtime32_server_timestamp = int(time.time()) self.client.send(sendmsg)
def set_ui_mode(self, uimode): """ Set UI mode. Show little icon next to name in friend list. (e.g phone, controller, other) :param uimode: UI mode integer :type uimode: :class:`EClientUIMode` These app ids will be recorded in :attr:`current_games_played`. """ self.send(MsgProto(EMsg.ClientCurrentUIMode), {'uimode': EClientUIMode(uimode)})
def remove(self, steamid): """ Remove a friend :param steamid: their steamid :type steamid: :class:`int`, :class:`steam.steamid.SteamID`, :class:`SteamUser` """ m = MsgProto(EMsg.ClientRemoveFriend) m.body.friendid = steamid self._steam.send(m)
def send_message(self, message): """Send chat message to this steam user :param message: message to send :type message: str """ self._steam.send(MsgProto(EMsg.ClientFriendMsg), { 'steamid': self.steam_id, 'chat_entry_type': EChatEntryType.ChatMsg, 'message': message.encode('utf8'), })
def logout(self): """ Logout from steam. Doesn't nothing if not logged on. .. note:: The server will drop the connection immediatelly upon logout. """ if self.logged_on: self.logged_on = False self.send(MsgProto(EMsg.ClientLogOff)) self.wait_event('disconnected')
def get_app_ticket(self, app_id): """Get app ownership ticket :param app_id: app id :type app_id: :class:`int` :return: `CMsgClientGetAppOwnershipTicketResponse <https://github.com/ValvePython/steam/blob/39627fe883feeed2206016bacd92cf0e4580ead6/protobufs/steammessages_clientserver.proto#L158-L162>`_ :rtype: proto message """ return self.send_job_and_wait(MsgProto( EMsg.ClientGetAppOwnershipTicket), {'app_id': app_id}, timeout=10)
def request_persona_state(self, steam_ids, state_flags=863): """Request persona state data :param steam_ids: list of steam ids :type steam_ids: :class:`list` :param state_flags: client state flags :type state_flags: :class:`.EClientPersonaStateFlag` """ m = MsgProto(EMsg.ClientRequestFriendData) m.body.persona_state_requested = state_flags m.body.friends.extend(steam_ids) self.send(m)
def get_encrypted_app_ticket(self, app_id, userdata): """Gets the encrypted app ticket :param app_id: app id :type app_id: :class:`int` :param userdata: userdata :type userdata: :class:`bytes` :return: `EncryptedAppTicket <https://github.com/ValvePython/steam/blob/39627fe883feeed2206016bacd92cf0e4580ead6/protobufs/encrypted_app_ticket.proto>_` :rtype: proto message """ return self.send_job_and_wait(MsgProto(EMsg.ClientRequestEncryptedAppTicket), {'app_id': app_id, 'userdata': userdata}, timeout=10 )
def get_product_changes(self, since_change_number): try: resp = self.steam.send_job_and_wait(MsgProto(EMsg.ClientPICSChangesSinceRequest), { 'since_change_number': since_change_number, 'send_app_info_changes': True, 'send_package_info_changes': True, }, timeout=10 ) return proto_to_dict(resp) or {} except Exception as e: print('get_product_changes() exception: ' + str(e)) return {}
def Send_Friend_Msg(self, friend_steam_id, message): """ Send a message to a friend with his steamID! friend_steam_id type: int friend_steam_id example: 77777777777777777 message type: string message example: 'Hello' """ msg = MsgProto(EMsg.ClientFriendMsg) msg.body.steamid = friend_steam_id msg.body.chat_entry_type = 1 msg.body.message = message.encode('utf-8') self.client.send_message_and_wait(msg, None)