// ==UserScript== // @name FastPic Upload // @name:en FastPic Upload // @namespace http://tampermonkey.net/ // @version 5.8 // @license MIT // @description Загрузка изображений на FastPic // @description:en Image uploading to FastPic // @author С // @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 // @grant GM_registerMenuCommand // @downloadURL https://update.greasyfork.icu/scripts/527366/FastPic%20Upload.user.js // @updateURL https://update.greasyfork.icu/scripts/527366/FastPic%20Upload.meta.js // ==/UserScript== (function() { 'use strict'; // Настройки по умолчанию const DEFAULT_SETTINGS = { uploadService: 'newfastpic', 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 } }, newfastpic: { codeFormat: 'bb_thumb', thumb: { checkThumb: 'size', thumbText: 'Увеличить', thumbSize: '150', thumbSizeVertical: false }, image: { origResize: { enabled: false, customSize: '1200' }, resizeFrontend: false, optimization: { enabled: false, jpegQuality: '80' }, poster: false }, deleteAfter: '0', albumName: '' }, 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 = ` .fastpicext-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 = 'fastpicext-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' || settings.uploadService === 'newfastpic') { // Форматы FastPic и New.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: ``, markdown_thumb: `[](${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: ``, // HTML-код изображения html_full_linked: ``, // HTML-код полноразмерного со ссылкой html_medium_linked: ``, // HTML-код среднего размера со ссылкой html_thumb_linked: ``, // 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: `` // 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 = /]*>([\s\S]*?)<\/UploadSettings>/g; const results = []; let match; while ((match = uploadSettingsRegex.exec(responseText)) !== null) { const settingsXml = match[0]; // Извлекаем нужные значения из каждого блока const status = settingsXml.match(/([^<]+)<\/status>/)?.[1]; if (status === 'ok') { const imagePath = settingsXml.match(/([^<]+)<\/imagepath>/)?.[1]; const thumbPath = settingsXml.match(/([^<]+)<\/thumbpath>/)?.[1]; const viewUrl = settingsXml.match(/([^<]+)<\/viewurl>/)?.[1]; const sessionUrl = settingsXml.match(/([^<]+)<\/sessionurl>/)?.[1]; if (imagePath && thumbPath && viewUrl) { results.push({ imagePath, thumbPath, viewUrl, sessionUrl }); } } else { const error = settingsXml.match(/([^<]+)<\/error>/)?.[1] || 'Неизвестная ошибка'; throw new Error(error); } } // Извлекаем URL сессии из XML ответа FastPic const sessionUrl = responseText.match(/([^<]+)<\/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); } // Функция для загрузки на New.FastPic async function uploadToNewFastPic(files) { const formData = new FormData(); for (let i = 0; i < files.length; i++) { formData.append(`file${i + 1}`, files[i]); } // Добавляем параметры New FastPic formData.append('uploading', files.length.toString()); // Добавляем настройки превью formData.append('check_thumb', settings.newfastpic.thumb.checkThumb); formData.append('thumb_text', settings.newfastpic.thumb.thumbText); formData.append('thumb_size', settings.newfastpic.thumb.thumbSize); formData.append('check_thumb_size_vertical', settings.newfastpic.thumb.thumbSizeVertical.toString()); // Добавляем настройки изображения formData.append('check_orig_resize', settings.newfastpic.image.origResize.enabled.toString()); if (settings.newfastpic.image.origResize.enabled) { formData.append('orig_resize', settings.newfastpic.image.origResize.customSize); } formData.append('check_resize_frontend', settings.newfastpic.image.resizeFrontend.toString()); formData.append('check_optimization', settings.newfastpic.image.optimization.enabled.toString()); if (settings.newfastpic.image.optimization.enabled) { formData.append('jpeg_quality', settings.newfastpic.image.optimization.jpegQuality); } formData.append('check_poster', settings.newfastpic.image.poster.toString()); formData.append('delete_after', settings.newfastpic.deleteAfter); // Добавляем название альбома if (settings.newfastpic.albumName) { formData.append('album_name', settings.newfastpic.albumName); } const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://new.fastpic.org/v2upload/', data: formData, onload: (response) => resolve(response), onerror: (error) => reject(error) }); }); const data = JSON.parse(response.responseText); // console.log('Server response:', response.responseText); // console.log('New FastPic parsed data:', data); // Преобразуем URL в абсолютные const BASE_URL = 'https://new.fastpic.org'; const makeAbsolute = url => url ? (url.startsWith('http') ? url : new URL(url, BASE_URL).href) : url; // Применяем преобразование ко всем URL const links = { direct: makeAbsolute(data.direct_link), thumb: makeAbsolute(data.thumb_link), view: makeAbsolute(data.view_link), album: makeAbsolute(data.album_link) }; // Определяем URL для сессии (альбом > сессия > просмотр) const sessionUrl = links.album || (data.session_id ? `${BASE_URL}/session/${data.session_id}` : links.view); return { results: [{ imagePath: links.direct, thumbPath: links.thumb, viewUrl: links.view, albumUrl: links.album, sessionId: data.session_id }], sessionUrl: sessionUrl }; } // Функция для загрузки на 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'); } // console.log('Server response:', response.responseText); // console.log('Parsed data:', data); 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('Не удалось получить XSRF-токен'); // Формируем данные для создания сессии 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' }, data: data, onload: resolve }); }); const sessionData = JSON.parse(uploadSessionResponse.responseText); if (!sessionData.session) throw new Error('Ошибка создания сессии: отсутствует параметр 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=${sessionData.session}`, headers: { 'X-XSRF-TOKEN': decodeURIComponent(xsrfToken), 'Cookie': `XSRF-TOKEN=${xsrfToken}` }, 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}` }, 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: sessionData.session }; } // Функция для загрузки изображений async function uploadImages(files) { switch (settings.uploadService) { case 'fastpic': return await uploadToFastPic(files); case 'newfastpic': return await uploadToNewFastPic(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 'newfastpic': 25 * 1024 * 1024, // 25MB 'imgbb': 32 * 1024 * 1024, // 32MB 'imagebam': 16 * 1024 * 1024, }; const maxFiles = { 'fastpic': 30, 'newfastpic': 30, 'imgbb': 30, 'imagebam': 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 = 'fastpicext-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, sessionUrl: uploadSessionUrl } = await uploadImages(validFiles); // Получаем sessionUrl в зависимости от сервиса let sessionUrl = null; // Для FastPic берем sessionUrl из ответа 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}`; } // Для New FastPic используем sessionUrl полученный при загрузке else if (settings.uploadService === 'newfastpic') { sessionUrl = uploadSessionUrl; } // Для ImgBB используем url_viewer else if (settings.uploadService === 'imgbb') { sessionUrl = images[0]?.url_viewer; } // Формируем код для всех изображений 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} изображений`, 15000, 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 = 'fastpicext-settings-btn'; menuButton.style.cssText = 'margin: 0 5px;'; // стили if (site === '4pda') { menuButton.className = 'zbtn zbtn-default'; } else if (site === 'nnmclub') { menuButton.className = 'input mainoption'; } // Добавление кнопки в зависимости от сайта 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') || document.querySelector('div[style*="margin-top:3px"]'); if (pdaNav) { pdaNav.appendChild(menuButton); } break; } menuButton.addEventListener('click', showSettingsDialog); } // Создание диалога настроек function showSettingsDialog(e) { if (e) e.preventDefault(); const dialog = document.createElement('div'); dialog.className = 'fastpicext-settings-dialog'; dialog.innerHTML = ` Настройки FastPic Upload Сервис загрузки: FastPic New FastPic ImgBB ImageBam Настройки FastPic Формат кода: Прямая ссылка BB-код (превью) BB-код (большое изображение) HTML Markdown Настройки превью Тип превью: Размер Текст Имя файла Без надписи Текст превью: Размер превью (px): по высоте Настройки изображения Уменьшить Предустановленный размер: 150px 320px 500px 640px 800px Размер по вертикали (px): Повернуть 0° 90° по часовой 180° 90° против часовой Оптимизировать в JPEG Качество JPEG (0-99): Постер Настройки New FastPic Формат кода: Прямая ссылка BB-код (превью) BB-код (большое изображение) HTML Markdown Настройки превью Тип превью: Размер Текст Имя файла Без надписи Текст превью: Размер превью (px): по высоте Настройки изображения Уменьшить Размер (px): Уменьшать в браузере Оптимизировать в JPEG Качество JPEG (0-99): Постер Автоудаление Не удалять Через 1 минуту Через 5 минут Через 10 минут Через 1 час Через 1 день Альбом Название альбома: Настройки ImgBB API ключ ImgBB: Формат кода: Ссылка на просмотр Прямая ссылка HTML-код изображения HTML-код полноразмерного со ссылкой HTML-код среднего размера со ссылкой HTML-код миниатюры со ссылкой BB-код полноразмерного BB-код полноразмерного со ссылкой BB-код среднего размера со ссылкой BB-код миниатюры со ссылкой Срок хранения: Никогда не удалять 1 минута 5 минут 15 минут 30 минут 1 час 3 часа 6 часов 12 часов 1 день 2 дня 3 дня 4 дня 5 дней 6 дней 1 неделя 2 недели 3 недели 1 месяц 2 месяца 3 месяца 4 месяца 5 месяцев 6 месяцев Оригинальное имя файла Настройки ImageBam Формат кода: Прямая ссылка BB-код (превью) HTML-код Размер превью: 100x100 (small) 180x180 (standard) 250x250 (large) 300x300 (extra large) Тип контента: Family Safe Content Adult Content Использовать галерею Название галереи: Сброс Отмена Сохранить `; const overlay = document.createElement('div'); overlay.className = 'fastpicext-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; // <-- Настройки New.FastPic --> const newfastpicSettings = settings.newfastpic; dialog.querySelector('#newfastpic-codeFormat').value = newfastpicSettings.codeFormat; // Настройки thumb dialog.querySelector('#newfastpic-checkThumb').value = newfastpicSettings.thumb.checkThumb; dialog.querySelector('#newfastpic-thumbText').value = newfastpicSettings.thumb.thumbText; dialog.querySelector('#newfastpic-thumbSize').value = newfastpicSettings.thumb.thumbSize; dialog.querySelector('#newfastpic-thumbSizeVertical').checked = newfastpicSettings.thumb.thumbSizeVertical; // Настройки image.origResize dialog.querySelector('#newfastpic-origResizeEnabled').checked = newfastpicSettings.image.origResize.enabled; dialog.querySelector('#newfastpic-customSize').value = newfastpicSettings.image.origResize.customSize; dialog.querySelector('#newfastpic-resizeFrontend').checked = newfastpicSettings.image.resizeFrontend; // Настройки image.optimization dialog.querySelector('#newfastpic-optimizationEnabled').checked = newfastpicSettings.image.optimization.enabled; dialog.querySelector('#newfastpic-jpegQuality').value = newfastpicSettings.image.optimization.jpegQuality; // Настройки постера dialog.querySelector('#newfastpic-poster').checked = newfastpicSettings.image.poster; // Настройки удаления dialog.querySelector('#newfastpic-deleteAfter').value = newfastpicSettings.deleteAfter; // Настройки альбома dialog.querySelector('#newfastpic-albumName').value = newfastpicSettings.albumName; // Обработчики видимости опций const updateNewFastpicOptionsVisibility = () => { const resizeOptions = dialog.querySelector('#newfastpic-resizeOptions'); const optimizationOptions = dialog.querySelector('#newfastpic-optimizationOptions'); resizeOptions.style.display = dialog.querySelector('#newfastpic-origResizeEnabled').checked ? 'block' : 'none'; optimizationOptions.style.display = dialog.querySelector('#newfastpic-optimizationEnabled').checked ? 'block' : 'none'; }; // Обработчик видимости типа превью const thumbNewFastpicTypeSelect = dialog.querySelector('#newfastpic-checkThumb'); const thumbNewFastpicTextContainer = dialog.querySelector('#newfastpic-thumbText-container'); function updateNewFastpicThumbTextVisibility() { thumbNewFastpicTextContainer.style.display = thumbNewFastpicTypeSelect.value === 'text' ? 'block' : 'none'; } thumbNewFastpicTypeSelect.addEventListener('change', updateNewFastpicThumbTextVisibility); updateNewFastpicThumbTextVisibility(); // Устанавливаем начальное состояние dialog.querySelector('#newfastpic-origResizeEnabled').addEventListener('change', updateNewFastpicOptionsVisibility); dialog.querySelector('#newfastpic-optimizationEnabled').addEventListener('change', updateNewFastpicOptionsVisibility); updateNewFastpicOptionsVisibility(); // <-- Настройки 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; // <-- Настройки ImageBam --> 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 } }; // Сохраняем настройки New FastPic settings.newfastpic = { codeFormat: dialog.querySelector('#newfastpic-codeFormat').value, thumb: { checkThumb: dialog.querySelector('#newfastpic-checkThumb').value, thumbText: dialog.querySelector('#newfastpic-thumbText').value, thumbSize: dialog.querySelector('#newfastpic-thumbSize').value, thumbSizeVertical: dialog.querySelector('#newfastpic-thumbSizeVertical').checked }, image: { origResize: { enabled: dialog.querySelector('#newfastpic-origResizeEnabled').checked, customSize: dialog.querySelector('#newfastpic-customSize').value }, resizeFrontend: dialog.querySelector('#newfastpic-resizeFrontend').checked, optimization: { enabled: dialog.querySelector('#newfastpic-optimizationEnabled').checked, jpegQuality: dialog.querySelector('#newfastpic-jpegQuality').value }, poster: dialog.querySelector('#newfastpic-poster').checked }, deleteAfter: dialog.querySelector('#newfastpic-deleteAfter').value, albumName: dialog.querySelector('#newfastpic-albumName').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.className = 'input mainoption'; // Используем стили nnmclub 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 setupClipboardPaste() { const textarea = findTextarea(); if (!textarea) return; textarea.addEventListener('paste', async (e) => { // Получаем данные из буфера обмена const clipboardData = e.clipboardData || window.clipboardData; // Сначала проверяем наличие файлов (важно для Firefox) if (clipboardData.files && clipboardData.files.length > 0) { const imageFiles = Array.from(clipboardData.files).filter(file => file.type.startsWith('image/')); if (imageFiles.length > 0) { e.preventDefault(); // Останавливаем стандартную вставку текста await handleImageUpload(imageFiles); return; } } // Проверяем наличие изображений в буфере обмена (для Chrome и других браузеров) const items = clipboardData.items; if (items) { // Ищем изображения среди элементов буфера const imageItems = Array.from(items) .filter(item => item.kind === 'file' && item.type.startsWith('image/')) .map(item => item.getAsFile()); if (imageItems.length > 0) { e.preventDefault(); // Останавливаем стандартную вставку текста await handleImageUpload(imageItems); } } }); } // Функция сохранения настроек function saveSettings() { GM_setValue('fastpicextSettings', settings); } // Добавляем пункт меню в Tampermonkey function registerTampermonkeyMenu() { if (typeof GM_registerMenuCommand !== 'undefined') { GM_registerMenuCommand('Настройки FastPic Upload', showSettingsDialog); } } // Инициализация скрипта function initializeScript() { addScriptStyles(); createSettingsMenu(); setupUploadButton(); setupDragAndDrop(); setupClipboardPaste(); // Добавляем функцию вставки из буфера обмена registerTampermonkeyMenu(); } // Загрузка сохраненных настроек let settings = Object.assign({}, DEFAULT_SETTINGS, GM_getValue('fastpicextSettings', {})); // Вызов инициализации initializeScript(); })();