// ==UserScript==
// @name Leetcode contest table
// @namespace http://tampermonkey.net/
// @version 0.0.3
// @description Get a better understanding of how you have performed across different contests, by getting a tabular view
// @author Prakash
// @match https://leetcode.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=leetcode.com
// @grant none
// @license GNU GPLv3
// @downloadURL https://update.greasyfork.icu/scripts/494272/Leetcode%20contest%20table.user.js
// @updateURL https://update.greasyfork.icu/scripts/494272/Leetcode%20contest%20table.meta.js
// ==/UserScript==
async function getUserName() {
// Query for getting the user name
const submissionDetailsQuery = {
query:
'\n query globalData {\n userStatus {\n username\n }\n}\n ',
operationName: 'globalData',
};
const options = {
method: 'POST',
headers: {
cookie: document.cookie, // required to authorize the API request
'content-type': 'application/json',
},
body: JSON.stringify(submissionDetailsQuery),
};
const username = await fetch('https://leetcode.com/graphql/', options)
.then(res => res.json())
.then(res => res.data.userStatus.username);
return username;
}
async function getContestInfo(theusername) {
// Query for getting the contest stats
const submissionDetailsQuery = {
query:
'\n query userContestRankingInfo($username: String!) {\n userContestRanking(username: $username) {\n attendedContestsCount\n rating\n globalRanking\n totalParticipants\n topPercentage\n badge {\n name\n }\n }\n userContestRankingHistory(username: $username) {\n attended\n trendDirection\n problemsSolved\n totalProblems\n finishTimeInSeconds\n rating\n ranking\n contest {\n title\n startTime\n }\n }\n}\n ',
variables: { username: theusername },
operationName: 'userContestRankingInfo',
};
const options = {
method: 'POST',
headers: {
cookie: document.cookie, // required to authorize the API request
'content-type': 'application/json',
},
body: JSON.stringify(submissionDetailsQuery),
};
const data = await fetch('https://leetcode.com/graphql/', options)
.then(res => res.json())
.then(res => res.data.userContestRankingHistory);
return data
}
// Apply alternating row background colors
function alternatingRowBackground(table) {
var rows = table.querySelectorAll('tr');
for (var i = 0; i < rows.length; i++) {
rows[i].classList.remove('even', 'odd');
rows[i].classList.add(i % 2 === 0 ? 'even' : 'odd');
}
}
// Function to create table
function createTable(data) {
var table = document.createElement('table');
table.id = 'leetCodeContestTable';
table.classList.add('styled-table'); // Add a class for styling
// Create table headers
var headers = ['StartTime', 'Title', 'Ranking', 'Rating', 'ProblemsSolved', 'FinishTimeInSeconds'];
var headerRow = document.createElement('tr');
headerRow.innerHTML += '
TimeSpan | ';
headers.forEach(function(header, index) {
var th = document.createElement('th');
th.textContent = header;
th.dataset.sortable = true;
th.dataset.columnIndex = index;
th.addEventListener('click', function() {
sortTable(table, index);
});
headerRow.appendChild(th);
});
table.appendChild(headerRow);
// Populate table rows
data.forEach(function(entry, index) {
var row = document.createElement('tr');
row.innerHTML += '' + entry.contest.startTime + ' | ';
row.innerHTML += '' + new Date(entry.contest.startTime * 1000).toLocaleString() + ' | ';
row.innerHTML += '' + entry.contest.title + ' | ';
row.innerHTML += '' + entry.ranking + ' | ';
row.innerHTML += '' + entry.rating + ' | ';
row.innerHTML += '' + entry.problemsSolved + ' | ';
row.innerHTML += '' + entry.finishTimeInSeconds + ' | ';
table.appendChild(row);
});
alternatingRowBackground(table);
// Add this table to top of page
var navbarContainer = document.getElementById('navbar-container');
navbarContainer.insertAdjacentElement('afterend', table);
}
// Function to sort table
function sortTable(table, columnIndex) {
var rows = Array.from(table.rows).slice(1); // Exclude header row
var isAscending = !table.querySelector('th[data-column-index="' + columnIndex + '"]').classList.contains('asc');
rows.sort(function(row1, row2) {
var value1 = row1.cells[columnIndex+1].textContent;
var value2 = row2.cells[columnIndex+1].textContent;
if (columnIndex === 0) {
value1 = row1.cells[columnIndex].textContent;
value2 = row2.cells[columnIndex].textContent;
} else {
value1 = parseFloat(value1) || value1;
value2 = parseFloat(value2) || value2;
}
return (isAscending ? 1 : -1) * (value1 > value2 ? 1 : -1);
});
// Reorder rows in table
while (table.rows.length > 1) {
table.deleteRow(1);
}
rows.forEach(function(row) {
table.appendChild(row);
});
// Remove sorting indicator from all headers
table.querySelectorAll('th[data-sortable]').forEach(function(header) {
header.classList.remove('asc', 'desc');
});
// Add sorting indicator to the clicked header
table.querySelector('th[data-column-index="' + columnIndex + '"]').classList.toggle(isAscending ? 'asc' : 'desc', true);
// Apply alternating background to rows
alternatingRowBackground(table);
}
// Inject CSS styles into the document head
function addTableCSS(){
document.head.innerHTML += `
`;
}
function addSpinnerCSS(){
document.head.innerHTML += `
`;
}
function toggleSpinner(startSpinner){
var initialLoadingDiv = document.getElementById('initial-loading');
var initialLoadingStyle = document.getElementById('initial-loading-style');
if (initialLoadingDiv && !startSpinner) {
initialLoadingDiv.parentNode.removeChild(initialLoadingDiv);
if (initialLoadingStyle) initialLoadingStyle.parentNode.removeChild(initialLoadingStyle);
}
else if(!initialLoadingDiv && startSpinner){
// Create initial loading div
var initialLoadingDiv1 = document.createElement('div');
initialLoadingDiv1.id = 'initial-loading';
// Create spinner div
var spinnerDiv = document.createElement('div');
spinnerDiv.className = 'spinner';
// Create bounce divs inside spinner div
for (var i = 0; i < 3; i++) {
var bounceDiv = document.createElement('div');
bounceDiv.className = 'bounce';
spinnerDiv.appendChild(bounceDiv);
}
// Append spinner div to initial loading div
initialLoadingDiv1.appendChild(spinnerDiv);
// Append initial loading div to the document body
document.body.appendChild(initialLoadingDiv1);
addSpinnerCSS();
}
}
function removeOldTable(){
var oldTable = document.getElementById("leetCodeContestTable");
var styleElement = document.getElementById("leetcodeContestTableStyle");
if (oldTable){
oldTable.parentNode.removeChild(oldTable);
if (styleElement) styleElement.parentNode.removeChild(styleElement);
return true;
}
return false;
}
async function execute(){
// remove existing table if it exists
if(removeOldTable()) return;
toggleSpinner(true);
try {
// fetch contest details
var theusername = await getUserName();
var contestdata = await getContestInfo(theusername);
var participatedContestData = contestdata.filter((entry) => entry.attended == true && entry.ranking != 0);
// Create and append table to the document body
addTableCSS();
createTable(participatedContestData);
} catch (error) {
console.error("An error occurred:", error);
} finally {
toggleSpinner(false);
}
}
execute();