您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fill Google Finance purchase data from CSV or Google Spreadsheet
// ==UserScript== // @name Google Finance Filler // @namespace http://tampermonkey.net/ // @version 1.1 // @description Fill Google Finance purchase data from CSV or Google Spreadsheet // @author MakMak // @icon https://www.gstatic.com/finance/favicon/favicon.png // @match https://www.google.com/finance/* // @match https://finance.google.com/* // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function() { 'use strict'; let buttonInjected = false; // Register menu command GM_registerMenuCommand("Open Finance Filler", showOverlay); // Create the overlay button inside the modal function createOverlayButton() { // Check if modal exists and button hasn't been injected yet const modal = document.querySelector('div[aria-modal="true"]'); const targetDiv = document.querySelector('div.Y0mrCc'); if (modal && targetDiv && !buttonInjected) { // Check if button already exists to avoid duplicates if (targetDiv.querySelector('#finance-filler-btn')) { return; } const button = document.createElement('button'); button.id = 'finance-filler-btn'; button.innerHTML = '📊 Filler'; button.style.cssText = ` background: #1A73E8; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-size: 14px; cursor: pointer; margin-left: 10px; font-family: 'Google Sans', Arial, sans-serif; font-weight: 500; transition: background-color 0.2s; `; // Add hover effect button.addEventListener('mouseenter', () => { button.style.background = '#1557B0'; }); button.addEventListener('mouseleave', () => { button.style.background = '#1A73E8'; }); button.addEventListener('click', showOverlay); targetDiv.appendChild(button); buttonInjected = true; console.log('Finance Filler button injected into modal'); } } // Monitor for modal appearance function monitorForModal() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { // Check if modal appeared const modal = document.querySelector('div[aria-modal="true"]'); if (modal && !buttonInjected) { // Small delay to ensure the target div is ready setTimeout(createOverlayButton, 100); } // Reset flag if modal disappeared if (!modal && buttonInjected) { buttonInjected = false; console.log('Modal closed, reset button injection flag'); } } }); }); observer.observe(document.body, { childList: true, subtree: true }); } // Create and show the overlay function showOverlay() { const overlay = document.createElement('div'); overlay.id = 'finance-filler-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10001; display: flex; justify-content: center; align-items: center; `; const modal = document.createElement('div'); modal.style.cssText = ` background: white; border-radius: 12px; border: 2px dashed #ccc; padding: 30px; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 10px 30px rgba(0,0,0,0.3); `; modal.innerHTML = ` <h2 style="margin-top: 0; color: #333; font-family: 'Google Sans', Arial, sans-serif;">Google Finance Filler</h2> <p style="color: #666; margin-bottom: 20px;">Paste your data in any format (supports tab, comma, semicolon, pipe delimiters):</p> <textarea id="csv-input" placeholder="Examples: 10 1/15/24 150.50 5,2/20/24,155.25 15;3/10/24;160.00 20|4/5/24|158.75 Or paste directly from Google Sheets!" style=" width: 100%; height: 150px; border: 1px solid #ddd; border-radius: 6px; padding: 10px; font-family: monospace; font-size: 14px; resize: vertical; box-sizing: border-box; "></textarea> <div style="margin-top: 20px;"> <button id="parse-btn" style=" background: #1A73E8; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; margin-right: 10px; font-size: 14px; ">Parse & Preview</button> <button id="close-btn" style=" background: #f1f3f4; color: #333; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; ">Cancel</button> </div> <div id="preview-container" style="margin-top: 20px;"></div> `; overlay.appendChild(modal); document.body.appendChild(overlay); // Event listeners document.getElementById('parse-btn').addEventListener('click', parseAndPreview); document.getElementById('close-btn').addEventListener('click', closeOverlay); overlay.addEventListener('click', (e) => { if (e.target === overlay) closeOverlay(); }); } // Intelligent CSV parsing with multiple delimiter support function parseCSVData(input) { const lines = input.split('\n').filter(line => line.trim()); if (lines.length === 0) return []; // Detect delimiter by checking the first line const firstLine = lines[0]; const delimiters = ['\t', ',', ';', '|']; let bestDelimiter = ';'; let maxColumns = 0; for (let delimiter of delimiters) { const columns = firstLine.split(delimiter).length; if (columns > maxColumns) { maxColumns = columns; bestDelimiter = delimiter; } } console.log(`Detected delimiter: "${bestDelimiter}" with ${maxColumns} columns`); const data = []; for (let i = 0; i < lines.length; i++) { const parts = lines[i].split(bestDelimiter).map(part => part.trim()); if (parts.length < 3) { alert(`Error on line ${i + 1}: Expected at least 3 columns (Quantity, Date, Price), got ${parts.length}`); return null; } // Take first 3 columns as Quantity, Date, Price const quantity = parseInt(parts[0].replace(/[^0-9]/g, '')); // Remove non-numeric chars const date = parts[1]; const price = parseFloat(parts[2].replace(/[^0-9.-]/g, '')); // Remove non-numeric chars except decimal point and minus if (isNaN(quantity) || isNaN(price)) { alert(`Error on line ${i + 1}: Invalid quantity (${parts[0]}) or price (${parts[2]})`); return null; } data.push({ quantity, date, price }); } return data; } // Parse CSV and show preview function parseAndPreview() { const input = document.getElementById('csv-input').value.trim(); if (!input) { alert('Please paste your CSV data first.'); return; } const data = parseCSVData(input); if (data) { showPreview(data); } } // Show preview table function showPreview(data) { const container = document.getElementById('preview-container'); container.innerHTML = ` <h3 style="color: #333; margin-bottom: 15px;">Preview (${data.length} entries):</h3> <div style="max-height: 300px; overflow-y: auto; border: 1px solid #ddd; border-radius: 6px;"> <table style="width: 100%; border-collapse: collapse;"> <thead> <tr style="background: #f8f9fa; position: sticky; top: 0;"> <th style="padding: 10px; border-bottom: 1px solid #ddd; text-align: left;">Quantity</th> <th style="padding: 10px; border-bottom: 1px solid #ddd; text-align: left;">Date</th> <th style="padding: 10px; border-bottom: 1px solid #ddd; text-align: left;">Price</th> </tr> </thead> <tbody> ${data.map(row => ` <tr> <td style="padding: 8px 10px; border-bottom: 1px solid #eee;">${row.quantity}</td> <td style="padding: 8px 10px; border-bottom: 1px solid #eee;">${row.date}</td> <td style="padding: 8px 10px; border-bottom: 1px solid #eee;">$${row.price.toFixed(2)}</td> </tr> `).join('')} </tbody> </table> </div> <div style="margin-top: 20px;"> <button id="fill-btn" style=" background: #34a853; color: white; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-size: 14px; margin-right: 10px; ">Fill Google Finance</button> <button id="back-btn" style=" background: #f1f3f4; color: #333; border: none; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-size: 14px; ">Back to Edit</button> </div> `; document.getElementById('fill-btn').addEventListener('click', () => fillGoogleFinance(data)); document.getElementById('back-btn').addEventListener('click', () => { container.innerHTML = ''; }); } // Find "More purchases" button intelligently function findMorePurchasesButton() { const spans = document.querySelectorAll('span[jsname="V67aGc"]'); for (let span of spans) { if (span.innerText && span.innerText.toLowerCase().includes('more purchases')) { return span; } } return null; } // Fill Google Finance form async function fillGoogleFinance(data) { closeOverlay(); if (data.length === 0) return; try { // Click "more purchases" button n-1 times for (let i = 0; i < data.length - 1; i++) { await new Promise(resolve => setTimeout(resolve, 500)); // Wait 500ms between clicks const moreButton = findMorePurchasesButton(); if (moreButton) { moreButton.click(); console.log(`Clicked "more purchases" button ${i + 1} times`); } else { console.error('Could not find "more purchases" button'); alert('Could not find "more purchases" button. Please check if you\'re on the correct page.'); return; } } // Wait a bit more for all fields to be created await new Promise(resolve => setTimeout(resolve, 1000)); // STEP 1: Fill Quantity and Date fields first (to bypass Google autofill for prices) console.log('Step 1: Filling Quantity and Date fields...'); const quantityInputs = document.querySelectorAll("input[jsname='YPqjbf']"); let currentIndex = 0; // Start from index 0 as corrected for (let i = 0; i < data.length; i++) { const row = data[i]; // Quantity field const quantityField = quantityInputs[currentIndex]; if (quantityField) { quantityField.value = row.quantity.toString(); quantityField.dispatchEvent(new Event('input', { bubbles: true })); quantityField.dispatchEvent(new Event('change', { bubbles: true })); } else { console.error(`Could not find quantity field at index ${currentIndex}`); } // Date field const dateField = quantityInputs[currentIndex + 1]; if (dateField) { dateField.value = row.date; dateField.dispatchEvent(new Event('input', { bubbles: true })); dateField.dispatchEvent(new Event('change', { bubbles: true })); } else { console.error(`Could not find date field at index ${currentIndex + 1}`); } console.log(`Step 1 - Row ${i + 1}: Quantity=${row.quantity}, Date=${row.date}`); // Move to next set of fields (each row uses 3 fields) currentIndex += 3; } // Wait for Google's autofill to complete console.log('Waiting for Google autofill to complete...'); await new Promise(resolve => setTimeout(resolve, 1500)); // STEP 2: Fill Price fields to overwrite Google's autofill console.log('Step 2: Filling Price fields to overwrite autofill...'); const updatedQuantityInputs = document.querySelectorAll("input[jsname='YPqjbf']"); currentIndex = 0; // Reset index for (let i = 0; i < data.length; i++) { const row = data[i]; // Price field (skip quantity and date, go directly to price) const priceField = updatedQuantityInputs[currentIndex + 2]; if (priceField) { // Clear the field first priceField.value = ''; priceField.dispatchEvent(new Event('input', { bubbles: true })); // Wait a tiny bit and then set our price await new Promise(resolve => setTimeout(resolve, 100)); priceField.value = row.price.toString(); priceField.dispatchEvent(new Event('input', { bubbles: true })); priceField.dispatchEvent(new Event('change', { bubbles: true })); priceField.dispatchEvent(new Event('blur', { bubbles: true })); } else { console.error(`Could not find price field at index ${currentIndex + 2}`); } console.log(`Step 2 - Row ${i + 1}: Price=${row.price} (overwriting autofill)`); // Move to next set of fields currentIndex += 3; } //alert(`Successfully filled ${data.length} entries!`); } catch (error) { console.error('Error filling form:', error); alert('An error occurred while filling the form. Check the console for details.'); } } // Close overlay function closeOverlay() { const overlay = document.getElementById('finance-filler-overlay'); if (overlay) { overlay.remove(); } } // Initialize the script function init() { // Wait for page to load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', monitorForModal); } else { monitorForModal(); } // Also check immediately in case modal is already open setTimeout(createOverlayButton, 500); } init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址