Mostrar Notas

Script para mostrar las notas actuales de todos los usuarios en el scoreboard del CTFd. (TODO: Manejo de errores en las requests)

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Mostrar Notas
// @namespace    http://tampermonkey.net/
// @license      MIT
// @version      0.1.551
// @description  Script para mostrar las notas actuales de todos los usuarios en el scoreboard del CTFd. (TODO: Manejo de errores en las requests)
// @author       Neftalí Toledo
// @match        https://ic.catedras.linti.unlp.edu.ar/scoreboard
// @icon         https://www.google.com/s2/favicons?sz=64&domain=edu.ar
// @grant        none
// @run-at       document-end
// ==/UserScript==

(async function() {
    'use strict';
    
    const ENDPOINT = "https://ic.catedras.linti.unlp.edu.ar/api/v1/users/USER_ID/solves";
    const RETOS_ENDPOINT = "https://ic.catedras.linti.unlp.edu.ar/api/v1/challenges"

    // Define los valores de suma correspondientes a cada categoría para el promedio ponderado
    //const cantidad_dificultades = 3; (SIN USO)
    const valorEasy = 4;
    const valorMedium = 3;
    const valorHard = 3;

    const valoresTotales = await obtenerValoresTotales(); //Obtiene los totales para calcular el promedio

    // Categorias que no se van a tener en cuenta para el calculo de la nota
    const categoriasExcluidas = [
        "Extras-(no-suman-nota)",
        "Practica-0_Scripting"
    ]

    //Promedio

    function calcularPromedioPonderado(cantidadEasy, cantidadMedium, cantidadHard, categoria) {
        // Calcula la suma ponderada
        const promedioEasy = calcularPromedio(cantidadEasy, valoresTotales[categoria]["Easy"] || 1);
        const promedioMedium = calcularPromedio(cantidadMedium, valoresTotales[categoria]["Medium"] || 1);
        const promedioHard = calcularPromedio(cantidadHard, valoresTotales[categoria]["Hard"] || 1);

        return (promedioEasy * valorEasy + promedioMedium * valorMedium + promedioHard * valorHard);
    }

    function calcularPromedio(retos_resueltos, total) {
        return (retos_resueltos / total);
    }

    // Filtros

    function filtrarDesafios(data, total = false) {
        const result = {};
    
        data.forEach(item => {
            const categoryKey = total ? item.category : item.challenge.category;
            const [categoria, dificultad] = categoryKey.split(" - ");
    
            result[categoria] = result[categoria] || {};
            result[categoria][dificultad] = (result[categoria][dificultad] || 0) + 1;
        });
    
        return result;
    }
    
    // Exclusiones

    function excluirCategorias(categorias) {
        const categoriasExcluidasSet = new Set(categoriasExcluidas); // Se convierte en Set por eficiencia para muchas categorías excluidas
        return categorias.filter(categoria => !categoriasExcluidasSet.has(categoria));
    }

    // Obtener la cantidad de retos resueltos por el usuario

    async function obtenerRetosResueltos(user) {
        const req = ENDPOINT.replace("USER_ID", user);
        const response = await fetch(req);
        const dataJSON = await response.json();
        
        return dataJSON;
    }

    // Obtener nota para cada practica

    function obtenerNotas(data) {
        const retos_resueltos = filtrarDesafios(data);
        const categorias = excluirCategorias(Object.keys(retos_resueltos));
        
        const notas = categorias
            .filter(categoria => Object.keys(retos_resueltos[categoria]).length > 0)
            .map(categoria => {
                const cantidadEasy = retos_resueltos[categoria]["Easy"] || 0;
                const cantidadMedium = retos_resueltos[categoria]["Medium"] || 0;
                const cantidadHard = retos_resueltos[categoria]["Hard"] || 0;
                
                const promedioPonderado = calcularPromedioPonderado(cantidadEasy, cantidadMedium, cantidadHard, categoria);
                return `${categoria}: ${parseFloat(promedioPonderado).toPrecision(3)}`;
            })
            .reverse().join("<br>");
    
        return notas;
    }

    // Obtener el total de retos por categoría
    async function obtenerValoresTotales(){
        const response = await fetch(RETOS_ENDPOINT);
        const retosJSON = await response.json();
        const retos_total = filtrarDesafios(retosJSON.data, true);
        return retos_total;
    }

    // Obtenemos la tabla de usuarios y sus filas
    const tablaUsuarios = document.querySelector(".table-striped");
    const filas = tablaUsuarios.querySelectorAll("tr");

    // Añadir columna de notas
    const columnaNotas = document.createElement("td");
    columnaNotas.setAttribute("scope", "col");
    columnaNotas.innerHTML = "<b>Notas</b>";
    filas[0].appendChild(columnaNotas);

    // Obtener las notas de cada usuario
    const userPromises = [];
    for (let i = 1; i < filas.length; i++) {
        //A veces este link cambia a "https://ic.catedras.linti.unlp.edu.ar/teams/" en vez de "https://ic.catedras.linti.unlp.edu.ar/users/"
        // No se por qué pasa
        const userID = parseInt(filas[i].querySelector("td a").href.replace("https://ic.catedras.linti.unlp.edu.ar/users/", ""));
        userPromises.push(obtenerRetosResueltos(userID));
    }

    // Esperar a que se resuelvan todas las promesas
    const userNotes = await Promise.all(userPromises);

    // Construir notas como una cadena HTML
    const notasHTML = userNotes.map(userNote => {
        const notas = obtenerNotas(userNote.data);
        return notas || "No resolvió ningún reto";
    });

    // Añadir notas a cada fila
    for (let i = 1; i < filas.length; i++) {
        const elementoNota = document.createElement("td");
        elementoNota.innerHTML = notasHTML[i - 1];
        filas[i].appendChild(elementoNota);
    }
})();