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