Универсальные кликабельные номера телефонов на странице (Международные)

Скрипт для замены номеров телефона на кликабельные ссылки, работает с любыми международными номерами на любых страницах.

目前為 2025-02-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name             Универсальные кликабельные номера телефонов на странице (Международные)
// @name:en          Universal Clickable Phone Numbers (International)
// @namespace        http://tampermonkey.net/
// @version          2.4
// @description      Скрипт для замены номеров телефона на кликабельные ссылки, работает с любыми международными номерами на любых страницах.
// @description:en   Script to replace phone numbers with clickable links, works with any international numbers on any pages.
// @author           Coffee_Feather
// @match            *://*/*
// @grant            none
// @noframes
// @license          MIT
// ==/UserScript==

(function() {
    'use strict';

    const config = {
        phoneRegex: /(?:\+?\d{1,4}[-\s]?)?(?:\(?\d{1,4}\)?[-\s]?)*\d{3}[-\s]?\d{2,4}(?:[-\s]?\d{2,4})?/g,
        excludedDomains: ['google.com', 'youtube.com'],
        processedClass: 'tm-phone-safe',
        debounceTime: 300
    };

    function processNode(node) {
        const text = node.nodeValue;
        const matches = text.match(config.phoneRegex);

        if (!matches || node.parentNode.classList.contains(config.processedClass)) return;

        let newContent = text;
        matches.forEach(match => {
            const clean = match.replace(/[^\d+]/g, '');
            newContent = newContent.replace(match,
                `<a href="tel:${clean}"
                    style="color: inherit; text-decoration: inherit; cursor: pointer;"
                    class="${config.processedClass}">
                    ${match}
                </a>`
            );
        });

        const wrapper = document.createElement('span');
        wrapper.innerHTML = newContent;

        // Сохраняем оригинальные стили родителя
        const originalParent = node.parentNode;
        originalParent.innerHTML = originalParent.innerHTML.replace(text, wrapper.innerHTML);
        originalParent.classList.add(config.processedClass);
    }

    function safeProcessor() {
        if (config.excludedDomains.includes(location.hostname)) return;

        const walker = document.createTreeWalker(
            document.body,
            NodeFilter.SHOW_TEXT,
            {
                acceptNode(node) {
                    return config.phoneRegex.test(node.nodeValue) &&
                        !node.parentNode.classList.contains(config.processedClass) &&
                        !node.parentNode.closest('a') ?
                        NodeFilter.FILTER_ACCEPT :
                        NodeFilter.FILTER_REJECT;
                }
            }
        );

        const nodes = [];
        while (walker.nextNode()) nodes.push(walker.currentNode);
        nodes.reverse().forEach(processNode);
    }

    // Инициализация
    if (!config.excludedDomains.includes(location.hostname)) {
        safeProcessor();
        new MutationObserver(() => safeProcessor()).observe(document.body, {
            childList: true,
            subtree: true
        });
    }
})();