Greasy Fork

FastPic Upload 5.3

Загрузка изображений на FastPic с расширенными форматами

目前为 2025-02-20 提交的版本。查看 最新版本

// ==UserScript==
// @name         FastPic Upload 5.3
// @name:en      FastPic Upload 5.3
// @namespace    http://tampermonkey.net/
// @version      5.3
// @description  Загрузка изображений на FastPic с расширенными форматами
// @description:en  Image uploading to FastPic with extended formats
// @author       С
// @license MIT
// @match        https://rutracker.org/forum/viewtopic.php?*
// @match        https://rutracker.org/forum/posting.php?*
// @match        https://nnmclub.to/forum/viewtopic.php?*
// @match        https://nnmclub.to/forum/posting.php?*
// @match        https://tapochek.net/viewtopic.php?*
// @match        https://tapochek.net/posting.php*
// @match        https://4pda.to/forum/index.php*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
    'use strict';

    // Настройки по умолчанию
    const DEFAULT_SETTINGS = {
        uploadService: 'fastpic',
        fastpic: {
            codeFormat: 'bb_thumb',  // direct, bb_thumb, bb_full, html_thumb, markdown_thumb
            thumb: {
                checkThumb: 'size',  // 'size', 'text', 'filename', 'no'
                thumbText: 'Увеличить',
                thumbSize: '150',
                thumbSizeVertical: false
            },
            image: {
                origResize: {
                    enabled: false,
                    resSelect: '500',  // '150', '320', '500', '640', '800'
                    customSize: '500'
                },
                origRotate: {
                    enabled: false,
                    value: '0'  // '0', '90', '180', '270'
                },
                optimization: {
                    enabled: false,
                    jpegQuality: '75'  // 0-99
                },
                poster: false
            }
        },
        imgbb: {
            codeFormat: 'bb_thumb_linked',  // viewer_link, direct, html_image, html_full_linked, html_medium_linked, html_thumb_linked, bb_full, bb_full_linked, bb_medium_linked, bb_thumb_linked
            apiKey: '',
            expiration: '',  //значение в секундах
            useOriginalFilename: false
        },
        imagebam: {
            codeFormat: 'bb_thumb',  // direct, bb_thumb, html_thumb
            thumbnailSize: '2',  // размер превью: 1, 2, 3, 4
            contentType: 'sfw',  // тип контента: nsfw, sfw
            galleryEnabled: false,  // включить галерею
            galleryTitle: '' // название галереи
        }
    };

    // Утилитарные функции стилизации
    function addScriptStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .fastpic-upload-progress {
                position: fixed;
                top: 20px;
                right: 20px;
                background: #fff;
                padding: 10px;
                border: 1px solid #ccc;
                border-radius: 5px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                z-index: 9999;
            }
        `;
        document.head.appendChild(style);
    }

    // Функция для определения текущего сайта
    function getCurrentSite() {
        const hostname = window.location.hostname;
        if (hostname.includes('rutracker')) return 'rutracker';
        if (hostname.includes('nnmclub')) return 'nnmclub';
        if (hostname.includes('tapochek')) return 'tapochek';
        if (hostname.includes('4pda')) return '4pda';
        return null;
    }

    // Функция для поиска textarea
    function findTextarea() {
        const site = getCurrentSite();

        switch(site) {
            case 'rutracker':
                return document.querySelector('#post-textarea');
            case 'tapochek':
                return document.querySelector('textarea.editor[name="message"]');
            case 'nnmclub':
                return document.querySelector('#post_body');
            case '4pda':
                return document.querySelector('#ed-0_textarea') || document.querySelector('.ed-textarea');
            default:
                return null;
        }
    }

    // Функция для показа уведомлений
    function showNotification(message, duration = 3000, sessionUrl = '') {
        const notification = document.createElement('div');
        notification.className = 'fastpic-notification';
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: #fff;
            padding: 10px 20px;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            z-index: 9999;
        `;

        if (sessionUrl) {
            const link = document.createElement('a');
            link.href = sessionUrl;
            link.target = '_blank';
            link.textContent = message;
            notification.appendChild(link);
        } else {
            notification.textContent = message;
        }

        document.body.appendChild(notification);

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

    // Функция форматирования кода
    function formatCode(image) {
        const serviceSettings = settings[settings.uploadService];

        if (settings.uploadService === 'fastpic') {

            // Форматы FastPic
            const formats = {
                direct: image.imagePath,
                bb_thumb: `[URL=${image.viewUrl}][IMG]${image.thumbPath}[/IMG][/URL]`,
                bb_full: `[URL=${image.viewUrl}][IMG]${image.imagePath}[/IMG][/URL]`,
                html_thumb: `<a href="${image.viewUrl}" target="_blank"><img src="${image.thumbPath}" border="0"></a>`,
                markdown_thumb: `[![FastPic.Ru](${image.thumbPath})](${image.viewUrl})`
            };

            return formats[serviceSettings.codeFormat]
                .replace('{viewUrl}', image.viewUrl)
                .replace('{imagePath}', image.imagePath)
                .replace('{thumbPath}', image.thumbPath);

            // Форматы ImgBB
        } else if (settings.uploadService === 'imgbb') {
            const formats = {
                viewer_link: image.url_viewer,  // Ссылка на просмотр
                direct: image.url,  // Прямая ссылка

                html_image: `<img src="${image.url}" alt="${image.name}" border="0">`,  // HTML-код изображения
                html_full_linked: `<a href="${image.url_viewer}"><img src="${image.url}" alt="${image.name}" border="0"></a>`,  // HTML-код полноразмерного со ссылкой
                html_medium_linked: `<a href="${image.url_viewer}"><img src="${image.medium?.url || image.url}" alt="${image.name}" border="0"></a>`,  // HTML-код среднего размера со ссылкой
                html_thumb_linked: `<a href="${image.url_viewer}"><img src="${image.thumb.url}" alt="${image.name}" border="0"></a>`,  // HTML-код миниатюры со ссылкой

                bb_full: `[img]${image.url}[/img]`,  // BB-код полноразмерного
                bb_full_linked: `[url=${image.url_viewer}][img]${image.url}[/img][/url]`,  // BB-код полноразмерного со ссылкой
                bb_medium_linked: `[url=${image.url_viewer}][img]${image.medium?.url || image.url}[/img][/url]`,  // BB-код среднего размера со ссылкой
                bb_thumb_linked: `[url=${image.url_viewer}][img]${image.thumb.url}[/img][/url]`  // BB-код миниатюры со ссылкой
            };

            return formats[serviceSettings.codeFormat]
                .replace('{viewUrl}', image.url_viewer)
                .replace('{imagePath}', image.url)
                .replace('{thumbPath}', image.thumb.url)
                .replace('{mediumPath}', image.medium?.url || image.url);

            // Форматы ImageBam
        } else if (settings.uploadService === 'imagebam') {
            const formats = {
                direct: image.url_viewer,  // Прямая ссылка - ссылка на просмотр
                bb_thumb: `[URL=${image.url_viewer}][IMG]${image.thumb.url}[/IMG][/URL]`,  // BB-код с превью
                html_thumb: `<a href="${image.url_viewer}" target="_blank"><img src="${image.thumb.url}" alt=""/></a>`  // HTML-код
            };
            return formats[serviceSettings.codeFormat]
                .replace('{viewUrl}', image.url_viewer)
                .replace('{imagePath}', image.thumb.url.replace('_t.', '.'))
                .replace('{thumbPath}', image.thumb.url);
        }
    }

    // Функция для парсинга ответа FastPic
    function parseFastPicResponse(responseText) {
        // Ищем все блоки UploadSettings в ответе
        const uploadSettingsRegex = /<UploadSettings[^>]*>([\s\S]*?)<\/UploadSettings>/g;
        const results = [];
        let match;

        while ((match = uploadSettingsRegex.exec(responseText)) !== null) {
            const settingsXml = match[0];

            // Извлекаем нужные значения из каждого блока
            const status = settingsXml.match(/<status>([^<]+)<\/status>/)?.[1];

            if (status === 'ok') {
                const imagePath = settingsXml.match(/<imagepath>([^<]+)<\/imagepath>/)?.[1];
                const thumbPath = settingsXml.match(/<thumbpath>([^<]+)<\/thumbpath>/)?.[1];
                const viewUrl = settingsXml.match(/<viewurl>([^<]+)<\/viewurl>/)?.[1];
                const sessionUrl = settingsXml.match(/<sessionurl>([^<]+)<\/sessionurl>/)?.[1];

                if (imagePath && thumbPath && viewUrl) {
                    results.push({
                        imagePath,
                        thumbPath,
                        viewUrl,
                        sessionUrl
                    });
                }
            } else {
                const error = settingsXml.match(/<error>([^<]+)<\/error>/)?.[1] || 'Неизвестная ошибка';
                throw new Error(error);
            }
        }

        // Извлекаем URL сессии из XML ответа FastPic
        const sessionUrl = responseText.match(/<viewurl>([^<]+)<\/viewurl>/)?.[1] || '';

        return { results, sessionUrl };
    }

    // Функция для загрузки на FastPic
    async function uploadToFastPic(files) {
        const formData = new FormData();
        for (let i = 0; i < files.length; i++) {
            formData.append(`file${i + 1}`, files[i]);
        }

        // Добавляем параметры FastPic
        formData.append('uploading', files.length.toString());
        formData.append('check_thumb', settings.fastpic.thumb.checkThumb);
        formData.append('thumb_text', settings.fastpic.thumb.thumbText);
        formData.append('thumb_size', settings.fastpic.thumb.thumbSize);

        if (settings.fastpic.thumb.thumbSizeVertical) {
            formData.append('check_thumb_size_vertical', '1');
        }

        if (settings.fastpic.image.origResize.enabled) {
            formData.append('check_orig_resize', '1');
            formData.append('res_select', settings.fastpic.image.origResize.resSelect);
            formData.append('orig_resize', settings.fastpic.image.origResize.customSize);
        } else {
            // Явно отключаем изменение размера
            formData.append('check_orig_resize', '0');
            formData.append('res_select', '0');
            formData.append('orig_resize', '0');
        }

        if (settings.fastpic.image.origRotate.enabled) {
            formData.append('check_orig_rotate', '1');
            formData.append('orig_rotate', settings.fastpic.image.origRotate.value);
        }

        if (settings.fastpic.image.optimization.enabled) {
            formData.append('check_optimization', 'on');
            formData.append('jpeg_quality', settings.fastpic.image.optimization.jpegQuality);
        }

        if (settings.fastpic.image.poster) {
            formData.append('check_poster', 'on');
        }

        formData.append('submit', 'Загрузить');

        const response = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: 'https://fastpic.org/upload?api=1',
                data: formData,
                onload: (response) => resolve(response),
                onerror: (error) => reject(error)
            });
        });

        return parseFastPicResponse(response.responseText);
    }

    // Функция для загрузки на ImgBB
    async function uploadToImgBB(file) {
        if (!settings.imgbb.apiKey) {
            throw new Error(`Требуется API ключ ImgBB`);
        }

        const base64 = await new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(reader.result.split(',')[1]);
            reader.onerror = reject;
            reader.readAsDataURL(file);
        });

        const formData = new FormData();
        formData.append('image', base64);

        // Передаем имя файла только если включена опция
        if (settings.imgbb.useOriginalFilename) {
            formData.append('name', file.name);
        }

        // Добавляем параметр только если выбран срок хранения
        if (settings.imgbb.expiration) {
            formData.append('expiration', settings.imgbb.expiration);
        }

        const response = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: `https://api.imgbb.com/1/upload?key=${settings.imgbb.apiKey}`,
                data: formData,
                onload: (response) => resolve(response),
                onerror: (error) => reject(error)
            });
        });

        const data = JSON.parse(response.responseText);
        if (!data.success) {
            throw new Error(data.error?.message || 'Ошибка загрузки на ImgBB');
        }

        return data.data;
    }

    // Функция для загрузки на ImageBam
    async function uploadToImageBam(file) {
        // Получаем XSRF токен
        const sessionResponse = await new Promise(resolve => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'https://www.imagebam.com',
                headers: { 'Accept': 'text/html' },
                onload: resolve
            });
        });

        const xsrfToken = sessionResponse.responseHeaders.match(/XSRF-TOKEN=([^;]+)/)?.[1];
        if (!xsrfToken) throw new Error('Не удалось получить токен');

        // Формируем данные для создания сессии
        let data = `thumbnail_size=${settings.imagebam.thumbnailSize}&content_type=${settings.imagebam.contentType}&comments_enabled=false`;
        if (settings.imagebam.galleryEnabled && settings.imagebam.galleryTitle) {
            data += `&gallery=true&gallery_title=${encodeURIComponent(settings.imagebam.galleryTitle)}`;
        }

        // Создаем сессию
        const uploadSessionResponse = await new Promise(resolve => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: 'https://www.imagebam.com/upload/session',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-XSRF-TOKEN': decodeURIComponent(xsrfToken),
                    'Cookie': `XSRF-TOKEN=${xsrfToken}`,
                    'Accept': 'application/json',
                    'Origin': 'https://www.imagebam.com',
                    'Referer': 'https://www.imagebam.com/upload'
                },
                data: data,
                onload: resolve
            });
        });

        const sessionData = JSON.parse(uploadSessionResponse.responseText);
        if (!sessionData.session) {
            throw new Error('Ошибка создания сессии: отсутствует параметр session');
        }

        const sessionId = sessionData.session;

        // Загружаем файл
        const formData = new FormData();
        formData.append('data', sessionData.data);
        formData.append('files[0]', file);

        const uploadResponse = await new Promise(resolve => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: `https://www.imagebam.com/upload?session=${sessionId}`,
                headers: {
                    'X-XSRF-TOKEN': decodeURIComponent(xsrfToken),
                    'Cookie': `XSRF-TOKEN=${xsrfToken}`,
                    'Origin': 'https://www.imagebam.com',
                    'Referer': 'https://www.imagebam.com/upload'
                },
                data: formData,
                onload: resolve
            });
        });

        const uploadResult = JSON.parse(uploadResponse.responseText);
        if (!uploadResult.success) throw new Error('Ошибка загрузки');

        // Получаем BB-код
        const completeResponse = await new Promise(resolve => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: uploadResult.success,
                headers: {
                    'X-XSRF-TOKEN': decodeURIComponent(xsrfToken),
                    'Cookie': `XSRF-TOKEN=${xsrfToken}`,
                    'Referer': 'https://www.imagebam.com/upload'
                },
                onload: resolve
            });
        });

        // Ищем BB-код в input'ах
        const doc = new DOMParser().parseFromString(completeResponse.responseText, 'text/html');
        const bbcode = Array.from(doc.querySelectorAll('input[type="text"]'))
            .map(input => input.value)
            .find(value => value.includes('[URL=') && value.includes('_t.'));

        if (!bbcode) throw new Error('Не удалось найти BB-код');

        // Извлекаем URL
        const urlMatch = bbcode.match(/\[URL=([^\]]+)\]/);
        const imgMatch = bbcode.match(/\[IMG\]([^\]]+)\[\/IMG\]/);

        if (!urlMatch || !imgMatch) throw new Error('Неправильный формат BB-кода');

        return {
            url: imgMatch[1].replace('_t.', '.'),
            url_viewer: urlMatch[1],
            thumb: { url: imgMatch[1] },
            session: sessionId
        };
    }

    // Функция для загрузки изображений
    async function uploadImages(files) {
        switch (settings.uploadService) {
            case 'fastpic':
                return await uploadToFastPic(files);

            case 'imgbb':
                const imgbbResults = [];
                for (const file of files) {
                    const result = await uploadToImgBB(file);
                    imgbbResults.push(result);
                }
                return { results: imgbbResults };

            case 'imagebam':
                const imagebamResults = [];
                for (const file of files) {
                    const result = await uploadToImageBam(file);
                    imagebamResults.push(result);
                }
                return { results: imagebamResults };

            default:
                throw new Error('Неподдерживаемый сервис загрузки');
        }
    }

    // Общая функция обработки загрузки изображений
    async function handleImageUpload(files) {
        if (files.length === 0) return;

        // Проверка форматов и размера
        const allowedFormats = ['image/gif', 'image/jpeg', 'image/png', 'image/webp', 'image/bmp'];
        const maxFileSizes = {
            'fastpic': 25 * 1024 * 1024,  // 25MB
            'imgbb': 32 * 1024 * 1024,    // 32MB
        };
        const maxFiles = {
            'fastpic': 30,
            'imgbb': 30,
        };

        const maxFileSize = maxFileSizes[settings.uploadService];
        const maxFileCount = maxFiles[settings.uploadService];

        // Фильтруем файлы по формату и размеру
        const validFiles = Array.from(files).filter(file => {
            if (!allowedFormats.includes(file.type)) {
                showNotification(`Файл ${file.name} имеет неподдерживаемый формат. Разрешены: gif, jpeg, png, webp, bmp`);
                return false;
            }
            if (file.size > maxFileSize) {
                showNotification(`Файл ${file.name} превышает максимальный размер в ${Math.floor(maxFileSize / 1024 / 1024)}MB`);
                return false;
            }
            return true;
        });

        if (validFiles.length === 0) {
            showNotification('Нет подходящих файлов для загрузки');
            return;
        }

        if (validFiles.length > maxFileCount) {
            showNotification(`Можно загрузить максимум ${maxFileCount} файлов одновременно`);
            return false;
        }

        // Создаем индикатор прогресса
        const progressDiv = document.createElement('div');
        progressDiv.className = 'fastpic-upload-progress';
        document.body.appendChild(progressDiv);

        const textarea = findTextarea();
        if (!textarea) return;

        // Сохраняем позицию курсора
        const cursorPos = textarea.selectionStart;
        let formattedCode = '';

        try {
            progressDiv.textContent = `Загрузка ${validFiles.length} изображений...`;

            const { results: images } = await uploadImages(validFiles);

            // Берем sessionUrl из первого изображения если это FastPic и ImageBam
            let sessionUrl = null;
            if (settings.uploadService === 'fastpic') {
                sessionUrl = images[0]?.sessionUrl;

            // Для ImageBam формируем ссылку на сессию
            } else if (settings.uploadService === 'imagebam') {
                sessionUrl = `https://www.imagebam.com/upload/complete?session=${images[0]?.session}`;
            }

            // Формируем код для всех изображений
            let formattedCode = images.map(image => formatCode(image)).join(' ');

            // Вставляем formattedCode в текстовое поле
            const textBefore = textarea.value.substring(0, cursorPos);
            const textAfter = textarea.value.substring(cursorPos);
            textarea.value = textBefore + formattedCode + textAfter;
            textarea.selectionStart = textarea.selectionEnd = cursorPos + formattedCode.length;

            showNotification(`Успешно загружено ${images.length} изображений`, 3000, sessionUrl);
        } catch (error) {
            console.error('Ошибка при загрузке изображений:', error);
            showNotification(`Ошибка при загрузке изображений: ${error.message}`, 5000);
        } finally {
            setTimeout(() => progressDiv.remove(), 3000);
        }
    }

    // Создание меню настроек
    function createSettingsMenu() {
        const site = getCurrentSite();
        if (!site) return;

        const menuButton = document.createElement('input');
        menuButton.type = 'button';
        menuButton.value = 'Настройки FastPic Upload';
        menuButton.id = 'fastpic-settings-btn';
        menuButton.style.cssText = 'margin: 0 5px;';

        // стиль
        if (site === '4pda') {
            menuButton.className = 'zbtn zbtn-default';
        }

        // Добавление кнопки в зависимости от сайта
        switch(site) {
            case 'rutracker':
                const rutrackerNav = document.querySelector('#ped-submit-buttons');
                if (rutrackerNav) {
                    rutrackerNav.appendChild(menuButton);
                }
                break;

            case 'tapochek':
                const tapochekNav = document.querySelector('.mrg_4.tCenter');
                if (tapochekNav) {
                    tapochekNav.appendChild(menuButton);
                }
                break;

            case 'nnmclub':
                const nnmNav = document.querySelector('td.row2[align="center"][valign="middle"][style*="padding: 6px"]');
                if (nnmNav) {
                    nnmNav.appendChild(menuButton);
                }
                break;

            case '4pda':
                const pdaNav = document.querySelector('.dfrms.text-center');
                if (pdaNav) {
                    pdaNav.appendChild(menuButton);
                }
                break;
        }

        menuButton.addEventListener('click', showSettingsDialog);
    }

    // Создание диалога настроек
    function showSettingsDialog(e) {
        e.preventDefault();

        const dialog = document.createElement('div');
        dialog.className = 'fastpic-settings-dialog';
        dialog.innerHTML = `
            <style>
                .fastpic-settings-dialog {
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background: #fff;
                    padding: 20px;
                    border-radius: 8px;
                    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
                    z-index: 10000;
                    min-width: 300px;
                    max-height: 80vh;
                    overflow-y: auto;
                }
                .fastpic-settings-dialog h3 {
                    margin-top: 0;
                    margin-bottom: 15px;
                }
                .fastpic-settings-dialog .setting-group {
                    margin-bottom: 15px;
                    padding: 10px;
                    border: 1px solid #ddd;
                    border-radius: 4px;
                }
                .fastpic-settings-dialog label {
                    display: block;
                    margin-bottom: 5px;
                }
                .fastpic-settings-dialog select,
                .fastpic-settings-dialog input[type="text"] {
                    width: 100%;
                    padding: 5px;
                    margin-bottom: 10px;
                }
                .fastpic-settings-dialog select[id="fastpic-checkThumb"]:not([value="text"]) ~ #fastpic-thumbText-container {
                    display: none;
                }
                .fastpic-settings-dialog .buttons {
                    text-align: right;
                    margin-top: 15px;
                    display: flex;
                    justify-content: space-between;
                }
                .fastpic-settings-dialog .right-buttons {
                    display: flex;
                    gap: 10px;
                }
                .fastpic-settings-dialog button.reset {
                    background-color: #fff;
                    color: black;
                    border: none;
                    padding: 5px 5px;
                    border-radius: 4px;
                    cursor: pointer;
                }
                .fastpic-settings-dialog button.reset:hover {
                    background-color: #fff;
                }
                .fastpic-overlay {
                    position: fixed;
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background: rgba(0,0,0,0.5);
                    z-index: 9999;
                }
                .service-settings {
                    display: none;
                }
                .service-settings.active {
                    display: block;
                }
            </style>
            <h3>Настройки FastPic Upload</h3>
            <div class="setting-group">
                <label>Сервис загрузки:</label>
                <select id="uploadService">
                    <option value="fastpic">FastPic</option>
                    <option value="imgbb">ImgBB</option>
                    <option value="imagebam">ImageBam</option>
                </select>
            </div>

            <!-- Настройки FastPic -->
            <div id="fastpic-settings" class="service-settings setting-group">
                <h4>Настройки FastPic</h4>

                <!-- Existing code format setting -->
                <label>Формат кода:</label>
                <select id="fastpic-codeFormat">
                    <option value="direct">Прямая ссылка</option>
                    <option value="bb_thumb">BB-код (превью)</option>
                    <option value="bb_full">BB-код (большое изображение)</option>
                    <option value="html_thumb">HTML</option>
                    <option value="markdown_thumb">Markdown</option>
                </select>
                <!-- Preview settings -->
                <div class="setting-group">
                    <h5>Настройки превью</h5>
                    <label>Тип превью:</label>
                    <select id="fastpic-checkThumb">
                        <option value="size">Размер</option>
                        <option value="text">Текст</option>
                        <option value="filename">Имя файла</option>
                        <option value="no">Без превью</option>
                    </select>
                    <div id="fastpic-thumbText-container">
                        <label>Текст превью:</label>
                        <input type="text" id="fastpic-thumbText" value="Увеличить">
                    </div>
                    <label>Размер превью (px):</label>
                    <input type="number" id="fastpic-thumbSize" min="1" max="999" value="150">
                    <label>
                        <input type="checkbox" id="fastpic-thumbSizeVertical">
                        по высоте
                    </label>
                </div>

                <!-- Image settings -->
                <div class="setting-group">
                    <h5>Настройки изображения</h5>

                    <!-- Resize settings -->
                    <label>
                        <input type="checkbox" id="fastpic-origResizeEnabled">
                        Изменить размер
                    </label>
                    <div id="fastpic-resizeOptions" style="margin-left: 20px;">
                        <label>Предустановленный размер:</label>
                        <select id="fastpic-resSelect">
                            <option value="150">150px</option>
                            <option value="320">320px</option>
                            <option value="500">500px</option>
                            <option value="640">640px</option>
                            <option value="800">800px</option>
                        </select>
                        <label>Размер по вертикали (px):</label>
                        <input type="number" id="fastpic-customSize" value="500">
                    </div>

                    <!-- Rotation settings -->
                    <label>
                        <input type="checkbox" id="fastpic-origRotateEnabled">
                        Повернуть изображение
                    </label>
                    <div id="fastpic-rotateOptions" style="margin-left: 20px;">
                        <select id="fastpic-origRotate">
                            <option value="0">0°</option>
                            <option value="90">90° по часовой</option>
                            <option value="180">180°</option>
                            <option value="270">90° против часовой</option>
                        </select>
                    </div>

                    <!-- Optimization settings -->
                    <label>
                        <input type="checkbox" id="fastpic-optimizationEnabled">
                        Оптимизация JPEG
                    </label>
                    <div id="fastpic-optimizationOptions" style="margin-left: 20px;">
                        <label>Качество JPEG (0-99):</label>
                        <input type="number" id="fastpic-jpegQuality" min="0" max="99" value="85">
                    </div>

                    <!-- Poster setting -->
                    <label>
                        <input type="checkbox" id="fastpic-poster">
                        Создать постер
                    </label>
                </div>
            </div>

            <!-- Настройки ImgBB -->
            <div id="imgbb-settings" class="service-settings setting-group">
                <h4>Настройки ImgBB</h4>
                <label><a href="https://api.imgbb.com/" target="_blank">API ключ ImgBB</a>:</label>
                <input type="text" id="imgbb-apiKey" placeholder="Введите API ключ ImgBB">
                <label>Формат кода:</label>
                <select id="imgbb-codeFormat">
                    <option value="viewer_link">Ссылка на просмотр</option>
                    <option value="direct">Прямая ссылка</option>
                    <option value="html_image">HTML-код изображения</option>
                    <option value="html_full_linked">HTML-код полноразмерного со ссылкой</option>
                    <option value="html_medium_linked">HTML-код среднего размера со ссылкой</option>
                    <option value="html_thumb_linked">HTML-код миниатюры со ссылкой</option>
                    <option value="bb_full">BB-код полноразмерного</option>
                    <option value="bb_full_linked">BB-код полноразмерного со ссылкой</option>
                    <option value="bb_medium_linked">BB-код среднего размера со ссылкой</option>
                    <option value="bb_thumb_linked">BB-код миниатюры со ссылкой</option>
                </select>
                <label>Срок хранения:</label>
                <select id="imgbb-expiration">
                    <option value="" selected="">Никогда не удалять</option>
                    <option value="60">1 минута</option>
                    <option value="300">5 минут</option>
                    <option value="900">15 минут</option>
                    <option value="1800">30 минут</option>
                    <option value="3600">1 час</option>
                    <option value="10800">3 часа</option>
                    <option value="21600">6 часов</option>
                    <option value="43200">12 часов</option>
                    <option value="86400">1 день</option>
                    <option value="172800">2 дня</option>
                    <option value="259200">3 дня</option>
                    <option value="345600">4 дня</option>
                    <option value="432000">5 дней</option>
                    <option value="518400">6 дней</option>
                    <option value="604800">1 неделя</option>
                    <option value="1209600">2 недели</option>
                    <option value="1814400">3 недели</option>
                    <option value="2592000">1 месяц</option>
                    <option value="5184000">2 месяца</option>
                    <option value="7776000">3 месяца</option>
                    <option value="10368000">4 месяца</option>
                    <option value="12960000">5 месяцев</option>
                    <option value="15552000">6 месяцев</option>
                </select>
                <label>
                    <input type="checkbox" id="imgbb-useOriginalFilename"> Оригинальное имя файла
                </label>
            </div>

            <!-- Настройки ImageBam -->
            <div id="imagebam-settings" class="service-settings setting-group">
                <h4>Настройки ImageBam</h4>
                <label>Формат кода:</label>
                <select id="imagebam-codeFormat">
                    <option value="direct">Прямая ссылка</option>
                    <option value="bb_thumb">BB-код (превью)</option>
                    <option value="html_thumb">HTML-код</option>
                </select>
                <label>Размер превью:</label>
                <select id="imagebam-thumbnailSize">
                    <option value="1">100x100 (small)</option>
                    <option value="2">180x180 (standard)</option>
                    <option value="3">250x250 (large)</option>
                    <option value="4">300x300 (extra large)</option>
                </select>
                <label>Тип контента:</label>
                <select id="imagebam-contentType">
                    <option value="sfw">Family Safe Content</option>
                    <option value="nsfw">Adult Content</option>
                </select>
                <div>
                    <label>
                        <input type="checkbox" id="imagebam-galleryEnabled"> Использовать галерею
                    </label>
                </div>
                <div id="imagebam-gallery-options" style="display:none; margin-left: 20px; margin-top: 10px;">
                    <label>Название галереи:</label>
                    <input type="text" id="imagebam-galleryTitle" placeholder="Введите название галереи">
                </div>
            </div>

            <div class="buttons">
                <button class="reset" id="resetSettings">Сброс</button>
                <div class="right-buttons">
                    <button id="cancelSettings">Отмена</button>
                    <button id="saveSettings">Сохранить</button>
                </div>
            </div>
        `;

        const overlay = document.createElement('div');
        overlay.className = 'fastpic-overlay';
        document.body.appendChild(overlay);
        document.body.appendChild(dialog);

        overlay.addEventListener('click', (e) => {
            // Проверяем, что клик был именно по оверлею, а не по диалогу
            if (e.target === overlay) {
                dialog.remove();
                overlay.remove();
            }
        });

        // Предотвращение закрытия при клике по самому диалогу
        dialog.addEventListener('click', (e) => {
            e.stopPropagation();
        });

        // Заполняем текущими настройками
        dialog.querySelector('#uploadService').value = settings.uploadService;

        // Обработчик кнопки сброса
        dialog.querySelector('#resetSettings').addEventListener('click', () => {
            // Сохраняем API ключ
            const apiKey = settings.imgbb.apiKey;

            // Сохраняем текущий выбранный сервис
            const currentService = dialog.querySelector('#uploadService').value;

            // Сбрасываем настройки к значениям по умолчанию
            settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));

            // Восстанавливаем API ключ
            settings.imgbb.apiKey = apiKey;

            // Восстанавливаем текущий сервис
            settings.uploadService = currentService;

            // Сохраняем обновленные настройки
            saveSettings();

            // Перезагружаем диалог с сохранением текущего сервиса
            dialog.remove();
            overlay.remove();
            showSettingsDialog(new Event('click'));

            // Показываем уведомление
            showNotification('Настройки сброшены до значений по умолчанию');
        });

        // FastPic настройки
        const fastpicSettings = settings.fastpic;
        // codeFormat
        dialog.querySelector('#fastpic-codeFormat').value = fastpicSettings.codeFormat;

        // Настройки thumb
        dialog.querySelector('#fastpic-checkThumb').value = fastpicSettings.thumb.checkThumb;
        dialog.querySelector('#fastpic-thumbText').value = fastpicSettings.thumb.thumbText;
        dialog.querySelector('#fastpic-thumbSize').value = fastpicSettings.thumb.thumbSize;
        dialog.querySelector('#fastpic-thumbSizeVertical').checked = fastpicSettings.thumb.thumbSizeVertical;

        // Настройки image.origResize
        dialog.querySelector('#fastpic-origResizeEnabled').checked = fastpicSettings.image.origResize.enabled;
        dialog.querySelector('#fastpic-resSelect').value = fastpicSettings.image.origResize.resSelect;
        dialog.querySelector('#fastpic-customSize').value = fastpicSettings.image.origResize.customSize;

        // Настройки origRotate
        dialog.querySelector('#fastpic-origRotateEnabled').checked = fastpicSettings.image.origRotate.enabled;
        dialog.querySelector('#fastpic-origRotate').value = fastpicSettings.image.origRotate.value;

        // Настройки image.optimization
        dialog.querySelector('#fastpic-optimizationEnabled').checked = fastpicSettings.image.optimization.enabled;
        dialog.querySelector('#fastpic-jpegQuality').value = fastpicSettings.image.optimization.jpegQuality;

        // Настройки постера
        dialog.querySelector('#fastpic-poster').checked = fastpicSettings.image.poster;

        // Управление видимостью опций изменения размера
        const resizeOptions = dialog.querySelector('#fastpic-resizeOptions');
        resizeOptions.style.display = fastpicSettings.image.origResize.enabled ? 'block' : 'none';
        dialog.querySelector('#fastpic-origResizeEnabled').addEventListener('change', (e) => {
            resizeOptions.style.display = e.target.checked ? 'block' : 'none';
        });

        // Управление видимостью опций поворота
        const rotateOptions = dialog.querySelector('#fastpic-rotateOptions');
        rotateOptions.style.display = fastpicSettings.image.origRotate.enabled ? 'block' : 'none';
        dialog.querySelector('#fastpic-origRotateEnabled').addEventListener('change', (e) => {
            rotateOptions.style.display = e.target.checked ? 'block' : 'none';
        });

        // Управление видимостью опций оптимизации
        const optimizationOptions = dialog.querySelector('#fastpic-optimizationOptions');
        optimizationOptions.style.display = fastpicSettings.image.optimization.enabled ? 'block' : 'none';
        dialog.querySelector('#fastpic-optimizationEnabled').addEventListener('change', (e) => {
            optimizationOptions.style.display = e.target.checked ? 'block' : 'none';
        });

        // Обработчик изменения типа превью
        const thumbTypeSelect = dialog.querySelector('#fastpic-checkThumb');
        const thumbTextContainer = dialog.querySelector('#fastpic-thumbText-container');
        function updateThumbTextVisibility() {
            thumbTextContainer.style.display = thumbTypeSelect.value === 'text' ? 'block' : 'none';
        }
        thumbTypeSelect.addEventListener('change', updateThumbTextVisibility);
        updateThumbTextVisibility(); // Устанавливаем начальное состояние

        // Синхронизация предустановленного и пользовательского размера
        const resSelect = dialog.querySelector('#fastpic-resSelect');
        const customSize = dialog.querySelector('#fastpic-customSize');
        // Обработчик изменения предустановленного размера
        resSelect.addEventListener('change', (e) => {
            customSize.value = e.target.value;
        });
        // Начальная синхронизация при открытии диалога
        customSize.value = resSelect.value;

        // ImgBB настройки
        dialog.querySelector('#imgbb-apiKey').value = settings.imgbb.apiKey;
        dialog.querySelector('#imgbb-codeFormat').value = settings.imgbb.codeFormat;
        dialog.querySelector('#imgbb-expiration').value = settings.imgbb.expiration;
        dialog.querySelector('#imgbb-useOriginalFilename').checked = settings.imgbb.useOriginalFilename;

        // ImgBam настройки
        dialog.querySelector('#imagebam-codeFormat').value = settings.imagebam.codeFormat;

        // Управление видимостью настроек сервисов
        const updateServiceSettings = () => {
            const service = dialog.querySelector('#uploadService').value;
            document.querySelectorAll('.service-settings').forEach(el => {
                el.classList.remove('active');
            });
            dialog.querySelector(`#${service}-settings`).classList.add('active');
        };

        dialog.querySelector('#uploadService').addEventListener('change', updateServiceSettings);
        updateServiceSettings();

        // imagebam-galleryEnabled
        const galleryCheckbox = dialog.querySelector('#imagebam-galleryEnabled');
        const galleryOptions = dialog.querySelector('#imagebam-gallery-options');
        galleryCheckbox.addEventListener('change', () => {
            galleryOptions.style.display = galleryCheckbox.checked ? 'block' : 'none';
        });

        // Заполнение значений imagebam
        dialog.querySelector('#imagebam-thumbnailSize').value = settings.imagebam.thumbnailSize;
        dialog.querySelector('#imagebam-contentType').value = settings.imagebam.contentType;
        dialog.querySelector('#imagebam-galleryEnabled').checked = settings.imagebam.galleryEnabled;
        dialog.querySelector('#imagebam-galleryTitle').value = settings.imagebam.galleryTitle;
        galleryOptions.style.display = settings.imagebam.galleryEnabled ? 'block' : 'none';

        // Обработчики кнопок
        dialog.querySelector('#cancelSettings').addEventListener('click', () => {
            overlay.remove();
            dialog.remove();
        });

        dialog.querySelector('#saveSettings').addEventListener('click', () => {
            settings.uploadService = dialog.querySelector('#uploadService').value;

            // Сохраняем настройки FastPic
            settings.fastpic = {
                codeFormat: dialog.querySelector('#fastpic-codeFormat').value,
                thumb: {
                    checkThumb: dialog.querySelector('#fastpic-checkThumb').value,
                    thumbText: dialog.querySelector('#fastpic-thumbText').value,
                    thumbSize: dialog.querySelector('#fastpic-thumbSize').value,
                    thumbSizeVertical: dialog.querySelector('#fastpic-thumbSizeVertical').checked
                },
                image: {
                    origResize: {
                        enabled: dialog.querySelector('#fastpic-origResizeEnabled').checked,
                        resSelect: dialog.querySelector('#fastpic-resSelect').value,
                        customSize: dialog.querySelector('#fastpic-customSize').value
                    },
                    origRotate: {
                        enabled: dialog.querySelector('#fastpic-origRotateEnabled').checked,
                        value: dialog.querySelector('#fastpic-origRotate').value
                    },
                    optimization: {
                        enabled: dialog.querySelector('#fastpic-optimizationEnabled').checked,
                        jpegQuality: dialog.querySelector('#fastpic-jpegQuality').value
                    },
                    poster: dialog.querySelector('#fastpic-poster').checked
                }
            };

            // Сохраняем настройки ImgBB
            settings.imgbb = {
                apiKey: dialog.querySelector('#imgbb-apiKey').value,
                codeFormat: dialog.querySelector('#imgbb-codeFormat').value,
                expiration: dialog.querySelector('#imgbb-expiration').value,
                useOriginalFilename: dialog.querySelector('#imgbb-useOriginalFilename').checked
            };

            // Сохраняем настройки ImageBam
            settings.imagebam = {
                codeFormat: dialog.querySelector('#imagebam-codeFormat').value,
                thumbnailSize: dialog.querySelector('#imagebam-thumbnailSize').value,
                contentType: dialog.querySelector('#imagebam-contentType').value,
                galleryEnabled: dialog.querySelector('#imagebam-galleryEnabled').checked,
                galleryTitle: dialog.querySelector('#imagebam-galleryTitle').value
            };

            saveSettings();
            overlay.remove();
            dialog.remove();

            showNotification('Настройки сохранены');
        });
    }

    // Функция настройки кнопки загрузки
    function setupUploadButton() {
        const site = getCurrentSite();
        if (!site) return;

        // Создаем input для файлов
        const input = document.createElement('input');
        input.type = 'file';
        input.multiple = true;
        input.accept = 'image/*';
        input.style.display = 'none';
        document.body.appendChild(input);

        // Находим существующую кнопку загрузки изображений или добавляем новую
        let uploadButton;
        switch(site) {
            case 'rutracker':
                uploadButton = document.querySelector('#load-pic-btn');
                break;
            case 'tapochek':
                // Создаем новую кнопку для tapochek
                uploadButton = document.createElement('input');
                uploadButton.type = 'button';
                uploadButton.value = 'Загрузить картинку';
                uploadButton.style.cssText = 'margin: 0 5px;';
                const tapochekNav = document.querySelector('.mrg_4.tCenter');
                if (tapochekNav) {
                    // Вставляем в начало
                    tapochekNav.insertBefore(uploadButton, tapochekNav.firstChild);
                }
                break;
            case 'nnmclub':
                // Создаем новую кнопку для nnmclub
                uploadButton = document.createElement('input');
                uploadButton.type = 'button';
                uploadButton.value = 'Загрузить картинку';
                uploadButton.style.cssText = 'margin: 0 5px;';
                const nnmNav = document.querySelector('td.row2[align="center"][valign="middle"][style*="padding: 6px"]');
                if (nnmNav) {
                    // Вставляем в начало
                    nnmNav.insertBefore(uploadButton, nnmNav.firstChild);
                }
                break;
            case '4pda':
                // Создаем новую кнопку для 4pda
                uploadButton = document.createElement('input');
                uploadButton.type = 'button';
                uploadButton.value = 'Загрузить картинку';
                uploadButton.className = 'zbtn zbtn-default';  // Используем стили 4pda
                uploadButton.style.cssText = 'margin: 0 5px;';
                const pdaNav = document.querySelector('.dfrms.text-center') || document.querySelector('div[style*="margin-top: 3px"]');
                if (pdaNav) {
                    // Вставляем в начало
                    pdaNav.insertBefore(uploadButton, pdaNav.firstChild);
                }
                break;
        }

        if (uploadButton) {
            uploadButton.onclick = (e) => {
                e.preventDefault();
                input.click();
            };

            // Добавляем обработчик выбора файлов
            input.addEventListener('change', async (e) => {
                const files = Array.from(e.target.files).filter(file => file.type.startsWith('image/'));
                if (files.length > 0) {
                    await handleImageUpload(files);
                }
                input.value = '';  // Сброс input для возможности повторной загрузки тех же файлов
            });
        }
    }

    // Настройка drag&drop для textarea
    function setupDragAndDrop() {
        const textarea = findTextarea();
        if (!textarea) return;

        textarea.addEventListener('dragover', (e) => {
            e.preventDefault();
            e.stopPropagation();
            textarea.style.border = '2px dashed #4a90e2';
        });

        textarea.addEventListener('dragleave', (e) => {
            e.preventDefault();
            e.stopPropagation();
            textarea.style.border = '';
        });

        textarea.addEventListener('drop', async (e) => {
            e.preventDefault();
            e.stopPropagation();
            textarea.style.border = '';

            const files = Array.from(e.dataTransfer.files).filter(file => file.type.startsWith('image/'));
            if (files.length > 0) {
                await handleImageUpload(files);
            }
        });
    }

    // Функция сохранения настроек
    function saveSettings() {
        GM_setValue('fastpicSettings', settings);
    }

    // Инициализация скрипта
    function initializeScript() {
        addScriptStyles();
        createSettingsMenu();
        setupUploadButton();
        setupDragAndDrop();
    }

    // Загрузка сохраненных настроек
    let settings = Object.assign({}, DEFAULT_SETTINGS, GM_getValue('fastpicSettings', {}));

    // Вызов инициализации
    initializeScript();
})();