Skip to content

Instantly share code, notes, and snippets.

@FootballFan141
Forked from saiteja09/XboxGamerScore.js
Created May 18, 2024 02:25
Show Gist options
  • Save FootballFan141/dd685046d46399fbba83a1c294f8bf38 to your computer and use it in GitHub Desktop.
Save FootballFan141/dd685046d46399fbba83a1c294f8bf38 to your computer and use it in GitHub Desktop.
Widget for Yearly Xbox GamerScore Tracking for use with Scriptable app in iOS
let xbox_refreshtoken = null
let xbox_clientid = null
let xbox_clientsecret = null
let xbox_credential_base64 = null
let xbox_authorization = null
let xbox_id = null
let xbox_profileurl = 'https://peoplehub.xboxlive.com/users/me/people/xuids(<xid>)/decoration/detail,preferredColor,presenceDetail,multiplayerSummary'
let xbox_titleHistoryurl = 'https://titlehub.xboxlive.com/users/xuid(<xid>)/titles/titleHistory/decoration/GamePass,TitleHistory,Achievement,Stats'
let xbox_achievementsurl = 'https://achievements.xboxlive.com/users/xuid(<xid>)/achievements?orderBy=UnlockTime&unlockedOnly=true'
const xbox_tokenurl = 'https://login.live.com/oauth20_token.srf'
const xbox_live_authurl = 'https://user.auth.xboxlive.com/user/authenticate'
const xbox_live_xstsurl = 'https://xsts.auth.xboxlive.com/xsts/authorize'
const xbox_logourl = 'https://user-images.githubusercontent.com/8601809/202868884-b3b47156-8314-4022-ab96-aa1168437464.png'
const xbox_gs_logourl = 'https://user-images.githubusercontent.com/8601809/202863495-ae6c706b-66d9-47a8-b035-46c70dffec74.png'
const xbox_ach_logourl = 'https://user-images.githubusercontent.com/8601809/202863432-84bae84a-7025-4705-b2d9-8171f13ffb8b.png'
const quick_chart_url = 'https://quickchart.io/chart'
let numOfAchByMnth = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let sumofGscByMnth = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let sumGamerScore = 0
let sumOfAch = 0
// Read Refresh Token from Keychain
if (Keychain.contains('xbox_refreshtoken')) {
xbox_refreshtoken = Keychain.get('xbox_refreshtoken')
} else {
console.error('Refresh Token not found in Keychain. Please store Refresh Token in the key \'xbox_refreshtoken\'')
Script.complete()
}
// Read Client ID from Keychain
if (Keychain.contains('xbox_clientid')) {
xbox_clientid = Keychain.get('xbox_clientid')
} else {
console.error('Client ID not found in Keychain. Please store Client ID in the key \'xbox_clientid\'')
Script.complete()
}
// Read Client Secret from Keychain
if (Keychain.contains('xbox_clientsecret')) {
xbox_clientsecret = Keychain.get('xbox_clientsecret')
} else {
console.error('Client Secret not found in Keychain. Please store Client Secret in the key \'xbox_clientid\'')
Script.complete()
}
//Base 64 for ClientID and Client Secret
xbox_credential_base64 = 'Basic ' + btoa(xbox_clientid + ':' + xbox_clientsecret)
//Start Authentication
await authenticateWithXbox()
uPResp = await getUserProfile()
//Widget Rendering
xboxWidget = await renderWidget()
if (config.runsInWidget) {
Script.setWidget(xboxWidget)
} else {
xboxWidget.presentLarge()
}
Script.complete()
// Main function for Rendering widget
async function renderWidget() {
widget = new ListWidget()
widget.backgroundColor = new Color('#107C10')
firstStack = widget.addStack()
firstStack.centerAlignContent()
xboxlogo = firstStack.addImage(await getImageFromURL(xbox_logourl))
xboxlogo.tintColor = Color.white()
xboxlogo.imageSize = new Size(100, 40)
firstStack.addSpacer()
uPImage = firstStack.addImage(await getImageFromURL(uPResp.people[0].displayPicRaw))
uPImage.imageSize = new Size(30, 30)
uPImage.cornerRadius = 100
firstStack.addSpacer(5)
uPGamerTag = firstStack.addText(uPResp.people[0].gamertag)
uPGamerTag.leftAlignText()
uPGamerTag.font = Font.boldRoundedSystemFont(15)
uPGamerTag.textColor = Color.white()
secondStack = widget.addStack(10)
secondStack.centerAlignContent()
secondStack.addText(' ')
secondStack.addSpacer()
tgsImage = secondStack.addImage(await getImageFromURL(xbox_gs_logourl))
tgsImage.tintColor = Color.white()
tgsImage.imageSize = new Size(15, 15)
secondStack.addSpacer(5)
tgsTxt = secondStack.addText(uPResp.people[0].gamerScore)
tgsTxt.font = Font.boldRoundedSystemFont(14)
tgsTxt.textColor = Color.white()
secondStack.setPadding(0, 0, 10, 0)
thirdStack = widget.addStack()
thirdStack.centerAlignContent()
thirdStack.addSpacer()
gsBMnthTxt = thirdStack.addText('Yearly GamerScore Tracker')
gsBMnthTxt.font = Font.boldRoundedSystemFont(12)
gsBMnthTxt.textColor = Color.white()
thirdStack.addSpacer()
thirdStack.setPadding(0, 0, 10, 0)
await getAchievementsByMonth()
fourthStack = widget.addStack()
fourthStack.addImage(await getGamerScoreChart())
fifthStack = widget.addStack()
fifthStack.setPadding(10, 0, 0, 0)
tgwTxt = fifthStack.addText("Total GamerScore Won \n" + sumGamerScore.toString())
tgwTxt.font = Font.boldMonospacedSystemFont(12)
tgwTxt.textColor = Color.white()
fifthStack.addSpacer()
ngsTxt = fifthStack.addText("Num. Of Achievements Won \n" + sumOfAch.toString())
ngsTxt.font = Font.boldMonospacedSystemFont(12)
ngsTxt.textColor = Color.white()
return widget
}
// Get GamerScore Chart from QuickChart
async function getGamerScoreChart() {
body = {
"version": "2",
"backgroundColor": "transparent",
"width": 500,
"height": 300,
"devicePixelRatio": 2.0,
"format": "png",
"chart": {
"type": "line",
"data": {
"labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
"datasets": [{
"data": sumofGscByMnth,
"fill": false,
"borderColor": "#fff",
"borderWidth": 5,
"pointRadius": 0,
"lineTension": 0.4
}]
},
"options": {
"legend": {
"display": false
},
"scales": {
"xAxes": [{
"display": true,
"gridLines": {
"display": false
},
"ticks": {
"fontColor": "#fff",
"fontStyle": "bold"
}
}],
"yAxes": [{
"display": true,
"gridLines": {
"display": false
},
"ticks": {
"fontColor": "#fff",
"fontStyle": "bold"
}
}]
}
}
}
}
let req = new Request(quick_chart_url)
req.method = 'post'
req.headers = {
'Content-Type': 'application/json'
}
req.body = JSON.stringify(body)
return req.loadImage()
}
// Calculate Sum of GamerScore and Number of Achievements each month
async function getAchievementsByMonth() {
let breakwhile = false
let skip = 0
while (1) {
achResp = await getUserAchievements(skip)
achievements = achResp.achievements
if (achievements.length == 0) {
break
}
for (let i = 0; i < achievements.length; i++) {
rewards = achievements[i].rewards
const d = new Date(achievements[i].progression.timeUnlocked)
if (d.getFullYear() == getCurrentYear()) {
for (let j = 0; j < rewards.length; j++) {
if (rewards[j].type == 'Gamerscore') {
numOfAchByMnth[d.getMonth()] = numOfAchByMnth[d.getMonth()] + 1
sumofGscByMnth[d.getMonth()] = sumofGscByMnth[d.getMonth()] + parseInt(rewards[j].value)
sumGamerScore = sumGamerScore + parseInt(rewards[j].value)
sumOfAch++
}
}
}
}
skip = skip + 1000;
}
}
// Call User Achievements Endpoint
async function getUserAchievements(skip) {
let url = xbox_achievementsurl.replace('<xid>', xbox_id)
url = url + '&maxItems=1000&skipItems=' + skip
let req = new Request(url)
req.headers = {
'Authorization': xbox_authorization,
'x-xbl-contract-version': '2',
'Content-Type': 'application/json'
}
return await req.loadJSON()
}
// GET GAME TITLES PLAYED BY USER
async function getUserTitleHistory() {
let url = xbox_titleHistoryurl.replace('<xid>', xbox_id)
let req = new Request(url)
req.headers = {
'Authorization': xbox_authorization,
'x-xbl-contract-version': '2',
'Content-Type': 'application/json'
}
return await req.loadJSON()
}
// READ XBOX PROFILE INFO
async function getUserProfile() {
let url = xbox_profileurl.replace('<xid>', xbox_id)
let req = new Request(url)
req.headers = {
'Authorization': xbox_authorization,
'x-xbl-contract-version': '3',
'Content-Type': 'application/json'
}
return await req.loadJSON()
}
// GET XSTS TOKEN, USER HASH AND XBOX ID
async function getXSTSAndUHS(xblt) {
let body = {
'Properties': {
'SandboxId': 'RETAIL',
'UserTokens': [xblt]
},
'RelyingParty': 'http://xboxlive.com',
'TokenType': 'JWT'
}
let req = new Request(xbox_live_xstsurl)
req.method = 'post'
req.headers = {
'Content-Type': 'application/json'
}
req.body = JSON.stringify(body)
return req.loadJSON()
}
// GET XBOX LIVE TOKEN
async function getXBLToken(msat) {
let body = {
'Properties': {
'AuthMethod': 'RPS',
'RpsTicket': 'd=' + msat,
'SiteName': 'user.auth.xboxlive.com'
},
'RelyingParty': 'http://auth.xboxlive.com',
'TokenType': 'JWT'
}
// console.log(JSON.stringify(body))
let req = new Request(xbox_live_authurl)
req.method = 'post'
req.headers = {
'Content-Type': 'application/json'
}
req.body = JSON.stringify(body)
return await req.loadJSON()
}
// GET ACCESS TOKEN FROM MICROSOFT OAUTH2.0
async function getMSAccessToken() {
let req = new Request(xbox_tokenurl)
req.method = 'POST'
req.headers = {
'Authorization': xbox_credential_base64,
'Content-Type': 'application/x-www-form-urlencoded'
}
req.body = 'grant_type=' + encodeURIComponent('refresh_token') + '&refresh_token=' + encodeURIComponent(xbox_refreshtoken)
return await req.loadJSON();
}
//START AUTHENTICATION AND COLLECT INFO
async function authenticateWithXbox() {
msatr = await getMSAccessToken()
Keychain.set('xbox_refreshtoken', msatr.refresh_token)
xlatr = await getXBLToken(msatr.access_token)
xstsr = await getXSTSAndUHS(xlatr.Token)
xsts = xstsr.Token
uhs = xstsr.DisplayClaims.xui[0].uhs
xbox_id = xstsr.DisplayClaims.xui[0].xid
xbox_authorization = 'XBL3.0 x=' + uhs + ';' + xsts
}
// GET IMAGE FROM URL
async function getImageFromURL(url) {
let req = new Request(url)
return await req.loadImage()
}
//Get Current Year
function getCurrentYear() {
const d = new Date();
return d.getFullYear();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment