- // ==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);
- });
- })();