pythonhacktoberfeststeamauthenticationauthenticatorsteam-authenticatorsteam-clientsteam-guard-codessteam-websteamworksvalvewebapi
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
12 KiB
363 lines
12 KiB
# -*- coding: utf-8 -*-
|
|
"""
|
|
Wrapping methods for `ISteamWebUserPresenceOAuth` and `Polling` functionality
|
|
|
|
Example usage:
|
|
|
|
.. code:: python
|
|
import steam.webauth
|
|
import steam.webpresence
|
|
webAuth = steam.webauth.MobileWebAuth('username', 'password')
|
|
webAuth.login()
|
|
|
|
def my_callback(messages):
|
|
for message in messages:
|
|
print '[%s] %s from %s' % (message.timestamp, message.type, message.steamid_from.as_64)
|
|
|
|
webPresence = steam.webpresence.WebUserPresence(webAuth)
|
|
webPresence.logon()
|
|
webPresence.start_polling(my_callback)
|
|
print 'Started polling'
|
|
"""
|
|
import threading
|
|
import requests
|
|
|
|
from steam.webauth import MobileWebAuth
|
|
from steam import SteamID
|
|
|
|
DEFAULT_MOBILE_HEADERS = {
|
|
'X-Requested-With': 'com.valvesoftware.android.steam.community',
|
|
'User-agent': 'Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) \
|
|
AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30'
|
|
}
|
|
|
|
API_BASE = 'https://api.steampowered.com'
|
|
|
|
DEFAULT_TIMEOUT = 30
|
|
|
|
PERSONA_STATES = ['Offline', 'Online', 'Busy', 'Away', 'Snooze', 'Looking to trade', 'Looking to play']
|
|
|
|
class WebUserPresence:
|
|
"""
|
|
Wrapping methods for `ISteamWebUserPresenceOAuth` and `Polling` functionality
|
|
"""
|
|
_loggedon = False
|
|
_timeout = None
|
|
|
|
_oauth_token = None
|
|
_session = None
|
|
|
|
_steamid = None
|
|
_umqid = None
|
|
_message_base = None
|
|
|
|
def __init__(self, mobile_web_auth, timeout=None):
|
|
if not isinstance(mobile_web_auth, MobileWebAuth):
|
|
raise InvalidInstanceSupplied('The instance supplied as parameter is no valid instance of `MobileWebAuth`.')
|
|
|
|
if not mobile_web_auth.complete:
|
|
raise InstanceNotReady('Please make sure your `MobileWebAuth` instance is logged in.')
|
|
|
|
self._timeout = timeout
|
|
self._oauth_token = mobile_web_auth.oauth_token
|
|
self._session = mobile_web_auth.session
|
|
|
|
def _request(self, uri, data, timeout=None):
|
|
"""
|
|
HTTP Request
|
|
:param uri: URI to be requested
|
|
:param data: Data to be delivered
|
|
:param timeout: HTTP timeout
|
|
:return: `requests.Response`
|
|
"""
|
|
timeout = timeout or self._timeout or DEFAULT_TIMEOUT
|
|
return self._session.post(uri, data=data, headers=DEFAULT_MOBILE_HEADERS, timeout=timeout)
|
|
|
|
def _call(self, method, data, timeout=None):
|
|
"""
|
|
Calling an `ISteamWebUserPresenceOAuth` api method
|
|
:param method: Method to be called
|
|
:param data: Data to be delivered
|
|
:param timeout: HTTP timeout
|
|
:return: If successful, response as tuple, otherwise exceptions are thrown
|
|
"""
|
|
uri = '%s/ISteamWebUserPresenceOAuth/%s/v1/' % (API_BASE, method)
|
|
|
|
try:
|
|
response = self._request(uri, data, timeout=timeout)
|
|
except requests.exceptions.ReadTimeout:
|
|
raise HTTPError('Timeout')
|
|
|
|
if response.status_code == 401:
|
|
raise NotAuthorized('Not authorized. Please check your OAuth token and verify your `MobileWebAuth` login.')
|
|
elif response.status_code != 200:
|
|
raise HTTPError('HTTP request failed. Status code: %s' % response.status_code)
|
|
|
|
if self._loggedon:
|
|
self._message_base += 1
|
|
|
|
try:
|
|
json_response = response.json()
|
|
except:
|
|
raise ValueError('Could not build json_response')
|
|
else:
|
|
return json_response
|
|
|
|
def logon(self):
|
|
"""
|
|
Sends logon to the api
|
|
:return: True if successful. Raises an `LogonFailed` exception otherwise
|
|
"""
|
|
login_data = {
|
|
'access_token': self._oauth_token
|
|
}
|
|
response = self._call('Logon', login_data)
|
|
if response.get('error') == 'OK':
|
|
self._steamid = SteamID(response.get('steamid'))
|
|
self._umqid = response.get('umqid')
|
|
self._message_base = response.get('message')
|
|
|
|
self._loggedon = True
|
|
return True
|
|
else:
|
|
raise LogonFailed('Logon failed. Please check your OAuth token and verify your `MobileWebAuth` login.')
|
|
|
|
def logoff(self):
|
|
"""
|
|
Sends logoff to the api
|
|
:return: True if logoff was successful, False otherwise.
|
|
"""
|
|
response = self._call('Logoff', self._build_call_data({}), True)
|
|
self._loggedon = False
|
|
return response.get('error') == 'OK' or False
|
|
|
|
def poll(self):
|
|
"""
|
|
Starts an poll request
|
|
:return: Full response as Tuple
|
|
"""
|
|
poll_data = {
|
|
'pollid': 0,
|
|
'sectimeout': 5,
|
|
'secidletime': 0,
|
|
'use_accountids': 1
|
|
}
|
|
response = self._call('Poll', self._build_call_data(poll_data, True), timeout=60)
|
|
if response.get('error') == 'OK':
|
|
return response
|
|
else:
|
|
raise PollCreationFailed(response.get('error'))
|
|
|
|
def start_polling(self, callback):
|
|
"""
|
|
Creates an instance of `WebPolling` and starts the thread
|
|
:param callback: Function reference for handling `WebPollMessages`
|
|
:return: True
|
|
"""
|
|
web_polling = self._spawn_web_polling(callback)
|
|
web_polling.start()
|
|
return True
|
|
|
|
def stop_polling(self):
|
|
"""
|
|
Stops the active `WebPolling`
|
|
:return: True, False.
|
|
"""
|
|
return self._kill_web_polling()
|
|
|
|
def message(self, steamid, text):
|
|
"""
|
|
Delivers a steam message
|
|
:param steamid: SteamID64 of the target user
|
|
:param text: Message to deliver
|
|
:return: True if deliver was successful, False otherwise
|
|
"""
|
|
message_data = self._build_call_data({
|
|
'text': text,
|
|
'type': 'saytext',
|
|
'steamid_dst': steamid
|
|
})
|
|
response = self._call('Message', message_data)
|
|
return response.get('error') == 'OK' or False
|
|
|
|
def _spawn_web_polling(self, callback):
|
|
"""
|
|
Spawns a `WebPolling` instance
|
|
:param callback: A method reference for handling incoming `WebPollMessages`
|
|
:return: Instance reference
|
|
"""
|
|
self._web_polling = WebPolling(self, callback)
|
|
return self._web_polling
|
|
|
|
def _kill_web_polling(self):
|
|
"""
|
|
Stops the run() method of WebPolling after the current poll request
|
|
:return: True if `_web_polling` is spawned and could be stopped, False otherwise
|
|
"""
|
|
if getattr(self, '_web_polling'):
|
|
self._web_polling._active = False
|
|
return True
|
|
return False
|
|
|
|
def _prep_messages(self, messages):
|
|
"""
|
|
Prepares incoming raw `WebPolling` messages by creating `WebPollMessage` instances
|
|
:param messages: raw `WebPolling` messages
|
|
:return: List of `WebPollMessages` instances
|
|
"""
|
|
prepped_messages = [ ]
|
|
for message in messages:
|
|
prepped_messages.append(WebPollMessage(message, self))
|
|
return prepped_messages
|
|
|
|
def _build_call_data(self, data, set_message_base=False):
|
|
"""
|
|
Builds the data for `_call`'s
|
|
:param data: Data to deliver as Tuple
|
|
:param set_message_base: True, False. Only required for calls where `message` has to be set
|
|
:return: Prepared call data
|
|
"""
|
|
base_data = {
|
|
'umqid': self._umqid,
|
|
'access_token': self._oauth_token
|
|
}
|
|
if set_message_base:
|
|
base_data.__setitem__('message', self._message_base)
|
|
|
|
for key in data:
|
|
base_data.__setitem__(key, data.get(key))
|
|
|
|
return base_data
|
|
|
|
class WebPolling(threading.Thread):
|
|
"""
|
|
Threaded class for `Polling`
|
|
"""
|
|
_active = True
|
|
_web_user_presence = None
|
|
_callback = None
|
|
|
|
def __init__(self, web_user_presence, callback):
|
|
threading.Thread.__init__(self)
|
|
|
|
if not isinstance(web_user_presence, WebUserPresence):
|
|
raise InvalidInstanceSupplied('The instance supplied as parameter is no valid instance of `WebUserPresence`.')
|
|
|
|
if not web_user_presence._loggedon:
|
|
raise InstanceNotReady('The `WebUserPresence` has to be logged on.')
|
|
|
|
self._web_user_presence = web_user_presence
|
|
self._callback = callback
|
|
|
|
self.setDaemon(False)
|
|
|
|
def run(self):
|
|
"""
|
|
Sends HTTP requests while class is `_active` and calls a specified callback
|
|
:return:
|
|
"""
|
|
while self._active:
|
|
try:
|
|
response = self._web_user_presence.poll()
|
|
except PollCreationFailed:
|
|
"""
|
|
Ignore timeout exception
|
|
"""
|
|
pass
|
|
except HTTPError:
|
|
"""
|
|
Ignore http exceptions
|
|
"""
|
|
pass
|
|
else:
|
|
prepped_messages = self._web_user_presence._prep_messages(response.get('messages'))
|
|
if self._callback:
|
|
self._callback(prepped_messages)
|
|
|
|
class WebPollMessage(object):
|
|
"""
|
|
Class for proper handling of polling messages
|
|
"""
|
|
complete = False
|
|
|
|
_web_user_presence = None
|
|
|
|
timestamp = 0
|
|
type = None
|
|
steamid_from = SteamID()
|
|
|
|
text = None
|
|
|
|
persona_state = 0
|
|
persona_name = None
|
|
status_flags = None
|
|
|
|
_answerable = False
|
|
_full_data = None
|
|
|
|
def __init__(self, message, web_user_presence):
|
|
if not isinstance(web_user_presence, WebUserPresence):
|
|
raise InvalidInstanceSupplied('The instance supplied as parameter is no valid instance of `WebUserPresence`.')
|
|
|
|
if not web_user_presence._loggedon:
|
|
raise InstanceNotReady('The `WebUserPresence` has to be logged on.')
|
|
|
|
self._full_data = message
|
|
|
|
self._web_user_presence = web_user_presence
|
|
|
|
self.timestamp = message.get('utc_timestamp')
|
|
self.type = message.get('type')
|
|
self.steamid_from = SteamID(message.get('accountid_from'))
|
|
|
|
if self.type == 'typing':
|
|
self._answerable = True
|
|
elif self.type == 'saytext':
|
|
self._answerable = True
|
|
self.text = message.get('text')
|
|
elif self.type == 'personastate':
|
|
self.persona_name = message.get('persona_name')
|
|
self.persona_state = message.get('persona_state')
|
|
self.status_flags = message.get('status_flags')
|
|
|
|
def answer(self, message):
|
|
"""
|
|
If the current message is `_answerable` ( current message has something to do with the steam chat ) an
|
|
answer can be sent
|
|
:param message: Message to be delivered
|
|
:return: True if message could be delivered, False otherwise
|
|
"""
|
|
if not self._answerable:
|
|
return False
|
|
self._web_user_presence.message(self.steamid_from.as_64, message)
|
|
return True
|
|
|
|
def persona_state_to_str(self):
|
|
"""
|
|
Returns the `persona_state` as string
|
|
:return: `persona_state` as String or False if `persona_state` is not available
|
|
"""
|
|
if self.persona_state:
|
|
return PERSONA_STATES.__getitem__(self.persona_state)
|
|
return False
|
|
|
|
class WebUserPresenceException(Exception):
|
|
pass
|
|
|
|
class InvalidInstanceSupplied(WebUserPresenceException):
|
|
pass
|
|
|
|
class InstanceNotReady(WebUserPresenceException):
|
|
pass
|
|
|
|
class NotAuthorized(WebUserPresenceException):
|
|
pass
|
|
|
|
class LogonFailed(WebUserPresenceException):
|
|
pass
|
|
|
|
class PollCreationFailed(WebUserPresenceException):
|
|
pass
|
|
|
|
class HTTPError(WebUserPresenceException):
|
|
pass
|