See your friends' last online time directly on the codeforces.com/friends page — with a green dot for anyone active in the last 5 minutes.
MOTIVATION: Seeing my friends online motivates me to open a problem and start coding.
Preview:
- (green dot)Online now — active within 5 minutes
- ⬜ 3 hour(s) ago — last seen time
- ⬜ 2 day(s) ago
How to Install
- Install Tampermonkey(Extension) → Chrome | Firefox
- Click the Tampermonkey icon → Create new script
- Delete everything and paste the code below
- Press Ctrl+S to save
- Visit codeforces.com/friends
Code
// ==UserScript==
// @name Codeforces Friends Online Status (Final Stable)
// @namespace cf-friends-status
// @version 2.3
// @description Shows last online status of Codeforces friends with online indicator
// @match https://mirror.codeforces.com/friends
// @grant GM_xmlhttpRequest
// @connect codeforces.com
// @connect api.codeforces.com
// ==/UserScript==
(function () {
const ONLINE_THRESHOLD = 5 * 60; // 5 minutes = considered "online now"
function timeText(lastOnline) {
let diff = Math.floor(Date.now() / 1000) - lastOnline;
let min = Math.floor(diff / 60);
let hr = Math.floor(min / 60);
let day = Math.floor(hr / 24);
if (diff < ONLINE_THRESHOLD) return "Online now";
if (day > 0) return `${day} day(s) ago`;
if (hr > 0) return `${hr} hour(s) ago`;
return `${min} min(s) ago`;
}
function makeStatusCell(lastOnlineSeconds) {
let diff = Math.floor(Date.now() / 1000) - lastOnlineSeconds;
let isOnline = diff < ONLINE_THRESHOLD;
let wrapper = document.createElement('span');
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.gap = '6px';
if (isOnline) {
// Pulsing green dot
let dot = document.createElement('span');
dot.style.cssText = `
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background: #22c55e;
flex-shrink: 0;
box-shadow: 0 0 0 0 rgba(34,197,94,0.6);
animation: cf-pulse 1.5s infinite;
`;
wrapper.appendChild(dot);
}
let label = document.createElement('span');
label.innerText = timeText(lastOnlineSeconds);
label.style.color = isOnline ? '#16a34a' : '#555';
label.style.fontWeight = isOnline ? 'bold' : 'normal';
wrapper.appendChild(label);
return wrapper;
}
// Inject pulse animation once
function injectStyle() {
if (document.getElementById('cf-online-style')) return;
let style = document.createElement('style');
style.id = 'cf-online-style';
style.textContent = `
@keyframes cf-pulse {
0% { box-shadow: 0 0 0 0 rgba(34,197,94,0.6); }
70% { box-shadow: 0 0 0 7px rgba(34,197,94,0); }
100% { box-shadow: 0 0 0 0 rgba(34,197,94,0); }
}
`;
document.head.appendChild(style);
}
window.addEventListener('load', function () {
console.log("CF Friends script started");
injectStyle();
// Pick the largest table that has profile links = main friends list
let allTables = [...document.querySelectorAll('table')]
.filter(t => t.querySelector('a[href*="/profile/"]'));
if (allTables.length === 0) {
console.log(" No tables with profile links found");
return;
}
let table = allTables.reduce((biggest, t) =>
t.rows.length > biggest.rows.length ? t : biggest
);
console.log(" Targeting table with", table.rows.length, "rows");
let rows = table.rows;
if (rows.length <= 1) {
console.log(" No friend rows found");
return;
}
// Header cell
let headCell = rows[0].insertCell(-1);
headCell.innerText = "Last Online";
headCell.style.fontWeight = "bold";
let handles = [];
for (let i = 1; i < rows.length; i++) {
let row = rows[i];
let profileLink = row.querySelector('a[href*="/profile/"]');
let handle = profileLink
? profileLink.href.split('/profile/')[1].trim()
: null;
if (!handle) continue;
handles.push({ handle, rowIndex: i });
let cell = row.insertCell(-1);
cell.innerText = "Loading...";
cell.style.color = "#aaa";
cell.style.fontSize = "0.9em";
}
console.log("Handles found:", handles.map(h => h.handle));
if (handles.length === 0) return;
const BATCH = 500;
for (let b = 0; b < handles.length; b += BATCH) {
let batch = handles.slice(b, b + BATCH);
let url = "https://mirror.codeforces.com/api/user.info?handles="
+ batch.map(h => h.handle).join(";");
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function (res) {
try {
let data = JSON.parse(res.responseText);
if (data.status !== "OK") {
console.log(" API error:", data.comment);
return;
}
let map = {};
data.result.forEach(user => {
map[user.handle.toLowerCase()] = user.lastOnlineTimeSeconds;
});
batch.forEach(({ handle, rowIndex }) => {
let ts = map[handle.toLowerCase()];
let row = rows[rowIndex];
let cell = row?.cells[row.cells.length - 1];
if (cell) {
cell.innerText = ''; // clear "Loading..."
if (ts) {
cell.appendChild(makeStatusCell(ts));
} else {
cell.innerText = "N/A";
cell.style.color = "#aaa";
}
}
});
console.log(" Updated", batch.length, "friends");
} catch (e) {
console.log(" Parse error:", e);
}
},
onerror: function (err) {
console.log(" Request failed:", err);
}
});
}
});
})();
How it works
- Uses the official Codeforces API (user.info) — one batch call for all friends, no scraping
- Picks the correct friends table automatically
- Matches results by handle name, not index — so no mismatches
- Active within last 5 minutes → (green dot)Online now







