Making things, writing code, wasting time...

Category: Raspberry Pi (Page 1 of 2)

Flask asynchronous background tasks with Celery and Redis

Introduction:

This blog post will look at a practical example of how to implement asynchronous background tasks in a Flask environment, with an example taken from my ongoing project of building a Destiny the game inventory management web application.

During the design of DestinyVaultRaider.com one of the main pain points was manually updating my production environment every time the Destiny Manifest was changed. The development crew in Bungie were very active and were updating the Manifest right up to the launch of Destiny 2.

Before adding the background tasks, I had a small Python script running from my Raspberry Pi, which would send a request to Bungie, every 10 minutes, for the current Manifest version – if the stored Manifest version was different, the script would send me a message on a private Slack channel to notify me that the Manifest had changed and I’d need to update my Heroku environment.

If the Manifest version stored on my production environment didn’t match the current revision of the Manifest Bungie were using, this would cause an error in my Flask application, sending the user a HTTP 500 internal error response and be diverting them to a generic error page. This leads to a negative user experience, and with the Manifest being updated randomly – sometimes twice a week, I was left scrambling to update it as quickly as possible.

I store the Manifest in a Redis database, so  using Redis as a Message Broker (see below) made sense for my application.

Introduction to Celery:

From the Celery docs: “Celery is an asynchronous task queue/job queue based on distributed message passing. It is focused on real-time operation, but supports scheduling as well”.

From the perspective of my app, I will be using Celery to create a scheduled task, which checks the Destiny Manifest every few minutes, and updates my Redis database as needed. I will also be creating an asynchronous task that will check the Manifest every time an authorised user sends a request to a specific endpoint on my server, such as https://www.destinyvaultraider.com/updateManifest.

For another look at implementing Celery with Flask, have a read of Miguel Grinberg’s blog on Celery with Flask.

How Celery works:

The asynchronous tasks will be set up as follows.

  1. Celery client:
    • This will be connect your Flask application to the Celery task. The client will issue the commands for the task.
  2. Celery worker:
    • A process that runs a background task, I will have 2 workers, a scheduled task and an asynchronous task called every time I visit a particular endpoint (/updateManifest).
  3. Message broker:
    • The Celery client communicates to the Celery worker through a message broker, I will be using Redis but you can also use RabbitMQ or RQ.

Installing Celery and Redis:

An important note: One of the main reasons I went with Celery and Redis; is because I do all of my development work on a Windows laptop – most of the libraries and tutorials I found were geared on developing in a Linux environment (which makes sense as your web application is most likely deployed on a Linux system). Celery has dropped Windows support but you can still install an earlier version to allow development on a Windows system.

Shout out to /u/WTRipper on Reddit for this information.

Installing Celery:

Celery can be installed from pip, version 3.1.25 supports Windows and worked well for me:

pip uninstall celery 
pip install celery==3.1.25

Installing Redis:

Redis is not officially supported on windows – but the Microsoft open tech group maintain a Windows port, which you can download here. (I downloaded the installer Redis-x64-3.0.504.msi).

The Flask application factory:

The Flask application factory concept is a methodology of structuring your app as a series of Blueprints, which can run individually, or together (even with different configurations). More than just this, it sets out a more standardised approach to designing an application.

This can also add a bit of complexity to designing an application, as most Celery tutorials focus on standalone applications and ignore the detail of integrating Celery into a large Flask application.

In my case, I will have a Blueprint for my API and a Main Blueprint for everything else.

Destiny Vault Raider app structure:

As Destiny Vault Raider uses the Flask application factory structure, each Blueprint is contained in it’s own folder. For DVR, I only need a “main” and “api” Blueprint, as I don’t require separate views for unauthenticated visitors (although it’s probably something I’ll add in future).

The main items to look out for are highlighted in red.

DestinyVaultRaider
│   celery_worker.py
│   config.py
│   manage.py
│
├───app
│   │   email.py
│   │   getManifest.py
│   │   models.py
│   │   __init__.py
│   │  
│   ├───api_1_0
│   │       views.py
│   │       __init__.py
│   │      
│   ├───main
│   │       errors.py
│   │       forms.py
│   │       Inventory_Management.py
│   │       OAuth_functions.py
│   │       views.py
│   │       __init__.py
│   │      
│   ├───static
│   │   │   style.css
│   │          
│   └───templates
│       │   index.html
│

Destiny Vault Raider updating manifest flow chart:

Flask application with Redis and Celery.

Flask application with Redis and Celery.

From the diagram, we can see:

  • How the Flask application connects to the Redis message broker.
  • The Message broker talks to the Celery worker.
  • The Celery worker calls (either the asynchronous or periodic) Python function to update the Redis Manifest database.
  • The Flask application can access the Manifest database directly, when a user makes a request to view their items.

Now, lets tun these ideas into code!

Creating the Celery worker:

Create an instance of the Celery worker, add the Celery configuration. The Celery configuration will be defined a little later in config.py.

app/__init__.py:

from celery import Celery

celery = Celery(__name__, broker=Config.CELERY_BROKER_URL)

def create_app(config_name):
    app = Flask(__name__)
    :
    :
    celery.conf.update(app.config)
    :

Adding the Celery worker to the app instance:

Take the instance of the celery object we created and and add it to the app context (read about the app_context here).

celery_worker.py:

import os
from app import celery, create_app

app = create_app(os.getenv('FLASK_CONFIG') or 'default')
app.app_context().push()

Configuring Celery and Redis:

During development, your Celery client and Redis broker will be running on your local machine, however during deployment – these connections will be to a remote server. As you’ll need 2 setups, you’ll need to create the Config setup for both development and deployment. On DVR, I set an environment variable “is_prod” to True, which allows me to test if I’m in the deployed environment.

All of this configuration will be added to the Celery object in app/__init__.py, when we create the celery object and pass in the config with the command: celery.conf.update(app.config).

config.py:

First, I create the setup for the Celery beat schedule, I set the schedule for 5 minutes, which is 300 seconds.

# Create Celery beat schedule:
celery_get_manifest_schedule = {
    'schedule-name': {
        'task': 'app.getManifest.periodic_run_get_manifest',
        'schedule': timedelta(seconds=300),
    },
}

Note: The task  is named app.getManifest.periodic_run_get_manifest, the task is located in the “app” folder, in the “getManifest” file, and the function is called periodic_run_get_manifest.

Next, I create the Config object, with the Celery and Redis settings, for both production and development.

class Config:
    CELERYBEAT_SCHEDULE = celery_get_manifest_schedule
    # Development setup:
    if not is_prod:
        CELERY_BROKER_URL = 'redis://localhost:6379/0'
        CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
        REDIS_HOST = 'localhost'
        REDIS_PASSWORD = ''
        REDIS_PORT = 6379
        REDIS_URL = 'redis://localhost:6379/0'

    # Production setup:
    else:
        # Celery:
        CELERY_BROKER_URL = os.environ.get('REDIS_URL')
        CELERY_RESULT_BACKEND = os.environ.get('REDIS_URL')
        # Redis:
        REDIS_URL = os.environ.get('REDIS_URL')

Note: Both the Celery Broker URL is the same as the Redis URL (I’m using Redis as my messge Broker) the environment variable “REDIS_URL” is used for this.

Connecting to the Celery and Redis server:

Now that we’ve created the setup for the Celery and Redis we need to instantiate the Redis object and create the connection to the Redis server.

I also ping the Redis server to check the connection.

getManifest.py:

from . import celery
from celery.task.base import periodic_task
from config import config, Config

# Set Redis connection:
redis_url = urlparse.urlparse(Config.REDIS_URL)
r = redis.StrictRedis(host=redis_url.hostname, port=redis_url.port, db=1, password=redis_url.password)

# Test the Redis connection:
try: 
    r.ping()
    print "Redis is connected!"
except redis.ConnectionError:
    print "Redis connection error!"

Note: I tried to manually add the hostname, port and password as strings and populate the redis.StrictRedis command, however, this wouldn’t work for me and I could only connect to the Redis server if I used urlparse to format the URL for me (I presume it’s looking for a URL object and not a String object but couldn’t I figure out why).

The db=1 sets the database table number to 1, it defaults to 0 if not added.

REDIS_URL:

redis://h:p0dc...449@ec2-...-123.compute-1.amazonaws.com:48079

which is on the format of:

redis://<username>:<PASSWORD>@<HOST>:<PORT>

You can set this as an environment variable on Heroku by using the following command:

heroku config:set REDIS_URL redis://h:p0dc...449@ec2-...-123.compute-1.amazonaws.com:48079

Creating the asynchronous task:

Here is the definition of the run_get_manifest() function, it’s pretty huge so I won’t include all of the code.

However, the important thing to note is the @celery.task decorator.

@celery.task(name='tasks.async_run_get_manifest')
def run_get_manifest():
    """ Run the entire get_manifest flow as a single function """
    build_path()
    manifest_version = request_manifest_version()
    if check_manifest(manifest_version) is True:
        getManifest()
        buildTable()
        manifest_type = "full"
        all_data = buildDict(DB_HASH)
        writeManifest(all_data, manifest_type)
        cleanUp()
    else:
        print "No change detected!"

    return

To create the asynchronous function, I create a new  function async_run_get_manifest().

Inside this function, I call the original run_get_manifest function but add the delay() method, we can access the delay() method as we have wrapped the run_get_manifest() function in the @celery_task decorator.

def async_run_get_manifest():
    """ Asynchronous task, called from API endpoint. """
    run_get_manifest.delay()
    return

Creating the periodic task:

To create the periodic function, I create a new  function periodic_run_get_manifest().

This function is decorated with the @periodic_task decorator. The “run_every” parameter is required and sets the time interval.

@periodic_task(run_every=timedelta(seconds=300))
def periodic_run_get_manifest():
    """ Perodic task, run by Celery Beat process """
    run_get_manifest()
    return

So now I have 2 functions, that do the same thing, but with some important differences:

  1. periodic_run_get_manifest(): This is the periodic task that is run every 5 minutes.
  2. async_run_get_manifest(): This is the asynchronous task that will run in the background when a request is sent to the /updateManifest endpoint.

Starting the Celery workers:

To start the Celery workers, you need both a Celery worker and a Beat instance running in parallel. Here are the commands for running them:

worker -A celery_worker.celery --loglevel=info
celery beat -A celery_worker.celery --loglevel=info

Now that they are running, we can execute the tasks.

Calling the asynchronous task:

The asynchronous task will be called anytime an authorised account visits a designated endpoint, I’m using the endpoint “/updateManifest”. This will call the asynchronous task “async_run_get_manifest()” which will be executed in the background.

api_1_0/views.py:

You’ll need to implement a feature to detect if the user is authorised to access this endpoint, I’ve left that out for clarity’s sake.

In this case I return back to the index.html page, depending on how your API is setup, you may return a text or JSON response. I had a system in place where I would receive update messages on a private Slack channel – depending on how the update went.

@api.route('/updateManifest')
@login_required
def updateManifest():
    async_run_get_manifest()
    return render_template('index.html',
                            site_details    = site_details,
                            ) 

Executing the periodic task:

The Periodic task will be executed every 5 minutes when the Celery Beat scheduler is running. Here I can check the progress from the Celery output:

[2017-11-22 13:38:08,000: INFO/MainProcess] Received task: app.getManifest.periodic_run_get_manifest[97a82703-af22-4a43-b189-8dc192f55b84]
[2017-11-22 13:38:08,059: INFO/Worker-1] Starting new HTTPS connection (1): www.bungie.net
[2017-11-22 13:38:10,039: WARNING/Worker-1] Detected a change in version number: 60480.17.10.23.1314-2
[2017-11-22 13:38:10,042: INFO/Worker-1] Starting new HTTPS connection (1): slack.com
[2017-11-22 13:38:10,803: WARNING/Worker-1] Detected a change in mobileWorldContentPaths: [u'en']

We can see from here:

  • Recieved the task: app.getManifest.periodic_run_get_manifest()
  • Created a new HTTPS connection to www.bungie.net – this is the request to check the Manifest version.
  • Next I check the version number of the Manifest, and print the line “Detected a change in version number: 60480.17.10.23.1314-2”
  • Created a new HTTPS connection to www.slack.com and I send this line to Slack as a message.
  • Next I check the mobileWorldContentPaths version for the English Manifest, and print the line “Detected a change in mobileWorldContentPaths: [u’en’]”

In my case I didn’t need my app to keep track of the task status or check if it’s completed correctly, but Celery has that option. I get updates from the Slack messages.

Creating a development Start up script:

Here’s the script I use to start up the development server, Redis server, Celery worker and Celery Beat worker. Save the following into a file called “Startup.bat” and you can just double click on the file icon to start each process, in it’s own window, in your development environment.

This can save a lot of time as opening 4 command windows and starting each process separately.

C:\
cd /d "C:\Users\AllynH\Documents\Python\Flask\DestinyVaultRaider_Redis_Celery_API"
start /K redis-cli shutdown
start timeout 5
start CMD /K redis-server
start CMD /K celery worker -A celery_worker.celery --loglevel=info
start CMD /K celery beat -A celery_worker.celery --loglevel=info
start CMD /K python manage.py runserver

Here’s a breakdown of what the script is doing:

  • The first line changes to our working directory.
  • Next I shutdown any existing Redis server (sometimes Redis wouldn’t start correctly unless I had done a shutdown first).
  • Then I wait for 5 seconds to allow the Redis server to shutdown.
  • The next 4 commands are used to start the Redis server, Celery worker, Celery Beat worker, and Flask server – each started in their own command shell.
Redis server, Celery workers and Flask server started.

Redis server, Celery workers and Flask server started via the Startup.bat script.

Running on Heroku:

Here are some Heroku specific changes, you can skip these if you’re not running on Heroku.

Creating a Redis broker and adding it to the app:

You’ll need to create a Redis broker and attach it to your app, this will give you the REDIS_URL mentioned above.

heroku addons:create heroku-redis -a destinyvaultraider

Editing the procfile:

To start the Celery worker and Beat processes, add the following to your procfile:

worker: celery worker -A celery_worker.celery --beat --loglevel=info

Note: we can kick off both the Celery worker and Beat scheduler in one command here, whereas we couldn’t on Windows.

Scaling the Worker dyno:

To start the process, you need to enable the Celery worker Dyno:

heroku ps:scale worker=1

Now your background tasks should be up and running!

Note on running Celery and Redis on Heroku:

The pricing on Heroku is really expensive to the point of being prohibitive, from my perspective I have had to disable the Redis database and Celery worker as Heroku require you to pay separately for each of these.

For example the pricing for the package I wanted worked out like this (as of November 2017), all figures are per month:

  • Hobby Dyno: $7 (Required for HTTPS certification).
  • Celery worker: $7.
  • Redis database 100MB: $30 (80MB required).

This is obviously not feasible for a hobby project that isn’t making any money.

In comparison, a Digital Ocean, Vultr or OVH also provide Virtual Private Server services from ~$5 per month, which would allow you to run Redis and Celery inclusive of that price.

So before you invest your time in Heroku, research some of the alternatives 🙂

Creating a Python app for Destiny – Part 8: Displaying the Vault contents.

Introduction:

Big news: I’ve managed to launch my own Destiny based website!!! I’ve been working on an inventory management system for a while now, and following on from my previous blog posts I’ve managed to deploy my work to date as a live website.

You can test it out for yourself here https://www.destinyvaultraider.com once you’ve authorised your account, you can click on Vault, Character or Xur (if he’s around).

The aim of this website is to use it as a live learning tool, where I can continue to develop and add new features. For example, I can display the vault and character inventory but I can’t transfer items yet, so this is a feature I hope to add in the future.

If you’d like to review the previous Destiny API posts, check them out here:

  1. Send a request to read Xurs inventory.
  2. Send a HTML formatted email with Xurs inventory.
  3. Log into Destiny and Bungie.net via your PSN account.
  4. Transfer items from your vault to your character and equip them.
  5. Reading a characters inventory and vault contents.
  6. Creating a Python web server with Flask.
  7. Authenticating our app with Bungie.net OAuth.

You can also find me on Twitter here @Allyn_H_

Flow chart:

This section is quite confusing as I’ll be making a request to Bungie, then decoding that request and changing the format of the response a few times before plugging it into my Flask app, so here’s a quick guide as to how I’m creating the Vault route. Excuse the colours, I’m a representing Future War Cult. You can click on the image to make it bigger.

Future War Cult representing!

Flow chart detailing the steps for creating the vault page.reate vault route:

Create vault route:

In the previous section we created an index route, which was used to display the index.html page. Now that we have authorised the user, we can build on this and add a new route to display the users Vault contents.

To create the Vault route, the code looks like this:

@main.route('/vault')
def vault():
    :
    # Do something...
    :
    return render_template('vault.html') 

Right now, ‘vault.html’ is an empty file that extends from our ‘base.html’ – it will only display our navbar. For a quick refresh on the Flask setup, check out this post: Creating a Python app for Destiny – Part 7: Authenticating our app with Bungie.net OAuth.

Right now the “vault.hmtl” template looks like this:

{% extends "base.html" %}
{% block content %}

{% endblock %}

We are extending from the “base.hmtl” template and our content block is empty. Now we can build on this and add some content.

in order to build our Vault page we will need to do a few things:

  1. Send a request to Bungie.net to get the users account details:
    1. We need the users membershipType, destinyMembershipId and characterId.
  2. Send a request to Bungie.net to get the users Vault contents.
  3. Parse through the users vault contents and take out the data we wish to display.
    • Again, we’ve done this above, we just need to tweak the code a bit.
  4. Categorise and display each item.
    • The JSON response from Bungie doesn’t return the items in any order, so we need to categorise them in order to display them properly.

In order to better categorise the vault items, I created a dictionary of each item category. These categories are stored in the item details returned from the manifest,  as item[‘bucket’]. I’ve split these out as below:

invItems = {
    1 : 'Primary Weapons', 
    2 : 'Special Weapons', 
    3 : 'Heavy Weapons', 
    4 : 'Ghost', 
    5 : 'Helmet', 
    6 : 'Gauntlets', 
    7 : 'Chest Armor', 
    8 : 'Leg Armor', 
    9 : 'Class Armor', 
    10 : 'Artifacts', 
    11 : 'Vehicle', 
    12 : 'Sparrow Horn', 
    13 : 'Ships',
    14 : 'Shaders',
    15 : 'Emblems',
    16 : 'Emotes',
    17 : 'Weapon Bundles',
    18 : 'Materials',
    19 : 'Consumables',
    20 : 'Ornaments',
    }

Each of these categories will be used to create a section in our vault page. We will loop through these categories and create the HTML needed to display each item. First though, we need to get the users vault details…

1: Send a request to Bungie.net to get the users account details:

Once the user has authenticated their account, we can request information about their account.

Send a request to Bungie.net to get the users account details

Once the user has been authenticated by Bungie, we can send a request to Bungie to get the account details of the current user. This will return  some important information like the users PSX / Xbox Live username, membershipType, destinyMembershipId, and all of the users character IDs.

Here’s the code to send the “GetCurrentBungieAccount” request:

def GetCurrentBungieAccount(session):
    req_string = 'https://www.bungie.net/Platform/User/GetCurrentBungieAccount/'
    res = session.get(req_string)
    error_state = res.json()['ErrorStatus'].decode('utf-8')
    return res

There’s nothing there we haven’t seen before. The function takes the authenticated session data as a parameter and returns the JSON response. As we can see, there’s a lot of important account data returned  that will enable us to view and interact with the users account:

Lots of important user data returned here.

The response from getcurrentbungieaccount returns a lot of important account data.

This Endpoint will also give character details, such as race, gender and class, details of the users clan affiliation, the users Grimore and more.

2: Send a request to Bungie.net to get the users Vault details:

Request and Response

Sending a request to Bungie.net to get the users Vault details

Now that we have the users membershipType and destinyMembershipId, we can send a request to Bungie for their vault contents.

def getVault(session, membershipType, destinyMembershipId):
    getVault_url = base_url + membershipType + "/MyAccount/Vault/"
    res = session.get(getVault_url, params={'accountId': destinyMembershipId})
    return res

Again we send a request, with the users membershipType and destinyMembershipId added to the URL as parameters, the the function then returns the JSON response from Bungie.

3: Parse the Vault response and return only the required data:

Now that we have a response from Bungie, we need to convert this into a human readable format. The JSON response from the getVault request (in my case) is 35,147 lines of text… That’s a lot of data, also there is no real human readable data in there, it doesn’t return the item names for example – just a itemHash value. So we now need to strip out each item hash and search the manifest for the human readable format of that data. Again, the response after decrypting these items from the Manifest is 54,150 lines of text, so we’ll need to extract only the lines of code we’re interested in. Each item will be condensed down to 10 pieces of useful information (we can build on this at a later stage if needed).

Parsing manifest and categorising the data.

Decoding the itemHash from the manifest and stripping out the required data.

One of the things we can also do, to make life a little easier for us, is to categorise each item by its “bucketName”  – this is a value stored in the manifest that is used to categorise each item, for example “Primary Weapon”, “Consumables” or “Ghost”. The response from getVault is split into 3 buckets, “Weapons,” “Armor” and “Inventory”. This can be seen when you visit your vault in the tower or on any app.  In our case, we’re going to add them all to 1 page, with the ‘bucketName’ (i.e. “Primary Weapon”, “Chest Armor” or “Consumables”) used to categorise each item.

First, lets create a list of blank dictionary objects with the items we want to display for our vault:

array_size = 0
weapon_list = [{
    'itemReferenceHash': 0,
    'itemId': 0,
    'itemName': '',
    'tierTypeName': '',
    'itemTypeName': '',
    'itemLightLevel ': '',
    'stackSize': 1,
    'icon': '',
    'bucket': '',
    'equipped': '',
} for array_size in range(vaultSize)]
array_size = 0

Now that the list is created, lets loop through each inventory item, decode the values from the manifest and store the data we want to display.

Nested JSON dicts and lists...

The JSON response from getVault shows the itemHash we need to decrypt.

We are also going to query each “itemHash”  from the manifest to get the full item details, you can read back  to part 5 for a refresh on what I’m doing here.

If we look at the JSON response from the getVault request, shown in the picture above, both “buckets” and “items” are lists of nested dictionary items , so we need to loop through “buckets” and then each of the “items”, to get to the items “itemHash”. (You can see they’re lists because of the square brackets “[“, denote the start of the list.)

The code for looping through the nested lists is below:

for bucket in vaultResult.json()['Response']['data']['buckets']:
    for item in bucket['items']:
        weapon_list[array_size]['itemReferenceHash'] = item['itemHash']
        inventoryItem = all_data['DestinyInventoryItemDefinition'][item['itemHash']]
        weapon_list[array_size]['itemName'] = inventoryItem['itemName']

Once we have the “itemHash” we can query the Manifest definition “DestinyInventoryItemDefinition” for the item details. We can then store the information we want to keep in our own list of dictionary items.

Stripping out the important data:

The function parseVault takes 3 parameters; the authorised “session”, the “vaultResult” JSON response and the Manifest data in the format of a Python dictionary as “all_data”. Here’s what the full code for stripping each item from the manifest looks like:

def parseVault(session, vaultResult, all_data):
    for bucket in vaultResult.json()['Response']['data']['buckets']:
        for item in bucket['items']:
            weapon_list[array_size]['itemReferenceHash'] = item['itemHash']
            inventoryItem = all_data['DestinyInventoryItemDefinition'][item['itemHash']]
            weapon_list[array_size]['itemName'] = inventoryItem['itemName']
            weapon_list[array_size]['itemLightLevel'] = item.get('primaryStat', {}).get('value', "")
            if ((inventoryItem['itemName'] != "Classified") and (inventoryItem['itemHash'] != 1826822442)):
                bucketHash = all_data['DestinyInventoryBucketDefinition'][inventoryItem['bucketTypeHash']]
                weapon_list[array_size]['itemName'] = inventoryItem['itemName']
                weapon_list[array_size]['tierTypeName'] = inventoryItem['tierTypeName']
                weapon_list[array_size]['itemTypeName'] = inventoryItem['itemTypeName']
                weapon_list[array_size]['icon'] = "https://www.bungie.net/" + inventoryItem['icon']
                weapon_list[array_size]['bucket'] = bucketHash['bucketName']
            # Classified items won't have this information, if not overwritten can cause fails:
            if ((inventoryItem['itemName'] == "Classified") or (inventoryItem['itemHash'] == 1826822442)):
                weapon_list[array_size]['itemName'] = inventoryItem['itemName']
                weapon_list[array_size]['tierTypeName'] = "Classified"
                weapon_list[array_size]['itemTypeName'] = "Classified"
                weapon_list[array_size]['bucket'] = "Classified"
            array_size += 1 
    return weapon_list

Also note, as I’ve hit a few issues recently with Bungie releasing new items – but forgetting to change them from “Classified” in the Manifest, I’ve added a default value for any items with an “itemName” of “Classified” – this will prevent any errors occurring if Bungie add a Classified item at a later stage.

Populating the vault route with our data:

Now that we have stripped the important data out of the manifest, into a list of dictionary’s,  we can pass this list to the ‘vault’ route in the ‘views.py’ file. As a side note, it would have been possible to populate the Vault route without stripping out this data – but it’s much harder to parse through lists of nested dictionary items in the Jinja2 template than in Python. Also, if we were creating a high traffic site, it’s not good practice to send large files of data every time the user refreshes a webpage.

In the case of our Vault route, this could get to about 5MB per page view… Users on mobile data wouldn’t be happy about that, so we need to cut this down.

Creating the Flask Vault route with data taken from the manifest.

Populating the Vault route in our Flask app with the data stripped from the manifest.

Now that we have our important data stripped out and stored in a list of dictionary objects, we need to pass this dictionary to the render_template function of the vault route as a parameter.  The “invItems” dictionary, containing the item categories is also passed as a parameter.

@main.route('/vault')
def vault():
    userSummary = GetCurrentBungieAccount(oauth_session)
    vault = getVault(oauth_session, user.membershipType, user.destinyMembershipId)
    weaponList = parseVault(oauth_session, vault, all_data)
    return render_template('vault.html',
        weaponList = weaponList, 
        invItems = invItems,
        character = userSummary.json()['Response']['destinyAccounts'][0]['userInfo']['displayName'], 
        characterHash = characterHash, 
        charId = userSummary.json()['Response']['destinyAccounts'][0]['characters'][0]['characterId'],
        lightLevel = userSummary.json()['Response']['destinyAccounts'][0]['characters'][0]['powerLevel'],
        emblemImage = userSummary.json()['Response']['destinyAccounts'][0]['characters'][0]['emblemPath'],
        backgroundImage = userSummary.json()['Response']['destinyAccounts'][0]['characters'][0]['backgroundPath'],
        ) 

The other parameters (character, characterHash, charId, lightLevel, emblemImage and backgroundImage) are all taken from the response from getcurrentbungieaccount() and were shown in the last blog post, Part 7: Authenticating our app with Bungie.net OAuth.

Displaying an item in HTML:

To display each item, I created a HTML template file called “itemBlock.html”

The code is quite basic, 2 <div>’s an <img> and 2 <p>’s. The first <div> sets out a Bootstrap column, col-md-3 will allow 4 images to be tiled, per row, before moving to a new row. For the image, I’m passing a lot of parameters but I’m not actually using them yet, as I haven’t found a tidy way to display all of the information. In the <p> paragraph sections, I’m displaying the itemName, itemLightLevel, tierTypeName and itemTypeName.

Here’s the code in the “itemBlock.html”:

<div class="col-md-3 col-sm-4 col-xs-6">
    <div class="thumbnail">
        <img class="img-responsive med-tile" src="{{ dict_item['icon'] }}" title="{{ dict_item['itemName'] }}" tierTypeName="{{ dict_item['tierTypeName'] }}" itemTypeName="{{ dict_item['itemTypeName'] }}" tier="{{ dict_item['tier'] }}" bucket="{{ dict_item['bucket'] }}" itemLightLevel="{{ dict_item['itemLightLevel'] }}" itemReferenceHash="{{ dict_item['itemReferenceHash'] }}">
        <p>{{ dict_item['itemName'] }}: {{ dict_item['itemLightLevel'] }}</p>
        <p>{{ dict_item['tierTypeName'] }} {{ dict_item['itemTypeName'] }}</p>
    </div>
</div>

Here’s what that will look like when displayed:

WOOT Fragment of the Prime!

Here’s what the output from the ‘itemBlock.html’ template file looks like.

Edit vault.html template to display all items:

To display the vault contents in the correct order, we need to step through the invItems list and pick out each item of each category.

Here’s the code in the “itemBlock.html” file:

<div class="inventory-container">
{% for item in invItems -%}
    {% for dict_item in weaponList -%}
 
        {% if invItems[item] in dict_item['bucket'] -%}
            {% include 'itemBlock.html' -%}
        {% endif -%}

    {% endfor -%} 
{% endfor -%}
 
</div>

In the code below, we test to see if the current item in our list is one of the category of items we want to display:

{% if invItems[item] in dict_item['bucket'] -%}
    {% include 'itemBlock.html' -%}
{% endif -%}

If so we include the “itemBlock.html” file to display the item.

Viewing our web site:

As usual, the full set of code can be found on my GitHub page:

https://github.com/AllynH/Destiny_Flask_Webserver

There are a few files / folders, so you’ll need to download it from there.

To run the code you can type:

python app.py

This will start the Flask Web Server, you’ll see some output like this:

* Restarting with stat
Opening Manifest...
Finished!
 * Debugger is active!
* Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)

First, click on the “Authenticate with Bungie” link and follow the instructions to authenticate your account.

Index page view:

Here is the view of the index page.

You should see a screen like this, asking you to review and approve the permissions required by this app.

Authorise your app.

Don’t forget to review what permissions your giving the app.

You should then be redirected back to the index page.

Now that you’ve been authorised and logged into Bungie via our app, we can open the link “view your vault contents”, you’ll see something like this:

Vault view.

Here’s what the vault view currently looks like!

Next steps:

Right now, we’ve created the basic shell of the website / app. This is a good start and we can build onto this, add features, create new pages.

The next steps for me will be to add more functionality to the website:

  • Character inventory view.
  • Xur inventory view.
  • Finish the refresh token flow, so users don’t need to authenticate every 30 mins.
  • Add a user database to securely store the users refresh tokens.
  • Add a transfer item feature.

All of the above is finished (except the transfer item feature), I just need to write up the blog post 🙂

Check out my website:

https://www.destinyvaultraider.com/

Creating a Python app for Destiny – Part 6: Creating a Python web server with Flask

Introduction:

In my previous blog posts, I’ve been working to build a wep app based on Bungies API for Destiny the game. I’ve made great progress with the server side requests and processing the data responses from Bungie, the next steps are to integrate this code into a web server. This will also work out well as I move my code over to the new Bungie OAuth fow.

After a bit of research, I’ve decided to go with the Flask microframework. As their tag line suggests, Flask is a very light weight microframework and appears to be a little easier than Django to setup for smaller projects. In order to learn more about Flask, I’ve been working from Miguel Grinbergs book and his massive online tutorial, both of which I would highly recommend. This blog post is designed as a general overview of Flask, Jinja2, Python as there are many other better thought out learning material on these subjects I’d like to get stuck into the OAuth and adding Destiny features into the code.

If you’d like to review the previous Destiny API posts, check them out here:

  1. Send a request to read Xurs inventory.
  2. Send a HTML formatted email with Xurs inventory.
  3. Log into Destiny and Bungie.net via your PSN account.
  4. Transfer items from your vault to your character and equip them.
  5. Reading a characters inventory and vault contents.

Hello World:

To get started, lets start with the “Hello World!” example on the Flask homepage.

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
 app.run()

What the code is doing:

from flask import Flask
app = Flask(__name__)

We are importing the Flask Class from the flask package. We then create an instance of a Flask object and name it “app”. We pass “__name__” as the first parameter to our Flask object, this is the “import_name” parameter and you can read more about it here.

@app.route("/") 
def hello(): 
    return "Hello World!" 

The @app.route() decorator is used to tell Flask what URL will trigger our function. In this case, a visitor to our homepage will trigger the function hello(). Within the hello() function, we are returning the string “Hello World!”.

if __name__ == "__main__":
    app.run()

When you execute the Python code from the command line, “__name__” will be set to “__main__”, this will execute the code to run our instance of the Flask object, called “app”.

When we execute the code it will print something like this to the screen:

> python app.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [25/Jan/2017 12:45:43] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [25/Jan/2017 12:45:43] "GET /favicon.ico HTTP/1.1" 404 -

When we navigate to address in our browser we will see this:

Flask Hello World app

This is what our app looks like in our web browser.

If we view the source of the web page (Right mouse button -> View source), we can see. There is no HTML displayed, just the string “Hello World!”:

Flask Hello World app - page source

No HTML here – just a string.

This is a good start, we have created a web server with 7 lines of code.

Adding features to our server:

Now that we have a very basic web server up and running, lets add some features.

Flask_script Manager:

Flask Manager provides support for writing external scripts in Flask, in our case it will allow us to add parameters for the server directly from the command line. This will allow us to switch between debug mode and production mode, or to change the port and host IP numbers directly from the command line.

Here’s how we create a Manager instance and link it to our “app” object:

from flask_script import Manager
manager = Manager(app)

We can then restart our web server like so:

> python hello.py runserver

Or we could start on a different port like this:

> python hello.py  runserver -p 5001

Which would return:

 * Running on http://127.0.0.1:5001/ (Press CTRL+C to quit)

Flask-Bootstrap:

If you’re not familiar with HTML or web development, Bootstrap is a free web framework for designing websites. Bootstrap contains HTML and CSS templates used to design web pages. It really takes a lot of the pain out of designing websites 🙂

For more info on Python-Bootstrap, see here.

Here’s how we import Bootstrap into our app:

from flask_bootstrap import Bootstrap

bootstrap = Bootstrap(app)

This will allow us to create Bootstrap based designs by adding the following to our base.html template file (I’ll talk about this more in a minute):

{% extends "bootstrap/base.html" %}

HTML with the Jinja2 template engine:

In my previous posts, I’ve used a series of the file.write() commands to take the JSON response, format it as HTML , with a bit of hacking, and write it to a HTML file. This is OK for small scale programs, like the one where I send Xurs inventory as a HTML formatted email. It’s not really suitable for large scale projects, or any sort of project where you’ll be displaying multiple pages of content.

A template file is a text file that contains the response from our server, we can control the flow and contents of the template file by using Jinja2’s inbuilt Control Statements. First, let’s change the above code from returning a string – to returning a rendered Jinja2 template.

from flask import Flask, render_template

@app.route("/") def hello():
    message = "Hello, World!"
    return render_template('index.html', message=message) 

In the above code, instead of just returning the string “Hello, World!”, we are passing the Python string to the Jinja2 engine, which then renders the index.html template using the data our Python sends.

Before we build the index.html template, we can strip out all of the generic HTML code, that is reused for each page, and keep that in a base template file. This base.html template will contain our HTML <head></head> data, our CSS links, and our navigation bar.

Here’s what our base.html will look like:

When Flask-Bootstrap is initialised by our app, the template “bootstrap/base.html” becomes available to us. This “bootstrap/base.html” template contains the general HTML structure of our page and allows us to derive all of our new templates from this.

(For convenience, I’ve stripped out the navigation bar HTML, I’m using the Bootstrap example code for this).

{% extends "bootstrap/base.html" %}

{% block title %}Hello World app{% endblock %}

{% block head %}
{{ super() }}
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
    <link rel="icon" href="{{ url_for('static', filename = 'favicon.ico') }}" type="image/x-icon">{% endblock %}
{% block navbar %}

<div class="navbar navbar-inverse" role="navigation">
    :
    :
    :
</div>

{% endblock %}

{% block content %}

<div class="container">
    {% block page_content %}{% endblock %}
</div>

{% endblock %}

Blocks like {% title%}, {% navbar %} or {% content %} are called “convenience” blocks, and are built directly into the Flask-Bootstrap, this means we can amend these blocks, without having to directly replace them. In the case of the {% head %} block, we call Flasks super() function, which allows to to amend data to the block, without overwriting the Bootstrap CSS links in the <head></head> tags.

The block we are most interested in is the {% block content %}, inside here we will find the {% block page_content %}, this will allow any template that calls our base.html to replace the contents of this block – through Template Inheritance.

{% block content %}

<div class="container">
    {% block page_content %}{% endblock %}
</div>

{% endblock %}

Here’s what our index.html will look like:

Now that all of our generic HTML has been defined in the base.html, we can create our index.html, as an extension of the base.html template.

{% extends "base.html" %}
{% block title %}Hello World app{% endblock %}

{% block page_content %}

<div>
<h1>{{ message }}</h1>
</div>

{% endblock %}

The index.html defines the contents of the page_content block, in our case we will print the string stored in the Python variable “message”, inside a set of <div> and <h1> tags:

<h1>{{ message }}</h1>
 

Folder structure:

In this example, we’ll have a base.html file for all of our generic HTML and an index.html, which will be populated by our Python code. This is a basic example, but later I’ll be building several more pages and HTML templates into this project.

Flask folder structure

Here is the folder structure Flask uses for defining template and static files.

Running the web server:

Now that the code is complete we can start our web server and see what our index page looks like.

We can start our web server like so:

> python hello.py runserver
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [25/Jan/2017 12:45:43] "GET / HTTP/1.1" 200 -

Then navigating to http://127.0.0.1:5000/ we can view our active web server:

Web server with HTML output.

Web server shown on the left and HTML source code on the right.

As we can see, we are no longer outputting just a text string, we are outputting a full HTML web page, created from our base.html, index.html and the output of our Python code.

Download the code:

The full set of Python code can also be found on my GitHub page here: https://github.com/AllynH/Flask_webserver

As there are a few files in this project, it’s best to copy it from there.

Next steps:

Now that we have a web server built in Python using Flask we can:

  • Update the code with the new Bungie OAuth login flow.
  • Build in our previous Bungie code.

The new Bungie authentication flow!

Hi there,

As of the 14th of December, Bungie have changed their authentication flow for the Destiny API. Bungie are moving away from the cookie based authentication flow, to the OAuth 2.0 flow.

The code in the previous blog posts will still work, but Bungie will soon be switching over fully to the new authentication system.

This is good news for anyone using the apps based on the Destiny API, as it is much more secure, offers the users more control over how their data is used, and what can controlled from external applications.

You can read about the changes from the official Bungie release here:

The new Bungie OAuth flow.

I’m currently creating the Python code for the new authentication flow. The code is all working but I’m taking a week off to spend Christmas with my family, so stay tuned for the blog post with updated code 🙂

Creating a Python app for Destiny – Part 5: Reading a characters inventory and vault contents.

Introduction:

In the previous sections I showed how to:

  1. Send a request to read Xurs inventory.
  2. Send a HTML formatted email with Xurs inventory.
  3. Log into Destiny and Bungie.net via your PSN account.
  4. Transfer items from your vault to your character and equip them.

In this section I’m going to lay the foundations of a full inventory management system. This section will detail how to read the list of items that are currently equipped on your character and how to read the contents of your vault.

I’ll also show how, by downloading the full Destiny Manifest, we can speed up our code and drastically reduce the amount of HTTP requests we need to send to Bungie.

Also, I like to say a big thanks to all of the people on Reddit, Twitter and Bungie.net for their help and interest 🙂

You can find me on Twitter here @Allyn_H_

The getCharacterInventory endpoint:

The BungieNetPlatform wiki for this endpoint can be found here. This endpoint will return a JSON response with a list of each of the equipped items for a given character ID. It works in a similar way to the Xur adviser endpoint (seen in parts 1 & 2). The request will return a series of encoded hashes, which we’ll then need to send as a series of separate requests to query the Destiny manifest, in order to decrypt this data into a human readable format.

  • This is a public endpoint – no need to be logged in, anyone can use this.
  • This a HTTP GET request – you don’t need to send a JSON package.
  • Your destinyMembershipId needs to be attached to the URL as a parameter – more info here.
  • Your characterId needs to be attached to the URL as a parameter – more info here.

Making the request:

First we build the endpoint URL and make the GET request:

base_url = "https://www.bungie.net/platform/Destiny/"
req_string = base_url + membershipType + "/Account/" + destinyMembershipId + "/Character/" + charId + "/Inventory"
res = session.get(req_string)

The request URL will look like this:

https://www.bungie.net/platform/Destiny/2/Account/4611686018436136301/Character/2305843009222916165/Inventory/

Parsing the response:

Next we need to parse through the multidimensional JSON response and pick out the relevant data:

for equipment in res.json()['Response']['data']['buckets']['Equippable']:
    for item in equipment['items']:
        print "itemHash is: \t\t", item['itemHash']
        print "itemInstanceId is: \t", item['itemInstanceId']
        hashReqString = base_url + "Manifest/6/" + str(item['itemHash'])
        res2 = requests.get(hashReqString, headers=HEADERS)
        item_name = res2.json()['Response']['data']['inventoryItem']['itemName']
        item_tier = res2.json()['Response']['data']['inventoryItem']['tierTypeName']
        item_type = res2.json()['Response']['data']['inventoryItem']['itemTypeName']
        item_icon = res2.json()['Response']['data']['inventoryItem']['icon']
        print "Item name is: " + item_name
        print "Item type is: " + item_tier + " " + item_type
        print "Item icon is: http://www.bungie.net" + item_icon

The above code will read through the large JSON response and print out the data we want. For each item in our inventory, we will print out the items name, its type, the tier type (common, legendary or exotic) its description and its icon. The dict item [‘Equippable’] is an array, so we need to loop through each piece of “equipment” and pick out each of the [‘items’] stored there.

The code will return something like this:

itemHash is: 1703777169
itemInstanceId is: 6917529081613012276
Item name is: 1000-Yard Stare
Item type is: Legendary Sniper Rifle
Item icon is: http://www.bungie.net/common/destiny_content/icons/c1c49acd0fd146d7b32184f23c64dfe5.jpg

The GetVault endpoint:

The BungieNetPlatform wiki for this endpoint can be found here. This endpoint also works in a similar way to the Xur adviser endpoint (seen in parts 1 & 2) and the getCharacterInventory endpoint, it will return a series of encoded hashes, which we’ll then need to send as a series of separate requests to query the Destiny manifest. If our vault was full, we currently have space for 288 items. This means we’ll need to make 289 (1 request to the vault + 288 requests to decrypt items ) requests to the Bungie servers to get all the data we want! Making that many requests takes way too much time and eats way too much data, I’ll explain how to remove these requests in a bit 🙂

  • This is a private endpoint – you need to be logged in, with a persistent HTTP session.
  • This is a GET request – you don’t need to send a JSON package.
  • Your destinyMembershipId needs to be attached to the URL as a parameter – more info here.

Making the request:

The code for this request will look like this:

getVault_url = base_url + membershipType + "/MyAccount/Vault/"
res = session.get(getVault_url, params={'accountId': destinyMembershipId})

Parsing the response:

Next we need to parse through the multidimensional JSON response and pick out the relevant data:

for bucket in vaultResult.json()['Response']['data']['buckets']:
    for item in bucket['items']:
        print item['itemHash']
        print item['itemInstanceId']
        hashReqString = base_url + "Manifest/6/" + str(item['itemHash'])
        res2 = requests.get(hashReqString, headers=HEADERS)
        myItem = item['itemHash']
        item_name = res2.json()['Response']['data']['inventoryItem']['itemName']
        item_tier = res2.json()['Response']['data']['inventoryItem']['tierTypeName']
        item_type = res2.json()['Response']['data']['inventoryItem']['itemTypeName']
        item_icon = res2.json()['Response']['data']['inventoryItem']['icon']
        print "Item name is: " + item_name
        print "Item type is: " + item_tier + " " + item_type
        print "Item icon is: http://www.bungie.net" + item_icon

The above code will read through the large JSON response and print out the data we want. For each item in our inventory, we will print out the items name, its type, the tier type (common, legendary or exotic) its description and its icon. The dict item [‘buckets’] is an array, so we need to loop through each “bucket” and pick out each of the [‘items’] stored there. The [‘buckets’] item is a storage category in your vault, such as Armour, Weapons, Consumables, Shaders, Emblems, etc. Each of the [‘items’] refers to the item stored in that vault space.

It’s also important to remember, we need to collect the “itemInstanceId” for each item, as this is required to transfer the item to and from the vault.

hashReqString = base_url + "Manifest/6/" + str(item['itemHash'])
res2 = requests.get(hashReqString, headers=HEADERS)

The above lines makes each of the requests to the manifest to decrypt the “itemsHash” into a human readable format. This request is made for each of the items in our vault. This is the real time consuming part of the program.

Downloading the manifest:

So we’ve figured out how to find our vault contents and list the equipped items on our characters. We’ve also seen, for decrypting and reading the contents of our vault, we need to make 289 requests (more if Bungie ever decide to increase the vault capacity). This takes a huge chunk of time. I measured the amount of time it took to read my inventory and to send 267 requests (I had 266 items in my inventory), the script took 2 mins and 43 seconds to finish!

So in order to speed things up, I downloaded the Destiny Manifest database onto my local computer, the code and directions can be found here on the destiny devs page.

The code can be downloaded here.

Once downloaded you can execute the code like so:

python manifest_destiny.py

The output looks something like this:

Output of the script used to download the Destiny Manifest.

Output of the script used to download the Destiny Manifest.

This will download the full Manifest and store it in a Pickle file. This is also my first time using Pickle files, so I assume this is done as it’s easier to get the data from a Pickle file than an SQL database.

Here’s what they look like:

Copies of the Destiny Manifest and the Manifest as a Pickle file.

Copies of the Destiny Manifest and the Manifest as a Pickle file.

The files are 53MB and 71MB, so they’re big enough. The file sizes can be reduced by commenting out any of the Manifest items you don’t need – for example I downloaded second copy of the Manifest with only the “itemHash” data included.

Here’s how I edited the code:

Changing the code to download only the itemHash data.

Changing the code to download only the itemHash data.

Here are the file sizes:

Copies of the itemHash manifest and Pickle file.

Copies of the itemHash manifest and Pickle file.

We can see here, the Pickle file we need has reduced from 71MB to 37MB. This would also further reduce the run time of our programs. As memory isn’t really an issue for me I will be sticking to the full Manifest.

Reading the vault contents using the manifest:

Now that we have saved the Manifest as a Pickle file, we can pull the required data directly from this file instead of needing to make hundreds of HTTP requests every time we want to read our inventory or our vault contents.

To import the data from our Pickle file we use the following code:

import pickle

with open('manifest.pickle', 'rb') as data:
    all_data = pickle.load(data)

Now the object all_data will contain the entire Manifest contents.

Previously we made requests to the Manifest via a HTTP request:

hashReqString = base_url + "Manifest/6/" + str(item['itemHash'])
res2 = requests.get(hashReqString, headers=HEADERS)
myItem = item['itemHash']
item_name = res2.json()['Response']['data']['inventoryItem']['itemName']

Where we pass the parameter “6”, which refers to the definition type “InventoryItem” and the specific “itemHash”.
Now we can read the item information from the nested dict object stored in all_data:

inventoryItem = all_data['DestinyInventoryItemDefinition'][item['itemHash']]
item_name = inventoryItem['itemName']

Updating the code to pull from our local Manifest copy:

Here’s how I modified the code used to parse our vault to pull the data directly from our manifest.pickle file instead of making a HTTP request:

for bucket in vaultResult.json()['Response']['data']['buckets']:
    for item in bucket['items']:
        weapon_list[array_size]['itemReferenceHash'] = item['itemHash']
        weapon_list[array_size]['itemId'] = item['itemInstanceId']
        inventoryItem = all_data['DestinyInventoryItemDefinition'][item['itemHash']]
        item_name = inventoryItem['itemName']
        item_tier = inventoryItem['tierTypeName']
        item_type = inventoryItem['itemTypeName']
        item_icon = inventoryItem['icon']
        print "Item name is: " + item_name
        print "Item type is: " + item_tier + " " + item_type
        print "Item icon is: http://www.bungie.net" + item_icon

Run time improvements:

We can see from the table below that downloading the Manifest and removing the need for ~290 additional HTTP requests can drop the run time from 220 seconds down to 16 seconds. A lot of 16 seconds seems to be from parsing the Pickle file, which again could be optimised by either using the raw SQL data or storing it in some other format. For now though the convenience of using the Pickle file far out weighs the downsides.

Manifest Type:

Run time:

Multiple HTTP requests to Bungie Manifest 220.0 seconds
manifest.pickle – full download 22.3 seconds
item.pickle – only item 16.2 seconds

Viewing the vault contents as a HTML file:

As a quick and easy way of outputting the vault contents into a HTML file, I used the same template file and code format as used in the guide for emailing Xurs inventory.

Here are the steps:

  • Append the HTML header to the top of the my_html string.
  • Modify the code used to parse the vault contents to output a HTML formatted string.
  • Append the HTML footer to the bottom of the my_html string.
  • Concatenate the header, vault contents and footer HTML strings into the my_html string and output this to a file called vault_contents.html.

Parsing the response as HTML:

for bucket in vaultResult.json()['Response']['data']['buckets']:
    for item in bucket['items']:
        inventoryItem = all_data['DestinyInventoryItemDefinition'][item['itemHash']]
        item_name = inventoryItem['itemName']
        item_tier = inventoryItem['tierTypeName']
        item_type = inventoryItem['itemTypeName']
        item_icon = "http://www.bungie.net/" + inventoryItem['icon']
        print "Item name is: " + item_name
        array_size += 1
        print "Item is: " + item_name
        print "Item type is: " + item_tier + " " + item_type + "\n"
        my_html = my_html + "\t\t<div class=\"col-md-4\">\n"
        my_html = my_html + "\t\t\t<div class=\"thumbnail\">\n"
        my_html = my_html + "\t\t\t\t<a href=\"" + item_icon + "\">\n"
        my_html = my_html + "\t\t\t\t<img src=\"" + item_icon + "\">\n"
        my_html = my_html + "\t\t\t\t</a>\n"
        my_html = my_html + "\t\t\t\t<h3>" + item_name + "</h3>\n"
        my_html = my_html + "\t\t\t\t<p>" + item_tier + " " + item_type + "</p>\n"
        my_html = my_html + "\t\t\t</div>\n"
        my_html = my_html + "\t\t</div>\n"

Here’s what the output of the code looks like:

Even have the VoG boots. #Y1RaidStruggles

Vault contents as formatted HTML.

Running the code:

The full set of Python code can also be found on my GitHub page here: https://github.com/AllynH/Destiny_Read_Characters_Inventory_Vault

As there are a few files you’ll need to copy it from there,. In order to make the code work for you – you’ll need to input your username, password, api_key, destinyMembershipId and characterId in the Header_file.py.

Here are the values you’ll need to change:

# PSN Username:
username = emailaddr
password = mypassword

# Destiny API X-Key:
API_KEY = ""

# Destiny parameters:
membershipType = "2" # PS4 = 2
destinyMembershipId = ""

characterId = ""

You can then run the code like so:

> python Read_Inventory_and_Vault.py

As always, I’ll try to keep the GitHub repo up to date with any changes I make.

Next steps:

The code as it is, does what we want, reads the contents of our vault and prints it to a HTML file, but it’s not really a fully functioning inventory management system.

To create a fully working web application the code will need to be built into a web framework, luckily for me I’ve been reading up on the Python Flask web framework for the last few weeks 🙂

Creating a Python app for Destiny – Part 4: Transferring and equipping items.

Introduction:

In the previous sections I showed how to:

  1. Send a request to read Xurs inventory.
  2. Send a HTML formatted email with Xurs inventory.
  3. Log into Destiny and Bungie.net via your PSN account.

I’m now going to build on the previously created code to create an app that can transfer an item to and from the vault, once the item has been transferred – we can then equip it.

If you’ve been reading along with me up to this point, you should understand all of this, if not – now is a good time to check out my previous posts.

Here’s the code working (sorry about the camera shaking, it was made using my phone ):

Destiny the game, Equipping an Item with the Destiny API. from Allyn H on Vimeo.

Also, I like to say a big thanks to all of the people on Reddit, Twitter and Bungie.net for their help and interest 🙂

You can find me on Twitter here @Allyn_H_

The transferItem endpoint:

The Destiny API uses web server endpoints to execute commands, the endpoint used to transfer items is: https://www.bungie.net/platform/Destiny/transferItem/

The BungieNetPlatform wiki for this endpoint can be found here. We can see this method is very different from the Xur advisors endpoint we used in the previous examples.

Here are the main differences:

  • We still need to use the X-API key in our header.
  • We also have to attach our x-csrf token in our header.
  • This is a private endpoint – we need to be logged in to use this method.
    • We need to attach the required cookies to our POST request.
  • This is a POST method – we will need to send data with this request.
    • We need to send the data as a JSON packet.
  • The response will only tell us if we have been successful or not – the Xur advisors response was >1000 lines of text.

The code for storing the transferItem endpoint variable will look like this:

base_url = "https://www.bungie.net/platform/Destiny/"
req_string = base_url + "TransferItem/"

Important note: If the trailing “/” is missing from the “transferItem/” endpoint – the request will fail. I’ve spent way longer than I’m willing to admit debugging that issue :/

Creating the POST request payload:

The “transferItem” endpoint takes a JSON payload as an input. This JSON payload contains a lot of information. So lets take a look at how it’s put together.

The data gathered to populate this payload was found by looking at items I had equipped using the “GetCharacterInventory” endpoint, you can find more info here. This will list the relevant information for all of your equipped items, let me know if you want to see the code for this.

Name

Description

membershipType

This will be 2 for PSN and 1 for Xbox live.

itemReferenceHash

This is the “itemHash” number from our previous examples. This refers to the generic item for example: 1000-Yard Stare.

itemId

This is a unique item number relating to a specific item, that you own. For example: Allyn’s 385 light level 1000-Yard Stare with the Ambush Scope, Quickdraw and Firefly.

stackSize

How many items to transfer. Should be “1” for equipable items.

characterId

The characterId the item is being moved to or from. You can find the characterId here.

transferToVault

Move the item to or from the vault; true or false

Here’s how this method expects the JSON data to be formatted:

This is the BungieNetPlatform example for the transferItem POST payload

This is the BungieNetPlatform example for the transferItem POST payload

The good news here is it’s pretty easy to put this together in Python by creating a Dictionary object.

Making a POST request:

We’ve already looked at making HTTP GET requests with Python, but the Python Requests library also makes it really easy to make a POST request.

You can view the Requests documentation here. Here’s the quick example of how to send some data formatted as a Python dictionary:

Python Requests POST example

Python Requests POST example

Or the Requests library will also automatically encode a Python dict into a JSON object for you, when you use the “json” parameter:

Python Requests POST JSON example

Python Requests POST JSON example

Now that we know how to create a Python POST request and what data the transferItem endpoint are looking for, lets turn that into some code!

Moving an item from your vault to your inventory:

As mentioned above – the data gathered to populate this payload was found by looking at items I had equipped by using the “GetCharacterInventory” endpoint, you can find more info here. I’m planning on putting together a blog post on this too but let me know if you’d like to see the code for this.

First we create our Python dictionary object:

text_payload = {
    "membershipType": 2,
    "itemReferenceHash": 1519376148, # The Ram
    "itemId": 6917529085991104887, # The Ram
    "characterId": characterId_Warlock,
    "stackSize": 1,
    "transferToVault": False
}

Next, we convert the dictionary object into a JSON object:

payload = json.dumps(text_payload)

Now we can make the actual POST request to the Destiny servers to transfer our item.

base_url = "https://www.bungie.net/platform/Destiny/"
req_string = base_url + "TransferItem/"
res = session.post(req_string, data=payload)

The JSON response should look like this:

{
    "ThrottleSeconds": 0, 
    "ErrorCode": 1, 
    "ErrorStatus": "Success", 
    "Message": "Ok", 
    "Response": 0, 
    "MessageData": {}
}

If the item was not found in your vault, you’ll get a response like this:

{
    "ThrottleSeconds": 0, 
    "ErrorCode": 1623, 
    "ErrorStatus": "DestinyItemNotFound", 
    "Message": "The item requested was not found.", 
    "Response": 0, 
    "MessageData": {}
}

For any other issues, for example if you were to omit the trailing “/” from “https://www.bungie.net/platform/Destiny/TransferItem/” URL, the request would fail but you wouldn’t receive a JSON response, instead your code would error and you’d receive a HTTP 405 status code. You can find the status code by using the following code:

print res.status_code
405

If you see something like that coming up, you’ll need to review and fix your code.

Equipping an item from your inventory:

Now that the item is in our characters inventory – we can equip the item on that character.

The endpoint used to equip an item from your inventory is: https://www.bungie.net/platform/Destiny/EquipItem/ you can find more information about this endpoint here.

Just a note: this can only be done when in a social space or in orbit (otherwise it’d be really easy to cheat in PvP – can you imagine if you could do this in Trials???). Otherwise the action will fail and return an error message.

Creating the POST request payload:

The POST request to equip an item is smaller and we already have the details of the item we want to equip.

text_equip_payload = {
    "membershipType": 2,
    "itemId": 6917529085991104887, # The Ram
    "characterId": characterId_Warlock
}
equip_payload = json.dumps(text_equip_payload)

 

equip_url = base_url + "EquipItem/"
res = session.post(equip_url, data=equip_payload)

Again, if the item was not found, you’ll get a response like this:

{
    "ThrottleSeconds": 0, 
    "ErrorCode": 1623, 
    "ErrorStatus": "DestinyItemNotFound", 
    "Message": "The item requested was not found.", 
    "Response": 0, 
    "MessageData": {}
}

If you’re trying to equip an exotic and you already have one equipped, you’ll see an error message like this:

{
    "ThrottleSeconds": 0, 
    "ErrorCode": 1641, 
    "ErrorStatus": "DestinyItemUniqueEquipRestricted", 
    "Message": "You can only have one item of this type equipped.", 
    "Response": 0, 
    "MessageData": {}
}

Running the code:

Here is the full set of Python code, this can be copied into a file called “equipItem.py” and executed from the command prompt like so:

> python equipItem.py

The code can also be found on my GitHub page here: https://github.com/AllynH/Destiny_Equip_Item

As always, I’ll try to keep the GitHub repo up to date with any changes I make.

Creating a Python app for Destiny – Part 3: Logging in to Bungie.net and authenticating with PSN

Introduction:

In the previous sections I showed how to:

  1. Send a request to read Xurs inventory.
  2. Send a HTML formatted email with Xurs inventory.

I want to build on the previously created code to create an app that can transfer an item to and from the vault, and equip items.

In order to do that, our code will need to log in to Bungie.net and authenticate the account with PSN.

Logging in to Bungie.net and authenticating with PSN:

We are going to use the Python “Requests” package to login to Bungie.net by using our PSN account details and OAuth 2.0 to authenticate our connection with PlayStation Network.,

The good people at BungieNetPlatform have put together some guides on how to connect with Bungie.net, get authenticated with PSN (or Xbox Live – but I’m on PS4) and grab the required cookies. For this example, I used the code provided by Quantum Ascend here.

You can also see his step by step instructions here.

Here are the steps to this section:

  1. Sign in on Bungie.net via PSN – this will redirect you to the PSN sign in page.
  2. Grab our PSN session ID.
  3. Login to PSN (via OAuth) using our PSN username, password and adding our session ID as a cookie.
  4. Receive PSN a unique sign in URL and updated JSESSIONID.
  5. Request PSN  X-NP-GRANT-CODE, using updated JSESSIONID.
  6. Sign in to Bungie.net by adding the grant code to our original URL.
  7. Grab our Bungie.net authentication cookies.

Here is a flow chart detailing these steps:

Future War Cult colours - representing!

Bungie.net Sign-in flow chart

Request 1 is done in this way, to accommodate both Playstation and Xbox accounts to log in – however as I only have a Playstation 4, I’m not working on the Xbox live sign in, you can find code for that in the BungieNetPlatform guide here.

request1 = requests.get(BUNGIE_SIGNIN_URI, allow_redirects=True)
jsessionid0 = request1.history[1].cookies["JSESSIONID"]
params = urlparse(request1.url).query
params64 = b64encode(params)

Request 2 sends a POST request to the PSN sign in page. Our log in credentials are passed in a dictionary format, these are then form-encoded (by the Requests package as a HTML form) when the request is made. We also create a cookie with the JESSIONID we received from Request 1.

The response from Request 2, returns an updated JSESSIONID, also stored in a cookie – we save this updated value. The if statement checks for an authentication error being returned – this confirms our log in credentials were correct and no errors were returned.

request2 = requests.post(PSN_OAUTH_URI, data={"j_username": username, "j_password": password, "params": params64}, cookies={"JSESSIONID": jsessionid0}, allow_redirects=False)
if "authentication_error" in request2.headers["location"]:
    logger.warning("Invalid credentials")
jsessionid1 = request2.cookies["JSESSIONID"]

Request 3 sends a GET request to the returned PSN OAtuh sign in URL, adding the updated JSESSION ID, to the header. This will give us our x-np-grant-code.

request3 = requests.get(request2.headers["location"], allow_redirects=False, cookies={"JSESSIONID": jsessionid1})
grant_code = request3.headers["x-np-grant-code"]

The PSN OAtuh sign in URL will look something like this:

https://auth.api.sonyentertainmentnetwork.com/2.0/oauth/authorize?response_type=code&client_id=78xxx&redirect_uri=https%3a%2f%2fwww.bungie.net%2fen%2fUser%2fSignIn%2fPsnid&scope=psn:s2s&request_locale=en

Request 4 makes the final request to the Bungie.net sign in page, attaching the x-np-grant-code to the URL. The “params” function in the requests library attaches this code to the URL.

request4 = requests.get(BUNGIE_SIGNIN_URI, params={"code": grant_code})

The Bungie.net sign in URL with the x-np-grant-code attached should look something like this:

https://www.bungie.net/en/User/SignIn/Psnid?code=Nxxxh

Now that we have authorised our Bungie.net account with PSN, we can create a persistent session and send multiple requests.

Creating a persistent HTTP Session:

A persistent HTTP session is used to keep our HTTP connection alive allowing us to make multiple requests without the need to sign in and authenticate each time. This means we will only need to authorise our account once and can make multiple requests – so long as we attach the relevant authorisation data. This authorisation data is stored in the cookies and header data we send in our requests. The python requests package has a “Session” object, used for just this thing!

To create a HTTP session, we need to do 2 things:

  1. Send the required HTTP header data:
    • X-API-Key – the Application Programming Interface key we got from registering at Bungie.net.
    • x-csrf – our Cross Site Request Forgery protection token, received from Bungie.net after we have authenticated out app with PSN.
  2. Attach the required cookies with the correct, authenticated data:
    • bungled – received from Bungie.net after we have authenticated out app with PSN (This is also our x-csrf token).
    • bungleatk – received from Bungie.net after we have authenticated out app with PSN.
    • bungledid – received from Bungie.net after we have authenticated out app with PSN.

Here’s what that looks like when translated into Python code – first we create a requests Session object:

session = requests.Session()

Next, we add our X-API-KEY and x-csrf token to the session header:

session.headers["X-API-Key"] = API_KEY
session.headers["x-csrf"] = request4.cookies["bungled"]

Then we create our Cookies and attach them to the requests session object:

session.cookies.update(
 {
    "bungleatk": request4.cookies["bungleatk"], 
    "bungled": request4.cookies["bungled"], 
    "bungledid": request4.cookies["bungledid"]
 })

That’s it! We’re done – our app can now log into Destiny via PSN. This will allow us to use all of the private endpoints provided by the API and do lots of cool stuff, such as transferring items, equipping items, locking items, etc.
I’ll build on this code again in my next blog post.

Running the code:

Here is the full set of Python code, this can be copied into a file called “PSN_login.py”, in the same directory as your own code, and implemented like so:

from PSN_login import login

username = emailaddr
password = mypassword
api_key = API_KEY

# Log in via PSN and create our persistant HTTP session: 
session = requests.Session()
session = login(username, password, api_key)

Here’s the link to the code on my GitHub account:

https://github.com/AllynH/Destiny_Equip_Item/blob/master/PSN_login.py

Here’s the GitHub Gist:

Creating a Python app for Destiny – Part 2: Emailing Xurs inventory.

Introduction:

In the previous tutorial, I showed how you could use Python to send a request using the Destiny API, to the Bungie servers and how to decrypt the JSON reply. If you haven’t read the previous tutorial, it’s right here. I’m going to gloss over how to write the HTML, as there are plenty of really good online resources for creating and styling websites, checkout codecademy if you’re interested in a free guide.

In this tutorial I will continue on with the program we have created, to:

  1. Change our Get_Xur_inventory.py program so it writes the output as a HTML file.
  2. Have the program send this HTML, via Gmail, directly to our  email.

By writing our output as HTML we have a lot more control over the design and formatting of the email. Using HTML will also allow us to directly embed the item pictures into out email, from the URL links provided in the JSON response from Xur.

The hard part of the code is already done, with a few minor tweaks, we can have our program output Xurs inventory into a HTML formatted email.

 

Outputting Xurs inventory as HTML:

In order to create our output HTML, we are going to use a template HTML file to store as much of the generic HTML as possible, and change all of the print statements in our code to output HTML formatted data.

HTML file structure is split into 3 main parts, such as HTML version information, <head>, and <body> tags. We are going to take advantage of that and save the HTML version information and <head> section in a template HTML file, which we can reuse every time we run the script. This would allow us to update any of the generic HTML code separately – for example if you wanted to change some styling or CSS information, without messing with your Python code

We can then use our script to generate only the HTML needed to display the items from Xurs inventory, this will change every week so needs to be generated every time we run our script.

Opening our template HTML file:

The following code will open a file called “template.html” for reading, and save all of the contents into a string object called “my_html”, then close the file.

template_file = open('template.html', "r")
my_html = template_file.read() template_file.close()

Outputting HTML from our Python code:

Below is an example of how we’d like to format the HTML code, essentially we wrap it in a couple of <div>’s and display the image and text.

<div class="col-md-4">
 <div class="thumbnail">
 <a href="item_url">
 <img src="item_url">
 </a>
 <h3>Item name: item_name</h3>
 <p>Item type is: item_type<p>
 <p>Description: item_description<p>
 </div>
</div>

Here’s what the corresponding Python code looks like:

my_html = my_html + "<div class=\"col-md-4\">\n"
my_html = my_html + "\t<div class=\"thumbnail\">\n"
my_html = my_html + "\t\t<a href=\"" + item_url + "\">\n"
my_html = my_html + "\t\t<img src=\"" + item_url + "\">\n"
my_html = my_html + "\t\t</a>\n"
my_html = my_html + "\t\t<h3>" + item_name + "</h3>\n"
my_html = my_html + "\t\t<p>" + item_type + "<p>\n"
my_html = my_html + "\t\t<p>" + item_description + "<p>\n"
my_html = my_html + "\t</div>\n"
my_html = my_html + "</div>\n"

We’ve wrapped each of the lines of HTML code in a write statement. We’ve already populated all of the item_url, item_name, item_type and item_description variables with our code.

So here’s what each line is doing. In this example we are creating a <h3> heading, with the text from our “item_name”. “\t” creates a tab and “\n” moves to a new line. All of this text is concatenated into one string, which we add on to the end of the “my_html” string object.

my_html = my_html + "\t\t<h3>" + item_name + "</h3>\n"

Closing the HTML:

Now that we have created and populated <div>’s with each of Xurs items, we can add the code to close the HTML </body> and </html> tags.

my_html = my_html + "\t\t</div> <!-- row -->\n"
my_html = my_html + "\t</div> <!-- container -->\n"
my_html = my_html + "</div> <!-- inventory-container -->\n"
my_html = my_html + "</body>\n"
my_html = my_html + "</html>\n"

The “my_html” string will store all of the HTML code used in the body of the email we send.

Sending an email with Python:

Python comes installed with a number of really useful libraries, including the smtplib (Simple Mail Transfer Protocol library) and the MIMEMultipart and MIMEText (Multipurpose Internet Mail Extensions, allows text, images and other options) libraries.

For this section I referenced two tutorials, here Nael Shaib shows how to make a basic email program, and here Darren Massena shows how to create a HTML formatted email and attach external pictures.

Some important notes:

Google recently changed their security requirements, so to use this program, you will need to change your Google settings to allow less secure apps. I needed to do the following:

  1. Click here to allow less secure apps. Google will still reject access to your account, until you authorise it.
  2. Click here to authorise your app. This will authorise your app access to your Google account.
  3. Gmail and some other mail clients do not support CSS styling, so if you wanted to convert some existing CSS to inline HTML, you could use a tool like this. That being said, my iPad and Android phone both display mails with full CSS – so I’ve included some CSS styling in my code 🙂
Enable access for less secure apps.

Enable access for less secure apps.

Alright, that’s enough talk – lets get to coding!

Importing the required libraries:

First lets import the required libraries:

import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText

Next we’ll set up our email address parameters, enter your details as follows.

# Mail parameters:
fromaddr = "TO_ADDRESS"
toaddr = "FROM_ADDRESS"
password = "GMAIL_PASSWORD"

Next, lets create the email header information:

# Compose mail: 
msgRoot = MIMEMultipart() 
msgRoot['From'] = fromaddr 
msgRoot['To'] = toaddr 
msgRoot['Subject'] = "Xurs Inventory." 
msgRoot.preamble = "This is a multipart message in MIME format." 

The line:

msgRoot = MIMEMultipart()

creates an email MIMEMultipart object, we then set the [‘To’], [‘From’] and [‘Subject’] parameters of the email on the following 3 lines.

Creating the email body:

We’ve already populated the HTML into a string object called “my_html”.

The “my_html” string object is added to the body of the email with the following commands, the 2nd parameter  passed to the MIMEText object sets the email type as HTML:

msgText = MIMEText(my_html, 'html')
msgAlternative.attach(msgText)

The following commands create an SMTP mail object, connect to the Gmail SMTP server on port 587.

server = smtplib.SMTP('smtp.gmail.com', 587)
server.ehlo()
server.starttls()

The next line actually logs into our Gmail account, we pass our Gmail address and password as parameters to this.

server.login(fromaddr, password)

The next 2 lines actually send the email,

server.sendmail(fromaddr, toaddr, msgRoot.as_string())
server.quit()

Running the code:

Here is the full set of Python code, this can be copied into a file called “Get_Xur_inventory_email.py” and executed from the command prompt like so:

> python Get_Xur_inventory_email.py

The code can also be found on my GitHub page here: https://github.com/AllynH/Destiny_Get_Xur_inventory_email

As always, I’ll try to keep the GitHub repo up to date with any changes I make.

Creating a Python App for the Destiny API

Introduction:

I’m a huge fan of Bungie’s Destiny game, and have been playing it since launch. In Destiny there is a vendor, called Xur, who visits the game each week and sells exotic weapons and armour. Xur is only available from Friday to Sunday mornings, and appears in a different location each week – because of this he can be quite hard to track down. As with most people – I’m at work every Friday and usually busy with family time over the weekend, so I’m often left in a situation where I miss Xur or don’t have the time to log in and see what he’s selling.

This just wasn’t good enough, I couldn’t risk missing out on the next Gjallarhorn! So let’s built an app to find Xurs inventory each week.

 

API registration:

In order to build an app using the Bungie API, you need to create a Bungie.net account, and register as a developer. This only takes a few minutes, to register as a developer, follow this link: https://www.bungie.net/en/User/API

This will give you your unique X-API-Key. For every request we make to the Bungie servers, we need to send this API key in the HTTP header of the request.

 

App flow chart:

Finding Xurs inventory isn’t as straight-forward as expected. Bungie have implemented a method for reading Xurs inventory – but it returns the data in an encrypted format, as an item hash.

The reason behind this is, even items of the same type can have different perks. For example, each gun can have a different scope type, different perks, and different barrel modifications. So even 2 of the same guns can be completely different.

Because of this, we’ll need to send multiple requests to the Bungie servers and read multiple replies.

FWC representing!

Flow chart for the app.

 

Python HTTP requests:

Requests is a Python HTTP library, if you’re not familiar with it, start here. You may need to install it on your computer, If you have PIP installed on your computer requests can be installed by typing:

pip install requests

 

Sending our request to Xurs advisors page:

Our first HTTP request will be to Xurs Advisors page, here is the link https://www.bungie.net/Platform/Destiny/Advisors/Xur/

The X-API-Key is passed as a parameter with every HTTP request made to the Bungie servers:

HEADERS = {"X-API-Key":'MY-X-API-Key'}

 

Here is how we make the request:

xur_url = "https://www.bungie.net/Platform/Destiny/Advisors/Xur/"
print "\n\n\nConnecting to Bungie: " + xur_url + "\n"
print "Fetching data for: Xur's Inventory!"
res = requests.get(xur_url, headers=HEADERS)

It’s as simple as that.

So what are we doing? The line:

res = requests.get(xur_url, headers=HEADERS)

Makes a HTTP request to the URL stored in the “xur_url” string, the X-API-Key is also added to the request in the “headers” dictionary object.

Now the object “res” contains the JSON response received from the Bungie servers.

 

Parsing the JSON response:

The JSON response received from the request is pretty big, in my case it contained 1018 lines of text! So what do we do with this data? First things first, we need to know if our request was received and processed correctly. The JSON object contains a key called “ErrorStatus”, this key is used to store status of the request, so lets print the value of this key:

error_stat = res.json()['ErrorStatus']
print "Error status: " + error_stat + "\n"

Here is what the  code outputs:

Successful connection - whoop whoop!

Successful connection.

Here is an example of what the code would output if there was a successful request but Xur was not available (he’s only available from Friday to Sunday):

Office hours are Friday to Sunday morning!

Connection is successful but Xur is nowhere to be found.

 

Parsing the multidimensional JSON response:

Looking through the “res” JSON object, we can see a key called “itemHash”, located in res[‘Response’][‘data’][‘saleItemCategories’][‘saleItem’][‘item’][‘itemHash’], shown on line 23. This is the location of the encoded item details we are looking for!

Here is where things get tricky… JSON data is used to represent key-value pairs, called dictionaries in Python. However the item “saleItemCategories”, shown on line 11 – itself contains a series of key-value pairs. Also the item “saleItems”, shown on line 14, is also a dictionary.

Our JSON object contains nested dictionaries, also known as a multidimensional array.

Lots and lots of data!

Decoding the JSON response for Xurs inventory.

 

So in order to make a list of each of the “itemHash” values, we need a for loop to iterate through each of the “saleItemCategories” and another for loop to iterate through each of the “saleItems”.

for saleItem in res.json()['Response']['data']['saleItemCategories']:
	mysaleItems = saleItem['saleItems']
	for myItem in mysaleItems:
		hashID = str(myItem['item']['itemHash'])

 

Request 2 – decoding the item hash:

Now that we have a list of our itemHash’s – we need to make send a request to the Bungie Destiny Manifest page for each of the hashes.

This request takes the form of: http://www.bungie.net/Platform/Destiny/Manifest/{type}/{id}/

Where the {id} is the itemHash we just took from Xurs inventory.

A list of the hash {type}’s can be found here: http://bungienetplatform.wikia.com/wiki/DestinyDefinitionType but for this example, we know {type} will be “6”, as we are searching for an “InventoryItem”.

We can add on another request to our nested loops above:

base_url = "https://www.bungie.net/platform/Destiny/"
for saleItem in res.json()['Response']['data']['saleItemCategories']:
	mysaleItems = saleItem['saleItems']
	for myItem in mysaleItems:
		hashID = str(myItem['item']['itemHash'])
		hashReqString = base_url + "Manifest/" + hashType + "/" + hashID
		res = requests.get(hashReqString, headers=HEADERS)
		item_name = res.json()['Response']['data']['inventoryItem']['itemName']
		item_type = res.json()['Response']['data']['inventoryItem']['itemTypeName']
		item_tier = res.json()['Response']['data']['inventoryItem']['tierTypeName']
		print "Item is: " + item_name
		print "Item type is: " + item_tier + " " + item_type + "\n"

Here is what the output of our code looks like:

Here's what Xur is selling today.

Here’s what Xur is selling today.

And when we checkout Xurs inventory on the Bungie.net vendors page, here’s what we see:

Xurs inventory, taken from the Bungie.net vendors page.

Xurs inventory, taken from the Bungie.net vendors page.

Yes! Our Python app can now send requests to Bungie and print the contents of Xurs inventory. (Also, if you haven’t got The Ram – get it)

There’s lots of cool stuff sent in the JSON objects, including hyperlinks to the item images and details on the price of each item. For now, this is good enough for me.

Running the code:

Here is our full set code,  we can copy this into a file called “Get_Xur_inventory.py” and execute it from the command prompt like so:

> python Get_Xur_inventory.py

The code can also be found on my GitHub page here: https://github.com/AllynH/Destiny_Get_Xur_inventory

I’ll keep the repo up to date with any improvements or changes.

from lxml import html
import requests
import json

# Uncomment this line to print JSON output to a file:
#f = open('output.txt', 'w')

HEADERS = {"X-API-Key":'YOUR-X-API-Key'}

base_url = "https://www.bungie.net/platform/Destiny/"
xur_url = "https://www.bungie.net/Platform/Destiny/Advisors/Xur/"
hashType = "6"

# Send the request and store the result in res:
print "\n\n\nConnecting to Bungie: " + xur_url + "\n"
print "Fetching data for: Xur's Inventory!"
res = requests.get(xur_url, headers=HEADERS)

# Print the error status:
error_stat = res.json()['ErrorStatus']
print "Error status: " + error_stat + "\n"

# Uncomment this line to print JSON output to a file:
#f.write(json.dumps(res.json(), indent=4))

print "##################################################"
print "## Printing Xur's inventory:"
print "##################################################"

for saleItem in res.json()['Response']['data']['saleItemCategories']:
	mysaleItems = saleItem['saleItems']
	for myItem in mysaleItems:
		hashID = str(myItem['item']['itemHash'])
		hashReqString = base_url + "Manifest/" + hashType + "/" + hashID
		res = requests.get(hashReqString, headers=HEADERS)
		item_name = res.json()['Response']['data']['inventoryItem']['itemName']
		print "Item is: " + item_name
		item_type = res.json()['Response']['data']['inventoryItem']['itemTypeName']
		item_tier = res.json()['Response']['data']['inventoryItem']['tierTypeName']
		print "Item type is: " + item_tier + " " + item_type + "\n"
		

 

Here are a few ideas for the next steps:

  1. Add e-mail function, so the script mails me automatically when Xur lands.
  2. Create a web server, so we can access the app from a web page.
  3. Find Xurs location and add it to the output.
  4. Play some Trials of Osiris and get ready for the Rise Of Iron expansion

I’m hoping to expand on this program and add features as I go, leave me a comment and let me know if there’s anything you’d like to see, or if you fancy carrying me to the Lighthouse!

 

Connecting a Raspberry Pi to a WD MY Cloud Network Attached Hard Drive:

Recently during a house move, I dropped my good old reliable Raspberry Pi – hard drive Network Attached Storage unit.
Basically I connected my external hard drive to a Raspberry Pi and had a Network Attached Storage drive which allowed me to access my media from any device on my home network.

I then realised it was time to buy a dedicated NAS.

I shelled out for the WE My Cloud 4 TB server, which by all reports is a great piece of gear – however I wanted something more than a standalone NAS and I wanted to be able to access the WD My Cloud from my Pi.

Here’s how I connected my Pi to the My Cloud! The guide below should work for any NAS – not just the WD My Cloud.

Find the IP address of your NAS:

If you don’t know the IP address of your NAS, you can perform an “arp-scan” from your Raspberry Pi to find it, here’s how I found mine:

# sudo arp-scan --localnet --interface=wlan0

If you still can’t find the IP address, for the WD My Cloud you can find the IP address in your settings, as per the instructions here.

In my case my NAS IP address was: 192.168.192.62

Mounting the NAS to the Raspberry Pi:

The first step to accessing the NAS from your Raspberry Pi is to mount the external HDD as a file system on the Raspberry Pi, this will allow you to view the NAS, as you would any directory on the Pi. This is pretty easy actually, as the CIFS (CIFS Common Internet File Share, a protocol dictating how different OS’ share files between them, including Windows and Linux) protocol takes care of everything.

First make a directory for the share:

# mkdir wdmycloud

Next mount the drive using the IP address and the Raspberry Pi directory you want to mount to:

# sudo mount -t cifs -o guest //192.168.192.62/Public /home/pi/wdmycloud
mount command

mount command

In this example, I am mounting the “Public” folder located on my NAS to the wdmycloud folder located on my Raspberry Pi.

The command syntax is:
mount -t <Mount Type> -o <Access> <SOURCE> <DESTINATION>

After executing the mount command, you should now be able to access the NAS file system as you would any other directory!

Automatically mount the NAS on power up:

 

Edit the FSTAB to mount your NAS automatically on power up:

To make the mount permanent, we need to add the NAS file system to the Raspberry Pi’s /etc/fstab file – the File System Table.

# sudo nano /etc/fstab
Edit the FSTAB file.

Edit the FSTAB file.

Add the NAS as a file system in the FSTAB file.

//192.168.192.62/Public /home/pi/wdmycloud cifs guest     0       0
Adding the NAS to the FSTAB.

Adding the NAS to the FSTAB.

You can see from the last line in the FSTAB file above, I have added the NAS as a file system in my FSTAB file.

This will automatically mount the NAS every time you power up your Raspberry Pi!

Testing the NAS is connected automatically on power up:

First step, reboot your Pi… :

# sudo reboot
Reboot your Pi.

Reboot your Pi.

 

Next step, check your NAS directory from the Pi:

# cd wdmycloud
# ls -larth
Confirm the share is working.

Confirm the share is working.

Success! The mount works, I can now access all of my pictures, music and movies from my Raspberry Pi. Everything is safely stored on my WD My Cloud, which keeps 2 copies of all of my data – so if anything goes wrong, I’ll always have my data backed up 😉

 

« Older posts

© 2024 Allyn H

Theme by Anders NorenUp ↑