Claude Billing Tracker v1.1.1

← Back to User Scripts

Description for Billing Tracker v1.1.1.

Script Content

// ==UserScript==
// @name         Kagi: Billing Progress Tracker
// @namespace    https://github.com/tjhleeds/user-scripts/
// @version      1.1.1
// @description  Add progress percentages to Kagi billing page
// @author       tjhleeds
// @match        https://kagi.com/settings/billing
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const SEARCHES_PER_MONTH = 300;
    const SEARCHES_PER_YEAR = SEARCHES_PER_MONTH * 12;
    const MAX_RETRIES = 3;
    const RETRY_DELAY_MS = 1000;

    function addProgressInfo(retryCount = 0) {
        console.log('addProgressInfo called, attempt:', retryCount + 1);
        
        // Find the renewal date element
        const renewalElement = findElementByText("Next renewal is");
        if (!renewalElement) {
            if (retryCount < MAX_RETRIES) {
                console.log('Renewal element not found, retrying...');
                setTimeout(() => addProgressInfo(retryCount + 1), RETRY_DELAY_MS);
            } else {
                console.log(`Renewal element not found after ${MAX_RETRIES} attempts, giving up`);
            }
            return;
        }

        console.log('Found renewal element:', renewalElement);

        // Extract renewal date
        const renewalText = renewalElement.textContent;
        const renewalMatch = renewalText.match(/Next renewal is (\d{4}-\d{2}-\d{2})/);
        if (!renewalMatch) {
            console.log('Could not parse renewal date from:', renewalText);
            return;
        }

        console.log('Parsed renewal date:', renewalMatch[1]);

        const renewalDate = new Date(renewalMatch[1]);
        const currentDate = new Date();
        const yearAgo = new Date(renewalDate);
        yearAgo.setFullYear(yearAgo.getFullYear() - 1);

        // Calculate time-based progress
        const totalYearMs = renewalDate.getTime() - yearAgo.getTime();
        const elapsedYearMs = currentDate.getTime() - yearAgo.getTime();
        const yearProgress = Math.min(100, Math.max(0, (elapsedYearMs / totalYearMs) * 100));
        const elapsedMonths = (elapsedYearMs / totalYearMs) * 12;

        console.log('Year progress calculated:', yearProgress.toFixed(1) + '%');

        // Find search count
        const searchCountElement = document.querySelector('.billing_box_count_num_1');
        if (!searchCountElement) {
            console.log('Search count element not found');
            return;
        }

        console.log('Found search count element:', searchCountElement);

        const searchCount = parseInt(searchCountElement.textContent);
        const searchProgress = Math.min(100, (searchCount / SEARCHES_PER_YEAR) * 100);

        console.log('Search count:', searchCount, 'Search progress:', searchProgress.toFixed(1) + '%');

        // Calculate search usage rate (searches per month vs expected)
        const expectedSearches = elapsedMonths * SEARCHES_PER_MONTH;
        const searchRate = expectedSearches > 0 ? (searchCount / expectedSearches) * 100 : 0;

        console.log('Search rate:', searchRate.toFixed(1) + '%');

        // Create progress info elements
        const progressDiv = document.createElement('div');
        progressDiv.style.cssText = `
            border: 1px solid rgb(163 158 52);
            background-color: rgb(57 58 17);
            padding: 12px;
            margin-top: 8px;
            border-radius: 6px;
            color: rgb(204 204 204);
            font-size: 14px;
        `;
        progressDiv.innerHTML = `
            
Year progress: ${yearProgress.toFixed(1)}%
Search progress: ${searchProgress.toFixed(1)}% (${searchCount}/${SEARCHES_PER_YEAR})
Search rate: ${searchRate.toFixed(1)}% of expected usage
`; // Insert after the renewal date element renewalElement.parentNode.insertBefore(progressDiv, renewalElement.nextSibling); console.log('Progress info added to page'); } // Helper function since :contains() doesn't work in querySelector function findElementByText(text) { const elements = document.querySelectorAll('span, div'); console.log('Searching through', elements.length, 'span and div elements for text:', text); for (let element of elements) { if (element.textContent.includes(text) && element.textContent.trim().startsWith(text)) { console.log('Found exact match element:', element); return element; } } console.log('No element found containing text:', text); return null; } // Wait for page to load and try to add progress info console.log('Script loaded, document ready state:', document.readyState); if (document.readyState === 'loading') { console.log('Document still loading, waiting for DOMContentLoaded'); document.addEventListener('DOMContentLoaded', addProgressInfo); } else { console.log('Document already loaded, calling addProgressInfo immediately'); addProgressInfo(); } })();