Skip to content

Instantly share code, notes, and snippets.

@Maistho
Last active July 9, 2019 02:44
Show Gist options
  • Save Maistho/5a150cf798e73a25ab527d104755d510 to your computer and use it in GitHub Desktop.
Save Maistho/5a150cf798e73a25ab527d104755d510 to your computer and use it in GitHub Desktop.
CSGO stats bookmarklet
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