Lolz Paint

Ручной paint

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Lolz Paint
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Ручной paint
// @author       Forest
// @license      MIT
// @match        https://lolz.live/*
// @match        https://zelenka.guru/*
// @match        https://lolz.guru/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const COLORS = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF', '#FFFFFF', '#000000'];

    let history = [];
    let historyStep = -1;
    let currentTool = 'brush';
    let currentColor = '#FF0000';
    let currentLineWidth = 3;
    let isDrawing = false;
    let startX, startY;
    let snapshot;
    let activeTextObj = null;
    let currentBgType = 'color';
    let currentBgColor = '#FFFFFF';

    function createPaintModal() {
        history = []; historyStep = -1; activeTextObj = null;
        currentBgType = 'color'; currentBgColor = '#FFFFFF';

        const modal = document.createElement('div');
        modal.style.cssText = `
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.85); z-index: 99999; display: flex;
            justify-content: center; align-items: center; flex-direction: column;
            user-select: none; font-family: 'Segoe UI', sans-serif;
        `;

        const editorBox = document.createElement('div');
        editorBox.style.cssText = `
            background: #222; padding: 10px; border-radius: 8px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5); display: flex; flex-direction: column; gap: 10px;
            max-width: 98vw; max-height: 98vh; position: relative;
        `;

        const toolbar = document.createElement('div');
        toolbar.style.cssText = 'display: flex; gap: 10px; align-items: center; background: #333; padding: 8px; border-radius: 6px; flex-wrap: wrap;';

        const toolsData = [
            { id: 'brush', icon: '🖌️', title: 'Кисть' },
            { id: 'rect', icon: '⬜', title: 'Прямоугольник' },
            { id: 'arrow', icon: '↗️', title: 'Стрелка' },
            { id: 'text', icon: 'T', title: 'Текст' },
            { id: 'blur', icon: '💧', title: 'Блюр (цензура)' },
            { id: 'eraser', icon: '🧹', title: 'Ластик' }
        ];

        const toolsContainer = document.createElement('div');
        toolsContainer.style.display = 'flex';
        toolsContainer.style.gap = '5px';

        toolsData.forEach(tool => {
            const btn = document.createElement('button');
            btn.innerHTML = tool.icon;
            btn.title = tool.title;
            btn.style.cssText = `
                padding: 6px 10px; background: ${tool.id === 'brush' ? '#555' : '#333'};
                border: 1px solid #444; color: white; cursor: pointer; border-radius: 4px; font-size: 16px;
            `;
            btn.onclick = () => {
                applyText();
                currentTool = tool.id;
                Array.from(toolsContainer.children).forEach(b => b.style.background = '#333');
                btn.style.background = '#555';
            };
            toolsContainer.appendChild(btn);
        });

        const bgControls = document.createElement('div');
        bgControls.style.display = 'flex'; bgControls.style.gap = '5px'; bgControls.style.marginLeft = '10px'; bgControls.style.borderLeft = '1px solid #555'; bgControls.style.paddingLeft = '10px';

        const fillBtn = document.createElement('button');
        fillBtn.innerHTML = '🪣';
        fillBtn.title = 'Залить фон цветом';
        fillBtn.style.cssText = 'padding: 6px 10px; background: #333; border: 1px solid #444; color: white; cursor: pointer; border-radius: 4px; font-size: 16px;';
        fillBtn.onclick = () => {
            applyText();
            currentBgType = 'color'; currentBgColor = currentColor;
            fillCanvasBackground(); saveState();
        };

        const transparentBtn = document.createElement('button');
        transparentBtn.innerHTML = '🏁';
        transparentBtn.title = 'Прозрачный фон';
        transparentBtn.style.cssText = 'padding: 6px 10px; background: #333; border: 1px solid #444; color: white; cursor: pointer; border-radius: 4px; font-size: 16px;';
        transparentBtn.onclick = () => {
            applyText();
            currentBgType = 'transparent';
            fillCanvasBackground(); saveState();
        };
        bgControls.append(fillBtn, transparentBtn);

        const palette = document.createElement('div');
        palette.style.display = 'flex'; palette.style.gap = '4px'; palette.style.marginLeft = '10px'; palette.style.alignItems = 'center';
        COLORS.forEach(color => {
            const swatch = document.createElement('div');
            swatch.style.cssText = `width: 20px; height: 20px; background: ${color}; border-radius: 3px; cursor: pointer; border: 1px solid #555;`;
            if(color === currentColor) swatch.style.border = '2px solid white';
            swatch.onclick = () => {
                updateColor(color);
                Array.from(palette.querySelectorAll('.swatch')).forEach(c => c.style.border = '1px solid #555');
                swatch.style.border = '2px solid white';
            };
            swatch.className = 'swatch';
            palette.appendChild(swatch);
        });

        const colorInputLabel = document.createElement('label');
        colorInputLabel.innerHTML = '🌈';
        colorInputLabel.style.cssText = 'cursor: pointer; font-size: 20px; margin-left: 5px;';
        const colorInput = document.createElement('input');
        colorInput.type = 'color'; colorInput.value = currentColor;
        colorInput.style.cssText = 'width: 0; height: 0; visibility: hidden; position: absolute;';
        colorInputLabel.appendChild(colorInput);
        colorInput.oninput = (e) => {
             updateColor(e.target.value);
             Array.from(palette.querySelectorAll('.swatch')).forEach(c => c.style.border = '1px solid #555');
        };
        palette.appendChild(colorInputLabel);

        function updateColor(newColor) {
            currentColor = newColor;
            if(activeTextObj) activeTextObj.style.color = newColor;
        }

        const sizeInput = document.createElement('input');
        sizeInput.type = 'range'; sizeInput.min = 1; sizeInput.max = 40; sizeInput.value = currentLineWidth;
        sizeInput.style.width = '80px';
        sizeInput.oninput = (e) => {
            currentLineWidth = parseInt(e.target.value);
            if(activeTextObj) activeTextObj.style.fontSize = (currentLineWidth + 12) + 'px';
        };

        const undoBtn = document.createElement('button');
        undoBtn.innerHTML = '↩️'; undoBtn.onclick = undo;
        undoBtn.style.cssText = 'background:none; border:none; color:#ccc; cursor:pointer; font-size:18px; margin-left: auto;';

        toolbar.append(toolsContainer, bgControls, palette, sizeInput, undoBtn);

        const canvasWrapper = document.createElement('div');
        canvasWrapper.style.cssText = `
            position: relative; width: 800px; height: 500px;
            background-color: #eee;
            background-image: linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%);
            background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
            overflow: hidden; border: 2px solid #444;
        `;

        const canvas = document.createElement('canvas');
        canvas.width = 800; canvas.height = 500;
        canvas.style.display = 'block';
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height);
        saveState();

        const resizer = document.createElement('div');
        resizer.style.cssText = `width: 15px; height: 15px; background: linear-gradient(135deg, transparent 50%, #e91e63 50%); position: absolute; bottom: 0; right: 0; cursor: nwse-resize; z-index: 20;`;
        let isResizing = false;
        resizer.onmousedown = (e) => { isResizing = true; e.preventDefault(); applyText(); };
        window.addEventListener('mouseup', () => isResizing = false);
        window.addEventListener('mousemove', (e) => {
            if (!isResizing) return;
            const rect = canvasWrapper.getBoundingClientRect();
            const newW = e.clientX - rect.left; const newH = e.clientY - rect.top;
            if (newW > 100 && newH > 100) resizeCanvas(newW, newH);
        });
        canvasWrapper.append(canvas, resizer);

        function fillCanvasBackground() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            if (currentBgType === 'color') {
                ctx.fillStyle = currentBgColor;
                ctx.fillRect(0, 0, canvas.width, canvas.height);
            }
        }

        function resizeCanvas(w, h, skipSave = false) {
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = canvas.width; tempCanvas.height = canvas.height;
            tempCanvas.getContext('2d').drawImage(canvas, 0, 0);
            canvasWrapper.style.width = w + 'px'; canvasWrapper.style.height = h + 'px';
            canvas.width = w; canvas.height = h;
            if (currentBgType === 'color') {
                ctx.fillStyle = currentBgColor; ctx.fillRect(0, 0, w, h);
            } else { ctx.clearRect(0, 0, w, h); }
            ctx.drawImage(tempCanvas, 0, 0);
            if (!skipSave) saveState();
        }

        function pixelate(x, y, size) {
            const pixelSize = 6;
            const w = size * 2;
            const h = size * 2;
            const sx = x - size;
            const sy = y - size;
            try {
                const sampleW = Math.max(1, Math.floor(w / pixelSize));
                const sampleH = Math.max(1, Math.floor(h / pixelSize));
                ctx.imageSmoothingEnabled = false;
                ctx.drawImage(canvas, sx, sy, w, h, sx, sy, sampleW, sampleH);
                ctx.drawImage(canvas, sx, sy, sampleW, sampleH, sx, sy, w, h);
                ctx.imageSmoothingEnabled = true;
            } catch(e) {}
        }

        function createFloatingText(x, y) {
            applyText();
            const div = document.createElement('div');
            div.contentEditable = true; div.innerHTML = 'Текст';
            div.style.cssText = `position: absolute; left: ${x}px; top: ${y}px; color: ${currentColor}; font-size: ${currentLineWidth + 12}px; font-family: Arial; border: 1px dashed #000; padding: 2px; min-width: 20px; z-index: 15; cursor: move; outline: none; background: rgba(255,255,255,0.3);`;
            let isDraggingDiv = false; let divOffsetX, divOffsetY;
            div.onmousedown = (e) => {
                if(e.target !== div) return;
                isDraggingDiv = true; divOffsetX = e.offsetX; divOffsetY = e.offsetY;
            };
            modal.onmousemove = (e) => {
                if(isDraggingDiv) {
                    const rect = canvasWrapper.getBoundingClientRect();
                    div.style.left = (e.clientX - rect.left - divOffsetX) + 'px';
                    div.style.top = (e.clientY - rect.top - divOffsetY) + 'px';
                }
            };
            modal.onmouseup = () => isDraggingDiv = false;
            div.onkeydown = (e) => { if(e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); applyText(); } };
            canvasWrapper.appendChild(div); activeTextObj = div; setTimeout(() => div.focus(), 0);
        }

        function applyText() {
            if (!activeTextObj) return;
            const rect = activeTextObj.getBoundingClientRect();
            const canvasRect = canvasWrapper.getBoundingClientRect();
            const x = rect.left - canvasRect.left; const y = rect.top - canvasRect.top;
            const fontSize = parseInt(activeTextObj.style.fontSize);
            ctx.font = `${fontSize}px Arial`; ctx.fillStyle = activeTextObj.style.color; ctx.textBaseline = 'top';
            ctx.fillText(activeTextObj.innerText, x, y + 2);
            activeTextObj.remove(); activeTextObj = null; saveState();
        }

        canvas.onmousedown = (e) => {
            if (activeTextObj && e.target !== activeTextObj) applyText();
            if (currentTool === 'text') {
                const rect = canvasWrapper.getBoundingClientRect();
                createFloatingText(e.clientX - rect.left, e.clientY - rect.top);
                return;
            }
            isDrawing = true;
            const rect = canvasWrapper.getBoundingClientRect();
            startX = e.clientX - rect.left; startY = e.clientY - rect.top;
            snapshot = ctx.getImageData(0, 0, canvas.width, canvas.height);

            if (currentTool === 'blur') {
                 pixelate(startX, startY, currentLineWidth * 2);
            } else {
                 ctx.beginPath(); ctx.moveTo(startX, startY);
            }
        };

        canvas.onmousemove = (e) => {
            if (!isDrawing) return;
            const rect = canvasWrapper.getBoundingClientRect();
            const x = e.clientX - rect.left; const y = e.clientY - rect.top;

            if (currentTool === 'blur') {
                pixelate(x, y, currentLineWidth * 2);
                return;
            }

            ctx.lineWidth = currentLineWidth; ctx.strokeStyle = (currentTool === 'eraser') ? (currentBgType === 'color' ? currentBgColor : 'rgba(0,0,0,1)') : currentColor;
            if (currentTool === 'eraser') ctx.globalCompositeOperation = 'destination-out';
            else ctx.globalCompositeOperation = 'source-over';

            ctx.lineCap = 'round'; ctx.lineJoin = 'round';
            if (currentTool === 'brush' || currentTool === 'eraser') { ctx.lineTo(x, y); ctx.stroke(); }
            else if (currentTool === 'rect') {
                ctx.globalCompositeOperation = 'source-over';
                ctx.putImageData(snapshot, 0, 0); ctx.strokeRect(startX, startY, x - startX, y - startY);
            }
            else if (currentTool === 'arrow') {
                ctx.globalCompositeOperation = 'source-over';
                ctx.putImageData(snapshot, 0, 0); drawArrow(ctx, startX, startY, x, y);
            }
            if (currentTool !== 'eraser') ctx.globalCompositeOperation = 'source-over';
        };
        canvas.onmouseup = () => { if (isDrawing) { isDrawing = false; saveState(); } ctx.beginPath(); ctx.globalCompositeOperation = 'source-over'; };

        function saveState() { historyStep++; if (historyStep < history.length) history.length = historyStep; history.push(canvas.toDataURL()); }
        function undo() { if (historyStep > 0) { historyStep--; restoreState(); } }
        function restoreState() { const img = new Image(); img.src = history[historyStep]; img.onload = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); }; }
        function drawArrow(ctx, fromx, fromy, tox, toy) {
            const headlen = 15 + currentLineWidth; const dx = tox - fromx, dy = toy - fromy, angle = Math.atan2(dy, dx);
            ctx.beginPath(); ctx.moveTo(fromx, fromy); ctx.lineTo(tox, toy);
            ctx.lineTo(tox - headlen * Math.cos(angle - Math.PI/6), toy - headlen * Math.sin(angle - Math.PI/6));
            ctx.moveTo(tox, toy); ctx.lineTo(tox - headlen * Math.cos(angle + Math.PI/6), toy - headlen * Math.sin(angle + Math.PI/6));
            ctx.stroke();
        }

        window.addEventListener('paste', (e) => {
            const items = (e.clipboardData || e.originalEvent.clipboardData).items;
            for (let item of items) {
                if (item.kind === 'file' && item.type.includes('image/')) {
                    applyText();
                    const blob = item.getAsFile();
                    const reader = new FileReader();
                    reader.onload = (event) => {
                        const img = new Image();
                        img.onload = () => {
                            let w = img.width, h = img.height;
                            const maxW = window.innerWidth - 100, maxH = window.innerHeight - 200;
                            if (w > maxW) { h *= maxW/w; w = maxW; }
                            if (h > maxH) { w *= maxH/h; h = maxH; }
                            resizeCanvas(w, h, true);
                            ctx.drawImage(img, 0, 0, w, h);
                            saveState();
                        };
                        img.src = event.target.result;
                    };
                    reader.readAsDataURL(blob);
                }
            }
        });
        window.addEventListener('keydown', (e) => { if (e.ctrlKey && e.code === 'KeyZ') { e.preventDefault(); undo(); } });

        const bottomBar = document.createElement('div');
        bottomBar.style.cssText = 'display: flex; justify-content: flex-end; gap: 10px; margin-top: 5px;';

        const closeBtn = document.createElement('button');
        closeBtn.textContent = 'Закрыть';
        closeBtn.onclick = () => modal.remove();

        const copyBtn = document.createElement('button');
        copyBtn.textContent = 'Скопировать изображение';
        copyBtn.style.cssText = 'background: #4CAF50; color: white; border: none; padding: 5px 15px; border-radius: 4px; font-weight: bold; cursor: pointer;';
        copyBtn.onclick = () => {
            applyText();
            canvas.toBlob(blob => {
                const item = new ClipboardItem({ "image/png": blob });
                navigator.clipboard.write([item]).then(() => {
                    alert('Изображение скопировано! \nТеперь нажми Ctrl+V в поле ответа.');
                    modal.remove();
                }).catch(err => {
                    alert('Ошибка доступа к буферу. Проверь настройки браузера.');
                });
            });
        };

        bottomBar.append(closeBtn, copyBtn);
        editorBox.append(toolbar, canvasWrapper, bottomBar);
        modal.appendChild(editorBox);
        document.body.appendChild(modal);
    }

    function addPaintButton() {
        const toolbars = document.querySelectorAll('.fr-toolbar, .redactor_toolbar, .bbCodeEditor-toolbar');
        toolbars.forEach(toolbar => {
            if (toolbar.querySelector('.lolz-paint-btn')) return;
            const btn = document.createElement('button');
            btn.className = 'lolz-paint-btn';
            btn.innerHTML = '🎨'; btn.type = 'button'; btn.title = 'Paint';
            btn.style.cssText = 'background:none; border:none; cursor:pointer; font-size:18px; padding:0 5px; transition:transform 0.2s;';
            btn.onclick = (e) => { e.preventDefault(); createPaintModal(); };
            toolbar.appendChild(btn);
        });
    }
    setInterval(addPaintButton, 1000);
})();