Skip to content

Instantly share code, notes, and snippets.

@louim
Last active January 3, 2026 16:00
Show Gist options
  • Select an option

  • Save louim/def1bf8f76392bb32018c84c25e94afa to your computer and use it in GitHub Desktop.

Select an option

Save louim/def1bf8f76392bb32018c84c25e94afa to your computer and use it in GitHub Desktop.
Are you curious how much time you spend tupleing with colleagues? Go to https://production.tuple.app/profile/usage. Paste script in console. Enjoy!
(async function () {
// ========== CONFIGURE DATE RANGE HERE ==========
const startDate = new Date("2025-01-01");
const endDate = new Date("2025-12-31T23:59:59");
// ===============================================
const timePerPerson = {};
const logs = [];
// Parse time string like "46 min", "1 hr 30 min", "2 hr" to minutes
function parseTimeToMinutes(timeStr) {
let totalMinutes = 0;
const hrMatch = timeStr.match(/(\d+)\s*hr/);
const minMatch = timeStr.match(/(\d+)\s*min/);
if (hrMatch) totalMinutes += parseInt(hrMatch[1]) * 60;
if (minMatch) totalMinutes += parseInt(minMatch[1]);
return totalMinutes;
}
// Format minutes to readable string
function formatMinutes(minutes) {
const hrs = Math.floor(minutes / 60);
const mins = minutes % 60;
if (hrs > 0 && mins > 0) return `${hrs} hr ${mins} min`;
if (hrs > 0) return `${hrs} hr`;
return `${mins} min`;
}
// Check if date is within range
function isInDateRange(dateStr) {
const date = new Date(dateStr);
return date >= startDate && date <= endDate;
}
// Extract data from a page's HTML
function extractFromPage(doc) {
const callItems = doc.querySelectorAll("[data-call-item]");
let callsInRange = 0;
let callsOutOfRange = 0;
callItems.forEach((call) => {
const callDate = call.getAttribute("data-date");
if (!isInDateRange(callDate)) {
callsOutOfRange++;
return;
}
callsInRange++;
const popup = call.querySelector("div.space-y-2");
if (!popup) return;
const participantRows = popup.querySelectorAll(":scope > div");
participantRows.forEach((row) => {
const nameEl = row.querySelector("p.font-medium");
const timeEl = row.querySelector("p.ml-auto");
if (nameEl && timeEl) {
const name = nameEl.textContent.trim();
const time = timeEl.textContent.trim();
const minutes = parseTimeToMinutes(time);
if (name && minutes > 0) {
timePerPerson[name] = (timePerPerson[name] || 0) + minutes;
}
}
});
});
return { callsInRange, callsOutOfRange };
}
// Get total number of pages from pagination
function getLastPage(doc) {
const lastLink = doc.querySelector(".pagination .last a");
if (lastLink) {
const match = lastLink.href.match(/page=(\d+)/);
return match ? parseInt(match[1]) : 1;
}
const pageLinks = doc.querySelectorAll(".pagination .page a");
let maxPage = 1;
pageLinks.forEach((link) => {
const match = link.href.match(/page=(\d+)/);
if (match) maxPage = Math.max(maxPage, parseInt(match[1]));
});
return maxPage;
}
// Fetch and parse a page
async function fetchPage(pageNum) {
const url = `/profile/usage?page=${pageNum}`;
logs.push(`Fetching page ${pageNum}...`);
const response = await fetch(url);
const html = await response.text();
const parser = new DOMParser();
return parser.parseFromString(html, "text/html");
}
try {
logs.push(
`Date range: ${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()}`
);
let totalCallsInRange = 0;
let totalCallsOutOfRange = 0;
let stats = extractFromPage(document);
totalCallsInRange += stats.callsInRange;
totalCallsOutOfRange += stats.callsOutOfRange;
const totalPages = getLastPage(document);
logs.push(`Found ${totalPages} total pages`);
for (let page = 2; page <= totalPages; page++) {
const doc = await fetchPage(page);
stats = extractFromPage(doc);
totalCallsInRange += stats.callsInRange;
totalCallsOutOfRange += stats.callsOutOfRange;
}
const sorted = Object.entries(timePerPerson).sort((a, b) => b[1] - a[1]);
logs.push("");
logs.push("========================================");
logs.push("TOTAL TUPLE TIME PER PERSON");
logs.push(
`${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()}`
);
logs.push(
`(${totalCallsInRange} calls in range, ${totalCallsOutOfRange} excluded)`
);
logs.push("========================================");
logs.push("");
sorted.forEach(([name, minutes]) => {
logs.push(`${name}: ${formatMinutes(minutes)}`);
});
logs.push("");
logs.push("========================================");
console.log(logs.join("\n"));
return Object.fromEntries(
sorted.map(([name, mins]) => [name, formatMinutes(mins)])
);
} catch (error) {
console.error("Error:", error);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment