-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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