WME Auto Name Places

Busca Places sin nombre y les asigna la categoría como nombre en español.

// ==UserScript==
// @name         WME Auto Name Places
// @namespace    https://gf.qytechs.cn/es-419/users/67894-crotalo
// @version      2.57
// @description  Busca Places sin nombre y les asigna la categoría como nombre en español.
// @author       Crotalo
// @match        https://www.waze.com/*editor*
// @match       https://beta.waze.com/*editor*
// @exclude     https://beta.waze.com/*user/*editor/*
// @exclude     https://www.waze.com/*user/*editor/*
// @exclude     https://www.waze.com/discuss/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Configuración
    const CONFIG = {
        delayBetweenUpdates: 100, // ms entre actualizaciones
        modalStyle: {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: 'white',
            padding: '20px',
            border: '2px solid #ccc',
            borderRadius: '5px',
            boxShadow: '0 0 10px rgba(0,0,0,0.2)',
            zIndex: '10000',
            maxHeight: '70vh',
            overflowY: 'auto',
            width: 'auto',
            minWidth: '500px'
        }
    };

    // Diccionario de traducción de categorías a español
    const CATEGORY_TRANSLATIONS = {
        'restaurant': 'Restaurante',
        'swimming_pool': 'Piscina',
        'factory_industrial': 'Fábrica',
        'farm': 'Granja',
        'sea_lake_pool': 'Lago',
        'river_stream': 'Río',
        'forest_grove': 'Bosque',
        'cafe': 'Cafetería',
        'sports_court': 'Cancha Deportiva',
        'shopping_and_services': 'Tienda',
        'car_services': 'Servios para el Automovil',
        'hotel': 'Hotel',
        'sport_court': 'Escenario Deportivo',
        'gas station': 'Estación de servicio',
        'hospital': 'Hospital',
        'pharmacy': 'Farmacia',
        'bank': 'Banco',
        'atm': 'Cajero automático',
        'parking': 'Estacionamiento',
        'parking_lot': 'Parqueadero',
        'garage_automotive_shop': 'Tienda para Vehículos',
        'natural_features': 'Características Naturales',
        'school': 'Escuela',
        'university': 'Universidad',
        'museum': 'Museo',
        'park': 'Parque',
        'mall': 'Centro comercial',
        'stadium_arena': 'Estadio',
        'supermarket': 'Supermercado',
        'gym': 'Gimnasio',
        'church': 'Iglesia',
        'police': 'Comisaría',
        'fire station': 'Estación de bomberos',
        'library': 'Biblioteca',
        'stadium': 'Estadio',
        'cinema': 'Cine',
        'theater': 'Teatro',
        'zoo': 'Zoológico',
        'airport': 'Aeropuerto',
        'train station': 'Estación de tren',
        'bus station': 'Estación de autobuses',
        'car wash': 'Lavado de coches',
        'car repair': 'Taller mecánico',
        'construction_site': 'Sitio en Construcción',
        'dentist': 'Dentista',
        'doctor': 'Médico',
        'clinic': 'Clínica',
        'veterinary': 'Veterinario',
        'post office': 'Oficina de correos',
        'shopping': 'Tiendas',
        'bakery': 'Panadería',
        'butcher': 'Carnicería',
        'market': 'Mercado',
        'florist': 'Florería',
        'book store': 'Librería',
        'electronics': 'Electrónica',
        'furniture': 'Mueblería',
        'jewelry': 'Joyería',
        'optician': 'Óptica',
        'pet store': 'Tienda de mascotas',
        'sports': 'Artículos deportivos',
        'toy store': 'Juguetería',
        'department store': 'Grandes almacenes'
    };

    // Función para esperar a que WME esté listo
    function waitForWME() {
        return new Promise(resolve => {
            if (window.W && W.model && W.model.venues && W.model.actionManager) {
                resolve();
            } else {
                setTimeout(() => resolve(waitForWME()), 500);
            }
        });
    }

    // Obtener places sin nombre (optimizado)
    function getUnnamedPlaces() {
        if (!W.model.venues?.objects) return [];

        return Object.values(W.model.venues.objects).filter(place => {
            const name = place.attributes?.name;
            return !name || name.trim() === '';
        });
    }

    // Generar nombre automático en español
    function generateName(place) {
        if (!place.attributes) return "Desconocido";

        if (place.attributes.residential) return "Residencial";
        if (place.attributes.categories?.[0]) {
            const category = place.attributes.categories[0].toLowerCase();
            return CATEGORY_TRANSLATIONS[category] ||
                   category.charAt(0).toUpperCase() + category.slice(1);
        }
        return "Desconocido";
    }

    // Crear tabla de aprobación (optimizado)
    function createApprovalTable(places) {
        // Eliminar modal existente
        document.getElementById('wme-auto-name-modal')?.remove();

        const modal = document.createElement('div');
        modal.id = 'wme-auto-name-modal';
        Object.assign(modal.style, CONFIG.modalStyle);

        // Botón de cierre
        const closeBtn = document.createElement('button');
        closeBtn.innerHTML = '×';
        closeBtn.style.position = 'absolute';
        closeBtn.style.right = '10px';
        closeBtn.style.top = '10px';
        closeBtn.style.background = 'transparent';
        closeBtn.style.border = 'none';
        closeBtn.style.fontSize = '20px';
        closeBtn.style.cursor = 'pointer';
        closeBtn.onclick = () => modal.remove();
        modal.appendChild(closeBtn);

        // Título
        const title = document.createElement('h3');
        title.textContent = `Places sin nombre encontrados: ${places.length}`;
        title.style.marginTop = '0';
        title.style.color = '#333';
        modal.appendChild(title);

        // Tabla
        const table = document.createElement('table');
        table.style.width = '100%';
        table.style.borderCollapse = 'collapse';
        table.style.marginBottom = '15px';

        const thead = document.createElement('thead');
        thead.innerHTML = `
            <tr style="background-color: #f5f5f5;">
                <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">ID</th>
                <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">Categoría</th>
                <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">Nuevo Nombre</th>
                <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">Aprobar</th>
            </tr>
        `;
        table.appendChild(thead);

        const tbody = document.createElement('tbody');
        places.forEach(place => {
            const row = document.createElement('tr');
            row.style.borderBottom = '1px solid #eee';
            const newName = generateName(place);
            const category = place.attributes.categories?.[0] ?
                           (CATEGORY_TRANSLATIONS[place.attributes.categories[0].toLowerCase()] ||
                            place.attributes.categories[0]) :
                           "N/A";
            row.innerHTML = `
                <td style="padding: 8px;">${place.attributes.id}</td>
                <td style="padding: 8px;">${category}</td>
                <td style="padding: 8px;">${newName}</td>
                <td style="padding: 8px; text-align: center;">
                    <input type='checkbox' data-id='${place.attributes.id}' checked>
                </td>
            `;
            tbody.appendChild(row);
        });
        table.appendChild(tbody);
        modal.appendChild(table);

        // Botones
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'flex-end';
        buttonContainer.style.gap = '10px';

        const createButton = (text, color, onClick) => {
            const btn = document.createElement('button');
            btn.textContent = text;
            btn.style.padding = '8px 16px';
            btn.style.background = color;
            btn.style.color = 'white';
            btn.style.border = 'none';
            btn.style.borderRadius = '4px';
            btn.style.cursor = 'pointer';
            btn.onclick = onClick;
            return btn;
        };

        buttonContainer.appendChild(
            createButton('Cancelar', '#f44336', () => modal.remove())
        );
        buttonContainer.appendChild(
            createButton('Aplicar Cambios', '#4CAF50', () => applyChanges(places))
        );

        modal.appendChild(buttonContainer);
        document.body.appendChild(modal);
    }

    // Aplicar cambios seleccionados
    async function applyChanges(places) {
        const modal = document.getElementById('wme-auto-name-modal');
        if (!modal) return;

        const applyBtn = modal.querySelector('button:last-child');
        const checkboxes = modal.querySelectorAll("input[type='checkbox']:checked");
        const UpdateObject = require("Waze/Action/UpdateObject");

        if (!checkboxes.length) {
            alert('No hay cambios seleccionados para aplicar.');
            return;
        }

        try {
            // Deshabilitar botón durante la operación
            applyBtn.disabled = true;
            applyBtn.textContent = 'Guardando...';
            applyBtn.style.background = '#cccccc';

            let changesMade = false;
            let successCount = 0;

            // Procesar cada checkbox seleccionado
            for (const checkbox of checkboxes) {
                const placeId = checkbox.dataset.id;
                const place = W.model.venues.getObjectById(placeId);

                if (!place) {
                    console.warn(`⛔ No se encontró el lugar con ID: ${placeId}`);
                    continue;
                }

                const newName = generateName(place);
                const currentName = place.attributes.name || "";

                if (newName && newName !== "" && currentName.trim() !== newName) {
                    try {
                        const action = new UpdateObject(place, { name: newName });
                        W.model.actionManager.add(action);
                        console.log(`✅ Nombre actualizado: "${currentName}" → "${newName}"`);
                        changesMade = true;
                        successCount++;

                        // Pequeña pausa entre cambios
                        await new Promise(resolve => setTimeout(resolve, CONFIG.delayBetweenUpdates));
                    } catch (error) {
                        console.error(`⛔ Error actualizando place ${placeId}:`, error);
                    }
                } else {
                    console.log(`⏭ Sin cambios reales para ID ${placeId} (ya tiene nombre o no es necesario cambiarlo)`);
                }
            }

            modal.remove();

            if (changesMade) {
                alert(`💾 ${successCount} cambios aplicados correctamente. Recuerda presionar el botón de guardar en el editor.`);
                // Forzar actualización visual del mapa
                W.map?.invalidate?.();
            } else {
                alert("ℹ️ No hubo cambios para aplicar.");
            }

        } catch (error) {
            console.error('⛔ Error al aplicar cambios:', error);
            alert('Error al guardar cambios: ' + (error.message || error));
        } finally {
            if (applyBtn) {
                applyBtn.disabled = false;
                applyBtn.textContent = 'Aplicar Cambios';
                applyBtn.style.background = '#4CAF50';
            }
        }
    }

    // Añadir pestaña al panel de scripts
    function addScriptToSettingsTab() {
        const tabName = 'Auto Name Places';
        const tabSelector = `#user-tabs a[title="${tabName}"]`;

        if (document.querySelector(tabSelector)) return;

        const observer = new MutationObserver((mutations, obs) => {
            const settingsPanel = document.querySelector("#user-tabs");
            if (settingsPanel) {
                obs.disconnect();

                const newTab = document.createElement("li");
                newTab.innerHTML = `<a href="#" title="${tabName}">${tabName}</a>`;
                settingsPanel.appendChild(newTab);

                newTab.querySelector('a').addEventListener("click", function(e) {
                    e.preventDefault();
                    init();
                });
            }
        });

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

    // Función de inicialización
    async function init() {
        try {
            await waitForWME();
            const unnamedPlaces = getUnnamedPlaces();

            if (!unnamedPlaces.length) {
                alert("No se encontraron Places sin nombre en el área actual.");
                return;
            }

            createApprovalTable(unnamedPlaces);
        } catch (error) {
            console.error('⛔ Error en init:', error);
            alert('Error al buscar places sin nombre: ' + (error.message || error));
        }
    }

    // Iniciar script cuando WME esté listo
    waitForWME().then(() => {
        addScriptToSettingsTab();
    }).catch(error => {
        console.error('⛔ Error al inicializar WME:', error);
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址