您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fixed chart selector to handle dynamic chart container IDs, making it work on date range changes.
- // ==UserScript==
- // @name Cursor Dashboard Enhancer
- // @namespace http://tampermonkey.net/
- // @version 1.0
- // @description Fixed chart selector to handle dynamic chart container IDs, making it work on date range changes.
- // @author Gemini 2.5 Pro, NoahBPeterson
- // @match https://www.cursor.com/dashboard
- // @match https://cursor.com/dashboard
- // @grant none
- // @run-at document-idle
- // @icon https://www.cursor.com/favicon-48x48.png
- // @license MIT
- // ==/UserScript==
- (function() { // Main Tampermonkey IIFE START
- 'use_strict';
- let injectionDone = false;
- // This is the key fix: Use a generic attribute selector.
- const CHART_HOST_SELECTOR = 'div[data-highcharts-chart]';
- const DATE_RANGE_BUTTON_CONTAINER_SELECTOR = '.min-w-\\[180px\\] .flex.items-center.gap-2';
- const DATE_RANGE_BUTTON_SELECTOR = 'button';
- const METRIC_CARDS_PARENT_SELECTOR = '.mb-4.grid.grid-cols-2';
- const METRIC_CARD_SELECTOR = 'div.flex.cursor-pointer';
- const DELAY_AFTER_MOUSEOVER_FOR_POLL_MS = 50;
- let mainChartContentObserver = null;
- let initialElementsObserver = null;
- let eventTriggerDebounceTimer = null;
- let handlePotentialChartChangeGlobal;
- let setupDynamicListenersGlobal;
- let fetchInvoiceDataAndApply;
- let injectAndInitializeGlobal;
- let triggerFakeMouseInteraction;
- const codeToInject = function() {
- // Use the same generic selector inside the injected code.
- const chartContainerSelector_Injected = 'div[data-highcharts-chart]';
- const pathSelector = 'path.highcharts-graph';
- const POLLING_INTERVAL_MS = 75;
- let persistentPollTimer = null;
- const HC = window._Highcharts || window.Highcharts;
- function getUTCDateKey_v1733(timestamp) {
- const date = new Date(timestamp);
- const year = date.getUTCFullYear();
- const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
- const day = date.getUTCDate().toString().padStart(2, '0');
- return `${year}-${month}-${day}`;
- }
- function parseMonthDayCategory_v1733(categoryString, referenceYear) {
- if (typeof categoryString !== 'string') return null;
- const dateMatch = categoryString.match(/([a-zA-Z]+)\s+(\d+)/i);
- if (dateMatch) {
- const monthNames = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
- const monthIndex = monthNames.indexOf(dateMatch[1].toLowerCase());
- if (monthIndex !== -1) {
- const day = parseInt(dateMatch[2], 10);
- if (!isNaN(day) && day >= 1 && day <= 31) {
- return { year: referenceYear, month: monthIndex, day: day };
- }
- }
- }
- return null;
- }
- function buildDateToXValueMap_v1733(chart, referenceYearForCategories) {
- const dateToXMap = new Map();
- let earliestDateFound = null;
- let latestDateFound = null;
- if (!chart) { console.warn("MapBuilder_v1733: Chart object is null."); return { map: dateToXMap, firstDate: null, lastDate: null }; }
- if (!chart.series || chart.series.length === 0) { console.warn("MapBuilder_v1733: Chart has no series."); return { map: dateToXMap, firstDate: null, lastDate: null }; }
- if (!chart.xAxis || !chart.xAxis[0]) { console.warn("MapBuilder_v1733: Chart has no xAxis[0]."); return { map: dateToXMap, firstDate: null, lastDate: null }; }
- const existingSeries = chart.series.find(s => s.visible && s.data && s.data.length > 0 && s.xAxis);
- if (!existingSeries) { console.warn("MapBuilder_v1733: No suitable existing series found."); return { map: dateToXMap, firstDate: null, lastDate: null }; }
- const xAxis = existingSeries.xAxis;
- existingSeries.data.forEach((point) => {
- if (!point || typeof point.x === 'undefined') return;
- let dateKeyToStore;
- let displayCategory = typeof point.category === 'string' ? point.category : (xAxis.categories && xAxis.categories[point.x]);
- let pointDateForMinMax = null;
- if (xAxis.options.type === 'datetime') {
- pointDateForMinMax = new Date(point.x);
- dateKeyToStore = getUTCDateKey_v1733(point.x);
- } else if (typeof displayCategory === 'string') {
- const parsedCatDate = parseMonthDayCategory_v1733(displayCategory, referenceYearForCategories);
- if (parsedCatDate) {
- pointDateForMinMax = new Date(Date.UTC(parsedCatDate.year, parsedCatDate.month, parsedCatDate.day));
- dateKeyToStore = getUTCDateKey_v1733(pointDateForMinMax.getTime());
- } else if (displayCategory.match(/^\d{4}-\d{2}-\d{2}$/)) {
- dateKeyToStore = displayCategory;
- const parts = displayCategory.split('-');
- if (parts.length === 3) pointDateForMinMax = new Date(Date.UTC(parseInt(parts[0],10), parseInt(parts[1],10)-1, parseInt(parts[2],10)));
- }
- } else if (Array.isArray(xAxis.categories) && xAxis.categories[point.x]) {
- displayCategory = xAxis.categories[point.x];
- const parsedCatDate = parseMonthDayCategory_v1733(displayCategory, referenceYearForCategories);
- if (parsedCatDate) {
- pointDateForMinMax = new Date(Date.UTC(parsedCatDate.year, parsedCatDate.month, parsedCatDate.day));
- dateKeyToStore = getUTCDateKey_v1733(pointDateForMinMax.getTime());
- } else if (typeof displayCategory === 'string' && displayCategory.match(/^\d{4}-\d{2}-\d{2}$/)) {
- dateKeyToStore = displayCategory;
- const parts = displayCategory.split('-');
- if (parts.length === 3) pointDateForMinMax = new Date(Date.UTC(parseInt(parts[0],10), parseInt(parts[1],10)-1, parseInt(parts[2],10)));
- }
- }
- if (pointDateForMinMax && !isNaN(pointDateForMinMax.getTime())) {
- if (!earliestDateFound || pointDateForMinMax.getTime() < earliestDateFound.getTime()) {
- earliestDateFound = new Date(pointDateForMinMax.getTime());
- }
- if (!latestDateFound || pointDateForMinMax.getTime() > latestDateFound.getTime()) {
- latestDateFound = new Date(pointDateForMinMax.getTime());
- }
- }
- if (dateKeyToStore && !dateToXMap.has(dateKeyToStore)) {
- dateToXMap.set(dateKeyToStore, point.x);
- }
- });
- return { map: dateToXMap, firstDate: earliestDateFound, lastDate: latestDateFound };
- }
- window.buildDateToXValueMap_v1733 = buildDateToXValueMap_v1733;
- function parsePathD(d){const p=[];if(!d)return p;const c=d.match(/[MLHVCSQTAZ][^MLHVCSQTAZ]*/ig);if(!c)return p;let x=0,y=0;c.forEach(s=>{const t=s[0],a=s.substring(1).trim();if(t.toUpperCase()!=="Z"&&!a&&s.length>1)return;const e=a?a.split(/[\s,]+/).map(parseFloat):[];if(e.some(isNaN))return;if(t==="M"||t==="m")for(let n=0;n<e.length;n+=2){if(n+1>=e.length)break;let o=e[n],r=e[n+1];t==="m"&&(o+=x,r+=y),p.push({x:o,y:r}),x=o,y=r}else if(t==="L"||t==="l")for(let n=0;n<e.length;n+=2){if(n+1>=e.length)break;let o=e[n],r=e[n+1];t==="l"&&(o+=x,r+=y),p.push({x:o,y:r}),x=o,y=r}else if(t==="H"||t==="h")for(let n=0;n<e.length;n++){let o=e[n];t==="h"&&(o+=x),p.push({x:o,y:y}),x=o}else if(t==="V"||t==="v")for(let n=0;n<e.length;n++){let o=e[n];t==="v"&&(o+=y),p.push({x:x,y:o}),y=o}});return p};
- function getControlPoints(p0,p1,p2,p3){const x1=p1.x+(p2.x-p0.x)/6,y1=p1.y+(p2.y-p0.y)/6,x2=p2.x-(p3.x-p1.x)/6,y2=p2.y-(p3.y-p1.y)/6;return[{x:x1,y:y1},{x:x2,y:y2}]};
- function pointsToSplinePath(p){if(p.length<2)return"";let d=`M ${p[0].x.toFixed(3)} ${p[0].y.toFixed(3)}`;if(p.length===2)return d+=` L ${p[1].x.toFixed(3)} ${p[1].y.toFixed(3)}`;for(let i=0;i<p.length-1;i++){const p0=p[i===0?0:i-1],p1=p[i],p2=p[i+1],p3=p[i+2<p.length?i+2:p.length-1],cps=getControlPoints(p0,p1,p2,p3);d+=` C ${cps[0].x.toFixed(3)} ${cps[0].y.toFixed(3)}, ${cps[1].x.toFixed(3)} ${cps[1].y.toFixed(3)}, ${p2.x.toFixed(3)} ${p2.y.toFixed(3)}`}return d};
- function smoothAllChartPaths() {
- const el = document.querySelector(chartContainerSelector_Injected);
- if (!el || !HC) return;
- el.querySelectorAll(pathSelector).forEach((pEl) => {
- const d = pEl.getAttribute('d');
- if (!d || d.toUpperCase().includes('C') || pEl.dataset.smoothedByV1733 === 'true') return;
- const pts = parsePathD(d);
- if (pts.length < 2) return;
- const nD = pointsToSplinePath(pts);
- if (nD && nD !== d) { pEl.setAttribute('d', nD); pEl.dataset.smoothedByV1733 = 'true';}
- });
- }
- if (persistentPollTimer) clearInterval(persistentPollTimer);
- persistentPollTimer = setInterval(smoothAllChartPaths, POLLING_INTERVAL_MS);
- window.forceChartCheckBySmoother_v1733 = smoothAllChartPaths;
- function ensureSecondaryYAxis_v1733(chart, axisId, axisTitle) {
- let yAxisObj = chart.yAxis.find(axis => axis.options.id === axisId);
- if (!yAxisObj) {
- yAxisObj = chart.addAxis({ id: axisId, title: { text: axisTitle }, opposite: true, gridLineWidth: 0, min: 0 }, false, true);
- yAxisObj = chart.yAxis.find(axis => axis.options.id === axisId);
- }
- if (!yAxisObj || typeof yAxisObj.index === 'undefined') {
- console.error(`Injected Script (v1.7.33): Y-axis '${axisId}' problem. Index: ${yAxisObj ? yAxisObj.index : 'N/A'}.`);
- return -1;
- }
- return yAxisObj.index;
- }
- window.ensureSecondaryYAxis_v1733 = ensureSecondaryYAxis_v1733;
- window.logInjectedChartAxes_v1733=function(chart,stage){if(chart&&chart.xAxis){chart.xAxis.forEach((axis,i)=>{console.log(` xAxis[${i}]: id='${axis.options.id}', type='${axis.options.type}', index=${axis.index}, categories=${axis.categories?axis.categories.length:"none"}, min=${axis.min}, max=${axis.max}, dataMin=${axis.dataMin}, dataMax=${axis.dataMax}`)})}else{console.log(" No xAxes found.")}if(chart&&chart.yAxis){chart.yAxis.forEach((axis,i)=>{console.log(` yAxis[${i}]: id='${axis.options.id}', title='${axis.options.title?.text}', index=${axis.index}, opposite=${axis.options.opposite}, min=${axis.min}, max=${axis.max}`)})}else{console.log(" No yAxes found.")}};
- function processAndAddDailyUsageSeries_v1733(responseData, seriesConfig, dateToXValueMapParam) {
- if(!HC||!HC.charts)return!1;const chart=HC.charts.find(c=>c&&c.renderTo===document.querySelector(chartContainerSelector_Injected));if(!chart||!chart.xAxis||!chart.xAxis[0])return!1;
- let dailyCountsPerDateKey = {};
- if (responseData && Array.isArray(responseData.usageEvents)) {
- responseData.usageEvents.forEach(event => {
- let tsStr = event.timestamp;
- if (typeof tsStr !== 'string' || tsStr.length === 0) return;
- let tsNum = parseInt(tsStr, 10);
- if (!isNaN(tsNum) && tsNum > 0) {
- const dateKey = getUTCDateKey_v1733(tsNum);
- dailyCountsPerDateKey[dateKey] = (dailyCountsPerDateKey[dateKey] || 0) + 1;
- }
- });
- }
- let transformedDataPoints = [];
- dateToXValueMapParam.forEach((originalX, dateKey_fromMap) => {
- const count = dailyCountsPerDateKey[dateKey_fromMap] || 0;
- transformedDataPoints.push([originalX, count]);
- });
- if (transformedDataPoints.length === 0 && dateToXValueMapParam.size > 0) {
- console.warn(`DailyUsage_v1733: No data points generated for ${seriesConfig.name}, map had ${dateToXValueMapParam.size} entries.`);
- } else if (transformedDataPoints.length === 0 && dateToXValueMapParam.size === 0) {
- console.warn(`DailyUsage_v1733: No data points and map is empty for ${seriesConfig.name}.`);
- return false;
- }
- transformedDataPoints.sort((a,b)=>a[0]-b[0]);
- const seriesOptions={name:seriesConfig.name,data:transformedDataPoints,type:seriesConfig.type||"line",color:seriesConfig.color,yAxis:0,tooltip:{pointFormatter:function(){const pointXDate = (this.series.xAxis.options.type === 'datetime') ? this.x : (this.series.xAxis.categories ? this.series.xAxis.categories[this.x] : this.x); return `<span style="color:${this.color}">●</span> ${this.series.name}: <b>${this.y}</b> events (${typeof pointXDate === 'number' && this.series.xAxis.options.type === 'datetime' ? HC.dateFormat('%b %e', pointXDate) : pointXDate})<br/>`}},marker:seriesConfig.marker||{enabled:transformedDataPoints.length<30,radius:3}};
- let existingSeries=chart.series.find(s=>s.name===seriesConfig.name&&s.yAxis.index===0);existingSeries?existingSeries.update(seriesOptions,!1):chart.addSeries(seriesOptions,!1);return!0;
- };
- window.processAndAddDailyUsageSeries_v1733 = processAndAddDailyUsageSeries_v1733;
- function processAndAddDailyCostSeries_v1733(responseData, seriesConfig, dateToXValueMapParam, yAxisIndexForDollars) {
- if (!HC || !HC.charts) { return false; }
- const chart = HC.charts.find(c => c && c.renderTo === document.querySelector(chartContainerSelector_Injected));
- if (!chart) { return false; }
- if (typeof yAxisIndexForDollars !== 'number' || yAxisIndexForDollars < 0) { console.error(`DailyCost_v1733: Invalid Y-idx (${yAxisIndexForDollars}).`); return false; }
- let dailyPriceCentsPerDateKey = {};
- if (responseData && Array.isArray(responseData.usageEvents)) {
- responseData.usageEvents.forEach(event => {
- let tsStr=event.timestamp,priceCents=event.priceCents;
- if("string"!=typeof tsStr||0===tsStr.length||"number"!=typeof priceCents)return;
- let tsNum=parseInt(tsStr,10);
- if(!isNaN(tsNum)&&tsNum>0){
- const dateKey = getUTCDateKey_v1733(tsNum);
- dailyPriceCentsPerDateKey[dateKey]=(dailyPriceCentsPerDateKey[dateKey]||0)+priceCents;
- }
- });
- }
- let transformedDataPoints = [];
- dateToXValueMapParam.forEach((originalX, dateKey_fromMap) => {
- const costInCents = dailyPriceCentsPerDateKey[dateKey_fromMap] || 0;
- transformedDataPoints.push([originalX, parseFloat((costInCents / 100).toFixed(2))]);
- });
- if (transformedDataPoints.length === 0 && dateToXValueMapParam.size > 0) {
- console.warn(`DailyCost_v1733: No data points generated for ${seriesConfig.name}, map had ${dateToXValueMapParam.size} entries.`);
- } else if (transformedDataPoints.length === 0 && dateToXValueMapParam.size === 0) {
- console.warn(`DailyCost_v1733: No data points and map is empty for ${seriesConfig.name}.`);
- return false;
- }
- transformedDataPoints.sort((a,b)=>a[0]-b[0]);
- const seriesOptions={name:seriesConfig.name,data:transformedDataPoints,type:seriesConfig.type||"line",color:seriesConfig.color,yAxis:yAxisIndexForDollars,tooltip:{pointFormatter:function(){const pointXDate = (this.series.xAxis.options.type === 'datetime') ? this.x : (this.series.xAxis.categories ? this.series.xAxis.categories[this.x] : this.x); return `<span style="color:${this.color}">●</span> ${this.series.name}: <b>$${this.y.toFixed(2)}</b> (${typeof pointXDate === 'number' && this.series.xAxis.options.type === 'datetime' ? HC.dateFormat('%b %e', pointXDate) : pointXDate})<br/>`}},marker:seriesConfig.marker||{enabled:transformedDataPoints.length<30,radius:2}};
- console.log(`Injected Script (v1.7.33): LOGGING DATA for series '${seriesConfig.name}' (points: ${seriesOptions.data.length}, yAxisIndex: ${seriesOptions.yAxis}): First 5:`,JSON.parse(JSON.stringify(seriesOptions.data.slice(0,5))));
- let existingSeries=chart.series.find(s=>s.name===seriesConfig.name&&s.yAxis.index===yAxisIndexForDollars);existingSeries?existingSeries.update(seriesOptions,!1):chart.addSeries(seriesOptions,!1);return true;
- };
- window.processAndAddDailyCostSeries_v1733 = processAndAddDailyCostSeries_v1733;
- function processAndAddPricingSeries_v1733(responseData, dateToXValueMapParam) {
- const HC_local=window._Highcharts||window.Highcharts;if(!HC_local||!responseData||!responseData.pricingDescription||"string"!=typeof responseData.pricingDescription.description)return!1;const pricingString=responseData.pricingDescription.description,chart=HC_local.charts.find(c=>c&&c.renderTo===document.querySelector(chartContainerSelector_Injected));if(!chart||!chart.xAxis||!chart.xAxis[0])return!1;const includedRequestsMatch=pricingString.match(/(\d+)\s+requests\s+per\s+day\s+included/i);let pricingSeriesChanged=!1;if(includedRequestsMatch&&includedRequestsMatch[1]){const includedRequests=parseInt(includedRequestsMatch[1],10),xAxis=chart.xAxis[0],extremes=xAxis.getExtremes();let xMin=xAxis.min,xMax=xAxis.max;if("number"!=typeof xMin||"number"!=typeof xMax||xMin===xMax)if(dateToXValueMapParam&&dateToXValueMapParam.size>0){const mappedXValues=Array.from(dateToXValueMapParam.values()).filter(x=>"number"==typeof x);mappedXValues.length>0?(xMin=Math.min(...mappedXValues),xMax=Math.max(...mappedXValues)):(xMin=extremes.dataMin,xMax=extremes.dataMax)}else xMin=extremes.dataMin,xMax=extremes.dataMax;if("number"!=typeof xMin||"number"!=typeof xMax||xMin===xMax)xMin=0,xMax=dateToXValueMapParam.size>0?dateToXValueMapParam.size-1:chart.xAxis[0].categories?chart.xAxis[0].categories.length-1:10;if("number"==typeof xMin&&"number"==typeof xMax&&xMax>=xMin){const quotaData=[[xMin,includedRequests],[xMax,includedRequests]],seriesName="Daily Included Requests Quota",seriesOptions={name:seriesName,data:quotaData,type:"line",color:"forestgreen",dashStyle:"shortdash",marker:{enabled:!1},zIndex:1,yAxis:0};console.log(`Injected Script (v1.7.33): LOGGING DATA for series '${seriesName}' (points: ${seriesOptions.data.length}, yAxisIndex: ${seriesOptions.yAxis}):`,JSON.parse(JSON.stringify(seriesOptions.data.slice(0,5))));let existingQuotaSeries=chart.series.find(s=>s.name===seriesName&&s.yAxis.index===0);existingQuotaSeries?existingQuotaSeries.update(seriesOptions,!1):chart.addSeries(seriesOptions,!1),pricingSeriesChanged=!0}}return pricingSeriesChanged
- };
- window.processAndAddPricingSeries_v1733 = processAndAddPricingSeries_v1733;
- if(window.smoothCursorChartPollerReadyCallbacks_v1733) {
- window.smoothCursorChartPollerReadyCallbacks_v1733.forEach(cb => cb());
- delete window.smoothCursorChartPollerReadyCallbacks_v1733;
- }
- window.isSmoothCursorChartPollerReady_v1733 = true;
- console.log('Injected Script (v1.7.33): Initialized. HC object available:', !!HC);
- };
- async function fetchAndProcessMonthData(month, year, contextChart, isPrimaryCall) {
- const requestPayload = { month, year, includeUsageEvents: true };
- try {
- const response = await fetch('/api/dashboard/get-monthly-invoice', {
- method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': '*/*', },
- body: JSON.stringify(requestPayload)
- });
- if (!response.ok) { console.error(`TM Script (v1.7.33): HTTP error ${response.status} for ${year}-${month+1}`); return null; }
- const data = await response.json();
- data.requestContext = { month, year, isPrimaryCall };
- return data;
- } catch (error) { console.error(`TM Script (v1.7.33): Network error for ${year}-${month+1}:`, error); return null; }
- }
- fetchInvoiceDataAndApply = async function() {
- console.log("Tampermonkey Script (v1.7.33): fetchInvoiceDataAndApply triggered.");
- const HC_instance = window._Highcharts || window.Highcharts;
- if (!HC_instance) {
- console.warn("TM Script (v1.7.33): Highcharts instance not found at start of fetchInvoiceDataAndApply.");
- return;
- }
- const initialChartContainer = document.querySelector(CHART_HOST_SELECTOR);
- if (!initialChartContainer || !initialChartContainer.isConnected) {
- console.warn("TM Script (v1.7.33): Initial chart container not found or disconnected. Aborting.");
- return;
- }
- let chartForMapBuilding = null;
- if (HC_instance.charts) {
- chartForMapBuilding = HC_instance.charts.find(c => c && c.renderTo === initialChartContainer);
- }
- if (!chartForMapBuilding) {
- console.warn("TM Script (v1.7.33): Initial chart instance for map building not found. Aborting.");
- if (HC_instance.charts) HC_instance.charts.forEach((ch,i) => console.log(` Debug: Available chart ${i} renders to ${ch ? ch.renderTo?.id : 'N/A'}`));
- return;
- }
- console.log(`TM Script (v1.7.33): Initial chart acquired (title: "${chartForMapBuilding.options.title?.text || 'N/A'}", categories: ${chartForMapBuilding.xAxis[0].categories ? chartForMapBuilding.xAxis[0].categories.length : 'N/A'}).`);
- let apiYear = new Date().getFullYear();
- let apiMonth = new Date().getMonth();
- if (typeof window.buildDateToXValueMap_v1733 === 'function') {
- const mapInfoResult = window.buildDateToXValueMap_v1733(chartForMapBuilding, apiYear);
- if (mapInfoResult && mapInfoResult.lastDate) {
- apiYear = mapInfoResult.lastDate.getUTCFullYear();
- apiMonth = mapInfoResult.lastDate.getUTCMonth();
- console.log(`TM Script (v1.7.33): API call will primarily target month/year from chart's last date: ${apiYear}-${String(apiMonth + 1).padStart(2,'0')}`);
- } else {
- console.warn(`TM Script (v1.7.33): Defaulting API call. mapInfoResult.lastDate was ${mapInfoResult ? (mapInfoResult.lastDate ? mapInfoResult.lastDate.toISOString().substring(0,10) : 'null') : 'undefined'}`);
- }
- } else {
- console.warn("TM Script (v1.7.33): buildDateToXValueMap_v1733 not found. Defaulting API call params.");
- }
- const apiResponses = [];
- console.log(`TM Script (v1.7.33): Fetching primary data for ${apiYear}-${String(apiMonth + 1).padStart(2,'0')}`);
- const primaryData = await fetchAndProcessMonthData(apiMonth, apiYear, chartForMapBuilding, true);
- if (primaryData) {
- apiResponses.push(primaryData);
- } else {
- console.warn(`TM Script (v1.7.33): Primary data fetch failed for ${apiYear}-${String(apiMonth+1).padStart(2,'0')}. Aborting.`);
- return;
- }
- if (typeof window.buildDateToXValueMap_v1733 === 'function') {
- const mapInfoResultAgain = window.buildDateToXValueMap_v1733(chartForMapBuilding, apiYear);
- if (mapInfoResultAgain && mapInfoResultAgain.firstDate) {
- const firstDayOfPrimaryDataMonth = new Date(Date.UTC(apiYear, apiMonth, 1));
- if (mapInfoResultAgain.firstDate.getTime() < firstDayOfPrimaryDataMonth.getTime()) {
- let prevMonth = apiMonth - 1;
- let prevYear = apiYear;
- if (prevMonth < 0) { prevMonth = 11; prevYear--; }
- console.log(`TM Script (v1.7.33): Chart's earliest date (${mapInfoResultAgain.firstDate.toISOString().substring(0,10)}) is before primary data month's first day (${firstDayOfPrimaryDataMonth.toISOString().substring(0,10)}). Fetching data for ${prevYear}-${String(prevMonth+1).padStart(2,'0')}`);
- const secondaryData = await fetchAndProcessMonthData(prevMonth, prevYear, chartForMapBuilding, false);
- if (secondaryData) apiResponses.push(secondaryData);
- } else {
- console.log(`TM Script (v1.7.33): Chart's earliest date (${mapInfoResultAgain.firstDate.toISOString().substring(0,10)}) is NOT before primary data month's first day (${firstDayOfPrimaryDataMonth.toISOString().substring(0,10)}). No secondary fetch needed.`);
- }
- } else {
- console.warn(`TM Script (v1.7.33): Could not determine chart's first date to check for previous month's data need.`);
- }
- }
- if (apiResponses.length === 0) { console.warn("TM Script (v1.7.33): No data fetched from any source. Aborting."); return; }
- const combinedUsageEvents = [];
- apiResponses.forEach(resp => { if (resp && resp.usageEvents) combinedUsageEvents.push(...resp.usageEvents); });
- console.log(`TM Script (v1.7.33): Combined ${combinedUsageEvents.length} usage events from ${apiResponses.length} API responses.`);
- const finalResponseData = {
- usageEvents: combinedUsageEvents,
- items: primaryData.items || [],
- pricingDescription: primaryData.pricingDescription || {},
- };
- const finalChartContainer = document.querySelector(CHART_HOST_SELECTOR);
- if (!finalChartContainer || !finalChartContainer.isConnected) {
- console.warn(`TM Script (v1.7.33): Final chart container not found or disconnected before series processing. Aborting.`);
- return;
- }
- let chartForSeriesProcessing = null;
- if (HC_instance.charts) {
- chartForSeriesProcessing = HC_instance.charts.find(c => c && c.renderTo === finalChartContainer);
- }
- if (!chartForSeriesProcessing) {
- console.warn(`TM Script (v1.7.33): Chart instance for series processing not found. Aborting.`);
- if (HC_instance.charts) HC_instance.charts.forEach((ch,i) => console.log(` Debug: Available chart ${i} renders to ${ch ? ch.renderTo?.id : 'N/A'}, isConnected: ${ch && ch.renderTo ? ch.renderTo.isConnected : 'N/A'}`));
- return;
- }
- console.log(`TM Script (v1.7.33): Chart for series processing acquired (title: "${chartForSeriesProcessing.options.title?.text || 'N/A'}", categories: ${chartForSeriesProcessing.xAxis[0].categories ? chartForSeriesProcessing.xAxis[0].categories.length : 'N/A'}).`);
- const finalMapBuildResult = typeof window.buildDateToXValueMap_v1733 === 'function' ? window.buildDateToXValueMap_v1733(chartForSeriesProcessing, apiYear) : null;
- const dateToXValueMapForSeries = finalMapBuildResult ? finalMapBuildResult.map : new Map();
- if (!finalMapBuildResult || dateToXValueMapForSeries.size === 0) {
- console.warn("TM Script (v1.7.33): Final dateToXValueMap for series is empty or couldn't be built. Aborting series addition.");
- return;
- }
- console.log(`TM Script (v1.7.33): Final dateToXValueMap (size ${dateToXValueMapForSeries.size}) obtained. First: ${finalMapBuildResult.firstDate ? finalMapBuildResult.firstDate.toISOString().substring(0,10) : 'N/A'}, Last: ${finalMapBuildResult.lastDate ? finalMapBuildResult.lastDate.toISOString().substring(0,10) : 'N/A'}`);
- if (typeof window.logInjectedChartAxes_v1733 === 'function') { window.logInjectedChartAxes_v1733(chartForSeriesProcessing, "Before Any Script Operations This Cycle"); }
- let yAxisIndexForDollars = -1;
- if (typeof window.ensureSecondaryYAxis_v1733 === 'function') {
- yAxisIndexForDollars = window.ensureSecondaryYAxis_v1733(chartForSeriesProcessing, 'dollarsYAxis_v1733', 'Cost (Dollars)');
- if (yAxisIndexForDollars !== -1) {
- const refreshedChart = HC_instance.charts.find(c => c && c.renderTo === finalChartContainer);
- if (refreshedChart) {
- chartForSeriesProcessing = refreshedChart;
- } else {
- console.error("TM Script (v1.7.33): Chart lost after Y-axis ensure. Critical error. Aborting.");
- return;
- }
- } else {
- console.error("TM Script (v1.7.33): Failed to ensure secondary Y-axis. Aborting.");
- return;
- }
- } else { console.error("TM Script (v1.7.33): ensureSecondaryYAxis_v1733 not found. Aborting."); return; }
- if (typeof window.logInjectedChartAxes_v1733 === 'function') { window.logInjectedChartAxes_v1733(chartForSeriesProcessing, "After ensureSecondaryYAxis in wrapper"); }
- let anySeriesDataChanged = false;
- if (typeof window.processAndAddDailyUsageSeries_v1733 === 'function') {
- if(window.processAndAddDailyUsageSeries_v1733(finalResponseData, { name: 'Daily Event Count', type: 'line', color: '#3498db', }, dateToXValueMapForSeries)) { anySeriesDataChanged = true; }
- }
- if (typeof window.processAndAddDailyCostSeries_v1733 === 'function') {
- if(window.processAndAddDailyCostSeries_v1733(finalResponseData, { name: 'Daily Usage Cost ($)', type: 'line', color: '#e67e22' }, dateToXValueMapForSeries, yAxisIndexForDollars )) { anySeriesDataChanged = true; }
- }
- if (typeof window.processAndAddPricingSeries_v1733 === 'function') {
- if(window.processAndAddPricingSeries_v1733(finalResponseData, dateToXValueMapForSeries)) { anySeriesDataChanged = true; }
- }
- if (anySeriesDataChanged) {
- console.log("Tampermonkey Script (v1.7.33): Requesting final chart redraw.");
- if (typeof window.logInjectedChartAxes_v1733 === 'function') { window.logInjectedChartAxes_v1733(chartForSeriesProcessing, "After All Series Processing, Before Final Redraw"); }
- chartForSeriesProcessing.redraw();
- if (typeof window.logInjectedChartAxes_v1733 === 'function') { setTimeout(() => { const finalChart = HC_instance.charts.find(c => c && c.renderTo === finalChartContainer); if(finalChart) window.logInjectedChartAxes_v1733(finalChart, "After Final Redraw"); }, 100); }
- setTimeout(() => window.forceChartCheckBySmoother_v1733 && window.forceChartCheckBySmoother_v1733(), 200);
- } else { console.log("Tampermonkey Script (v1.7.33): No series data changes required a final redraw."); }
- };
- triggerFakeMouseInteraction=function(elementSelector){const targetElement=document.querySelector(elementSelector);if(targetElement){const e=new MouseEvent("mouseenter",{bubbles:!0,cancelable:!0,view:window}),t=new MouseEvent("mouseover",{bubbles:!0,cancelable:!0,view:window}),n=targetElement.querySelector('div[id^="highcharts-"]');(n||targetElement).dispatchEvent(e),(n||targetElement).dispatchEvent(t)}};
- handlePotentialChartChangeGlobal=function(){triggerFakeMouseInteraction(CHART_HOST_SELECTOR);clearTimeout(eventTriggerDebounceTimer);eventTriggerDebounceTimer=setTimeout(()=>{if("function"==typeof window.forceChartCheckBySmoother_v1733){const e=document.querySelector(CHART_HOST_SELECTOR);e&&e.querySelectorAll("path.highcharts-graph").forEach(el=>{el.dataset.smoothedByV1733="false"}),window.forceChartCheckBySmoother_v1733()}},DELAY_AFTER_MOUSEOVER_FOR_POLL_MS)};
- setupDynamicListenersGlobal=function(){if(mainChartContentObserver)mainChartContentObserver.disconnect();const e=document.querySelector(CHART_HOST_SELECTOR);if(e){mainChartContentObserver=new MutationObserver(()=>{window.handlePotentialChartChangeGlobal()});mainChartContentObserver.observe(e,{childList:!0,subtree:!0})}const t=[document.querySelector(METRIC_CARDS_PARENT_SELECTOR),document.querySelector(DATE_RANGE_BUTTON_CONTAINER_SELECTOR)].filter(el=>el);t.forEach(el=>{const listenerKey=`_hasSmoothClickListener_v1733_${el.id||el.className.replace(/\s+/g,"_").substring(0,30)}`;if(!el[listenerKey]){el.addEventListener("click",event=>{console.log(`TM Script (v1.7.33): Click detected on interactive element (class: ${el.className}), scheduling full update.`);let currentTarget=event.target;const relevantSelector=el.matches(METRIC_CARDS_PARENT_SELECTOR)?METRIC_CARD_SELECTOR:DATE_RANGE_BUTTON_SELECTOR;while(currentTarget&¤tTarget!==el){if(currentTarget.matches&¤tTarget.matches(relevantSelector)){window.handlePotentialChartChangeGlobal();setTimeout(()=>window.fetchInvoiceDataAndApply(),500);break}currentTarget=currentTarget.parentElement}});el[listenerKey]=!0}})}
- injectAndInitializeGlobal=function(){
- if(injectionDone)return;
- if(document.body){
- const scriptId="tampermonkeyChartLogic_v1_7_33";
- const existingScript=document.getElementById(scriptId);if(existingScript)existingScript.remove();
- const scriptEl=document.createElement("script");scriptEl.id=scriptId;scriptEl.type="text/javascript";
- scriptEl.textContent=`(${codeToInject.toString()})();`;document.body.appendChild(scriptEl);injectionDone=!0;
- window.smoothCursorChartPollerReadyCallbacks_v1733=window.smoothCursorChartPollerReadyCallbacks_v1733||[];
- const initCallback=()=>{console.log("TM Script (v1.7.33): Injected code ready, running initial setup.");window.handlePotentialChartChangeGlobal();window.setupDynamicListenersGlobal();window.fetchInvoiceDataAndApply()};
- window.smoothCursorChartPollerReadyCallbacks_v1733.push(initCallback);
- if(window.isSmoothCursorChartPollerReady_v1733&&"function"==typeof window.forceChartCheckBySmoother_v1733){
- console.log("TM Script (v1.7.33): Injected code was already ready, executing pending callbacks.");
- window.smoothCursorChartPollerReadyCallbacks_v1733.forEach(cb=>cb());
- delete window.smoothCursorChartPollerReadyCallbacks_v1733;
- }
- setTimeout(()=>{const tempScript=document.getElementById(scriptId);if(tempScript&&tempScript.parentNode)tempScript.parentNode.removeChild(tempScript)},2000)
- }else setTimeout(window.injectAndInitializeGlobal,200)
- };
- window.handlePotentialChartChangeGlobal = handlePotentialChartChangeGlobal;
- window.setupDynamicListenersGlobal = setupDynamicListenersGlobal;
- window.fetchInvoiceDataAndApply = fetchInvoiceDataAndApply;
- window.injectAndInitializeGlobal = injectAndInitializeGlobal;
- initialElementsObserver=new MutationObserver((mutations,observer)=>{const chartHost=document.querySelector(CHART_HOST_SELECTOR),dateButtons=document.querySelector(DATE_RANGE_BUTTON_CONTAINER_SELECTOR),metricCards=document.querySelector(METRIC_CARDS_PARENT_SELECTOR);if(chartHost&&(dateButtons||metricCards)){observer.disconnect();initialElementsObserver=null;window.injectAndInitializeGlobal()}});
- const chartHostInitial=document.querySelector(CHART_HOST_SELECTOR),dateButtonsInitial=document.querySelector(DATE_RANGE_BUTTON_CONTAINER_SELECTOR),metricCardsInitial=document.querySelector(METRIC_CARDS_PARENT_SELECTOR);
- if(chartHostInitial&&(dateButtonsInitial||metricCardsInitial)){console.log("TM Script (v1.7.33): Initial elements already present, injecting script immediately.");if(initialElementsObserver){initialElementsObserver.disconnect();initialElementsObserver=null}window.injectAndInitializeGlobal()}else if(initialElementsObserver){initialElementsObserver.observe(document.documentElement,{childList:!0,subtree:true});}
- })();