Leetcode contest table

Get a better understanding of how you have performed across different contests, by getting a tabular view

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==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 
// ==/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 += '<th class="hidden">TimeSpan</th>';
    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 += '<td class="hidden">' + entry.contest.startTime + '</td>';
        row.innerHTML += '<td>' + new Date(entry.contest.startTime * 1000).toLocaleString() + '</td>';
        row.innerHTML += '<td>' + entry.contest.title + '</td>';
        row.innerHTML += '<td>' + entry.ranking + '</td>';
        row.innerHTML += '<td>' + entry.rating + '</td>';
        row.innerHTML += '<td>' + entry.problemsSolved + '</td>';
        row.innerHTML += '<td>' + entry.finishTimeInSeconds + '</td>';

        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 += `
    <style id='leetcodeContestTableStyle'>
      .styled-table {
      border-collapse: collapse;
      width: 100%;
      }

      .styled-table th, .styled-table td {
      padding: 8px;
      text-align: left;
      border-bottom: 1px solid #ddd;
      position: relative;
      }

      .styled-table th::after {
      content: '';
      position: absolute;
      top: 50%;
      right: 8px;
      transform: translateY(-50%);
      font-size: 12px;
      }

      .styled-table th.asc::after {
      content: '↑';
      }

      .styled-table th.desc::after {
      content: '↓';
      }

      .styled-table th {
      background-color: #f2f2f2;
      cursor: pointer;
      }

      .styled-table tr.even {
      background-color: #f9f9f9;
      }

      .styled-table tr.odd {
      background-color: #ffffff;
      }

      .hidden {
      display: none;
      }
    </style>
    `;
  }

function addSpinnerCSS(){
    document.head.innerHTML += `
    <style id="initial-loading-style">
      #initial-loading {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        background: white;
        transition: opacity .6s;
        z-index: 1;
      }

      #initial-loading[data-is-hide="true"] {
        opacity: 0;
        pointer-events: none;
      }

      #initial-loading .spinner {
        display: flex;
      }

      #initial-loading .bounce {
        width: 18px;
        height: 18px;
        margin: 0 3px;
        background-color: #999999;
        border-radius: 100%;
        display: inline-block;
        -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
        animation: sk-bouncedelay 1.4s infinite ease-in-out both;
      }

      #initial-loading .bounce:nth-child(1) {
        -webkit-animation-delay: -0.32s;
        animation-delay: -0.32s;
      }

      #initial-loading .bounce:nth-child(2) {
        -webkit-animation-delay: -0.16s;
        animation-delay: -0.16s;
      }

      @-webkit-keyframes sk-bouncedelay {

        0%,
        80%,
        100% {
          -webkit-transform: scale(0);
          transform: scale(0);
        }

        40% {
          -webkit-transform: scale(1.0);
          transform: scale(1.0);
        }
      }

      @keyframes sk-bouncedelay {

        0%,
        80%,
        100% {
          -webkit-transform: scale(0);
          transform: scale(0);
        }

        40% {
          -webkit-transform: scale(1.0);
          transform: scale(1.0);
        }
      }
    </style>
  `;
  }

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