Codeforces Blocker
A collection of 2 actions bundled together.

Code Snippet

1// ==========================================
2// ACTION GROUP (2 Actions)
3// ==========================================
4
5// ------------------------------------------
6// Action: Codeforces Blocker / UI
7// Description: N/A
8// ------------------------------------------
9
10// ==============================================
11// 🧩 CODEFORCES GATE — Setup
12// Run this to configure (or change) your daily gate.
13// Settings are read by the background "CF Gate Check" action.
14// ==============================================
15
16async function setup() {
17    const existing = (await storage.get("cf_gate_settings")) || {};
18
19    const username = await input.prompt({
20        title: "Codeforces username",
21        placeholder: "e.g. tourist",
22        defaultValue: existing.username || "",
23        position: "center"
24    });
25    if (!username || !username.trim()) {
26        await notify.show("Setup cancelled — no username entered.", "warning");
27        return;
28    }
29
30    const targetStr = await input.prompt({
31        title: "How many problems must you solve per day?",
32        placeholder: "e.g. 3",
33        defaultValue: existing.target ? String(existing.target) : "3",
34        position: "center"
35    });
36    const target = parseInt(targetStr, 10);
37    if (!target || target < 1) {
38        await notify.show("Setup cancelled — enter a valid number.", "warning");
39        return;
40    }
41
42    const modeChoice = await input.prompt({
43        type: "select",
44        title: "Until you hit your goal, should this...",
45        choices: ["Block browsing", "Just remind me"],
46        position:"center"
47    });
48    if (!modeChoice) {
49        await notify.show("Setup cancelled.", "warning");
50        return;
51    }
52
53    const settings = {
54        username: username.trim(),
55        target,
56        mode: modeChoice === "Block browsing" ? "block" : "remind"
57    };
58
59    await storage.set("cf_gate_settings", settings);
60
61    await notify.show(
62        `Codeforces Gate saved: ${target} problem(s)/day for "${settings.username}" — ${settings.mode === "block" ? "blocking" : "reminder"} mode.`,
63        "success"
64    );
65}
66
67await setup();
68
69
70// ------------------------------------------
71// Action: Codeforces Blocker / backend
72// Description: N/A
73// ------------------------------------------
74
75// ==============================================
76// 🧩 CODEFORCES GATE — Check (Background)
77// Configure this Action's trigger (in tee's action settings) to run
78// "on page load". Reads settings from cf_gate_settings (set via the
79// CF Gate Setup action) and either blocks the current page or shows
80// a reminder toast until today's target is met.
81// ==============================================
82
83const BLOCK_ID = "cf-gate-block-overlay";
84
85function startOfTodayEpochSeconds() {
86    const now = new Date();
87    const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
88    return Math.floor(start.getTime() / 1000);
89}
90
91function todayKey() {
92    return new Date().toISOString().split("T")[0];
93}
94
95// Counts DISTINCT problems solved today (an OK verdict), not raw
96// submission count, so resubmitting an already-solved problem doesn't
97// inflate the number.
98async function getSolvedTodayCount(username) {
99    const res = await http.fetch(
100        `https://codeforces.com/api/user.status?handle=${encodeURIComponent(username)}&from=1&count=200`
101    );
102
103    if (!res || res.status !== "OK" || !Array.isArray(res.result)) {
104        throw new Error("Couldn't reach Codeforces API or invalid username.");
105    }
106
107    const cutoff = startOfTodayEpochSeconds();
108    const solvedKeys = new Set();
109
110    // The API returns submissions newest-first, so we can stop as soon
111    // as we hit one from before today.
112    for (const sub of res.result) {
113        if (sub.creationTimeSeconds < cutoff) break;
114        if (sub.verdict === "OK") {
115            const contestPart = sub.problem.contestId !== undefined ? sub.problem.contestId : "gym";
116            solvedKeys.add(`${contestPart}-${sub.problem.index}`);
117        }
118    }
119
120    return solvedKeys.size;
121}
122
123function buildOverlayHtml(solvedCount, target, username) {
124    const remaining = Math.max(0, target - solvedCount);
125    return `
126        <div id="${BLOCK_ID}" style="
127            position: fixed; inset: 0; z-index: 2147483647;
128            background: #0B0B0F; color: #FFF;
129            display: flex; flex-direction: column; align-items: center; justify-content: center;
130            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
131            text-align: center; padding: 24px;
132        ">
133            <div style="font-size: 48px; margin-bottom: 12px;">🧩</div>
134            <div style="font-size: 22px; font-weight: 700; margin-bottom: 8px;">
135                Solve ${remaining} more Codeforces problem${remaining === 1 ? "" : "s"} first
136            </div>
137            <div style="font-size: 15px; color: #AAA; margin-bottom: 24px;">
138                ${solvedCount}/${target} solved today — as ${username}
139            </div>
140            <div style="display:flex; gap: 12px;">
141                <a href="https://codeforces.com/problemset" target="_blank" style="
142                    background:#C8FF00; color:#000; font-weight:700; padding: 10px 20px;
143                    border-radius: 8px; text-decoration:none; font-size: 14px;
144                ">Open Codeforces</a>
145            </div>
146            <div style="font-size: 12px; color: #666; margin-top: 24px;">
147                This page unblocks automatically once you've hit today's target.
148            </div>
149        </div>
150    `;
151}
152
153async function showBlockOverlay(solvedCount, target, username) {
154    // Remove any stale overlay first so re-checks don't stack duplicates.
155    await page.mutate([{ selector: `#${BLOCK_ID}`, action: "remove" }]);
156
157    await page.mutate(
158        [
159            { selector: "body", action: "append", value: buildOverlayHtml(solvedCount, target, username) },
160            { selector: "#cf-recheck-btn", action: "listen", event: "click", id: "cf_recheck" }
161        ],
162        async (eventData) => {
163            if (eventData.id === "cf_recheck") {
164                await checkGate(); // re-runs the whole check; re-blocks if still short
165            }
166        }
167    );
168}
169
170async function checkGate() {
171    const settings = await storage.get("cf_gate_settings");
172    if (!settings || !settings.username || !settings.target) return; // not configured yet
173
174    // Never block Codeforces itself — you need it to solve the problems.
175    const currentDomain = new URL(selection.pageUrl).hostname;
176    if (currentDomain.includes("codeforces.com")) {
177        await page.mutate([{ selector: `#${BLOCK_ID}`, action: "remove" }]);
178        return;
179    }
180
181    let solvedCount;
182    try {
183        solvedCount = await getSolvedTodayCount(settings.username);
184    } catch (e) {
185        console.error("CF Gate: failed to fetch submissions", e);
186        return; // fail open — don't block on API/network errors
187    }
188
189    await storage.set("cf_gate_state", { date: todayKey(), solvedToday: solvedCount });
190
191    const remaining = Math.max(0, settings.target - solvedCount);
192
193    if (remaining === 0) {
194        await page.mutate([{ selector: `#${BLOCK_ID}`, action: "remove" }]);
195        return;
196    }
197
198    if (settings.mode === "remind") {
199        await notify.show(
200            `🧩 Codeforces: ${solvedCount}/${settings.target} solved today. ${remaining} to go!`,
201            "warning"
202        );
203        return;
204    }
205
206    await showBlockOverlay(solvedCount, settings.target, settings.username);
207}
208
209await checkGate();
Submitted on 7/28/2026