// ==UserScript==
// @name DeepCo Statistics
// @namespace https://deepco.app/
// @version 2025-07-17
// @description Track blocks mined and RC yield rate
// @author Corns
// @match https://deepco.app/dig
// @icon https://www.google.com/s2/favicons?sz=64&domain=deepco.app
// @license MIT
// @grant GM.setValue
// @grant GM.getValue
// ==/UserScript==
(async function() {
'use strict';
// Load existing logs or initialize with header row
let db = await GM.getValue('nudgeLogs', [['Timestamp', 'TileCount', 'RC', 'Level', 'DC', 'Players', 'DCIncome', 'Processing Rating']]);
new MutationObserver((mutation, observer) => {
const deptScaling = document.querySelector('.department-scaling');
if (deptScaling) {
observer.disconnect();
console.log("[DeepCo Stats] Started");
waitForTargetAndObserve()
}
}).observe(document.body, { childList: true, subtree: true });
function waitForTargetAndObserve() {
const frame = document.getElementById('flash-messages');
const footer = document.querySelector('[class^="grid-footer"]');
createPanel(footer);
const observer = new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
const text = node.textContent.trim();
if (text.startsWith('You got paid!')) {
logStats(text);
}
}
}
}).observe(frame, { childList: true, subtree: false });
console.log('[DeepCo Stats] Observer attached to parent frame.');
}
function createPanel(parentElement) {
const btnContainer = document.createElement("div");
// Export button
const exportBtn = document.createElement("button");
exportBtn.textContent = "Export Player Stats";
exportBtn.style.marginRight = "5px";
exportBtn.addEventListener("click", exportStats);
btnContainer.appendChild(exportBtn);
// Reset button
const resetBtn = document.createElement("button");
resetBtn.textContent = "Reset Stats";
resetBtn.addEventListener("click", resetStats);
btnContainer.appendChild(resetBtn);
parentElement.appendChild(btnContainer);
}
async function logStats(flashMessage) {
const timestamp = getTimestampForSheets();
const tileCount = getTileCount();
const rc = getRCCount();
const level = getLevel();
const dc = getDCCount();
const players = countPlayersInLevel();
const dcIncome = getDCIncome(flashMessage);
const rating = getProcessingRating();
db.push([timestamp, tileCount, rc, level, dc, players, dcIncome, rating]);
await GM.setValue('nudgeLogs', db);
// console.log(`[DeepCo Stats] ${timestamp}: ${tileCount}, ${rc}, ${level}, ${dc}, ${players}, ${dcIncome}`);
}
async function exportStats() {
const logs = await GM.getValue('nudgeLogs', []);
if (logs.length === 0) {
console.log('[DeepCo Stats] No logs to save.');
return;
}
// Wrap values with commas in quotes
const csvContent = logs.map(row =>
row.map(value =>
/,/.test(value) ? `"${value}"` : value
).join(',')
).join('\n');
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `nudge_log_${new Date().toISOString().replace(/[:.]/g, "_")}.csv`;
link.click();
URL.revokeObjectURL(url);
console.log('[CSV Export] Downloaded CSV with', logs.length, 'rows.');
}
async function resetStats() {
if (confirm('Are you sure you want to clear player stats?')) {
db = [['Timestamp', 'TileCount', 'RC', 'Level', 'DC', 'Players', 'DCIncome', 'Processing Rating']];
await GM.setValue('nudgeLogs', db);
alert('Tile logs have been cleared.');
}
}
function getTimestampForSheets() {
const d = new Date();
const pad = (n, z = 2) => String(n).padStart(z, '0');
const year = d.getFullYear();
const month = pad(d.getMonth() + 1);
const day = pad(d.getDate());
const hours = pad(d.getHours());
const minutes = pad(d.getMinutes());
const seconds = pad(d.getSeconds());
const milliseconds = pad(d.getMilliseconds(), 3);
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
}
function getTileCount() {
const frame = document.getElementById('tiles-defeated-badge');
if (!frame) {
console.log('[DeepCo Stats] turbo-frame element not found.');
return;
}
// Try strong with class nudge-animation first
let target = frame.querySelector('strong.nudge-animation');
// If not found, try strong inside span.tile-progression with style containing 'inline-block'
if (!target) {
target = frame.querySelector('span.tile-progression strong[style*="inline-block"]');
}
if (!target) {
console.log('[DeepCo Stats] Target element not found inside turbo-frame.');
return;
}
let value = target.textContent.trim();
// Remove commas
value = value.replace(/,/g, '');
// Remove trailing slash
value = value.replace(/\/$/, '');
return value;
}
function getRCCount() {
// Find RC value
const recursionSpan = document.getElementById('recursion-header');
let rc = 0;
if (recursionSpan) {
const a = recursionSpan.querySelector('a');
if (a) {
// Extract RC value using regex, e.g. [+15 RC]
const rcMatch = a.textContent.match(/\[\+([\d.]+)\s*RC\]/);
if (rcMatch) {
rc = parseFloat(rcMatch[1]);
}
}
}
return rc;
}
function getDCCount() {
// Find the UPGRADES link with badge
const upgradesLink = Array.from(document.querySelectorAll('a')).find(a =>
a.textContent.includes('UPGRADES'));
// Parse current DC value from its text
const match = upgradesLink.textContent.match(/\[DC\]\s*([\d,.]+)/);
const currentDC = match ? parseFloat(match[1].replace(/,/g, '')) : 0;
return currentDC;
}
// includes the current player
function countPlayersInLevel() {
const deptScaling = document.querySelector('.department-scaling');
const deptOperators = deptScaling ? deptScaling.querySelectorAll('a').length : 0;
return deptOperators;
}
function getDCIncome(flashMessage) {
// parse flashMessage which has DC income amount
const match = flashMessage.match(/You got paid!\s*([\d,]+(?:\.\d+)?)/);
const amount = parseFloat(match[1].replace(/,/g, ''));
return amount;
}
function getProcessingRating() {
const rating = Array.from(document.querySelectorAll('.stat-item'))
.map(item => {
const label = item.querySelector('.stat-label');
const value = item.querySelector('.stat-value');
return label && label.textContent.trim() === 'Processing Rating:' && value
? parseFloat(value.textContent.trim())
: null;
})
.filter(Boolean)[0] || null;
return rating;
}
function getLevel() {
// Find the department-stats element
const deptStats = document.querySelector('p.department-stats');
let dcValue = 0; // default if not found
if (deptStats) {
const text = deptStats.textContent.trim();
// Match DC followed by optional + and digits, e.g., DC4A or DC+4
const match = text.match(/DC\+?(\d+)/i);
if (match) {
dcValue = parseInt(match[1], 10);
}
}
return dcValue;
}
})();