Greasy Fork

FastPic Upload

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

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

// ==UserScript==
// @name         FastPic Upload
// @name:en      FastPic Upload
// @namespace    http://tampermonkey.net/
// @version      5.0
// @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
        },
        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;
            }
            .fastpic-settings-dialog {
                /* Стили для диалога настроек */
            }
            .fastpic-notification {
                /* Стили для уведомлений */
            }
        `;
        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]);
        }
        formData.append('uploading', files.length.toString());
        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;
                }
                .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 .buttons {
                    text-align: right;
                    margin-top: 15px;
                }
                .fastpic-settings-dialog button {
                    padding: 5px 15px;
                    margin-left: 10px;
                }
                .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>
                <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>
            </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 id="cancelSettings">Отмена</button>
                <button id="saveSettings">Сохранить</button>
            </div>
        `;

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

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

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

        // 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
            };

            // Сохраняем настройки 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();
})();