Last active
July 9, 2019 02:44
-
-
Save Maistho/5a150cf798e73a25ab527d104755d510 to your computer and use it in GitHub Desktop.
CSGO stats bookmarklet
This file contains hidden or 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
javascript:(function () { | |
let CsgoMap; | |
(function (CsgoMap) { | |
CsgoMap["de_mirage"] = "Mirage"; | |
CsgoMap["de_dust2"] = "Dust II"; | |
CsgoMap["de_subzero"] = "Subzero"; | |
CsgoMap["de_train"] = "Train"; | |
CsgoMap["de_nuke"] = "Nuke"; | |
CsgoMap["de_inferno"] = "Inferno"; | |
CsgoMap["de_cache"] = "Cache"; | |
CsgoMap["de_overpass"] = "Overpass"; | |
})(CsgoMap || (CsgoMap = {})); | |
async function getAllMatches() { | |
if (window.location.hostname != 'steamcommunity.com') { | |
alert('Redirecting to steamcommunity.com, please run the bookmarklet again after the page has reloaded'); | |
window.location.hostname = 'steamcommunity.com'; | |
} | |
const userAvatar = document.querySelector('#global_actions>a.user_avatar'); | |
if (!userAvatar) { | |
alert('You need to be logged in'); | |
throw new Error('User not logged in'); | |
} | |
const profileLink = userAvatar.href.replace(/\/$/, ''); | |
const matches = []; | |
let continueToken; | |
do { | |
const params = new URLSearchParams({ | |
ajax: '1', | |
tab: 'matchhistorycompetitive', | |
}); | |
if (continueToken) { | |
params.append('continue_token', continueToken); | |
} | |
const response = await fetch(`${profileLink}/gcpd/730?${params.toString()}`, { | |
credentials: 'include', | |
}).then(res => res.json()); | |
if (!response.success) { | |
throw new Error(JSON.stringify(response)); | |
} | |
const parser = new DOMParser(); | |
const dom = parser.parseFromString(response.html, 'text/html'); | |
matches.push(...Array.from(dom.querySelectorAll('.generic_kv_table.csgo_scoreboard_root>tbody>tr')) | |
.map(tr => { | |
return Object.assign({ map: getMap(tr), time: getTime(tr), waitTime: getWaitTime(tr), matchDuration: getMatchDuration(tr), score: getScore(tr), demo: getDemo(tr) }, getTeams(tr, profileLink)); | |
}) | |
.filter(({ map }) => !!map)); | |
continueToken = response.continue_token; | |
} while (continueToken); | |
return matches; | |
} | |
const getTeams = (tr, profileLink) => { | |
const teams = [[], []]; | |
const players = Array.from(tr.querySelectorAll('.csgo_scoreboard_inner_right>tbody>tr')); | |
let currentTeam = -1; | |
for (const player of players) { | |
const userLink = q(player, 'a.linkTitle'); | |
if (userLink == null) { | |
currentTeam += 1; | |
continue; | |
} | |
teams[currentTeam].push({ | |
link: userLink.attributes.getNamedItem('href').textContent || '', | |
name: getInfo(player, 'a.linkTitle'), | |
image: getAttribute(q(player, '.playerAvatar img'), 'src'), | |
ping: parseInt(getInfo(player, 'td:nth-child(2)')), | |
kills: parseInt(getInfo(player, 'td:nth-child(3)')), | |
assists: parseInt(getInfo(player, 'td:nth-child(4)')), | |
deaths: parseInt(getInfo(player, 'td:nth-child(5)')), | |
mvps: parseInt(getInfo(player, 'td:nth-child(6)').substr(1)) || 0, | |
headshotRate: getInfo(player, 'td:nth-child(7)'), | |
score: parseInt(getInfo(player, 'td:nth-child(8)')), | |
}); | |
} | |
const userTeam = teams.findIndex(team => team.some(player => player.link === profileLink)); | |
return { teams, userTeam }; | |
}; | |
const getDemo = tr => { | |
const text = q(tr, '.csgo_scoreboard_inner_left .csgo_scoreboard_btn_gotv'); | |
const button = text && text.parentElement; | |
return getAttribute(button, 'href'); | |
}; | |
const getScore = (tr) => getInfo(tr, '.csgo_scoreboard_score'); | |
const getMatchDuration = (tr) => getInfo(tr, '.csgo_scoreboard_inner_left>tbody>tr:nth-child(4)').replace(/^Match Duration: /, ''); | |
const getWaitTime = (tr) => getInfo(tr, '.csgo_scoreboard_inner_left>tbody>tr:nth-child(3)').replace(/^Wait Time: /, ''); | |
const getTime = (tr) => new Date(getInfo(tr, '.csgo_scoreboard_inner_left>tbody>tr:nth-child(2)')); | |
const getMap = (tr) => getInfo(tr, '.csgo_scoreboard_inner_left>tbody>tr:nth-child(1)').replace(/^Competitive /, ''); | |
function getInfo(el, query) { | |
return getText(q(el, query)); | |
} | |
function getText(el) { | |
return ((el && el.textContent) || '').trim(); | |
} | |
function q(el, query) { | |
return el && el.querySelector(query); | |
} | |
function getAttribute(el, attribute) { | |
return ((el && el.getAttribute(attribute)) || '').trim(); | |
} | |
function parseMatches(data) { | |
const maps = {}; | |
data.forEach((match) => { | |
const score = match.score.split(':').map(s => s.trim()); | |
const myTeam = match.userTeam; | |
const notMyTeam = myTeam === 0 ? 1 : 0; | |
if (!maps[match.map]) { | |
maps[match.map] = { | |
name: match.map, | |
timesPlayed: 0, | |
wins: 0, | |
losses: 0, | |
draws: 0, | |
winrate: 0, | |
}; | |
} | |
const map = maps[match.map]; | |
if (score[myTeam] > score[notMyTeam]) { | |
map.wins += 1; | |
} | |
else if (score[myTeam] < score[notMyTeam]) { | |
map.losses += 1; | |
} | |
else { | |
map.draws += 1; | |
} | |
map.timesPlayed += 1; | |
}); | |
Object.keys(maps).forEach(map => { | |
maps[map].winrate = | |
maps[map].wins / (maps[map].timesPlayed - maps[map].draws); | |
}); | |
return maps; | |
} | |
function printStatistics(maps) { | |
var data = Object.values(maps) | |
.sort((a, b) => b.winrate - a.winrate) | |
.map(map => { | |
return { | |
Map: map.name, | |
Winrate: formatWinrate(map.winrate), | |
Wins: map.wins, | |
Draws: map.draws, | |
Losses: map.losses, | |
}; | |
}); | |
console.table(data); | |
let res = `<table style="margin: 0 auto;"> | |
<tr> | |
<th>Map</th> | |
<th>Winrate</th> | |
<th>(W/D/L)</th> | |
</tr> | |
${data | |
.map(item => ` | |
<tr> | |
<td>${item.Map}</td> | |
<td>${item.Winrate}</td> | |
<td>(${item.Wins}/${item.Draws}/${item.Losses})</td> | |
</tr> | |
`) | |
.join('\n')} | |
</table>`; | |
const el = document.createElement('div'); | |
el.style.position = 'fixed'; | |
el.style.top = '0'; | |
el.style.left = '0'; | |
el.style.right = '0'; | |
el.style.zIndex = '999999'; | |
el.style.background = '#171a21'; | |
el.style.color = 'white'; | |
el.innerHTML = res; | |
document.body.appendChild(el); | |
const height = `${el.clientHeight}px`; | |
document.body.style.marginTop = height; | |
const header = document.querySelector('.responsive_header'); | |
if (header) { | |
header.style.marginTop = height; | |
} | |
} | |
function formatWinrate(winrate) { | |
return ((winrate * 100).toLocaleString(undefined, { | |
maximumFractionDigits: 2, | |
minimumFractionDigits: 0, | |
}) + '%'); | |
} | |
getAllMatches().then(matches => { | |
printStatistics(parseMatches(matches)); | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment