Skip to content

Creating an XBOX Live Gamerscore Custom App

Nicholas Penree edited this page Dec 29, 2021 · 4 revisions

Using the new Config Script feature, you can execute Node.js methods to dynamically build the configuration values passed to the Render Script. This allows you to tap into the vast ecosystem of modules available on npm to build your own connected applets.

In this tutorial, we will walk through the steps required to create your own Gamerscore applet that updates automatically on your desired schedule.

Prerequisites

Building the Custom App

Overview

Our new custom app will consist of two different parts:

  1. A Starlark render script that is used to create a webp animation using pixlet render.

  2. A Node.js config script that will use the XBOX Live API to get profile.

Project Structure

We'll need to create some files that are accessible to the homebridge-tidbyt plugin and Homebridge as a whole.

I'm running Homebridge on a pi, so I'm going to be using /home/pi/tidbyt/xbox-live as my path for purpose of this article. Please replace this with a path that makes sense for your setup.

Create a new directory to house the custom app under your projects root:

> mkdir -p /home/pi/tidbyt/xbox-live

Render Script

After several iterations, I've settled on the following minimal design:

I thought it might be nice to have the XBOX avatar displayed as well, here is a version with that:

Here is my finished render.star Starlark script:

# Tidbyt Xbox Live App
# MIT License
# by Nicholas Penree, Dec 29 2021

load("render.star", "render")
load("http.star", "http")
load("encoding/base64.star", "base64")
load("cache.star", "cache")

MOCK=False

def get_config(config):
    if MOCK:
        return dict(
            gamer_tag = "z3r0c00l",
            gamer_score = "10808",
        )
    return config

def GamerScoreLogo():
    return render.Circle(
        color = "#fff",
        diameter = 13,
        child = render.Text(
            content = "G",
            color = "#000",
            font = "Dina_r400-6"
        ),
    )

def GamerScore(gamer_score):
    return render.Row(
        expanded=True,
        main_align="center",
        cross_align="center",
        children = [
            GamerScoreLogo(),
            render.Box(width = 1),
            render.Text(content = gamer_score),
        ],
    )

def main(config):
    settings = get_config(config)
    gamer_tag = settings.get('gamer_tag', '')
    gamer_score = settings.get('gamer_score', '')
    avatar_url = settings.get('avatar_url', '')
    children = []

    if avatar_url != '':
        children += [
            render.Box(height = 2),
            render.Image(src=http.get(avatar_url).body(), height=14, width=14)
        ]

    children += [ GamerScore(gamer_score) ]

    if gamer_tag != '':
        if settings.get('scroll', False) or len(gamer_tag) > 8:
            gamer_tag_child = render.Marquee(
                width = 64,
                child = render.Text(color = '#3c3c3c', content = gamer_tag),
            )
        else:
            gamer_tag_child = render.Text(color = '#3c3c3c', content = gamer_tag)
        children += [ gamer_tag_child ]

    return render.Root(
        child = render.Box(
            render.Column(
                expanded=True,
                main_align="center",
                cross_align="center",
                children = children,
            ),
        ),
    )

Download it and move it into your project folder, with the name render.star (ie./home/pi/tidbyt/xbox-live/render.star).

Config Parameters

  • gamer_score - Required. The gamerscore value to show.
  • gamer_tag - Optional. The gamertag of the profile.
  • avatar_url - Optional. Profile avatar image URL that will be displayed instead of the Gamescore icon.

These values will need to be passed by our Config Script. We'll do that next!

Config Script

One of the great things about Config Scripts is that they are written in JavaScript using Node.js modules. This allows us to avoid reventing the wheel when someone else has already written code that does what we want.

We'll leverage two npm modules to deal with communicating with the XBOX Live API: @xboxreplay/xboxlive-auth and @xboxreplay/xboxlive-api.

Let's create an npm project and install these two packages as dependencies:

> cd /home/pi/tidbyt/xbox-live && npm init -y
> npm install @xboxreplay/xboxlive-auth @xboxreplay/xboxlive-api

This will create a package.json file and install these two packages in the node_modules folder under your project directory.

A Config Script is a very simple Node module. It exports a single function that returns a Promise that resolves to an array of key/value pair objects.

Here is a quick implementation that uses our new dependencies to fetch and map your XBOX profile data to the configuration parameters we created in the render script:

/*!
 * Tidbyt Xbox Live App
 * MIT License
 * by Nicholas Penree, Dec 29 2021
 */

const { authenticateWithUserRefreshToken } = require('@xboxreplay/xboxlive-auth')
const XboxLiveAPI = require('@xboxreplay/xboxlive-api')

async function fetch(config = []) {
    let auth
    let playerSettings
    let { value: refreshToken } = (config || []).find(c => c.key === 'refresh_token') || {}
    let { value: clientId } = (config || []).find(c => c.key === 'client_id') || {}

    if (!clientId) {
        // Defaults to the live (live.com) client ID used by the official Xbox desktop / mobile
        // applications during the authentication (OAuth) process.
        // Each live application has its own ID but this one allows service::user.auth.xboxlive.com::MBI_SSL scope 
        // usage which skip the authorization prompt and return user's token.
        //    
        //    source: https://github.com/XboxReplay/xboxlive-auth/issues/16#issuecomment-815601103
        // 
        clientId = process.env.XBOX_LIVE_CLIENT_ID || '0000000048093EE3'
    }

    if (!refreshToken) {
        refreshToken = process.env.XBOX_LIVE_REFRESH_TOKEN
    }

    try {
        auth = await authenticateWithUserRefreshToken(refreshToken, { clientId })
    } catch (err) {
        console.error(err.details);
    }

    const {
        user_hash: userHash,
        xuid,
        xsts_token: XSTSToken
    } = auth || {}

    if (xuid) {
        try {
            playerSettings = await XboxLiveAPI.getPlayerSettings(xuid, {
                userHash,
                XSTSToken,
            }, [
                'UniqueModernGamertag', 
                // 'GameDisplayPicRaw', 
                'Gamerscore', 
            ])
        } catch (error) {
            console.error(error)
        }
    }

    playerSettings = playerSettings || []

    const { value: gamerTag } = playerSettings.find(x => x.id === 'UniqueModernGamertag') || {}
    const { value: gamerScore } = playerSettings.find(x => x.id === 'Gamerscore') || {}
    // const { value: avatarImageUrl } = playerSettings.find(x => x.id === 'GameDisplayPicRaw') || {}

    return [
        // ...config,
        { key: 'gamer_tag', value: gamerTag },
        { key: 'gamer_score', value: gamerScore },
        // { key: 'avatar_url', value: avatarImageUrl },
    ]
}

module.exports = fetch;

Download it and move it into your project folder, with the name config.js (ie./home/pi/tidbyt/xbox-live/config.js).

Configuring the Custom App

Obtaining a Refresh Token

  1. Login to Xbox Live (using this link)

  2. Once logged in, you will see a white screen. Find the refresh_token value from the URL. You'll want to extract everything after refresh_token= and before the next &.

  3. To use the refresh token, you will need to URI decode it. You can do this using the Node.js CLI like this:

> node -e 'console.log("Decoded refresh_token:\n\n" + decodeURIComponent(`<your refresh token>`))'

Make sure to replace <your refresh token> with the actual refresh token value you extracted from the URL bar.

You should have a value printed to your console. Copy/take note of this value, you'll need it to configure the custom app definition in the next step.

Add the Custom App

Now that we have generated our new refresh token, we can configure the custom app in our Homebridge configuration.

Find your Tidbyt platform object and add to the customApps array like this:

    {
        "platform": "Tydbit",
        "managedDevices": [
            ...
        ],
        "customApps": [
            ...,
            {
                "id": "XboxGamerScore",
                "enabled": true,
                "updateOnStartup": true,
                "script": "/home/pi/tidbyt/xbox-live/render.star",
                "schedule": "*/30 * * * *",
                "configScript": "/home/pi/tidbyt/xbox-live/config.js",
                "config": [
                    {
                        "key": "refresh_token",
                        "value": "<your refresh token>"
                    }
                ]
            }
        ]
    }

Make sure to replace <your refresh token> with the actual decoded refresh token you obtained above. If you omit the refresh_token config value, the code will fallback to using the XBOX_LIVE_REFRESH_TOKEN environment variable.

Restart Homebridge!

Now that we've sucessfully configured the new applet, we can enable it by restarting Homebridge.

After you restart, you should see [homebridge-tidbyt] executing your new XboxGamerScore applet and pushing your sweet new Gamerscore to your Tidbyt.

Here is a shot of mine:

Happy Hacking :)