Tesla Model Y HW4 Filter

Precisely filters HW4 Model Y cars on Tesla used inventory page

当前为 2025-08-03 提交的版本,查看 最新版本

// ==UserScript==
// @name         Tesla Model Y HW4 Filter
// @namespace    http://tampermonkey.net/
// @version      3.9
// @description  Precisely filters HW4 Model Y cars on Tesla used inventory page
// @match        https://www.tesla.com/inventory/used/*
// @grant        none
// @license      MIT
// @run-at       document-idle
// @author		sambrears
// @homepage	https://greasyfork.org/en/scripts/542343-tesla-model-y-hw4-filter
// ==/UserScript==

(function () {
    'use strict';

    // HW4 thresholds
    const THRESHOLDS = {
        'F': 789500, // Fremont
        'A': 131200, // Austin
        'S': 380000, // Shanghai
        'B': 100000  // Berlin
    };

    // Model year mapping for VIN codes
    const MODEL_YEARS = {
        'N': 2022,
        'P': 2023,
        'R': 2024,
        'S': 2025
    };

    // Check if Model Y or Model 3 is currently selected
    function isModelYSelected() {
        // Check for Model Y radio button selection
        const modelYRadio = document.querySelector('input[name="Model"][value="my"]');
        if (modelYRadio && modelYRadio.checked) {
            return true;
        }

        // Fallback: check URL for Model Y
        return window.location.pathname.includes('/used/my');
    }

    function isModel3Selected() {
        // Check for Model 3 radio button selection
        const model3Radio = document.querySelector('input[name="Model"][value="m3"]');
        if (model3Radio && model3Radio.checked) {
            return true;
        }

        // Fallback: check URL for Model 3
        return window.location.pathname.includes('/used/m3');
    }

    function isTargetModelSelected() {
        return isModelYSelected() || isModel3Selected();
    }

    // Reset vehicles when switching away from target models
    function resetVehiclesIfNeeded() {
        if (!isTargetModelSelected()) {
            // Reset all vehicle displays when switching away from target models
            const allVehicles = document.querySelectorAll('.hw4-vehicle, .non-hw4-vehicle');
            allVehicles.forEach(vehicle => {
                vehicle.style.display = 'block';
                vehicle.style.border = '';
                vehicle.classList.remove('hw4-vehicle', 'non-hw4-vehicle');
                const badge = vehicle.querySelector('.hw4-badge, .year-2024-badge');
                if (badge) badge.remove();
            });
        }
    }
    function getModelYearFromCard(card) {
        // Look for model year in various text elements
        const textSelectors = [
            '.tds-text--contrast-low',
            '.card-info-details',
            '.result-details',
            '.vehicle-year'
        ];

        for (const selector of textSelectors) {
            const elements = card.querySelectorAll(selector);
            for (const element of elements) {
                const text = element.textContent.trim();
                // Look for 4-digit year at the beginning of text
                const yearMatch = text.match(/^(\d{4})\s/);
                if (yearMatch) {
                    const year = parseInt(yearMatch[1], 10);
                    if (year >= 2020 && year <= 2030) { // Reasonable year range
                        return year;
                    }
                }
            }
        }

        return null;
    }

    // Comprehensive HW4 detection logic for Model Y and 2024+ detection for Model 3
    function isHW4(vin, card) {
        if (!vin || vin.length !== 17) {
            return false;
        }

        // First, try to get model year from the card's displayed text
        let modelYear = getModelYearFromCard(card);
        
        // If we couldn't get it from the card, fall back to VIN decoding
        if (!modelYear) {
            const modelYearCode = vin.charAt(9);
            modelYear = MODEL_YEARS[modelYearCode] || 0;
        }

        // For Model 3: Only show 2024+ cars
        if (isModel3Selected()) {
            return modelYear >= 2024;
        }

        // For Model Y: Use HW4 detection logic
        if (isModelYSelected()) {
            // 2024+ cars are always HW4 - no VIN serial number check needed
            if (modelYear >= 2024) {
                return true;
            }

            // 2022 and earlier are always HW3
            if (modelYear <= 2022) {
                return false;
            }

            // For 2023, check against VIN serial number thresholds
            if (modelYear === 2023) {
                const factoryCode = vin.charAt(10);
                const serialNumber = parseInt(vin.substring(11), 10);
                const threshold = THRESHOLDS[factoryCode];
                return threshold !== undefined && serialNumber >= threshold;
            }
        }

        // Default to false for unknown years or models
        return false;
    }

    // Find VIN for a card with multiple strategies
    function findVINForCard(card) {
        const vinsToTry = [
            () => {
                // Try finding VIN in multiple places
                const vinSelectors = [
                    '.vin-display',
                    '[data-test="vehicle-vin"]',
                    '.inventory-details-link',
                    '.result-details-vin'
                ];

                for (const selector of vinSelectors) {
                    const vinElement = card.querySelector(selector);
                    if (vinElement) {
                        const vinText = vinElement.textContent.trim();
                        if (vinText.length === 17) {
                            return vinText;
                        }
                    }
                }
                return null;
            },
            () => {
                // Try extracting VIN from card's inner HTML
                const matches = card.innerHTML.match(/\b[A-HJ-NPR-Z0-9]{17}\b/);
                return matches ? matches[0] : null;
            }
        ];

        for (const vinFinder of vinsToTry) {
            const vin = vinFinder();
            if (vin && vin.length === 17) {
                return vin;
            }
        }

        return null;
    }

    // Filter and highlight HW4 cars (Model Y) or 2024+ cars (Model 3)
    function filterHW4Cards() {
        // Only run if target model is selected
        if (!isTargetModelSelected()) {
            return;
        }
        // More robust selector to capture cars
        const cards = document.querySelectorAll(
            '.results-container .result, ' +
            '.inventory-results .result, ' +
            '[data-test="inventory-results"] .result'
        );

        let hw4Count = 0;
        let totalCount = cards.length;

        cards.forEach((card, index) => {
            // Ensure we can process the card
            if (!card || !card.querySelector) {
                return;
            }

            // Find VIN for the card
            const vin = findVINForCard(card);

            if (!vin) {
                return;
            }

            // Pass both VIN and card to the detection function
            const isTargetVehicle = isHW4(vin, card);

            if (isTargetVehicle) {
                hw4Count++;
                card.classList.add('hw4-vehicle');
                card.classList.remove('non-hw4-vehicle');
                card.style.display = 'block';
                card.style.border = '3px solid limegreen';

                // Add appropriate badge based on model
                const badgeClass = isModel3Selected() ? 'year-2024-badge' : 'hw4-badge';
                const badgeText = isModel3Selected() ? '2024+' : 'HW4';
                
                let badge = card.querySelector('.hw4-badge, .year-2024-badge');
                if (!badge) {
                    badge = document.createElement('div');
                    badge.classList.add(badgeClass);
                    badge.textContent = badgeText;
                    badge.style.position = 'absolute';
                    badge.style.top = '8px';
                    badge.style.right = '8px';
                    badge.style.background = 'limegreen';
                    badge.style.color = 'black';
                    badge.style.fontWeight = 'bold';
                    badge.style.padding = '2px 6px';
                    badge.style.borderRadius = '4px';
                    badge.style.zIndex = '10';
                    badge.style.fontSize = '12px';
                    card.style.position = 'relative';
                    card.appendChild(badge);
                } else {
                    // Update existing badge text if model changed
                    badge.textContent = badgeText;
                    badge.className = badgeClass;
                }
            } else {
                card.classList.add('non-hw4-vehicle');
                card.classList.remove('hw4-vehicle');
                card.style.display = 'none';
                card.style.border = '1px solid red';

                // Remove any existing badge
                const existingBadge = card.querySelector('.hw4-badge, .year-2024-badge');
                if (existingBadge) {
                    existingBadge.remove();
                }
            }
        });

        updateFilterCount(hw4Count, totalCount);
    }

    // Log filter results to console
    function updateFilterCount(targetCount, totalCount) {
        const modelType = isModel3Selected() ? 'Model 3 (2024+)' : 'Model Y (HW4)';
        console.log(`Tesla Filter: Showing ${targetCount}/${totalCount} ${modelType} vehicles`);
    }

    // Run after delay to allow page to settle
    setTimeout(() => {
        createFilterToggleButton();

        // Initial setup
        toggleFilterButton();
        filterHW4Cards();

        // Set up mutation observer to catch dynamic content and model changes
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.addedNodes.length) {
                    // Check if model selection changed
                    setTimeout(() => {
                        toggleFilterButton();
                        filterHW4Cards();
                    }, 500);
                    break;
                }
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Also listen for radio button changes directly
        document.addEventListener('change', (event) => {
            if (event.target.name === 'Model') {
                setTimeout(() => {
                    toggleFilterButton();
                    filterHW4Cards();
                }, 100);
            }
        });
    }, 2000);
})();