Greasy Fork镜像 支持简体中文。

WME Auto Name Places

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

  1. // ==UserScript==
  2. // @name WME Auto Name Places
  3. // @namespace https://gf.qytechs.cn/es-419/users/67894-crotalo
  4. // @version 2.57
  5. // @description Busca Places sin nombre y les asigna la categoría como nombre en español.
  6. // @author Crotalo
  7. // @match https://www.waze.com/*editor*
  8. // @match https://beta.waze.com/*editor*
  9. // @exclude https://beta.waze.com/*user/*editor/*
  10. // @exclude https://www.waze.com/*user/*editor/*
  11. // @exclude https://www.waze.com/discuss/*
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Configuración
  19. const CONFIG = {
  20. delayBetweenUpdates: 100, // ms entre actualizaciones
  21. modalStyle: {
  22. position: 'fixed',
  23. top: '50%',
  24. left: '50%',
  25. transform: 'translate(-50%, -50%)',
  26. background: 'white',
  27. padding: '20px',
  28. border: '2px solid #ccc',
  29. borderRadius: '5px',
  30. boxShadow: '0 0 10px rgba(0,0,0,0.2)',
  31. zIndex: '10000',
  32. maxHeight: '70vh',
  33. overflowY: 'auto',
  34. width: 'auto',
  35. minWidth: '500px'
  36. }
  37. };
  38.  
  39. // Diccionario de traducción de categorías a español
  40. const CATEGORY_TRANSLATIONS = {
  41. 'restaurant': 'Restaurante',
  42. 'swimming_pool': 'Piscina',
  43. 'factory_industrial': 'Fábrica',
  44. 'farm': 'Granja',
  45. 'sea_lake_pool': 'Lago',
  46. 'river_stream': 'Río',
  47. 'forest_grove': 'Bosque',
  48. 'cafe': 'Cafetería',
  49. 'sports_court': 'Cancha Deportiva',
  50. 'shopping_and_services': 'Tienda',
  51. 'car_services': 'Servios para el Automovil',
  52. 'hotel': 'Hotel',
  53. 'sport_court': 'Escenario Deportivo',
  54. 'gas station': 'Estación de servicio',
  55. 'hospital': 'Hospital',
  56. 'pharmacy': 'Farmacia',
  57. 'bank': 'Banco',
  58. 'atm': 'Cajero automático',
  59. 'parking': 'Estacionamiento',
  60. 'parking_lot': 'Parqueadero',
  61. 'garage_automotive_shop': 'Tienda para Vehículos',
  62. 'natural_features': 'Características Naturales',
  63. 'school': 'Escuela',
  64. 'university': 'Universidad',
  65. 'museum': 'Museo',
  66. 'park': 'Parque',
  67. 'mall': 'Centro comercial',
  68. 'stadium_arena': 'Estadio',
  69. 'supermarket': 'Supermercado',
  70. 'gym': 'Gimnasio',
  71. 'church': 'Iglesia',
  72. 'police': 'Comisaría',
  73. 'fire station': 'Estación de bomberos',
  74. 'library': 'Biblioteca',
  75. 'stadium': 'Estadio',
  76. 'cinema': 'Cine',
  77. 'theater': 'Teatro',
  78. 'zoo': 'Zoológico',
  79. 'airport': 'Aeropuerto',
  80. 'train station': 'Estación de tren',
  81. 'bus station': 'Estación de autobuses',
  82. 'car wash': 'Lavado de coches',
  83. 'car repair': 'Taller mecánico',
  84. 'construction_site': 'Sitio en Construcción',
  85. 'dentist': 'Dentista',
  86. 'doctor': 'Médico',
  87. 'clinic': 'Clínica',
  88. 'veterinary': 'Veterinario',
  89. 'post office': 'Oficina de correos',
  90. 'shopping': 'Tiendas',
  91. 'bakery': 'Panadería',
  92. 'butcher': 'Carnicería',
  93. 'market': 'Mercado',
  94. 'florist': 'Florería',
  95. 'book store': 'Librería',
  96. 'electronics': 'Electrónica',
  97. 'furniture': 'Mueblería',
  98. 'jewelry': 'Joyería',
  99. 'optician': 'Óptica',
  100. 'pet store': 'Tienda de mascotas',
  101. 'sports': 'Artículos deportivos',
  102. 'toy store': 'Juguetería',
  103. 'department store': 'Grandes almacenes'
  104. };
  105.  
  106. // Función para esperar a que WME esté listo
  107. function waitForWME() {
  108. return new Promise(resolve => {
  109. if (window.W && W.model && W.model.venues && W.model.actionManager) {
  110. resolve();
  111. } else {
  112. setTimeout(() => resolve(waitForWME()), 500);
  113. }
  114. });
  115. }
  116.  
  117. // Obtener places sin nombre (optimizado)
  118. function getUnnamedPlaces() {
  119. if (!W.model.venues?.objects) return [];
  120.  
  121. return Object.values(W.model.venues.objects).filter(place => {
  122. const name = place.attributes?.name;
  123. return !name || name.trim() === '';
  124. });
  125. }
  126.  
  127. // Generar nombre automático en español
  128. function generateName(place) {
  129. if (!place.attributes) return "Desconocido";
  130.  
  131. if (place.attributes.residential) return "Residencial";
  132. if (place.attributes.categories?.[0]) {
  133. const category = place.attributes.categories[0].toLowerCase();
  134. return CATEGORY_TRANSLATIONS[category] ||
  135. category.charAt(0).toUpperCase() + category.slice(1);
  136. }
  137. return "Desconocido";
  138. }
  139.  
  140. // Crear tabla de aprobación (optimizado)
  141. function createApprovalTable(places) {
  142. // Eliminar modal existente
  143. document.getElementById('wme-auto-name-modal')?.remove();
  144.  
  145. const modal = document.createElement('div');
  146. modal.id = 'wme-auto-name-modal';
  147. Object.assign(modal.style, CONFIG.modalStyle);
  148.  
  149. // Botón de cierre
  150. const closeBtn = document.createElement('button');
  151. closeBtn.innerHTML = '×';
  152. closeBtn.style.position = 'absolute';
  153. closeBtn.style.right = '10px';
  154. closeBtn.style.top = '10px';
  155. closeBtn.style.background = 'transparent';
  156. closeBtn.style.border = 'none';
  157. closeBtn.style.fontSize = '20px';
  158. closeBtn.style.cursor = 'pointer';
  159. closeBtn.onclick = () => modal.remove();
  160. modal.appendChild(closeBtn);
  161.  
  162. // Título
  163. const title = document.createElement('h3');
  164. title.textContent = `Places sin nombre encontrados: ${places.length}`;
  165. title.style.marginTop = '0';
  166. title.style.color = '#333';
  167. modal.appendChild(title);
  168.  
  169. // Tabla
  170. const table = document.createElement('table');
  171. table.style.width = '100%';
  172. table.style.borderCollapse = 'collapse';
  173. table.style.marginBottom = '15px';
  174.  
  175. const thead = document.createElement('thead');
  176. thead.innerHTML = `
  177. <tr style="background-color: #f5f5f5;">
  178. <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">ID</th>
  179. <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">Categoría</th>
  180. <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">Nuevo Nombre</th>
  181. <th style="padding: 8px; text-align: left; border-bottom: 1px solid #ddd;">Aprobar</th>
  182. </tr>
  183. `;
  184. table.appendChild(thead);
  185.  
  186. const tbody = document.createElement('tbody');
  187. places.forEach(place => {
  188. const row = document.createElement('tr');
  189. row.style.borderBottom = '1px solid #eee';
  190. const newName = generateName(place);
  191. const category = place.attributes.categories?.[0] ?
  192. (CATEGORY_TRANSLATIONS[place.attributes.categories[0].toLowerCase()] ||
  193. place.attributes.categories[0]) :
  194. "N/A";
  195. row.innerHTML = `
  196. <td style="padding: 8px;">${place.attributes.id}</td>
  197. <td style="padding: 8px;">${category}</td>
  198. <td style="padding: 8px;">${newName}</td>
  199. <td style="padding: 8px; text-align: center;">
  200. <input type='checkbox' data-id='${place.attributes.id}' checked>
  201. </td>
  202. `;
  203. tbody.appendChild(row);
  204. });
  205. table.appendChild(tbody);
  206. modal.appendChild(table);
  207.  
  208. // Botones
  209. const buttonContainer = document.createElement('div');
  210. buttonContainer.style.display = 'flex';
  211. buttonContainer.style.justifyContent = 'flex-end';
  212. buttonContainer.style.gap = '10px';
  213.  
  214. const createButton = (text, color, onClick) => {
  215. const btn = document.createElement('button');
  216. btn.textContent = text;
  217. btn.style.padding = '8px 16px';
  218. btn.style.background = color;
  219. btn.style.color = 'white';
  220. btn.style.border = 'none';
  221. btn.style.borderRadius = '4px';
  222. btn.style.cursor = 'pointer';
  223. btn.onclick = onClick;
  224. return btn;
  225. };
  226.  
  227. buttonContainer.appendChild(
  228. createButton('Cancelar', '#f44336', () => modal.remove())
  229. );
  230. buttonContainer.appendChild(
  231. createButton('Aplicar Cambios', '#4CAF50', () => applyChanges(places))
  232. );
  233.  
  234. modal.appendChild(buttonContainer);
  235. document.body.appendChild(modal);
  236. }
  237.  
  238. // Aplicar cambios seleccionados
  239. async function applyChanges(places) {
  240. const modal = document.getElementById('wme-auto-name-modal');
  241. if (!modal) return;
  242.  
  243. const applyBtn = modal.querySelector('button:last-child');
  244. const checkboxes = modal.querySelectorAll("input[type='checkbox']:checked");
  245. const UpdateObject = require("Waze/Action/UpdateObject");
  246.  
  247. if (!checkboxes.length) {
  248. alert('No hay cambios seleccionados para aplicar.');
  249. return;
  250. }
  251.  
  252. try {
  253. // Deshabilitar botón durante la operación
  254. applyBtn.disabled = true;
  255. applyBtn.textContent = 'Guardando...';
  256. applyBtn.style.background = '#cccccc';
  257.  
  258. let changesMade = false;
  259. let successCount = 0;
  260.  
  261. // Procesar cada checkbox seleccionado
  262. for (const checkbox of checkboxes) {
  263. const placeId = checkbox.dataset.id;
  264. const place = W.model.venues.getObjectById(placeId);
  265.  
  266. if (!place) {
  267. console.warn(`⛔ No se encontró el lugar con ID: ${placeId}`);
  268. continue;
  269. }
  270.  
  271. const newName = generateName(place);
  272. const currentName = place.attributes.name || "";
  273.  
  274. if (newName && newName !== "" && currentName.trim() !== newName) {
  275. try {
  276. const action = new UpdateObject(place, { name: newName });
  277. W.model.actionManager.add(action);
  278. console.log(`✅ Nombre actualizado: "${currentName}" "${newName}"`);
  279. changesMade = true;
  280. successCount++;
  281.  
  282. // Pequeña pausa entre cambios
  283. await new Promise(resolve => setTimeout(resolve, CONFIG.delayBetweenUpdates));
  284. } catch (error) {
  285. console.error(`⛔ Error actualizando place ${placeId}:`, error);
  286. }
  287. } else {
  288. console.log(`⏭ Sin cambios reales para ID ${placeId} (ya tiene nombre o no es necesario cambiarlo)`);
  289. }
  290. }
  291.  
  292. modal.remove();
  293.  
  294. if (changesMade) {
  295. alert(`💾 ${successCount} cambios aplicados correctamente. Recuerda presionar el botón de guardar en el editor.`);
  296. // Forzar actualización visual del mapa
  297. W.map?.invalidate?.();
  298. } else {
  299. alert("ℹ️ No hubo cambios para aplicar.");
  300. }
  301.  
  302. } catch (error) {
  303. console.error('⛔ Error al aplicar cambios:', error);
  304. alert('Error al guardar cambios: ' + (error.message || error));
  305. } finally {
  306. if (applyBtn) {
  307. applyBtn.disabled = false;
  308. applyBtn.textContent = 'Aplicar Cambios';
  309. applyBtn.style.background = '#4CAF50';
  310. }
  311. }
  312. }
  313.  
  314. // Añadir pestaña al panel de scripts
  315. function addScriptToSettingsTab() {
  316. const tabName = 'Auto Name Places';
  317. const tabSelector = `#user-tabs a[title="${tabName}"]`;
  318.  
  319. if (document.querySelector(tabSelector)) return;
  320.  
  321. const observer = new MutationObserver((mutations, obs) => {
  322. const settingsPanel = document.querySelector("#user-tabs");
  323. if (settingsPanel) {
  324. obs.disconnect();
  325.  
  326. const newTab = document.createElement("li");
  327. newTab.innerHTML = `<a href="#" title="${tabName}">${tabName}</a>`;
  328. settingsPanel.appendChild(newTab);
  329.  
  330. newTab.querySelector('a').addEventListener("click", function(e) {
  331. e.preventDefault();
  332. init();
  333. });
  334. }
  335. });
  336.  
  337. observer.observe(document.body, { childList: true, subtree: true });
  338. }
  339.  
  340. // Función de inicialización
  341. async function init() {
  342. try {
  343. await waitForWME();
  344. const unnamedPlaces = getUnnamedPlaces();
  345.  
  346. if (!unnamedPlaces.length) {
  347. alert("No se encontraron Places sin nombre en el área actual.");
  348. return;
  349. }
  350.  
  351. createApprovalTable(unnamedPlaces);
  352. } catch (error) {
  353. console.error('⛔ Error en init:', error);
  354. alert('Error al buscar places sin nombre: ' + (error.message || error));
  355. }
  356. }
  357.  
  358. // Iniciar script cuando WME esté listo
  359. waitForWME().then(() => {
  360. addScriptToSettingsTab();
  361. }).catch(error => {
  362. console.error('⛔ Error al inicializar WME:', error);
  363. });
  364. })();

QingJ © 2025

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