4PDA Logo

Замена логотипа в посте (очистка старого лого в тексте + удаление вложения логотипа + причина редактирования )

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         4PDA Logo
// @namespace    http://4pda.to/forum/index.php
// @version      5.0
// @description  Замена логотипа в посте (очистка старого лого в тексте + удаление вложения логотипа + причина редактирования )
// @author       BrantX
// @match        http*://4pda.to/forum/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    console.log('Logo Replacer v5.0 LOADED');

    // ---------- База логотипов ----------

    const LOGOS = [
        { url: 'https://4pda.to/s/HL5V5qDA1xLMwNxwm561PwRaFF6TOoR9wwZ.png', description: 'AICP' },
        { url: 'https://4pda.to/s/HL5V1GHLxxp1z1TgHPZRKOvz0Rlz2z1H61bgCj7.png', description: 'AICP_2' },
        { url: 'https://4pda.to/s/HL5V6p1Ko5MmbdZ6sAUz0z25RaFFcDmsHOneE.png', description: 'AlphaDroid_2' },
        { url: 'https://4pda.to/s/HL5V7m7RgvmFbd3skM8Q33KypfPDmsnefKe.png', description: 'AlphaDroid' },
        { url: 'https://4pda.to/s/HL5VEdX4wHqbaab9ksu2VLJGz0IJyBy0ppDt.png', description: 'Ancient' },
        { url: 'https://4pda.to/s/HL5VFadBYjIQaa5vsgkbZJS81qiyByW3hnH.png', description: 'Android Beta' },
        { url: 'https://4pda.to/s/HL5V0xNpkJnip0z2Gn6Wz1PwxKtZ86YRSbq19.png', description: 'Android' },
        { url: 'https://4pda.to/s/HL5V1uHyslNJp0VWfQsPbyqCB5t6YRyLiz0l.png', description: 'ApolloOS' },
        { url: 'https://4pda.to/s/HL5V2z2TY5HKrim7SlLkb33qCB5NMAVs4dl2.png', description: 'AwakenOS' },
        { url: 'https://4pda.to/s/HL5V3yRjTjoAimdit9u2z25xKtZeMAVMqz2Ja.png', description: 'AxionOS' },
        { url: 'https://4pda.to/s/HL5V4p3HxIogyO3sEcmsj8cX6O2N9PvSRfj.png', description: 'BaikalOS' },
        { url: 'https://4pda.to/s/HL5V5m5UZkKLyOZ6MwcHHEfvwz1z0N9PPi3LB.png', description: 'BananaDroid' },
        { url: 'https://4pda.to/s/HL5V6t90GGNpZexwGrz1jtnfvwz1T7XTJz087c.png', description: 'Bliss ROM' },
        { url: 'https://4pda.to/s/HL5V7qFF8inCZeRA8feABtcX6OY7XTpDGx0.png', description: 'Bootleggers' },
        { url: 'https://4pda.to/s/HL5VEhvuSkq5qinhxtRKu95tXa4uXz0Z54PL.png', description: 'CalyxOS_1' },
        { url: 'https://4pda.to/s/HL5VFez2t4IIwqiHRZhDp4FAlT2xuXz03rSbp.png', description: 'CalyxOS_2' },
        { url: 'https://4pda.to/s/HL5V0tFF8inCZ8hoa73ez1cjphLV28Qz2J3Lh.png', description: 'Cherish OS' },
        { url: 'https://4pda.to/s/HL5V1q90GGNpZ8B2yRLF2WYhNpW28QVZRfD.png', description: 'CipherOS' },
        { url: 'https://4pda.to/s/HL5V2p5UZkKLyuJz1wKDpaVYhNp0IWULoGxW.png', description: 'ColtOS' },
        { url: 'https://4pda.to/s/HL5V3m3HxIogyupEY8RKOPjphLz2IWUr2876.png', description: 'crDroid' },
        { url: 'https://4pda.to/s/HL5V4z2RjTjoAiGNKRdJWAKm6QkLJZOQgiz0F.png', description: 'DerpFest' },
        { url: 'https://4pda.to/s/HL5V5yTY5HKriGta3x57sIz2Uc8gJZOwQq1f.png', description: 'DivestOS' },
        { url: 'https://4pda.to/s/HL5V6xHyslNJpWlO5qTxGjz2Uc8A3BSmBz2J4.png', description: 'Evolution X' },
        { url: 'https://4pda.to/s/HL5V7uNpkJnipWFeTeBSihm6Qkr3BSGxdlY.png', description: 'GrapheneOS' },
        { url: 'https://4pda.to/s/HL5VElniz1xr6oZfNT8x4mz0tgKLz2omMXWz0sz0.png', description: 'HentaiOS' },
        { url: 'https://4pda.to/s/HL5VFitZc7JvoZ9d5KjZCxuoep0omM1GbAR.png', description: 'HorizonDroid' },
        { url: 'https://4pda.to/s/HL5V0p7RgvmFb7pE2uZusIVkUaa8Pnz0sww3.png', description: 'Infinity' },
        { url: 'https://4pda.to/s/HL5V1m1Ko5Mmb7Jz1QarVAKGsY2R8PnT6Y6b.png', description: 'iode' },
        { url: 'https://4pda.to/s/HL5V2tDA1xLMwtB2ShjZihGsY2xOnrNNfK8.png', description: 'Kirisakura' },
        { url: 'https://4pda.to/s/HL5V3qB5P7pfwtho4tx4GjVkUa4Onrtdnek.png', description: 'LineageOS' },
        { url: 'https://4pda.to/s/HL5V4xJvz2up9gVFez0Opm2W2RlVkPopOFLId.png', description: 'LMODroid' },
        { url: 'https://4pda.to/s/HL5V5uLsd4LsgVlOb4bNz1cD3JvHPopuz2Dk1.png', description: 'Martixx' },
        { url: 'https://4pda.to/s/HL5V6z2PeKwMGrltaZBz0hOPD3Jvn9Qtok6yi.png', description: 'MistOS' },
        { url: 'https://4pda.to/s/HL5V7yVdC6mlrlNKxNhCaV2RlVE9QtIUU0A.png', description: 'Paranoid' },
        { url: 'https://4pda.to/s/HL5VEBfuSkq5qinhxtRKu95tXa4uXz0Z54PL.png', description: 'PhoenixAOSP' },
        { url: 'https://4pda.to/s/HL5VF8lt4IIwqiHRZhDp4FAlT2xuXz03rSbp.png', description: 'PixelDust' },
        { url: 'https://4pda.to/s/HL5V0NVF8inCZ8hoa73ez1cjphLV28Qz2J3Lh.png', description: 'PixelExperience' },
        { url: 'https://4pda.to/s/HL5V1KP0GGNpZ8B2yRLF2WYhNpW28QVZRfD.png', description: 'PixelOS' },
        { url: 'https://4pda.to/s/HL5V2JLUZkKLyuJz1wKDpaVYhNp0IWULoGxW.png', description: 'PixelPlus UI' },
        { url: 'https://4pda.to/s/HL5V3GJHxIogyupEY8RKOPjphLz2IWUr2876.png', description: 'Project Blaze' },
        { url: 'https://4pda.to/s/HL5V4VBjTjoAiGNKRdJWAKm6QkLJZOQgiz0F.png', description: 'Project Elixir-new' },
        { url: 'https://4pda.to/s/HL5V5SDY5HKriGta3x57sIz2Uc8gJZOwQq1f.png', description: 'Project Elixir' },
        { url: 'https://4pda.to/s/HL5V6R1yslNJpWlO5qTxGjz2Uc8A3BSmBz2J4.png', description: 'Project Zephyrus' },
        { url: 'https://4pda.to/s/HL5V7O7pkJnipWFeTeBSihm6Qkr3BSGxdlY.png', description: 'ProjectEverest' },
        { url: 'https://4pda.to/s/HL5VEFXiz1xr6oZfNT8x4mz0tgKLz2omMXWz0sz0.png', description: 'Radioactive Kernel' },
        { url: 'https://4pda.to/s/HL5VFCdZc7JvoZ9d5KjZCxuoep0omM1GbAR.png', description: 'RisingOS' },
        { url: 'https://4pda.to/s/HL5V0JNRgvmFb7pE2uZusIVkUaa8Pnz0sww3.png', description: 'Sigmadroid_2' },
        { url: 'https://4pda.to/s/HL5V1GHKo5Mmb7Jz1QarVAKGsY2R8PnT6Y6b.png', description: 'Sigmadroid' },
        { url: 'https://4pda.to/s/HL5V2NTA1xLMwtB2ShjZihGsY2xOnrNNfK8.png', description: 'SparkOS' },
        { url: 'https://4pda.to/s/HL5V3KR5P7pfwtho4tx4GjVkUa4Onrtdnek.png', description: 'StagOS' },
        { url: 'https://4pda.to/s/HL5V4R3vz2up9gVFez0Opm2W2RlVkPopOFLId.png', description: 'StatixOS' },
        { url: 'https://4pda.to/s/HL5V5O5sd4LsgVlOb4bNz1cD3JvHPopuz2Dk1.png', description: 'Superior_2' },
        { url: 'https://4pda.to/s/HL5V6V9eKwMGrltaZBz0hOPD3Jvn9Qtok6yi.png', description: 'Superior' },
        { url: 'https://4pda.to/s/HL5V7SFdC6mlrlNKxNhCaV2RlVE9QtIUU0A.png', description: 'SyberiaOS' }
    ];

    let buttonAdded = false;
    let logoPanel = null;

    // ---------- Кнопка "Лого" ----------

    function addLogoButton() {
        if (buttonAdded) return;

        const selectors = [
            'img[src*="folder_editor_buttons"]',
            'td.formbuttons img',
            '.buttons img',
            'input[value*="B"]',
            'input[value*="I"]',
            'input[value*="U"]',
            'textarea[name="Post"]'
        ];

        for (let selector of selectors) {
            const elements = document.querySelectorAll(selector);
            if (elements.length > 0) {
                const element = elements[0];
                const logoBtn = document.createElement('span');
                logoBtn.className = 'g-btn blue logo-replacer-btn';
                logoBtn.textContent = 'Лого';
                logoBtn.title = `Выбрать логотип из ${LOGOS.length} вариантов`;
                logoBtn.style.cssText = 'margin-left:4px;cursor:pointer;font-weight:bold;';
                logoBtn.addEventListener('click', showLogoSelector);

                const container = element.parentNode;
                container.insertBefore(logoBtn, container.firstChild);
                buttonAdded = true;
                return;
            }
        }
    }

    function showLogoSelector() {
        if (logoPanel) {
            logoPanel.remove();
            logoPanel = null;
            return;
        }

        logoPanel = document.createElement('div');
        logoPanel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 3px solid #4a6782;
            border-radius: 10px;
            padding: 15px;
            z-index: 10000;
            box-shadow: 0 8px 25px rgba(0,0,0,0.3);
            max-width: 95vw;
            max-height: 85vh;
            overflow-y: auto;
            font-family: Arial, sans-serif;
            font-size: 12px;
        `;

        const title = document.createElement('div');
        title.textContent = `Выберите логотип (${LOGOS.length} вариантов):`;
        title.style.cssText = 'font-weight: bold; margin-bottom: 12px; color: #333; font-size: 14px; text-align: center;';
        logoPanel.appendChild(title);

        const grid = document.createElement('div');
        grid.style.cssText = 'display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px;';
        logoPanel.appendChild(grid);

        LOGOS.forEach((logo) => {
            const logoContainer = document.createElement('div');
            logoContainer.style.cssText = 'border: 1px solid #ddd; border-radius: 6px; padding: 8px; text-align: center; background: #f9f9f9; cursor: pointer;';

            const name = document.createElement('div');
            name.textContent = logo.description;
            name.style.cssText = 'font-weight: bold; font-size: 10px; color: #4a6782; margin-bottom: 5px; height: 30px; display: flex; align-items: center; justify-content: center;';
            logoContainer.appendChild(name);

            const preview = document.createElement('img');
            preview.src = logo.url;
            preview.style.cssText = 'max-width: 100px; max-height: 50px; display: block; margin: 0 auto 8px; border: 1px solid #ccc;';
            preview.alt = logo.description;
            logoContainer.appendChild(preview);

            const logoBtn = document.createElement('button');
            logoBtn.textContent = 'Вставить';
            logoBtn.style.cssText = `
                width: 100%;
                padding: 4px 8px;
                background: #4a6782;
                color: white;
                border: none;
                border-radius: 3px;
                cursor: pointer;
                font-size: 10px;
                font-weight: bold;
            `;

            logoBtn.addEventListener('mouseenter', function () {
                this.style.background = '#5a7792';
                logoContainer.style.background = '#f0f5fa';
            });

            logoBtn.addEventListener('mouseleave', function () {
                this.style.background = '#4a6782';
                logoContainer.style.background = '#f9f9f9';
            });

            logoBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                insertLogo(logo.url, logo.description);
                logoPanel.remove();
                logoPanel = null;
            });

            logoContainer.appendChild(logoBtn);

            logoContainer.addEventListener('click', (e) => {
                if (e.target !== logoBtn) {
                    insertLogo(logo.url, logo.description);
                    logoPanel.remove();
                    logoPanel = null;
                }
            });

            grid.appendChild(logoContainer);
        });

        const closeBtn = document.createElement('button');
        closeBtn.textContent = 'Закрыть';
        closeBtn.style.cssText = `
            display: block;
            margin: 15px auto 0;
            padding: 8px 16px;
            background: #ff4444;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
        `;
        closeBtn.addEventListener('click', () => {
            logoPanel.remove();
            logoPanel = null;
        });

        logoPanel.appendChild(closeBtn);
        document.body.appendChild(logoPanel);

        setTimeout(() => {
            const closeHandler = (e) => {
                if (logoPanel && !logoPanel.contains(e.target)) {
                    logoPanel.remove();
                    logoPanel = null;
                    document.removeEventListener('click', closeHandler);
                }
            };
            document.addEventListener('click', closeHandler);
        }, 100);
    }

    // ---------- Анализ спойлеров и логотипов ----------

    function isPureAttachmentSpoiler(content) {
        let cleaned = content;
        cleaned = cleaned.replace(/\[attachment[^\]]*?]/gi, '');
        cleaned = cleaned.replace(/\[(?:\/?center|\/?b|\/?i|\/?u|\/?size|\/?color)[^\]]*]/gi, '');
        cleaned = cleaned.replace(/\s+/g, '');
        return !/[A-Za-zА-Яа-я0-9]/.test(cleaned);
    }

    function getLogoSpoilers(text) {
        const spoilerRegex = /\[spoiler(?:=([^\]]*))?]([\s\S]*?)\[\/spoiler]/gi;
        const result = [];
        let m;
        while ((m = spoilerRegex.exec(text)) !== null) {
            const whole = m[0];
            const rawTitle = m[1] || '';
            const content = m[2] || '';
            const titleTrim = rawTitle.trim();

            let isLogo = false;

            if (titleTrim) {
                const norm = titleTrim.replace(/["'\[\]]/g, '');
                if (/лого|logo/i.test(norm)) isLogo = true;
                if (/(скрин|screenshot|screens?)/i.test(norm)) isLogo = false;
            }

            if (!titleTrim && isPureAttachmentSpoiler(content)) {
                isLogo = true;
            }

            if (isLogo) result.push({ whole, content });
        }
        return result;
    }

    function removeTopLogoBlocks(text) {
        let t = text;
        const maxScan = 1200;

        // первый [center]...[/center] в начале поста
        const firstPart = t.slice(0, maxScan);
        const centerRegex = /\[center][\s\S]*?\[\/center]/i;
        const cm = centerRegex.exec(firstPart);
        if (cm && cm.index !== undefined && cm.index < 400) {
            t = t.replace(cm[0], '');
        }

        // любые [attachment]-картинки до первого [spoiler]
        const firstSpoilerIndex = t.search(/\[spoiler(?:=|])/i);
        const headerEnd = firstSpoilerIndex === -1 ? Math.min(maxScan, t.length) : Math.min(firstSpoilerIndex, maxScan);
        const headerPart = t.slice(0, headerEnd);
        let changed = false;
        const newHeader = headerPart.replace(/\[attachment[^\]]*?\.(?:png|jpe?g|gif|webp)[^\]]*]/gi, () => {
            changed = true;
            return '';
        });
        if (changed) {
            t = newHeader + t.slice(headerEnd);
        }

        return t;
    }

    // ---------- Вытаскиваем имена файлов из текста ----------

    function extractAttachmentNames(text) {
        const names = new Set();
        const regex = /\[attachment[^\]]*?["']?([^"'\]]+?\.(?:png|jpe?g|gif|webp))["']?]/gi;
        let m;
        while ((m = regex.exec(text)) !== null) {
            const full = m[1];
            const parts = full.split(':');
            const fileName = (parts.length > 1 ? parts[1] : parts[0]).toLowerCase().trim();
            if (fileName) names.add(fileName);
        }
        return Array.from(names);
    }

    function getRemovedAttachmentNames(originalText, cleanedText) {
        const before = extractAttachmentNames(originalText);
        const after = extractAttachmentNames(cleanedText);
        const afterSet = new Set(after);
        const removed = before.filter(n => !afterSet.has(n));
        if (removed.length) {
            console.log('Logo Replacer: attachments removed from text:', removed);
        }
        return removed;
    }

    // ---------- Удаляем логотипы из текста ----------

    function removeAllLogoReferences(text) {
        if (!text) return text;

        let result = text;

        // все "логотипные" спойлеры
        const spoilers = getLogoSpoilers(text);
        spoilers.forEach(sp => {
            result = result.replace(sp.whole, '');
        });

        // одиночные центровые логотипы
        const extraPatterns = [
            /\[center]\s*\[(?:img|attachment)[^\]]*logo[^\]]*]\s*\[\/center]/gi,
            /\[center]\s*\[(?:img|attachment)[^\]]*]\s*\[\/center]/gi,
            /\[(?:img|attachment)][^\]]*logo[^\]]*\[\/(?:img|attachment)]/gi,
            /\[attachment[^\]]*logo[^\]]*]/gi
        ];
        extraPatterns.forEach(p => {
            result = result.replace(p, '');
        });

        // верхний логотип без спойлера
        result = removeTopLogoBlocks(result);

        result = result.replace(/\n\s*\n\s*\n/g, '\n\n');
        result = result.replace(/(\r?\n){3,}/g, '\n\n');
        return result.trim();
    }

    // ---------- Удаление вложений из блока "Файлы" ----------

    function removeLogoAttachmentsFromForm(attachmentNames) {
        if (!attachmentNames || !attachmentNames.length) return;
        const names = attachmentNames.map(n => n.toLowerCase());

        const buttons = document.querySelectorAll(
            'button[data-forum-attach-action="delete"], button.attach-delete, .attach-delete button'
        );
        if (!buttons.length) {
            console.log('Logo Replacer: no delete buttons found');
            return;
        }

        buttons.forEach(btn => {
            let node = btn;
            let depth = 0;
            let matched = false;
            while (node && depth < 4 && !matched) {
                const text = (node.textContent || '').toLowerCase();
                if (names.some(name => text.includes(name))) {
                    btn.click();
                    console.log('Logo Replacer: attachment deleted near button, text:', text.trim().slice(0, 120));
                    matched = true;
                    break;
                }
                node = node.parentElement;
                depth++;
            }
        });
    }

    // ---------- Вставка нового логотипа ----------

    function insertLogo(logoUrl, logoName) {
        const bbcode = `[center][img]${logoUrl}[/img][/center]`;

        let textarea = document.querySelector('textarea[name="Post"]')
            || document.querySelector('#Post, textarea[id*="post"], textarea[class*="editor"]');

        if (!textarea) {
            const allTextareas = document.querySelectorAll('textarea');
            for (let ta of allTextareas) {
                if (ta.offsetWidth > 300 && ta.offsetHeight > 100) {
                    textarea = ta;
                    break;
                }
            }
        }

        if (!textarea) {
            alert('Не найдено поле для ввода текста! Откройте редактор поста.');
            return;
        }

        const originalText = textarea.value;

        // 1. Чистим старые логотипы
        const cleanedText = removeAllLogoReferences(originalText);

        // 2. Определяем, какие вложения реально исчезли из текста
        const removedAttachmentNames = getRemovedAttachmentNames(originalText, cleanedText);

        // 3. Вставляем новый логотип в начало
        textarea.value = bbcode + '\n\n' + cleanedText;
        textarea.scrollTop = 0;
        textarea.focus();

        showNotification(`✅ Логотип "${logoName}" добавлен!`);

        // 4. Причина редактирования
        const reasonInput = document.querySelector('input[name="post_edit_reason"], input[name="EditReason"]');
        if (reasonInput && !reasonInput.value.includes('Логотип 200х200')) {
            reasonInput.value = (reasonInput.value ? reasonInput.value + '; ' : '') + 'Правила раздела п.3.8';
        }

        // 5. Удаляем вложения, которые были убраны из текста
        removeLogoAttachmentsFromForm(removedAttachmentNames);
    }

    // ---------- Уведомление ----------

    function showNotification(message) {
        document.querySelectorAll('.logo-replacer-notification').forEach(n => n.remove());

        const notification = document.createElement('div');
        notification.className = 'logo-replacer-notification';
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: #4a6782;
            color: white;
            padding: 12px 24px;
            border-radius: 6px;
            z-index: 10000;
            font-family: Arial, sans-serif;
            font-size: 14px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            text-align: center;
            max-width: 400px;
            line-height: 1.4;
        `;
        notification.textContent = message;
        document.body.appendChild(notification);

        setTimeout(() => notification.remove(), 5000);
    }

    // ---------- Инициализация ----------

    function init() {
        setTimeout(addLogoButton, 1000);
        setTimeout(addLogoButton, 3000);
        setTimeout(addLogoButton, 5000);

        const observer = new MutationObserver(() => {
            setTimeout(addLogoButton, 500);
        });

        observer.observe(document.body, { childList: true, subtree: true });
        setInterval(addLogoButton, 5000);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();