([^<]+)<\/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 = `
Настройки FastPic Upload
Настройки FastPic
`;
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();
})();