// ==UserScript==
// @name WME Internationale Vorwahl DE - Final Working Version
// @version 2025.06.09.1
// @description Wandelt Telefonnummern automatisch in internationale Formate um, bereinigt deutsche Firmen-Rechtsformen und standardisiert POI-Namen - Funktionsfähige Version
// @author Hiwi234 (Final Working Version)
// @namespace https://greasyfork.org/de/users/863740-horst-wittlich
// @match https://www.waze.com/editor*
// @match https://www.waze.com/*/editor*
// @match https://beta.waze.com/editor*
// @match https://beta.waze.com/*/editor*
// @grant none
// @license MIT
// ==/UserScript==
/* global W */
(function() {
'use strict';
const SCRIPT_ID = 'wme-phone-formatter-final';
// Default settings
let settings = {
countryCode: '+49',
countryNumericCode: '0049',
enabled: true,
autoRemoveHttps: true,
customPatterns: [],
companyNameCleaning: true,
autoCleaningEnabled: false,
poiStandardization: true
};
// Standardized POI names from Waze community guidelines
const STANDARDIZED_POI_NAMES = {
// Tankstellen
'aral': 'Aral',
'esso': 'Esso',
'shell': 'Shell',
'jet': 'JET',
'totalenergies': 'TotalEnergies',
'bp': 'bp',
'hem': 'HEM',
'star': 'star',
'bft': 'bft',
'westfalen': 'Westfalen',
// Supermärkte
'aldi': 'ALDI',
'aldi nord': 'ALDI',
'aldi süd': 'ALDI',
'edeka': 'EDEKA',
'rewe': 'REWE',
'lidl': 'Lidl',
'penny': 'PENNY',
'netto': 'Netto',
'norma': 'NORMA',
'kaufland': 'Kaufland',
'metro': 'METRO',
// Elektronik
'media markt': 'Media Markt',
'mediamarkt': 'Media Markt',
'saturn': 'Saturn',
// Fast Food
'mcdonalds': "McDonald's",
'burger king': 'Burger King',
'kfc': 'KFC',
'subway': 'SUBWAY',
// Drogerie
'dm': 'dm-drogerie markt',
'dm-drogerie markt': 'dm-drogerie markt',
'rossmann': 'ROSSMANN',
'müller': 'Müller',
// Baumärkte
'bauhaus': 'BAUHAUS',
'obi': 'OBI',
'hornbach': 'HORNBACH',
'toom': 'toom Baumarkt',
'hagebaumarkt': 'hagebaumarkt'
};
// Load settings from localStorage
function loadSettings() {
try {
const savedSettings = localStorage.getItem(SCRIPT_ID + '_settings');
if (savedSettings) {
settings = Object.assign({}, settings, JSON.parse(savedSettings));
}
} catch (e) {
console.warn('Fehler beim Laden der Einstellungen:', e);
}
}
// Save settings to localStorage
function saveSettings() {
try {
localStorage.setItem(SCRIPT_ID + '_settings', JSON.stringify(settings));
} catch (e) {
console.warn('Fehler beim Speichern der Einstellungen:', e);
}
}
// Function to standardize POI names
function standardizePOIName(name) {
if (!name) return name;
const lowerName = name.toLowerCase().trim();
if (STANDARDIZED_POI_NAMES[lowerName]) {
return STANDARDIZED_POI_NAMES[lowerName];
}
for (const key in STANDARDIZED_POI_NAMES) {
if (lowerName.includes(key)) {
const regex = new RegExp('\\b' + key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'gi');
if (regex.test(lowerName)) {
return STANDARDIZED_POI_NAMES[key];
}
}
}
return name;
}
// Function to clean German company legal forms
function cleanCompanyName(name) {
if (!settings.companyNameCleaning || !name) return name;
if (settings.poiStandardization) {
name = standardizePOIName(name);
}
const cleanedName = name
.replace(/\bGmbH & Co\.?\s*KG\b/gi, "")
.replace(/\bGmbH\b/gi, "")
.replace(/\b AG\b/gi, "")
.replace(/\bAktiengesellschaft\b/gi, "")
.replace(/\bCo\.?\s*KG\b/gi, "")
.replace(/\be\.?V\.?\b/gi, "")
.replace(/\beingetragener Verein\b/gi, "")
.replace(/\bUG\b/gi, "")
.replace(/\bhaftungsbeschränkt\b/gi, "")
.replace(/\s+/g, ' ')
.trim();
return cleanedName || name;
}
// Function to convert phone numbers
function formatPhoneNumber(input) {
if (!settings.enabled || !input) return input;
if (settings.autoRemoveHttps) {
if (input.toLowerCase().startsWith('https://')) {
input = input.substring(8);
} else if (input.toLowerCase().startsWith('http://')) {
input = input.substring(7);
}
}
let number = input.replace(/[\s\-\(\)\/]/g, '');
const patterns = [
{
regex: new RegExp('^0(\\d+)$'),
replacement: settings.countryCode + '$1'
},
{
regex: new RegExp('^\\' + settings.countryCode.replace('+', '\\+') + '(\\d+)$'),
replacement: settings.countryCode + '$1'
},
{
regex: new RegExp('^' + settings.countryNumericCode + '0?(\\d+)$'),
replacement: settings.countryCode + '$1'
}
];
for (let i = 0; i < settings.customPatterns.length; i++) {
const pattern = settings.customPatterns[i];
if (pattern.regex && pattern.replacement) {
try {
patterns.push({
regex: new RegExp(pattern.regex),
replacement: pattern.replacement
});
} catch(e) {
console.warn('Ungültiges Regex-Pattern:', pattern.regex);
}
}
}
for (let i = 0; i < patterns.length; i++) {
const pattern = patterns[i];
if (pattern.regex.test(number)) {
return number.replace(pattern.regex, pattern.replacement);
}
}
return input;
}
// Function to format website fields
function formatWebsiteField(input) {
if (!settings.autoRemoveHttps || !input) return input;
if (input.toLowerCase().startsWith('https://')) {
return input.substring(8);
}
if (input.toLowerCase().startsWith('http://')) {
return input.substring(7);
}
return input;
}
// Field Detection Functions
function isPhoneField(element) {
if (!element) return false;
return (
element.name === 'phone' ||
element.type === 'tel' ||
(element.placeholder && element.placeholder.toLowerCase().includes('phone')) ||
(element.placeholder && element.placeholder.toLowerCase().includes('telefon'))
);
}
function isWebsiteField(element) {
if (!element) return false;
return (
element.name === 'url' ||
element.name === 'website' ||
element.type === 'url' ||
(element.placeholder && element.placeholder.toLowerCase().includes('website'))
);
}
function isNameField(element) {
if (!element || element.type !== 'text') return false;
if (isPhoneField(element) || isWebsiteField(element)) {
return false;
}
const isInVenueContext = element.closest('[class*="venue"]') ||
element.closest('[class*="poi"]') ||
element.closest('form');
const isNameInput = (
element.name === 'name' ||
element.name === 'subject' ||
(element.placeholder && element.placeholder.toLowerCase().includes('name'))
);
return isInVenueContext && isNameInput;
}
// Update WME model using WME's own edit system
function updateWMEModel(element, newValue) {
try {
console.log('🔄 Using WME Edit System...');
if (W.selectionManager && W.selectionManager.getSelectedDataModelObjects) {
const selectedObjects = W.selectionManager.getSelectedDataModelObjects();
if (selectedObjects && selectedObjects.length > 0) {
const selectedObject = selectedObjects[0];
let fieldName = null;
let originalValue = null;
// Determine field and get original value
if (isNameField(element)) {
if (selectedObject.type === 'venue' && selectedObject.attributes.name !== undefined) {
fieldName = 'name';
originalValue = selectedObject.attributes.name;
} else if (selectedObject.type === 'mapComment' && selectedObject.attributes.subject !== undefined) {
fieldName = 'subject';
originalValue = selectedObject.attributes.subject;
}
} else if (isPhoneField(element) && selectedObject.attributes.phone !== undefined) {
fieldName = 'phone';
originalValue = selectedObject.attributes.phone;
} else if (isWebsiteField(element) && selectedObject.attributes.url !== undefined) {
fieldName = 'url';
originalValue = selectedObject.attributes.url;
}
if (fieldName && originalValue !== newValue) {
console.log('🎯 Creating WME Edit Action...');
// Method 1: Try using WME's Action system
if (W.model && W.model.actionManager) {
try {
// Create proper WME edit action
const editAction = {
description: `Update ${fieldName}`,
object: selectedObject,
oldAttributes: {},
newAttributes: {}
};
editAction.oldAttributes[fieldName] = originalValue;
editAction.newAttributes[fieldName] = newValue;
// Apply the edit
selectedObject.attributes[fieldName] = newValue;
// Try to register as a proper WME action
if (W.model.actionManager.add && typeof W.model.actionManager.add === 'function') {
try {
W.model.actionManager.add(editAction);
console.log('✅ Edit registered with ActionManager');
} catch (actionError) {
console.log('⚠️ ActionManager registration failed:', actionError.message);
}
}
// Try alternative WME edit methods
if (W.model.actionManager.push && typeof W.model.actionManager.push === 'function') {
try {
W.model.actionManager.push(editAction);
console.log('✅ Edit pushed to ActionManager');
} catch (pushError) {
console.log('⚠️ ActionManager push failed:', pushError.message);
}
}
console.log('✅ WME Edit Action applied:', originalValue, '→', newValue);
} catch (actionError) {
console.warn('⚠️ WME Action system failed:', actionError.message);
}
}
// Method 2: Try triggering WME's change detection
try {
// Force WME to detect the change
if (selectedObject.trigger && typeof selectedObject.trigger === 'function') {
selectedObject.trigger('change:' + fieldName);
selectedObject.trigger('change');
console.log('✅ Triggered object change events');
}
// Trigger model-level changes
if (W.model && W.model.trigger) {
W.model.trigger('change');
console.log('✅ Triggered model change');
}
// Trigger selection manager events
if (W.selectionManager && W.selectionManager.trigger) {
W.selectionManager.trigger('selectionchanged');
console.log('✅ Triggered selection change');
}
} catch (eventError) {
console.warn('⚠️ Event triggering failed:', eventError.message);
}
// Method 3: Force UI refresh and validate change persistence
setTimeout(() => {
console.log('🔄 Validating change persistence...');
// Deselect and reselect to refresh UI
W.selectionManager.setSelectedModels([]);
setTimeout(() => {
W.selectionManager.setSelectedModels([selectedObject]);
// Check if change persisted
setTimeout(() => {
const currentValue = selectedObject.attributes[fieldName];
if (currentValue === newValue) {
console.log('✅ Change persisted successfully:', currentValue);
// Update form field if it reverted
if (element.value !== newValue) {
element.value = newValue;
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
console.log('✅ Form field synchronized');
}
} else {
console.log('⚠️ Change did not persist. Current:', currentValue, 'Expected:', newValue);
// Try to reapply the change
selectedObject.attributes[fieldName] = newValue;
element.value = newValue;
console.log('🔄 Change reapplied');
}
}, 200);
}, 100);
}, 50);
return true;
}
} else {
console.warn('⚠️ No objects selected');
}
}
return false;
} catch (error) {
console.error('❌ WME edit system failed:', error);
return false;
}
}
// Safe field update
function updateField(element, newValue) {
if (!element || !newValue) return;
try {
console.log('🔄 Updating field:', newValue);
updateWMEModel(element, newValue);
element.focus();
element.value = newValue;
element.dispatchEvent(new Event('input', { bubbles: true }));
element.dispatchEvent(new Event('change', { bubbles: true }));
element.blur();
console.log('✅ Field updated');
} catch (error) {
console.error('❌ Field update failed:', error);
try {
element.value = newValue;
element.dispatchEvent(new Event('change', { bubbles: true }));
} catch (fallbackError) {
console.error('❌ Fallback failed:', fallbackError);
}
}
}
// Event listener setup
function setupEventListeners() {
console.log('Setting up event listeners...');
document.addEventListener('input', function(event) {
const target = event.target;
if (isPhoneField(target)) {
const originalValue = target.value;
const formattedValue = formatPhoneNumber(originalValue);
if (formattedValue !== originalValue) {
setTimeout(() => updateField(target, formattedValue), 10);
console.log('📞 Phone scheduled:', originalValue, '→', formattedValue);
}
}
if (isWebsiteField(target)) {
const originalValue = target.value;
const formattedValue = formatWebsiteField(originalValue);
if (formattedValue !== originalValue) {
setTimeout(() => updateField(target, formattedValue), 10);
console.log('🌐 Website scheduled:', originalValue, '→', formattedValue);
}
}
if (settings.autoCleaningEnabled && isNameField(target)) {
const originalValue = target.value;
const cleanedValue = cleanCompanyName(originalValue);
if (cleanedValue !== originalValue && cleanedValue.length > 0) {
setTimeout(() => updateField(target, cleanedValue), 50);
console.log('🏢 Name scheduled:', originalValue, '→', cleanedValue);
}
}
}, true);
console.log('✅ Event listeners setup complete');
}
// Process all venues and POIs using WME edit system
function processVenuesAndPOIs() {
if (!settings.companyNameCleaning) {
console.log('Company name cleaning ist deaktiviert');
return 0;
}
try {
let processedCount = 0;
console.log('🔄 Starte WME-Edit-System Bereinigung...');
if (!W || !W.model) {
console.error('W.model ist nicht verfügbar');
return 0;
}
const changedObjects = [];
// Process venues with WME edit system
if (W.model.venues) {
const venues = W.model.venues.getObjectArray();
console.log('Gefunden: ' + venues.length + ' Venues');
venues.forEach(venue => {
if (venue && venue.attributes && venue.attributes.name) {
const originalName = venue.attributes.name;
const cleanedName = cleanCompanyName(originalName);
if (cleanedName !== originalName) {
console.log('🏢 Processing venue:', originalName, '→', cleanedName);
// Store change for WME action system
const change = {
object: venue,
field: 'name',
oldValue: originalName,
newValue: cleanedName,
type: 'venue'
};
// Apply the change
venue.attributes.name = cleanedName;
changedObjects.push(change);
console.log('✅ Venue cleaned:', originalName, '→', cleanedName);
processedCount++;
}
}
});
}
// Process POIs with WME edit system
if (W.model.mapComments) {
const pois = W.model.mapComments.getObjectArray();
console.log('Gefunden: ' + pois.length + ' POIs');
pois.forEach(poi => {
if (poi && poi.attributes && poi.attributes.subject) {
const originalName = poi.attributes.subject;
const cleanedName = cleanCompanyName(originalName);
if (cleanedName !== originalName) {
console.log('📝 Processing POI:', originalName, '→', cleanedName);
// Store change for WME action system
const change = {
object: poi,
field: 'subject',
oldValue: originalName,
newValue: cleanedName,
type: 'poi'
};
// Apply the change
poi.attributes.subject = cleanedName;
changedObjects.push(change);
console.log('✅ POI cleaned:', originalName, '→', cleanedName);
processedCount++;
}
}
});
}
// Register all changes with WME's action system
if (changedObjects.length > 0) {
console.log('🎯 Registering ' + changedObjects.length + ' changes with WME...');
try {
// Create a proper WME Action class
function NameCleaningAction(changes) {
this.description = 'Clean venue/POI names (' + changes.length + ' changes)';
this.changes = changes;
this.isLiveUpdates = true;
}
// Required WME Action methods
NameCleaningAction.prototype.undoSupported = function() {
return true;
};
NameCleaningAction.prototype.doAction = function() {
console.log('🔄 Executing WME Action...');
this.changes.forEach(change => {
change.object.attributes[change.field] = change.newValue;
});
return true;
};
NameCleaningAction.prototype.undoAction = function() {
console.log('🔄 Undoing WME Action...');
this.changes.forEach(change => {
change.object.attributes[change.field] = change.oldValue;
});
return true;
};
NameCleaningAction.prototype.getDescription = function() {
return this.description;
};
// Create and register the action
const nameCleaningAction = new NameCleaningAction(changedObjects);
// Try to register with WME ActionManager
if (W.model && W.model.actionManager) {
if (W.model.actionManager.add) {
try {
W.model.actionManager.add(nameCleaningAction);
console.log('✅ WME Action successfully registered!');
console.log('💾 Changes are now registered as edits in WME');
} catch (e) {
console.log('⚠️ ActionManager registration failed:', e.message);
console.log('🔄 Trying alternative registration...');
// Alternative: Try direct ActionManager methods
if (W.model.actionManager.doAction) {
try {
W.model.actionManager.doAction(nameCleaningAction);
console.log('✅ Action executed via ActionManager.doAction');
} catch (e2) {
console.log('⚠️ doAction failed:', e2.message);
}
}
}
}
}
// Trigger change events for all modified objects
changedObjects.forEach(change => {
try {
if (change.object.trigger) {
change.object.trigger('change:' + change.field);
change.object.trigger('change');
}
} catch (e) {
console.log('⚠️ Object event trigger failed:', e.message);
}
});
// Trigger global model events
if (W.model && W.model.trigger) {
try {
W.model.trigger('change');
W.model.trigger('batchupdate');
console.log('✅ Global model events triggered');
} catch (e) {
console.log('⚠️ Global model events failed:', e.message);
}
}
// Force UI refresh
if (W.selectionManager) {
try {
const currentSelection = W.selectionManager.getSelectedDataModelObjects();
W.selectionManager.setSelectedModels([]);
setTimeout(() => {
if (currentSelection && currentSelection.length > 0) {
W.selectionManager.setSelectedModels(currentSelection);
}
console.log('✅ UI refreshed');
}, 300);
} catch (e) {
console.log('⚠️ UI refresh failed:', e.message);
}
}
} catch (error) {
console.error('❌ WME action registration failed:', error);
console.log('🔄 Changes applied directly, but may not be recognized as WME edits');
}
}
console.log('🎉 WME-Edit-System Bereinigung abgeschlossen: ' + processedCount + ' Namen bereinigt');
console.log('💡 Hinweis: Verwende Strg+S zum Speichern der Änderungen in WME');
return processedCount;
} catch (error) {
console.error('❌ Fehler beim WME-Edit-System:', error);
return 0;
}
}
// Create settings UI
function createSettingsUI(tabPane) {
const container = document.createElement('div');
container.style.cssText = 'padding: 15px; font-family: Arial, sans-serif; max-height: 500px; overflow-y: auto;';
// Title
const title = document.createElement('h3');
title.textContent = '📞 Phone & POI Formatter';
title.style.cssText = 'margin: 0 0 15px 0; color: #333; border-bottom: 2px solid #00a8cc; padding-bottom: 5px;';
container.appendChild(title);
// Settings section
const settingsDiv = document.createElement('div');
settingsDiv.style.cssText = 'margin-bottom: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #00a8cc;';
// Checkboxes
const checkboxes = [
{ key: 'enabled', label: 'Script aktiviert' },
{ key: 'companyNameCleaning', label: 'Firmen-Rechtsformen bereinigen' },
{ key: 'poiStandardization', label: 'POI-Namen standardisieren' },
{ key: 'autoCleaningEnabled', label: 'Automatische Bereinigung' },
{ key: 'autoRemoveHttps', label: 'HTTP(S):// automatisch entfernen' }
];
checkboxes.forEach(config => {
const label = document.createElement('label');
label.style.cssText = 'display: block; margin-bottom: 10px; font-weight: bold;';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = settings[config.key];
checkbox.style.marginRight = '8px';
checkbox.addEventListener('change', () => {
settings[config.key] = checkbox.checked;
});
label.appendChild(checkbox);
label.appendChild(document.createTextNode(config.label));
settingsDiv.appendChild(label);
});
// Country code input
const countryLabel = document.createElement('label');
countryLabel.style.cssText = 'display: block; margin-bottom: 5px; font-weight: bold;';
countryLabel.textContent = 'Ländervorwahl:';
const countryInput = document.createElement('input');
countryInput.type = 'text';
countryInput.value = settings.countryCode;
countryInput.style.cssText = 'width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 10px;';
countryInput.addEventListener('input', () => {
settings.countryCode = countryInput.value;
});
settingsDiv.appendChild(countryLabel);
settingsDiv.appendChild(countryInput);
// Buttons
const buttonsDiv = document.createElement('div');
buttonsDiv.style.marginTop = '15px';
const saveButton = document.createElement('button');
saveButton.textContent = '💾 Speichern';
saveButton.style.cssText = 'background: #00a8cc; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;';
saveButton.addEventListener('click', () => {
saveSettings();
showStatusMessage('Einstellungen gespeichert!', 'success');
});
const processButton = document.createElement('button');
processButton.textContent = '🏢 Alle bereinigen';
processButton.style.cssText = 'background: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;';
processButton.addEventListener('click', () => {
if (confirm('Alle Venue- und POI-Namen bereinigen?')) {
const count = processVenuesAndPOIs();
showStatusMessage(count + ' Namen bereinigt!', 'success');
}
});
buttonsDiv.appendChild(saveButton);
buttonsDiv.appendChild(processButton);
settingsDiv.appendChild(buttonsDiv);
// Status message area
const statusDiv = document.createElement('div');
statusDiv.id = 'statusMessage';
statusDiv.style.cssText = 'margin-top: 10px; padding: 8px; border-radius: 4px; display: none;';
settingsDiv.appendChild(statusDiv);
container.appendChild(settingsDiv);
// Test area
const testDiv = document.createElement('div');
testDiv.style.cssText = 'margin-top: 15px; padding: 10px; background: #f0f0f0; border-radius: 5px;';
const testTitle = document.createElement('h4');
testTitle.textContent = '🧪 Test-Bereich';
testTitle.style.margin = '0 0 10px 0';
testDiv.appendChild(testTitle);
const testInput = document.createElement('input');
testInput.type = 'text';
testInput.placeholder = 'Test: aldi süd gmbh, 01234567890, https://example.com';
testInput.style.cssText = 'width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 5px;';
const testResult = document.createElement('div');
testResult.style.cssText = 'font-weight: bold; color: #28a745; min-height: 20px;';
testInput.addEventListener('input', () => {
const value = testInput.value;
let result = '';
if (/^\d/.test(value) || value.toLowerCase().startsWith('http')) {
result = 'Phone/URL: ' + formatPhoneNumber(value);
} else {
result = 'Name: ' + cleanCompanyName(value);
}
testResult.textContent = result;
});
testDiv.appendChild(testInput);
testDiv.appendChild(testResult);
container.appendChild(testDiv);
tabPane.appendChild(container);
}
// Show status message
function showStatusMessage(message, type) {
const statusEl = document.getElementById('statusMessage');
if (!statusEl) return;
statusEl.textContent = message;
if (type === 'success') {
statusEl.style.cssText = 'margin-top: 10px; padding: 8px; border-radius: 4px; display: block; background: #d4edda; color: #155724; border: 1px solid #c3e6cb;';
} else {
statusEl.style.cssText = 'margin-top: 10px; padding: 8px; border-radius: 4px; display: block; background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb;';
}
setTimeout(() => {
statusEl.style.display = 'none';
}, 3000);
}
// Initialize the script
async function initializeScript() {
try {
console.log('WME Phone Formatter wird initialisiert...');
loadSettings();
setupEventListeners();
if (W && W.userscripts && W.userscripts.registerSidebarTab) {
const { tabLabel, tabPane } = W.userscripts.registerSidebarTab(SCRIPT_ID);
tabLabel.textContent = '📞🏢';
tabLabel.title = 'Phone & POI Formatter';
await W.userscripts.waitForElementConnected(tabPane);
createSettingsUI(tabPane);
console.log('WME Phone Formatter erfolgreich initialisiert');
} else {
console.error('WME userscripts API nicht verfügbar');
}
} catch (error) {
console.error('Fehler bei der Initialisierung:', error);
}
}
// Wait for WME and initialize
if (W?.userscripts?.state.isInitialized) {
initializeScript();
} else {
document.addEventListener("wme-initialized", initializeScript, {
once: true
});
}
})();