您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Ultimate UR Management Toolkit with zoom refresh and panel update
当前为
// ==UserScript== // @name WME - UR Manager // @namespace http://waze.com/ // @version 2025.04.28.13 // @description Ultimate UR Management Toolkit with zoom refresh and panel update // @author Crotalo // @match https://www.waze.com/*/editor* // @match https://beta.waze.com/*/editor* // @grant GM_addStyle // @require https://code.jquery.com/jquery-3.6.0.min.js // ==/UserScript== (function() { 'use strict'; const CONFIG = { MENSAJE_RESPUESTA: "¡Hola, Wazer! Gracias por tu reporte. Para resolverlo de forma efectiva, necesitamos un poco más de detalle sobre lo sucedido. Quedamos atentos a tu respuesta.", MENSAJE_CIERRE: "¡¡Hola Wazer! Buen día, Lamentablemente no pudimos solucionar el error en esta ocasión. Por favor, déjanos más datos la próxima vez. Gracias por reportar.", MENSAJE_RESUELTA: "¡Hola Wazer! Buen día, el problema fue solucionado y se verá reflejado en la aplicación en la próxima actualización del mapa, esta tomará entre 3 y 5 días. ¡Gracias por reportar!!", DEBUG: true, BOTON_ID: 'urna-btn-fecha-exacta', PANEL_ID: 'urna-panel-fecha-exacta', INTERVALO_VERIFICACION: 5000, UMBRAL_VIEJO: 7, UMBRAL_RECIENTE: 3, RETRASO_ENTRE_ACCIONES: 800, RETRASO_ESPERA_UI: 1000, MAX_REINTENTOS: 3, ZOOM_ACTUALIZACION: 13 }; GM_addStyle(` #${CONFIG.BOTON_ID} { position: fixed !important; bottom: 20px !important; left: 20px !important; z-index: 99999 !important; padding: 10px 15px !important; background: #3498db !important; color: white !important; font-weight: bold !important; border: none !important; border-radius: 5px !important; cursor: pointer !important; font-family: Arial, sans-serif !important; box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important; } #${CONFIG.PANEL_ID} { position: fixed; top: 80px; right: 20px; width: 500px; max-height: 70vh; min-height: 200px; display: flex; flex-direction: column; background: white; border: 2px solid #999; z-index: 99998; font-family: Arial, sans-serif; font-size: 13px; box-shadow: 2px 2px 15px rgba(0,0,0,0.3); border-radius: 5px; display: none; } #${CONFIG.PANEL_ID} .panel-content { flex: 1; overflow-y: auto; padding: 15px; max-height: calc(70vh - 60px); } #${CONFIG.PANEL_ID} table { width: 100%; border-collapse: collapse; margin-top: 10px; } #${CONFIG.PANEL_ID} th { position: sticky; top: 0; background-color: #f2f2f2; z-index: 10; } #${CONFIG.PANEL_ID} th, #${CONFIG.PANEL_ID} td { border: 1px solid #ddd; padding: 6px; text-align: left; } .ur-old { color: #d9534f; font-weight: bold; } .ur-recent { color: #5bc0de; } .ur-new { color: #5cb85c; } .ur-visitada { background-color: #fdf5d4 !important; } .ur-no-fecha { color: #777; font-style: italic; } .btn-centrar { padding: 4px 8px; background: #3498db; color: white; border: none; border-radius: 3px; cursor: pointer; } .panel-footer { padding: 10px 15px; background: #f8f8f8; border-top: 1px solid #eee; display: flex; justify-content: center; gap: 10px; position: sticky; bottom: 0; z-index: 20; height: 60px; } .btn-global { padding: 8px 15px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; white-space: nowrap; } .btn-responder { background: #f0ad4e; color: white; } .btn-cerrar { background: #5cb85c; color: white; } .btn-resuelta { background: #5bc0de; color: white; } .btn-reiniciar { background: #d9534f; color: white; } `); let estado = { URsActuales: [], panelVisible: false, botonUR: null, intervaloVerificacion: null, timeouts: [], accionEnProgreso: false, reintentos: 0, urVisitadas: [], urCentradas: [], bloqueado: false }; function debugLog(message) { if (CONFIG.DEBUG) console.log('[UR Script] ' + message); } function limpiarTimeouts() { estado.timeouts.forEach(timeout => clearTimeout(timeout)); estado.timeouts = []; } function agregarTimeout(callback, delay) { const timeoutId = setTimeout(() => { callback(); estado.timeouts = estado.timeouts.filter(id => id !== timeoutId); }, delay); estado.timeouts.push(timeoutId); return timeoutId; } function resetearEstado() { estado.accionEnProgreso = false; estado.bloqueado = false; estado.reintentos = 0; limpiarTimeouts(); debugLog('Estado del script reiniciado'); } function togglePanelURs() { if (estado.panelVisible) { $(`#${CONFIG.PANEL_ID}`).fadeOut(300, function() { $(this).remove(); }); estado.panelVisible = false; limpiarTimeouts(); } else { mostrarPanelURs(); } } function crearBoton() { if ($(`#${CONFIG.BOTON_ID}`).length > 0) return; debugLog('Creando botón...'); estado.botonUR = $(`<button id="${CONFIG.BOTON_ID}">📝 UR Manager</button>`) .appendTo('body') .on('click', togglePanelURs); debugLog('Botón creado exitosamente'); } function parsearFecha(valor) { if (!valor) return null; if (typeof valor === 'object' && '_seconds' in valor) { try { return new Date(valor._seconds * 1000 + (valor._nanoseconds / 1000000)); } catch (e) { debugLog(`Error parseando Firebase Timestamp: ${JSON.stringify(valor)}`); } } if (typeof valor === 'string' && valor.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)) { try { return new Date(valor); } catch (e) { debugLog(`Error parseando fecha ISO: ${valor}`); } } if (/^\d+$/.test(valor)) { try { const num = parseInt(valor); return new Date(num > 1000000000000 ? num : num * 1000); } catch (e) { debugLog(`Error parseando timestamp numérico: ${valor}`); } } return null; } function obtenerFechaCreacionExacta(ur) { try { if (ur.attributes.driveDate) { const fecha = parsearFecha(ur.attributes.driveDate); if (fecha) return fecha; } return null; } catch (e) { debugLog(`Error obteniendo fecha: ${e}`); return null; } } function obtenerFechaUC(ur) { try { if (ur.attributes.createdOn) { const fecha = parsearFecha(ur.attributes.createdOn); if (fecha) return fecha; } if (ur.attributes.updatedOn) { const fecha = parsearFecha(ur.attributes.updatedOn); if (fecha) return fecha; } if (ur.attributes.comments && ur.attributes.comments.length > 0) { const primerComentario = ur.attributes.comments[0]; if (primerComentario.createdOn) { const fecha = parsearFecha(primerComentario.createdOn); if (fecha) return fecha; } } return null; } catch (e) { debugLog(`Error obteniendo fecha UC para UR ${ur.id}: ${e}`); return null; } } function calcularDiferenciaDias(fecha) { if (!fecha) return null; const hoy = new Date(); const diffTiempo = hoy.getTime() - fecha.getTime(); const diffDias = Math.floor(diffTiempo / (1000 * 60 * 60 * 24)); return diffDias; } function formatearDiferenciaDias(ur) { const fechaUC = obtenerFechaUC(ur); if (!fechaUC) return "No disponible"; const dias = calcularDiferenciaDias(fechaUC); if (dias === null) return "Error cálculo"; return `${dias} días`; } function clasificarUR(fecha) { if (!fecha) return { estado: "Sin fecha", clase: "ur-no-fecha" }; const hoy = new Date(); const diff = hoy - fecha; const dias = Math.floor(diff / (1000 * 60 * 60 * 24)); if (dias > CONFIG.UMBRAL_VIEJO) return { estado: `Antigua (${dias}d)`, clase: "ur-old" }; if (dias > CONFIG.UMBRAL_RECIENTE) return { estado: `Reciente (${dias}d)`, clase: "ur-recent" }; return { estado: `Nueva (${dias}d)`, clase: "ur-new" }; } function obtenerURsSinAtender() { try { if (!W.model?.mapUpdateRequests?.objects) return []; const bounds = W.map.getExtent(); return Object.values(W.model.mapUpdateRequests.objects) .filter(ur => { // Filtrar URs cerradas (open: false o resolved: true) if (ur.attributes.open === false || ur.attributes.resolved) { return false; } const geom = ur.getOLGeometry?.(); if (!geom) return false; const center = geom.getBounds().getCenterLonLat(); if (!bounds.containsLonLat(center)) return false; const comentarios = ur.attributes.comments || []; return !comentarios.some(c => c.type === 'user' && c.text?.trim().length > 0); }); } catch (e) { debugLog('Error obteniendo URs: ' + e); return []; } } function mostrarPanelURs() { estado.panelVisible = true; limpiarTimeouts(); $(`#${CONFIG.PANEL_ID}`).remove(); const panel = $(`<div id="${CONFIG.PANEL_ID}">`); const panelContent = $('<div class="panel-content">'); const panelFooter = $(` <div class="panel-footer"> <button class="btn-global btn-responder" id="responder-todas">Preguntar</button> <button class="btn-global btn-resuelta" id="resolver-todas">Resuelta</button> <button class="btn-global btn-cerrar" id="cerrar-todas">No Identificada</button> <button class="btn-global btn-reiniciar" id="actualizar-lista">Actualizar Lista</button> </div> `); // Función para actualizar el contenido del panel const actualizarContenidoPanel = () => { estado.URsActuales = obtenerURsSinAtender(); if (estado.URsActuales.length === 0) { panelContent.html('<div style="padding:15px;text-align:center;"><b>No hay URs sin atender visibles</b></div>'); } else { let tablaHTML = ` <h3 style="margin-top:0;">URs Activas: ${estado.URsActuales.length}</h3> <table> <thead> <tr> <th>ID</th> <th>Fecha Creación</th> <th>Estado</th> <th>UC (Días)</th> <th>Acción</th> </tr> </thead> <tbody>`; estado.URsActuales.forEach(ur => { const id = ur.attributes.id; const fecha = obtenerFechaCreacionExacta(ur); const clasificacion = clasificarUR(fecha); const diferenciaDias = formatearDiferenciaDias(ur); let fechaStr = 'No disponible'; if (fecha) { fechaStr = fecha.toLocaleDateString('es-ES', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } const esVisitada = estado.urVisitadas.includes(id) ? 'ur-visitada' : ''; const fueCentrada = estado.urCentradas.includes(id); tablaHTML += ` <tr id="fila-ur-${id}" class="${esVisitada}"> <td>${id}</td> <td>${fechaStr}</td> <td class="${clasificacion.clase}">${clasificacion.estado}</td> <td>${diferenciaDias}</td> <td><button class="btn-centrar" data-id="${id}" ${fueCentrada ? 'data-centered="true"' : ''}>🗺️ Centrar</button></td> </tr>`; }); panelContent.html(` ${tablaHTML} </tbody> </table> `); panelContent.on('click', '.btn-centrar', function() { if (estado.accionEnProgreso || estado.bloqueado) { debugLog('Acción de centrar bloqueada temporalmente'); return; } const id = $(this).data('id'); const $btn = $(this); if ($btn.attr('data-centered') === 'true') { const ur = W.model.mapUpdateRequests.getObjectById(Number(id)); if (ur) { try { if (W.control?.MapUpdateRequest?.show) { W.control.MapUpdateRequest.show(ur); } else if (W.control?.MapProblem?.show) { W.control.MapProblem.show(ur); } else if (W.control?.UR?.show) { W.control.UR.show(ur); } } catch (e) { debugLog(`Error al mostrar UR ${id}: ${e}`); } } } else { centrarYMostrarUR(id); $btn.attr('data-centered', 'true'); } $(`#fila-ur-${id}`).addClass('ur-visitada'); if (!estado.urVisitadas.includes(id)) { estado.urVisitadas.push(id); } }); } }; // Configurar el evento de actualización panelFooter.on('click', '#actualizar-lista', function() { if (estado.accionEnProgreso) return; // Ajustar el zoom a 13 W.map.getOLMap().zoomTo(CONFIG.ZOOM_ACTUALIZACION); // Pequeña espera antes de actualizar para que el mapa se estabilice agregarTimeout(() => { actualizarContenidoPanel(); debugLog(`Panel actualizado después de ajustar zoom a ${CONFIG.ZOOM_ACTUALIZACION}`); }, 1000); }); // Configurar otros eventos panelFooter.on('click', '#responder-todas', function() { if (estado.accionEnProgreso || estado.bloqueado) return; estado.URsActuales.forEach((ur, index) => { agregarTimeout(() => responderUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES); }); }); panelFooter.on('click', '#resolver-todas', function() { if (estado.accionEnProgreso || estado.bloqueado) return; estado.URsActuales.forEach((ur, index) => { agregarTimeout(() => resolverUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES); }); }); panelFooter.on('click', '#cerrar-todas', function() { if (estado.accionEnProgreso || estado.bloqueado) return; estado.URsActuales.forEach((ur, index) => { agregarTimeout(() => cerrarUR(ur.attributes.id), index * CONFIG.RETRASO_ENTRE_ACCIONES); }); }); // Cargar contenido inicial actualizarContenidoPanel(); panel.append(panelContent); panel.append(panelFooter); panel.appendTo('body').fadeIn(300); } function centrarYMostrarUR(id) { if (estado.accionEnProgreso || estado.bloqueado) { debugLog(`Acción bloqueada - accionEnProgreso: ${estado.accionEnProgreso}, bloqueado: ${estado.bloqueado}`); return; } estado.accionEnProgreso = true; limpiarTimeouts(); const ur = W.model.mapUpdateRequests.getObjectById(Number(id)); if (!ur) { debugLog(`UR ${id} no encontrada`); estado.accionEnProgreso = false; return; } if (!estado.urCentradas.includes(id)) { estado.urCentradas.push(id); } const geom = ur.getOLGeometry?.(); if (geom) { const center = geom.getBounds().getCenterLonLat(); W.map.setCenter(center, 17); agregarTimeout(() => { try { if (W.control?.MapUpdateRequest?.show) { W.control.MapUpdateRequest.show(ur); } else if (W.control?.MapProblem?.show) { W.control.MapProblem.show(ur); } else if (W.control?.UR?.show) { W.control.UR.show(ur); } else if (W.selectionManager) { W.selectionManager.select([ur]); } $(`#fila-ur-${id}`).addClass('ur-visitada'); if (!estado.urVisitadas.includes(id)) { estado.urVisitadas.push(id); } } catch (e) { debugLog(`Error al mostrar UR ${id}: ${e}`); } finally { estado.accionEnProgreso = false; } }, 300); } else { debugLog(`No se pudo obtener geometría para UR ${id}`); estado.accionEnProgreso = false; } } function responderUR(id) { if (estado.accionEnProgreso || estado.bloqueado) return; estado.accionEnProgreso = true; limpiarTimeouts(); const ur = W.model.mapUpdateRequests.getObjectById(Number(id)); if (!ur) { resetearEstado(); return; } centrarYMostrarUR(id); agregarTimeout(() => { try { const commentField = $('.new-comment-text'); if (!commentField.length) { throw new Error('Campo de comentario no encontrado'); } commentField.val(CONFIG.MENSAJE_RESPUESTA); commentField.trigger('input').trigger('change'); agregarTimeout(() => { const sendButton = $('.send-button:not(:disabled)'); if (!sendButton.length) { throw new Error('Botón enviar no encontrado o deshabilitado'); } sendButton[0].click(); resetearEstado(); }, 500); } catch (error) { debugLog(`Error en responderUR: ${error.message}`); resetearEstado(); } }, CONFIG.RETRASO_ESPERA_UI); } function resolverUR(id) { if (estado.accionEnProgreso || estado.bloqueado) return; estado.accionEnProgreso = true; estado.bloqueado = true; limpiarTimeouts(); const ur = W.model.mapUpdateRequests.getObjectById(Number(id)); if (!ur) { resetearEstado(); return; } //centrarYMostrarUR(id); agregarTimeout(() => { try { const commentField = $('.new-comment-text'); if (!commentField.length) { throw new Error('Campo de comentario no encontrado'); } commentField.val(CONFIG.MENSAJE_RESUELTA); commentField.trigger('input').trigger('change'); agregarTimeout(() => { const sendButton = $('.send-button:not(:disabled)'); if (!sendButton.length) { throw new Error('Botón enviar no encontrado o deshabilitado'); } sendButton[0].click(); agregarTimeout(() => { const solvedButton = document.querySelector('[data-status="SOLVED"], label[for="state-solved"], [data-testid="solved-button"]'); if (!solvedButton) { throw new Error('Botón "Resuelta" no encontrado'); } solvedButton.click(); agregarTimeout(() => { const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary'); if (confirmButton) { confirmButton.click(); } agregarTimeout(() => { $(`#fila-ur-${id}`).remove(); estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id); const contador = $('h3').first(); if (contador.length) { contador.text(`URs Activas: ${estado.URsActuales.length}`); } resetearEstado(); debugLog('Estado desbloqueado después de resolver UR'); }, 500); }, 500); }, 500); }, 500); } catch (error) { debugLog(`Error en resolverUR: ${error.message}`); resetearEstado(); } }, CONFIG.RETRASO_ESPERA_UI); if (estado.accionEnProgreso) return; } function cerrarUR(id) { if (estado.accionEnProgreso || estado.bloqueado) return; estado.accionEnProgreso = true; estado.bloqueado = true; limpiarTimeouts(); const ur = W.model.mapUpdateRequests.getObjectById(Number(id)); if (!ur) { resetearEstado(); return; } centrarYMostrarUR(id); agregarTimeout(() => { try { const commentField = $('.new-comment-text'); if (!commentField.length) { throw new Error('Campo de comentario no encontrado'); } commentField.val(CONFIG.MENSAJE_CIERRE); commentField.trigger('input').trigger('change'); agregarTimeout(() => { const sendButton = $('.send-button:not(:disabled)'); if (!sendButton.length) { throw new Error('Botón enviar no encontrado o deshabilitado'); } sendButton[0].click(); agregarTimeout(() => { const notIdentifiedButton = document.querySelector('[data-status="NOT_IDENTIFIED"], label[for="state-not-identified"], [data-testid="not-identified-button"]'); if (!notIdentifiedButton) { throw new Error('Botón "No Identificado" no encontrado'); } notIdentifiedButton.click(); agregarTimeout(() => { const confirmButton = document.querySelector('.buttons .button-primary, .dialog-footer .button-primary'); if (confirmButton) { confirmButton.click(); } agregarTimeout(() => { $(`#fila-ur-${id}`).remove(); estado.URsActuales = estado.URsActuales.filter(u => u.attributes.id !== id); const contador = $('h3').first(); if (contador.length) { contador.text(`URs Activas: ${estado.URsActuales.length}`); } resetearEstado(); debugLog('Estado desbloqueado después de cerrar UR'); }, 500); }, 500); }, 500); }, 500); } catch (error) { debugLog(`Error en cerrarUR: ${error.message}`); if (estado.reintentos < CONFIG.MAX_REINTENTOS) { estado.reintentos++; debugLog(`Reintentando (${estado.reintentos}/${CONFIG.MAX_REINTENTOS})...`); agregarTimeout(() => cerrarUR(id), 1000); } else { resetearEstado(); } } }, CONFIG.RETRASO_ESPERA_UI); } function inicializarScript() { debugLog('Inicializando script...'); window.togglePanelURs = togglePanelURs; crearBoton(); estado.intervaloVerificacion = setInterval(() => { if ($(`#${CONFIG.BOTON_ID}`).length === 0) { debugLog('Botón no encontrado, recreando...'); crearBoton(); } }, CONFIG.INTERVALO_VERIFICACION); debugLog('Script inicializado correctamente'); } function esperarWME() { if (typeof W === 'undefined' || !W.loginManager || !W.model || !W.map) { debugLog('WME no está completamente cargado, reintentando...'); setTimeout(esperarWME, 1000); return; } if (!W.model.mapUpdateRequests) { debugLog('Módulo mapUpdateRequests no está disponible, reintentando...'); setTimeout(esperarWME, 1000); return; } setTimeout(inicializarScript, 2000); } debugLog('Script cargado, esperando WME...'); esperarWME(); })();