LZT Article Summarizer

Утилита, которая сокращает текст темы, экономя ваше время на прочтение.

当前为 2024-11-03 提交的版本,查看 最新版本

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

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

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

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

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

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         LZT Article Summarizer
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Утилита, которая сокращает текст темы, экономя ваше время на прочтение.
// @author       @planetus (lolz)
// @match        https://lolz.live/threads/*
// @match        https://zelenka.guru/threads/*
// @match        https://lolz.guru/threads/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=zelenka.guru
// @license      MIT
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    const defaultSettings = {
        minTextLength: 200,
        maxSummaryLength: 500,
        maxKeyPoints: 5,
        buttonColor: '#228e5d',
        summaryBgColor: '#091e15',
        summaryTextColor: '#ecf0f1',
        animationSpeed: 300
    };

    let settings = Object.assign({}, defaultSettings, GM_getValue('lztSummarizerSettings', {}));

    function saveSettings() {
        GM_setValue('lztSummarizerSettings', settings);
        updateStyles();
    }

    function clearSettings() {
        GM_setValue('lztSummarizerSettings', {});
        settings = Object.assign({}, defaultSettings);
        updateStyles();
    }

    function createSettingsMenu() {
        const settingsHTML = `
            <div id="lzt-settings-overlay" class="lzt-settings-overlay">
                <div id="lzt-settings" class="lzt-settings">
                    <h2 class="lzt-settings-title">Настройки LZT Summarizer</h2>
                    <div class="lzt-settings-group">
                        <label for="minTextLength">Минимальная длина текста для резюме:</label>
                        <input type="number" id="minTextLength" value="${settings.minTextLength}">
                    </div>
                    <div class="lzt-settings-group">
                        <label for="maxSummaryLength">Максимальная длина резюме:</label>
                        <input type="number" id="maxSummaryLength" value="${settings.maxSummaryLength}">
                    </div>
                    <div class="lzt-settings-group">
                        <label for="maxKeyPoints">Максимальное количество ключевых моментов:</label>
                        <input type="number" id="maxKeyPoints" value="${settings.maxKeyPoints}">
                    </div>
                    <div class="lzt-settings-group">
                        <label for="buttonColor">Цвет кнопки:</label>
                        <input type="color" id="buttonColor" value="${settings.buttonColor}">
                    </div>
                    <div class="lzt-settings-group">
                        <label for="summaryBgColor">Цвет фона резюме:</label>
                        <input type="color" id="summaryBgColor" value="${settings.summaryBgColor}">
                    </div>
                    <div class="lzt-settings-group">
                        <label for="summaryTextColor">Цвет текста резюме:</label>
                        <input type="color" id="summaryTextColor" value="${settings.summaryTextColor}">
                    </div>
                    <div class="lzt-settings-group">
                        <label for="animationSpeed">Скорость анимации (мс):</label>
                        <input type="number" id="animationSpeed" value="${settings.animationSpeed}">
                    </div>
                    <div class="lzt-settings-buttons">
                        <button id="saveSettings" class="lzt-settings-button lzt-settings-save">Сохранить</button>
                        <button id="clearSettings" class="lzt-settings-button lzt-settings-clear">Очистить всё</button>
                        <button id="closeSettings" class="lzt-settings-button lzt-settings-close">Закрыть</button>
                    </div>
                </div>
            </div>
        `;

        const settingsDiv = document.createElement('div');
        settingsDiv.innerHTML = settingsHTML;
        document.body.appendChild(settingsDiv);

        const overlay = document.getElementById('lzt-settings-overlay');
        const settingsPanel = document.getElementById('lzt-settings');

        setTimeout(() => {
            overlay.style.opacity = '1';
            settingsPanel.style.transform = 'translate(-50%, -50%) scale(1)';
        }, 50);

        document.getElementById('saveSettings').addEventListener('click', function() {
            settings.minTextLength = parseInt(document.getElementById('minTextLength').value);
            settings.maxSummaryLength = parseInt(document.getElementById('maxSummaryLength').value);
            settings.maxKeyPoints = parseInt(document.getElementById('maxKeyPoints').value);
            settings.buttonColor = document.getElementById('buttonColor').value;
            settings.summaryBgColor = document.getElementById('summaryBgColor').value;
            settings.summaryTextColor = document.getElementById('summaryTextColor').value;
            settings.animationSpeed = parseInt(document.getElementById('animationSpeed').value);
            saveSettings();
            closeSettingsMenu();
        });

        document.getElementById('clearSettings').addEventListener('click', function() {
            if (confirm('Вы уверены, что хотите сбросить все настройки?')) {
                clearSettings();
                closeSettingsMenu();
                alert('Настройки сброшены до значений по умолчанию.');
            }
        });

        document.getElementById('closeSettings').addEventListener('click', closeSettingsMenu);

        function closeSettingsMenu() {
            overlay.style.opacity = '0';
            settingsPanel.style.transform = 'translate(-50%, -50%) scale(0.9)';
            setTimeout(() => {
                settingsDiv.remove();
            }, 300);
        }
    }

    function updateStyles() {
        GM_addStyle(`
            .lzt-summary-button {
                background: linear-gradient(45deg, ${settings.buttonColor}, ${lightenDarkenColor(settings.buttonColor, -20)});
                border: none;
                color: white;
                padding: 10px 15px;
                text-align: center;
                text-decoration: none;
                display: inline-block;
                font-size: 14px;
                user-select: none;
                margin: 10px 0;
                cursor: pointer;
                border-radius: 4px;
                transition: all ${settings.animationSpeed}ms ease;
                box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                animation: fadeInButton ${settings.animationSpeed}ms ease-out;
            }
            .lzt-summary-button:hover {
                background: linear-gradient(45deg, ${lightenDarkenColor(settings.buttonColor, -20)}, ${lightenDarkenColor(settings.buttonColor, -40)});
                transform: translateY(-2px);
                box-shadow: 0 4px 8px rgba(0,0,0,0.2);
            }
            .lzt-summary-button:active {
                transform: translateY(0);
            }
            .lzt-summary {
                position: relative;
                z-index: 99;
                background-color: ${settings.summaryBgColor};
                border-left: 4px solid ${settings.buttonColor};
                color: ${settings.summaryTextColor};
                padding: 20px;
                margin: 15px 0;
                border-radius: 8px;
                font-size: 14px;
                line-height: 1.6;
                animation: fadeIn ${settings.animationSpeed}ms ease-out;
                box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            }
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(-10px); }
                to { opacity: 1; transform: translateY(0); }
            }
            @keyframes fadeInButton {
                from {
                    opacity: 0;
                    transform: translateY(10px);
                }
                to {
                    opacity: 1;
                    transform: translateY(0);
                }
            }
            .lzt-summary-title {
                color: ${settings.buttonColor};
                font-weight: bold;
                margin-bottom: 15px;
                font-size: 18px;
            }
            .lzt-summary-content {
                margin-top: 10px;
            }
            .lzt-key-points {
                margin-top: 20px;
                padding-top: 15px;
                border-top: 1px solid rgba(255,255,255,0.2);
            }
            .lzt-key-points-title {
                color: ${settings.buttonColor};
                font-weight: bold;
                margin-bottom: 10px;
            }
            .lzt-key-point {
                margin: 8px 0;
                padding-left: 20px;
                position: relative;
            }
            .lzt-key-point:before {
                content: "•";
                color: ${settings.buttonColor};
                position: absolute;
                left: 0;
                font-size: 18px;
            }
            .lzt-popup {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%) scale(0.9);
                background-color: ${settings.summaryBgColor};
                padding: 30px;
                border-radius: 12px;
                box-shadow: 0 10px 30px rgba(0,0,0,0.3);
                z-index: 999999;
                width: 90%;
                max-width: 800px;
                max-height: 90vh;
                overflow-y: auto;
                opacity: 0;
                transition: all ${settings.animationSpeed}ms ease;
            }
            .lzt-popup.show {
                opacity: 1;
                transform: translate(-50%, -50%) scale(1);
            }
            .lzt-popup-close {
                position: absolute;
                top: 15px;
                right: 20px;
                font-size: 28px;
                color: ${settings.summaryTextColor};
                cursor: pointer;
                transition: color ${settings.animationSpeed}ms ease;
            }
            .lzt-popup-close:hover {
                color: ${settings.buttonColor};
            }
            .lzt-original-content {
                transition: opacity ${settings.animationSpeed}ms ease, height ${settings.animationSpeed}ms ease;
                overflow: hidden;
            }
            .lzt-overlay {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: rgba(0, 0, 0, 0.7);
                backdrop-filter: blur(8px);
                z-index: 99998;
                opacity: 0;
                transition: opacity ${settings.animationSpeed}ms ease;
            }
            .lzt-overlay.show {
                opacity: 1;
            }
            .lzt-settings-overlay {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: rgba(0, 0, 0, 0.8);
                z-index: 10000;
                display: flex;
                justify-content: center;
                align-items: center;
                opacity: 0;
                backdrop-filter: blur(8px);
                transition: opacity ${settings.animationSpeed}ms ease;
            }
            .lzt-settings {
                background-color: #303030b0;
                padding: 30px;
                border-radius: 12px;
                box-shadow: 0 10px 30px rgba(0,0,0,0.3);
                width: 90%;
                max-width: 500px;
                transform: translate(-50%, -50%) scale(0.9);
                transition: transform ${settings.animationSpeed}ms ease;
                position: fixed;
                top: 50%;
                left: 50%;
            }
            .lzt-settings-title {
                color: #ecf0f1;
                font-size: 24px;
                margin-bottom: 20px;
                text-align: center;
            }
            .lzt-settings-group {
                margin-bottom: 15px;
            }
            .lzt-settings-group label {
                display: block;
                color: #bdc3c7;
                margin-bottom: 5px;
            }
            .lzt-settings-group input {
                width: 100%;
                padding: 8px;
                border: none;
                border-radius: 4px;
                background-color: #303030;
                color: #ecf0f1;
                font-size: 14px;
            }
            .lzt-settings-group input[type="color"] {
                height: 40px;
                cursor: pointer;
            }
            .lzt-settings-buttons {
               display: flex;
               gap: 5px;
               justify-content: space-between;
               margin-top: 20px;
            }
            .lzt-settings-button {
                padding: 10px 20px;
                border: none;
                border-radius: 4px;
                font-size: 15px;
                cursor: pointer;
                transition: background-color ${settings.animationSpeed}ms ease;
            }
           .lzt-settings-save {
               display: block;
               width: -webkit-fill-available;
               background-color: #2ecc7161;
               color: white;
            }
            .lzt-settings-save:hover {
               background-color: #27ae60;
            }
            .lzt-settings-clear {
              display: block;
              width: -webkit-fill-available;
              background-color: #f39c1291;
              color: white;
             }
            .lzt-settings-clear:hover {
              background-color: #d35400;
            }
            .lzt-settings-close {
              display: block;
              width: -webkit-fill-available;
              background-color: #e74c3cde;
              color: white;
            }
            .lzt-settings-close:hover {
              background-color: #c0392b;
            }
        `);
    }

    function lightenDarkenColor(col, amt) {
        let usePound = false;
        if (col[0] == "#") {
            col = col.slice(1);
            usePound = true;
        }
        let num  = parseInt(col,16);
        let r = (num >> 16) + amt;
        if (r > 255) r = 255;
        else if  (r < 0) r = 0;
        let b = ((num >> 8) & 0x00FF) + amt;
        if (b > 255) b = 255;
        else if  (b < 0) b = 0;
        let g = (num &   0x0000FF) + amt;
        if (g > 255) g = 255;
        else if (g < 0) g = 0;
        return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16).padStart(6, '0');
    }

    function extractKeyPoints(text) {
        const sentences = text.match(/[^\.!\?]+[\.!\?]+/g) || [];
        const keyPoints = [];

        const indicators = [
            'важно', 'главное', 'необходимо', 'следует', 'нужно',
            'основной', 'ключевой', 'рекомендуется', 'обратите внимание',
            'в первую очередь', 'самое важное', 'ключевая идея', 'существенно',
            'критически', 'принципиально', 'фундаментально', 'приоритетно'
        ];

        sentences.forEach(sentence => {
            const lowercaseSentence = sentence.toLowerCase();
            if (indicators.some(indicator => lowercaseSentence.includes(indicator)) ||
                (sentence.length > 30 && sentence.length < 200 && /[0-9]/.test(sentence)) ||
                /^[•\-]/.test(sentence.trim())) {
                keyPoints.push(sentence.trim());
            }
        });

        const startIndicators = ['ключевые моменты', 'основные пункты', 'главные идеи'];
        let foundKeyPointSection = false;

        sentences.forEach(sentence => {
            const lowercaseSentence = sentence.toLowerCase().trim();
            if (startIndicators.some(indicator => lowercaseSentence.startsWith(indicator))) {
                foundKeyPointSection = true;
            }
            if (foundKeyPointSection && /^[•\-]/.test(sentence.trim())) {
                keyPoints.push(sentence.trim());
            }
        });

        return [...new Set(keyPoints)].slice(0, settings.maxKeyPoints);
    }

    function summarizeText(text) {
        text = text.replace(/\s+/g, ' ').trim();
        const sentences = text.match(/[^\.!\?]+[\.!\?]+/g) || [];

        if (sentences.length === 0) {
            return '';
        }

        const sentenceScores = sentences.map((sentence, index) => {
            const words = sentence.toLowerCase().split(' ');
            let score = 0;

            const importantWords = ['важно', 'главное', 'необходимо', 'ключевой', 'существенно', 'критически', 'спонсор', 'проект'];
            if (words.some(word => importantWords.includes(word))) {
                score += 5;
            }

            if (sentence.length > 30 && sentence.length < 200) {
                score += 2;
            }

            if (/[0-9]/.test(sentence)) {
                score += 2;
            }

            if (index < 5) {
                score += 3;
            }

            if (index > 0) {
                const prevSentence = sentences[index - 1].toLowerCase();
                const commonWords = words.filter(word => prevSentence.includes(word) && word.length > 3);
                score += commonWords.length;
            }

            return { sentence, score, index };
        });

        sentenceScores.sort((a, b) => b.score - a.score);

        let summary = '';
        let currentLength = 0;
        let usedIndexes = new Set();

        for (const { sentence, index } of sentenceScores) {
            if (currentLength + sentence.length <= settings.maxSummaryLength && !usedIndexes.has(index)) {
                if (summary && Math.abs(index - Array.from(usedIndexes).pop()) > 3) {
                    continue;
                }
                summary += sentence + ' ';
                currentLength += sentence.length;
                usedIndexes.add(index);
            }
            if (currentLength >= settings.maxSummaryLength) break;
        }

        return summary.trim();
    }

    function createPopup(content) {
        const overlay = document.createElement('div');
        overlay.className = 'lzt-overlay';
        document.body.appendChild(overlay);

        const popup = document.createElement('div');
        popup.className = 'lzt-popup';
        popup.innerHTML = `
            <div class="lzt-popup-close">&times;</div>
            ${content}
        `;

        document.body.appendChild(popup);

        setTimeout(() => {
            overlay.classList.add('show');
            popup.classList.add('show');
        }, 50);

        const closePopup = () => {
            overlay.classList.remove('show');
            popup.classList.remove('show');
            setTimeout(() => {
                overlay.remove();
                popup.remove();
            }, settings.animationSpeed);
        };

        popup.querySelector('.lzt-popup-close').addEventListener('click', closePopup);
        overlay.addEventListener('click', closePopup);
    }

function getTextContent(element) {
        let text = '';
        function extractText(node) {
            if (node.nodeType === Node.TEXT_NODE) {
                text += node.textContent.trim() + ' ';
            } else if (node.nodeType === Node.ELEMENT_NODE) {
                if (node.classList.contains('bbCodeBlock') ||
                    node.tagName === 'IMG' ||
                    node.tagName === 'SCRIPT' ||
                    node.tagName === 'STYLE' ||
                    node.classList.contains('messageTextEndMarker') ||
                    node.classList.contains('lzt-summary-button')) {
                    return;
                }
                node.childNodes.forEach(extractText);
            }
        }
        extractText(element);
        return text.replace(/\s+/g, ' ').trim();
    }

function addSummaryButton(postElement) {
        if (!postElement.classList.contains('firstPost')) return;

        if (postElement.querySelector('.lzt-summary-button')) return;

        const contentElement = postElement.querySelector('.messageContent');
        if (!contentElement) return;

        const fullText = getTextContent(contentElement);

        console.log('Длина текста:', fullText.length);
        console.log('Текстовое содержимое:', fullText);

        if (fullText.length < settings.minTextLength) {
            console.log('Слишком короткий текст');
            return;
        }

        const summaryButton = document.createElement('button');
        summaryButton.textContent = '📝 Показать резюме';
        summaryButton.className = 'lzt-summary-button';
        summaryButton.style.opacity = '0';

        let summaryElement = null;
        let isProcessing = false;

        summaryButton.addEventListener('click', function(e) {
            e.preventDefault();
            if (isProcessing) return;
            isProcessing = true;

            if (summaryElement) {
                summaryElement.style.display = summaryElement.style.display === 'none' ? 'block' : 'none';
                summaryButton.textContent = summaryElement.style.display === 'none' ? '📝 Показать резюме' : '❌ Скрыть резюме';

                contentElement.style.height = summaryElement.style.display === 'none' ? 'auto' : '0';
                contentElement.style.opacity = summaryElement.style.display === 'none' ? '1' : '0';
                isProcessing = false;
            } else {
                const summary = summarizeText(fullText);
                const keyPoints = extractKeyPoints(fullText);

                summaryElement = document.createElement('div');
                summaryElement.className = 'lzt-summary';

                let summaryHTML = `
                    <div class="lzt-summary-title">📌 Краткое содержание</div>
                    <div class="lzt-summary-content">${summary || 'Не удалось создать краткое содержание.'}</div>
                `;

                if (keyPoints.length > 0) {
                    summaryHTML += `
                        <div class="lzt-key-points">
                            <div class="lzt-key-points-title">🔑 Ключевые моменты:</div>
                            ${keyPoints.map(point => `<div class="lzt-key-point">${point}</div>`).join('')}
                        </div>
                    `;
                } else {
                    summaryHTML += `
                        <div class="lzt-key-points">
                            <div class="lzt-key-points-title">🔑 Ключевые моменты:</div>
                            <div class="lzt-key-point">Не удалось выделить ключевые моменты.</div>
                        </div>
                    `;
                }

                summaryElement.innerHTML = summaryHTML;
                contentElement.parentNode.insertBefore(summaryElement, contentElement.nextSibling);
                summaryButton.textContent = '❌ Скрыть резюме';

                contentElement.style.height = '0';
                contentElement.style.opacity = '0';

                setTimeout(() => {
                    isProcessing = false;
                }, settings.animationSpeed);
            }
        });

        contentElement.parentNode.insertBefore(summaryButton, contentElement);

        setTimeout(() => {
            summaryButton.style.opacity = '1';
        }, 100);
    }

    function summarizeMainArticle() {
        const firstPost = document.querySelector('li.message.firstPost');
        if (!firstPost) {
            alert('Не удалось найти основную статью на странице.');
            return;
        }

        const contentElement = firstPost.querySelector('.messageContent');
        if (!contentElement) {
            alert('Не удалось найти содержимое статьи.');
            return;
        }

        const fullText = getTextContent(contentElement);
        const summary = summarizeText(fullText);
        const keyPoints = extractKeyPoints(fullText);

        let popupContent = `
            <div class="lzt-summary-title">📝 Краткое содержание статьи</div>
            <div class="lzt-summary-content">${summary || 'Не удалось создать краткое содержание.'}</div>
        `;

        if (keyPoints.length > 0) {
            popupContent += `
                <div class="lzt-key-points">
                    <div class="lzt-key-points-title">🔑 Ключевые моменты:</div>
                    ${keyPoints.map(point => `<div class="lzt-key-point">${point}</div>`).join('')}
                </div>
            `;
        } else {
            popupContent += `
                <div class="lzt-key-points">
                    <div class="lzt-key-points-title">🔑 Ключевые моменты:</div>
                    <div class="lzt-key-point">Не удалось выделить ключевые моменты.</div>
                </div>
            `;
        }

        createPopup(popupContent);
    }

    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    GM_registerMenuCommand("LZT Резюме статьи", debounce(summarizeMainArticle, 300));
    GM_registerMenuCommand("Настройки LZT Summarizer", debounce(createSettingsMenu, 300));

    updateStyles();

    window.addEventListener('load', function() {
        const firstPost = document.querySelector('li.message.firstPost');
        if (firstPost) {
            addSummaryButton(firstPost);
        }
    });

    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(function(node) {
                    if (node.nodeType === 1 && node.matches('li.message.firstPost')) {
                        addSummaryButton(node);
                    }
                });
            }
        });
    });

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