您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add Contest History Link to pages under leetcode.com
// ==UserScript== // @name Add Contest History Link // @namespace http://tampermonkey.net/ // @version 1.0 // @description Add Contest History Link to pages under leetcode.com // @author V3L0CITY // @match *://leetcode.com/* // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; // Function to add Contest History Link function addContestHistoryLink() { // Find all <a> elements on the page const allLinks = document.getElementsByTagName('a'); for (let i = 0; i < allLinks.length; i++) { const link = allLinks[i]; // Check if the text content of the <a> element is "Explore" if (link.textContent.trim() === "Contest History") { return; } } // Loop through all <a> elements for (let i = 0; i < allLinks.length; i++) { const link = allLinks[i]; // Check if the text content of the <a> element is "Explore" if (link.textContent.trim() === "Explore") { // Find the second parent (grandparent) of the <a> element const bigGuy = link.parentElement.parentElement; if (bigGuy) { // Create a new list item element const listItem = document.createElement('li'); listItem.className = 'relative flex h-full items-center text-sm'; // Create a new <a> element for Contest History const contestHistoryLink = document.createElement('a'); contestHistoryLink.className = 'relative whitespace-nowrap hover:text-text-primary dark:hover:text-text-primary flex items-center text-base leading-[22px] cursor-pointer hover:text-text-primary dark:hover:text-text-primary text-text-secondary dark:text-text-secondary'; contestHistoryLink.textContent = 'Contest History'; // Add an event listener to the Contest History link contestHistoryLink.addEventListener('click', function() { // Execute your script here // Replace the following line with your script 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 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 newInitialLoadingDiv = document.createElement('div'); newInitialLoadingDiv.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 newInitialLoadingDiv.appendChild(spinnerDiv); // Append initial loading div to the document body document.body.appendChild(newInitialLoadingDiv); 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(); }); // Append the Contest History link to the list item listItem.appendChild(contestHistoryLink); // Append the list item to the big guy bigGuy.appendChild(listItem); // Break the loop since we found the first <a> element with the text "Explore" break; } } } } // Call the function initially addContestHistoryLink(); // Create a MutationObserver instance const observer = new MutationObserver(addContestHistoryLink); // Observe changes to the document body observer.observe(document.body, { childList: true, subtree: true }); })();