Greasy Fork

来自缓存

Greasy Fork is available in English.

4PDA Logo

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

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