Browse Source

added Simple Web API recipe

pull/51/head
Rossen Georgiev 9 years ago
parent
commit
f651762185
  1. 85
      recipes/2.SimpleWebAPI/README.rst
  2. 55
      recipes/2.SimpleWebAPI/run_webapi.py
  3. 129
      recipes/2.SimpleWebAPI/steam_worker.py

85
recipes/2.SimpleWebAPI/README.rst

@ -0,0 +1,85 @@
Simple Web API recipe
---------------------
Valve doesn't have a Web API for everything, and they don't need to.
We are going to use the ``steam`` and ``flask`` to build a Web API.
First, we need to install ``flask``.
.. code:: bash
(env)$ pip install flask
``run_webapi.py`` contains our HTTP server app and ``steam_worker.py`` is
a modified version of persistent login recipe that will talk with steam.
Let's run the app:
.. code:: bash
(env)$ python run_webapi.py
2016-11-01 00:00:01,000 | SimpleWebAPI | Simple Web API recipe
2016-11-01 00:00:02,000 | SimpleWebAPI | ------------------------------
2016-11-01 00:00:03,000 | SimpleWebAPI | Starting Steam worker...
Username: myusername
Password:
2016-11-01 00:00:04,000 | Steam Worker | Connected to (u'1.2.3.4', 27018)
2016-11-01 00:00:05,000 | SimpleWebAPI | Starting HTTP server...
2016-11-01 00:00:06,000 | Steam Worker | ------------------------------
2016-11-01 00:00:07,000 | Steam Worker | Logged on as: FriendlyGhost
...
127.0.0.1 - - [2016-01-01 00:00:08] "GET /ISteamApps/GetPlayerCount/?appid=0 HTTP/1.1" 200 155 0.262596
...
Here are the available endpoints:
.. code:: bash
$ curl -s 127.0.0.1:5000/ISteamApps/GetProductInfo/?appids=570,730 | head -56
{
"apps": [
{
"appid": 570,
"appinfo": {
"appid": "570",
"common": {
"clienticon": "c0d15684e6c186289b50dfe083f5c562c57e8fb6",
"clienttga": "5ca2b133f8fdf56c3d81dd73d1254f95f0614265",
"community_hub_visible": "1",
"controllervr": {
"steamvr": "1"
},
"exfgls": "1",
"gameid": "570",
"header_image": {
"english": "header.jpg"
},
"icon": "0bbb630d63262dd66d2fdd0f7d37e8661a410075",
"linuxclienticon": "e1c520b6a98b1fed674a117e9356cdb9ddc6d40c",
"logo": "d4f836839254be08d8e9dd333ecc9a01782c26d2",
"logo_small": "d4f836839254be08d8e9dd333ecc9a01782c26d2_thumb",
"metacritic_fullurl": "http://www.metacritic.com/game/pc/dota-2?ftag=MCD-06-10aaa1f",
"metacritic_name": "Dota 2",
"metacritic_score": "90",
$ curl -s 127.0.0.1:5000/ISteamApps/GetProductChanges/?since_changenumber=2397700 | head -10
{
"app_changes": [
{
"appid": 730,
"change_number": 2409212
},
{
"appid": 740,
"change_number": 2409198
},
$ curl 127.0.0.1:5000/ISteamApps/GetPlayerCount/?appid=0
{
"eresult": 1,
"player_count": 2727080
}

55
recipes/2.SimpleWebAPI/run_webapi.py

@ -0,0 +1,55 @@
from getpass import getpass
from gevent.wsgi import WSGIServer
from steam_worker import SteamWorker
from flask import Flask, request, abort, jsonify
import logging
logging.basicConfig(format="%(asctime)s | %(name)s | %(message)s", level=logging.INFO)
LOG = logging.getLogger('SimpleWebAPI')
app = Flask('SimpleWebAPI')
@app.route("/ISteamApps/GetProductInfo/", methods=['GET'])
def GetProductInfo():
appids = request.args.get('appids', '')
pkgids = request.args.get('packageids', '')
if not appids and not pkgids:
return jsonify({})
appids = map(int, appids.split(',')) if appids else []
pkgids = map(int, pkgids.split(',')) if pkgids else []
return jsonify(worker.get_product_info(appids, pkgids) or {})
@app.route("/ISteamApps/GetProductChanges/", methods=['GET'])
def GetProductChanges():
chgnum = int(request.args.get('since_changenumber', 0))
return jsonify(worker.get_product_changes(chgnum))
@app.route("/ISteamApps/GetPlayerCount/", methods=['GET'])
def GetPlayerCount():
appid = int(request.args.get('appid', 0))
return jsonify(worker.get_player_count(appid))
if __name__ == "__main__":
LOG.info("Simple Web API recipe")
LOG.info("-"*30)
LOG.info("Starting Steam worker...")
worker = SteamWorker()
try:
worker.start(username=raw_input('Username: '), password=getpass())
except:
raise SystemExit
LOG.info("Starting HTTP server...")
http_server = WSGIServer(('', 5000), app)
try:
http_server.serve_forever()
except KeyboardInterrupt:
LOG.info("Exit requested")
worker.close()

129
recipes/2.SimpleWebAPI/steam_worker.py

@ -0,0 +1,129 @@
import logging
import gevent
from binascii import hexlify
from steam import SteamClient
from steam.core.msg import MsgProto
from steam.enums.emsg import EMsg
from steam.util import proto_to_dict
import vdf
LOG = logging.getLogger("Steam Worker")
class SteamWorker(object):
def __init__(self):
self.logged_on_once = False
self.logon_details = {}
self.steam = client = SteamClient()
client.set_credential_location(".")
@client.on("error")
def handle_error(result):
LOG.info("Logon result: %s", repr(result))
@client.on("channel_secured")
def send_login():
if client.relogin_available:
client.relogin()
else:
client.login(**self.logon_details)
self.logon_details.pop('auth_code', None)
self.logon_details.pop('two_factor_code', None)
@client.on("connected")
def handle_connected():
LOG.info("Connected to %s", client.current_server_addr)
@client.on("reconnect")
def handle_reconnect(delay):
LOG.info("Reconnect in %ds...", delay)
@client.on("disconnected")
def handle_disconnect():
LOG.info("Disconnected.")
if self.logged_on_once:
LOG.info("Reconnecting...")
client.reconnect(maxdelay=30)
@client.on("auth_code_required")
def auth_code_prompt(is_2fa, mismatch):
if mismatch:
LOG.info("Previous code was incorrect")
if is_2fa:
code = raw_input("Enter 2FA Code: ")
self.logon_details['two_factor_code'] = code
else:
code = raw_input("Enter Email Code: ")
self.logon_details['auth_code'] = code
client.connect()
@client.on("logged_on")
def handle_after_logon():
self.logged_on_once = True
LOG.info("-"*30)
LOG.info("Logged on as: %s", client.user.name)
LOG.info("Community profile: %s", client.steam_id.community_url)
LOG.info("Last logon: %s", client.user.last_logon)
LOG.info("Last logoff: %s", client.user.last_logoff)
LOG.info("-"*30)
def start(self, username, password):
self.logon_details = {
'username': username,
'password': password,
}
self.steam.connect()
self.steam.wait_event('logged_on')
def close(self):
if self.steam.connected:
self.logged_on_once = False
LOG.info("Logout")
self.steam.logout()
def get_product_info(self, 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
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 get_player_count(self, appid):
resp = self.steam.send_job_and_wait(MsgProto(EMsg.ClientGetNumberOfCurrentPlayersDP),
{'appid': appid},
timeout=10
)
return proto_to_dict(resp) or {}
Loading…
Cancel
Save