// ==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();