Greasy Fork

Greasy Fork is available in English.

4PDA Logo

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

当前为 2025-11-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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();
    }
})();