cf potd

This Script will add A POTD link on codeforces

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         cf potd
// @namespace    http://tampermonkey.net/
// @version      2025-12-26
// @description  This Script will add A POTD link on codeforces
// @author       You
// @match        *://codeforces.com/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==




/*
  DESIGN CHOICES:

  1) To avoid API calls during contest all calls will be done only when clicking "POTD" thus making clicking POTD a bit slower but keeping refresh fast thus no lag in contest
  2) POTD are use specific that is for each user POTD will be different as POTD depend on the user curr_rating
  3) POTD will be in range [curr_rating-neg_delta*100 , curr_rating+pos_delta*100] where curr_rating is rounded down. For example: if user is 1650 POTD can be of [1400,1500,1600,1700,1800]
     Please change neg_delta and pos_delta constant variables if you want to change distribution
  3) POTD will always be an unsolved problem Unlike Leetcode where POTD can be an already solved problem. As they follow a global POTD model but this is using a user specic POTD
  4) on solving POTD and clicking "POTD" again you will be given a new unsolved "POTD" this is done as I want the "POTD" link to work as both a POTD and a random problem Generator
*/

/*
 TODO:
  1) ADD STREAKS
  2) For logged in users remove solved problems //DONE
*/
const neg_delta=2,pos_delta=2; //Change if you want a different distribution
const codeforces_url = "https://codeforces.com";
const all_problems_url = `${codeforces_url}/api/problemset.problems`;
const users_url =`${codeforces_url}/api/user.info?handles=`
const users_status_url =`${codeforces_url}/api/user.status?handle=`
const default_user_name = "Enter";
const default_rating = 800;
const OK_verdict="OK";
let all_problems = [];//initialized by calling get_all_problems on clicking POTD
(async () => {
    function get_user_name_div(){
        const header_div = document.querySelector("#header");
        if (!header_div) return;

        const lang_chooser_div = header_div.querySelector(".lang-chooser");
        if (!lang_chooser_div) return;

        const user_name_div = lang_chooser_div.querySelectorAll(":scope > div")[1];
        return user_name_div;
    }
    function get_user_name() {
        const user_name_div = get_user_name_div();
        if (!user_name_div) return default_user_name;

        const user_name_a = user_name_div.querySelectorAll("a")[1];
        if (!user_name_a) return default_user_name;

        return user_name_a.textContent.trim();
    }
    async function make_ui_change(streak) {
        const user_name_div = get_user_name_div();

        const potd_link = document.createElement("a");
        potd_link.textContent = `POTD ${streak}`;
        potd_link.href = "#";

        potd_link.addEventListener("click", async (e) => {
            all_problems=await get_all_problems();
            e.preventDefault();
            const url = await get_potd_url(get_user_name());
            window.location.href = url;
        });

        user_name_div.prepend(document.createTextNode(" | "));
        user_name_div.prepend(potd_link);
    }

    function get_date_ist() {
        const now = new Date();
        const istOffset = 5.5 * 60; // minutes
        const istTime = new Date(now.getTime() + istOffset * 60 * 1000 - now.getTimezoneOffset() * 60 * 1000);
        const year = istTime.getFullYear();
        const month = String(istTime.getMonth() + 1).padStart(2, '0');
        const day = String(istTime.getDate()).padStart(2, '0');
        return `${year}-${month}-${day}`;
    }

    function get_rating(rating) {
        if (!Number.isInteger(rating)) return 0;
        rating=Math.max(rating,default_rating);
        return Math.floor(rating / 100);
    }

    function get_problem_url(contest_id, problem_code) {
        return `${codeforces_url}/contest/${contest_id}/problem/${problem_code}`;
    }


    async function get_all_problems() {
        const key = `${get_date_ist()}_all_problems`;
        const value = GM_getValue(key, -1);
        if (value != -1) {
            console.log("using GM_getValue to get_all_problems");
            return value;
        }

        try {
            const response = await fetch(all_problems_url);
            if (!response.ok) throw new Error("Network response was not ok");
            const data = await response.json();
            const problems = data.result.problems;
            const ans = Array.from({ length: 50 }, () => []);
            for (let problem of problems) {
                const rating = get_rating(problem.rating);
                const contest_id = problem.contestId;
                const problem_code = problem.index;
                const problem_url = get_problem_url(contest_id, problem_code);
                ans[rating].push(problem_url);
            }
            GM_setValue(key, ans);
            return ans;
        } catch (err) {
            console.error("Error fetching problems:", err);
            return [];
        }
    }
    async function get_user_profile(user_name) {

        const url = `${users_url}${user_name}`;
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error("Network response was not ok");

            const data = await response.json();
            if (data.status !== "OK") throw new Error("API returned an error");
            return data.result[0];
        } catch (err) {
            console.error(`Error fetching profile for ${user_name}:`, err);
            return null;
        }
    }
    function get_potd_problems_around_rating(rating) {
        const potd_problems = [];

        for (let delta = -neg_delta; delta <= pos_delta; delta++) {
            const nrating = rating + delta;
            if (all_problems[nrating] && all_problems[nrating].length > 0) {
                potd_problems.push(...all_problems[nrating]);
            }
        }
        return potd_problems;
    }
    async function get_user_status(user_name){
        const url = `${users_status_url}${user_name}`;
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error("Network response was not ok");

            const data = await response.json();
            if (data.status !== "OK") throw new Error("API returned an error");
            return data.result;
        } catch (err) {
            console.error(`Error fetching profile for ${user_name}:`, err);
            return null;
        }
    }
    async function get_solved_problems(user_name){
        const solved = new Set();
        const user_statuses = await get_user_status(user_name);
        for (const sub of user_statuses) {
            if (sub.verdict === OK_verdict) {
                const p = sub.problem;
                const url=get_problem_url(p.contestId, p.index);
                solved.add(url);
            }
        }
        return solved;
    }
    function remove_solved_problems(solved_problems,problems){
        const unsolved_problems =[];
        for(const problem of problems){
            if(solved_problems.has(problem)){
                console.log("removing already solved: ",problem);
                continue;
            }
            unsolved_problems.push(problem);
        }
        return unsolved_problems;
    }
    async function get_potd_url(user_name) {
        const key = `${get_date_ist()}_potd_${user_name}`;
        const value = GM_getValue(key, -1);
        var flattened_problems;
        if(user_name==default_user_name){
            if(value !=-1){
                return value;
            }
            flattened_problems=all_problems.flat();
        }else{
            const solved_problems = await get_solved_problems(user_name);
            if(value !=-1 && !solved_problems.has(value)){
               return value; //if the user has not solved the previous POTD use it
            }
            const user_profile=await get_user_profile(user_name);
            const user_rating=get_rating(user_profile.rating);
            flattened_problems = get_potd_problems_around_rating(user_rating);
            flattened_problems = remove_solved_problems(solved_problems, flattened_problems);
        }
        const random_index = Math.floor(Math.random() * flattened_problems.length);
        GM_setValue(key, flattened_problems[random_index]);
        return flattened_problems[random_index];
    }
    var streak=""//change and implement calculating streak logic
    make_ui_change(streak);
})();