FastPic Upload 6.0

Загрузка изображений на FastPic

  1. // ==UserScript==
  2. // @name FastPic Upload 6.0
  3. // @name:en FastPic Upload 6.0
  4. // @namespace http://tampermonkey.net/
  5. // @version 6.0
  6. // @license MIT
  7. // @description Загрузка изображений на FastPic
  8. // @description:en Image uploading to FastPic
  9. // @author С
  10. // @match https://rutracker.org/forum/viewtopic.php?*
  11. // @match https://rutracker.org/forum/posting.php?*
  12. // @match https://nnmclub.to/forum/viewtopic.php?*
  13. // @match https://nnmclub.to/forum/posting.php?*
  14. // @match https://tapochek.net/viewtopic.php?*
  15. // @match https://tapochek.net/posting.php*
  16. // @match https://4pda.to/forum/index.php*
  17. // @grant GM_xmlhttpRequest
  18. // @grant GM_getValue
  19. // @grant GM_setValue
  20. // @grant GM_registerMenuCommand
  21. // ==/UserScript==
  22.  
  23. (function() {
  24. 'use strict';
  25.  
  26. // Настройки по умолчанию
  27. const DEFAULT_SETTINGS = {
  28. uploadService: 'newfastpic',
  29. fastpic: {
  30. codeFormat: 'bb_thumb', // direct, bb_thumb, bb_full, html_thumb, markdown_thumb
  31. thumb: {
  32. checkThumb: 'size', // 'size', 'text', 'filename', 'no'
  33. thumbText: 'Увеличить',
  34. thumbSize: '150',
  35. thumbSizeVertical: false
  36. },
  37. image: {
  38. origResize: {
  39. enabled: false,
  40. resSelect: '500', // '150', '320', '500', '640', '800'
  41. customSize: '500'
  42. },
  43. origRotate: {
  44. enabled: false,
  45. value: '0' // '0', '90', '180', '270'
  46. },
  47. optimization: {
  48. enabled: false,
  49. jpegQuality: '75' // 0-99
  50. },
  51. poster: false
  52. }
  53. },
  54. newfastpic: {
  55. codeFormat: 'bb_thumb',
  56. thumb: {
  57. checkThumb: 'size',
  58. thumbText: 'Увеличить',
  59. thumbSize: '150',
  60. thumbSizeVertical: false
  61. },
  62. image: {
  63. origResize: {
  64. enabled: false,
  65. customSize: '1200'
  66. },
  67. resizeFrontend: false,
  68. optimization: {
  69. enabled: false,
  70. jpegQuality: '80'
  71. },
  72. poster: false
  73. },
  74. deleteAfter: '0',
  75. albumName: '',
  76. useExistingAlbum: false, // использовать существующий альбом
  77. selectedAlbumId: '', // ID выбранного альбома
  78. availableAlbums: [] // список доступных альбомов
  79. },
  80. imgbb: {
  81. 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
  82. apiKey: '',
  83. expiration: '', //значение в секундах
  84. useOriginalFilename: false
  85. },
  86. imagebam: {
  87. codeFormat: 'bb_thumb', // direct, bb_thumb, html_thumb
  88. thumbnailSize: '2', // размер превью: 1, 2, 3, 4
  89. contentType: 'sfw', // тип контента: nsfw, sfw
  90. galleryEnabled: false, // включить галерею
  91. galleryTitle: '', // название галереи
  92. useExistingGallery: false, // использовать существующую галерею
  93. selectedGalleryToken: '', // токен выбранной галереи
  94. availableGalleries: [] // список доступных галерей
  95. }
  96. };
  97.  
  98. // Функция глубокого слияния объектов
  99. function deepMerge(target, source) {
  100. const result = { ...target };
  101.  
  102. for (const key in source) {
  103. if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
  104. result[key] = deepMerge(target[key] || {}, source[key]);
  105. } else {
  106. result[key] = source[key];
  107. }
  108. }
  109.  
  110. return result;
  111. }
  112.  
  113. // ===== ФУНКЦИИ ДЛЯ РАБОТЫ С АЛЬБОМАМИ NEW FASTPIC =====
  114.  
  115. // Функция получения списка альбомов с HTML страницы New FastPic
  116. async function fetchNewFastPicAlbums() {
  117. try {
  118. const response = await new Promise(resolve => {
  119. GM_xmlhttpRequest({
  120. method: 'GET',
  121. url: 'https://new.fastpic.org',
  122. headers: {
  123. 'Accept': 'text/html',
  124. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  125. },
  126. onload: resolve
  127. });
  128. });
  129.  
  130. // Ищем переменную uploadsList в JavaScript коде страницы
  131. const htmlContent = response.responseText;
  132.  
  133. // Ищем объявление uploadsList в JavaScript
  134. const uploadsListMatch = htmlContent.match(/uploadsList:\s*(\[.*?\])/s);
  135.  
  136. if (uploadsListMatch) {
  137. try {
  138. // Парсим JSON массив альбомов
  139. const uploadsListStr = uploadsListMatch[1];
  140. const uploadsList = JSON.parse(uploadsListStr);
  141. const existingAlbums = [];
  142.  
  143. uploadsList.forEach((upload) => {
  144. const albumId = upload.upload_id;
  145. const title = upload.upload_name;
  146.  
  147. // Пропускаем пустой option с текстом "Выбор из ваших альбомов"
  148. if (albumId && title && !title.includes('Выбор из ваших альбомов') && !title.includes('последние 50')) {
  149. existingAlbums.push({
  150. id: albumId,
  151. title: title
  152. });
  153. }
  154. });
  155.  
  156. return existingAlbums;
  157.  
  158. } catch (parseError) {
  159. console.error('Ошибка парсинга JSON альбомов New FastPic:', parseError);
  160. return [];
  161. }
  162. }
  163.  
  164. return [];
  165.  
  166. } catch (error) {
  167. console.error('Ошибка при получении списка альбомов New FastPic:', error);
  168. return [];
  169. }
  170. }
  171.  
  172. // Функция обновления списка альбомов в настройках New FastPic
  173. async function updateNewFastPicAlbums() {
  174. const albums = await fetchNewFastPicAlbums();
  175. settings.newfastpic.availableAlbums = albums;
  176. saveSettings();
  177. return albums;
  178. }
  179.  
  180. // ===== ФУНКЦИИ ДЛЯ РАБОТЫ С ГАЛЕРЕЯМИ IMAGEBAM =====
  181.  
  182. // Функция получения списка галерей с HTML страницы ImageBam
  183. async function fetchImageBamGalleries() {
  184. try {
  185. const response = await new Promise(resolve => {
  186. GM_xmlhttpRequest({
  187. method: 'GET',
  188. url: 'https://www.imagebam.com',
  189. headers: { 'Accept': 'text/html' },
  190. onload: resolve
  191. });
  192. });
  193.  
  194. const parser = new DOMParser();
  195. const doc = parser.parseFromString(response.responseText, 'text/html');
  196.  
  197. const gallerySelect = doc.querySelector('select[name="gallery"]');
  198. if (!gallerySelect) return [];
  199.  
  200. const existingGalleries = [];
  201. const optgroup = gallerySelect.querySelector('optgroup[label="Existing Galleries"]');
  202.  
  203. if (optgroup) {
  204. const options = optgroup.querySelectorAll('option');
  205. options.forEach(option => {
  206. const token = option.value;
  207. const title = option.textContent.trim();
  208. if (token && token !== '-1' && !isNaN(token)) {
  209. existingGalleries.push({
  210. token: token,
  211. title: title
  212. });
  213. }
  214. });
  215. }
  216.  
  217. return existingGalleries;
  218. } catch (error) {
  219. console.error('Ошибка при получении списка галерей ImageBam:', error);
  220. return [];
  221. }
  222. }
  223.  
  224. // Функция обновления списка галерей в настройках
  225. async function updateImageBamGalleries() {
  226. const galleries = await fetchImageBamGalleries();
  227. settings.imagebam.availableGalleries = galleries;
  228. saveSettings();
  229. return galleries;
  230. }
  231.  
  232. // Утилитарные функции стилизации
  233. function addScriptStyles() {
  234. const style = document.createElement('style');
  235. style.textContent = `
  236. .fastpicext-upload-progress {
  237. position: fixed;
  238. top: 20px;
  239. right: 20px;
  240. background: #fff;
  241. padding: 10px;
  242. border: 1px solid #ccc;
  243. border-radius: 5px;
  244. box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  245. z-index: 9999;
  246. }
  247.  
  248. #fastpicext-notifications-container {
  249. position: fixed;
  250. top: 20px;
  251. right: 20px;
  252. z-index: 10000;
  253. pointer-events: none;
  254. }
  255.  
  256. .fastpicext-notification {
  257. background: #fff;
  258. padding: 10px 20px;
  259. margin-bottom: 10px;
  260. border-radius: 5px;
  261. box-shadow: 0 2px 5px rgba(0,0,0,0.3);
  262. pointer-events: auto;
  263. max-width: 300px;
  264. word-wrap: break-word;
  265. border: 1px solid #e0e0e0;
  266. }
  267.  
  268. .fastpicext-notification a {
  269. color: #0066cc;
  270. text-decoration: none;
  271. }
  272.  
  273. .fastpicext-notification a:hover {
  274. text-decoration: underline;
  275. }
  276. `;
  277. document.head.appendChild(style);
  278. }
  279.  
  280. // Функция для определения текущего сайта
  281. function getCurrentSite() {
  282. const hostname = window.location.hostname;
  283. if (hostname.includes('rutracker')) return 'rutracker';
  284. if (hostname.includes('nnmclub')) return 'nnmclub';
  285. if (hostname.includes('tapochek')) return 'tapochek';
  286. if (hostname.includes('4pda')) return '4pda';
  287. return null;
  288. }
  289.  
  290. // Функция для поиска textarea
  291. function findTextarea() {
  292. const site = getCurrentSite();
  293.  
  294. switch(site) {
  295. case 'rutracker':
  296. return document.querySelector('#post-textarea');
  297. case 'tapochek':
  298. return document.querySelector('textarea.editor[name="message"]');
  299. case 'nnmclub':
  300. return document.querySelector('#post_body');
  301. case '4pda':
  302. return document.querySelector('#ed-0_textarea') || document.querySelector('.ed-textarea');
  303. default:
  304. return null;
  305. }
  306. }
  307.  
  308. // Функция для показа уведомлений
  309. function showNotification(message, duration = 3000, sessionUrl = '') {
  310. // Создаем или находим контейнер для уведомлений
  311. let notificationContainer = document.getElementById('fastpicext-notifications-container');
  312. if (!notificationContainer) {
  313. notificationContainer = document.createElement('div');
  314. notificationContainer.id = 'fastpicext-notifications-container';
  315. notificationContainer.style.cssText = `
  316. position: fixed;
  317. top: 20px;
  318. right: 20px;
  319. z-index: 10000;
  320. pointer-events: none;
  321. `;
  322. document.body.appendChild(notificationContainer);
  323. }
  324.  
  325. const notification = document.createElement('div');
  326. notification.className = 'fastpicext-notification';
  327. notification.style.cssText = `
  328. background: #fff;
  329. padding: 10px 20px;
  330. margin-bottom: 10px;
  331. border-radius: 5px;
  332. box-shadow: 0 2px 5px rgba(0,0,0,0.3);
  333. pointer-events: auto;
  334. opacity: 0;
  335. transform: translateX(100%);
  336. transition: all 0.3s ease;
  337. max-width: 300px;
  338. word-wrap: break-word;
  339. border: 1px solid #e0e0e0;
  340. `;
  341.  
  342. if (sessionUrl) {
  343. const link = document.createElement('a');
  344. link.href = sessionUrl;
  345. link.target = '_blank';
  346. link.textContent = message;
  347. link.style.cssText = 'color: #0066cc; text-decoration: none;';
  348. link.addEventListener('mouseover', () => link.style.textDecoration = 'underline');
  349. link.addEventListener('mouseout', () => link.style.textDecoration = 'none');
  350. notification.appendChild(link);
  351. } else {
  352. notification.textContent = message;
  353. }
  354.  
  355. // Добавляем уведомление в контейнер
  356. notificationContainer.appendChild(notification);
  357.  
  358. // Анимация появления
  359. requestAnimationFrame(() => {
  360. notification.style.opacity = '1';
  361. notification.style.transform = 'translateX(0)';
  362. });
  363.  
  364. // Удаление уведомления
  365. setTimeout(() => {
  366. notification.style.opacity = '0';
  367. notification.style.transform = 'translateX(100%)';
  368.  
  369. setTimeout(() => {
  370. if (notification.parentNode) {
  371. notification.remove();
  372. }
  373.  
  374. // Удаляем контейнер, если он пустой
  375. if (notificationContainer.children.length === 0) {
  376. notificationContainer.remove();
  377. }
  378. }, 300);
  379. }, duration);
  380. }
  381.  
  382. // Функция форматирования кода
  383. function formatCode(image) {
  384. const serviceSettings = settings[settings.uploadService];
  385.  
  386. // Форматы FastPic и New.FastPic
  387. if (settings.uploadService === 'fastpic' || settings.uploadService === 'newfastpic') {
  388. const formats = {
  389. direct: image.imagePath,
  390. bb_thumb: `[URL=${image.viewUrl}][IMG]${image.thumbPath}[/IMG][/URL]`,
  391. bb_full: `[URL=${image.viewUrl}][IMG]${image.imagePath}[/IMG][/URL]`,
  392. html_thumb: `<a href="${image.viewUrl}" target="_blank"><img src="${image.thumbPath}" border="0"></a>`,
  393. markdown_thumb: `[![FastPic.Ru](${image.thumbPath})](${image.viewUrl})`
  394. };
  395.  
  396. return formats[serviceSettings.codeFormat]
  397. .replace('{viewUrl}', image.viewUrl)
  398. .replace('{imagePath}', image.imagePath)
  399. .replace('{thumbPath}', image.thumbPath);
  400. }
  401.  
  402. // Форматы ImgBB
  403. else if (settings.uploadService === 'imgbb') {
  404. const formats = {
  405. viewer_link: image.url_viewer, // Ссылка на просмотр
  406. direct: image.url, // Прямая ссылка
  407.  
  408. html_image: `<img src="${image.url}" alt="${image.name}" border="0">`, // HTML-код изображения
  409. html_full_linked: `<a href="${image.url_viewer}"><img src="${image.url}" alt="${image.name}" border="0"></a>`, // HTML-код полноразмерного со ссылкой
  410. html_medium_linked: `<a href="${image.url_viewer}"><img src="${image.medium?.url || image.url}" alt="${image.name}" border="0"></a>`, // HTML-код среднего размера со ссылкой
  411. html_thumb_linked: `<a href="${image.url_viewer}"><img src="${image.thumb.url}" alt="${image.name}" border="0"></a>`, // HTML-код миниатюры со ссылкой
  412.  
  413. bb_full: `[img]${image.url}[/img]`, // BB-код полноразмерного
  414. bb_full_linked: `[url=${image.url_viewer}][img]${image.url}[/img][/url]`, // BB-код полноразмерного со ссылкой
  415. bb_medium_linked: `[url=${image.url_viewer}][img]${image.medium?.url || image.url}[/img][/url]`, // BB-код среднего размера со ссылкой
  416. bb_thumb_linked: `[url=${image.url_viewer}][img]${image.thumb.url}[/img][/url]` // BB-код миниатюры со ссылкой
  417. };
  418.  
  419. return formats[serviceSettings.codeFormat]
  420. .replace('{viewUrl}', image.url_viewer)
  421. .replace('{imagePath}', image.url)
  422. .replace('{thumbPath}', image.thumb.url)
  423. .replace('{mediumPath}', image.medium?.url || image.url);
  424.  
  425. }
  426.  
  427. // Форматы ImageBam
  428. else if (settings.uploadService === 'imagebam') {
  429. const formats = {
  430. direct: image.url_viewer, // Прямая ссылка - ссылка на просмотр
  431. bb_thumb: `[URL=${image.url_viewer}][IMG]${image.thumb.url}[/IMG][/URL]`, // BB-код с превью
  432. html_thumb: `<a href="${image.url_viewer}" target="_blank"><img src="${image.thumb.url}" alt=""/></a>` // HTML-код
  433. };
  434. return formats[serviceSettings.codeFormat]
  435. .replace('{viewUrl}', image.url_viewer)
  436. .replace('{imagePath}', image.thumb.url.replace('_t.', '.'))
  437. .replace('{thumbPath}', image.thumb.url);
  438. }
  439. }
  440.  
  441. // Функция для парсинга ответа FastPic
  442. function parseFastPicResponse(responseText) {
  443. // Ищем все блоки UploadSettings в ответе
  444. const uploadSettingsRegex = /<UploadSettings[^>]*>([\s\S]*?)<\/UploadSettings>/g;
  445. const results = [];
  446. let match;
  447.  
  448. while ((match = uploadSettingsRegex.exec(responseText)) !== null) {
  449. const settingsXml = match[0];
  450.  
  451. // Извлекаем нужные значения из каждого блока
  452. const status = settingsXml.match(/<status>([^<]+)<\/status>/)?.[1];
  453.  
  454. if (status === 'ok') {
  455. const imagePath = settingsXml.match(/<imagepath>([^<]+)<\/imagepath>/)?.[1];
  456. const thumbPath = settingsXml.match(/<thumbpath>([^<]+)<\/thumbpath>/)?.[1];
  457. const viewUrl = settingsXml.match(/<viewurl>([^<]+)<\/viewurl>/)?.[1];
  458. const sessionUrl = settingsXml.match(/<sessionurl>([^<]+)<\/sessionurl>/)?.[1];
  459.  
  460. if (imagePath && thumbPath && viewUrl) {
  461. results.push({
  462. imagePath,
  463. thumbPath,
  464. viewUrl,
  465. sessionUrl
  466. });
  467. }
  468. } else {
  469. const error = settingsXml.match(/<error>([^<]+)<\/error>/)?.[1] || 'Неизвестная ошибка';
  470. throw new Error(error);
  471. }
  472. }
  473.  
  474. // Извлекаем URL сессии из XML ответа FastPic
  475. const sessionUrl = responseText.match(/<viewurl>([^<]+)<\/viewurl>/)?.[1] || '';
  476.  
  477. return { results, sessionUrl };
  478. }
  479.  
  480. // Функция для загрузки на FastPic
  481. async function uploadToFastPic(files) {
  482. const formData = new FormData();
  483. for (let i = 0; i < files.length; i++) {
  484. formData.append(`file${i + 1}`, files[i]);
  485. }
  486.  
  487. // Добавляем параметры FastPic
  488. formData.append('uploading', files.length.toString());
  489.  
  490. // Добавляем настройки превью
  491. formData.append('check_thumb', settings.fastpic.thumb.checkThumb);
  492. formData.append('thumb_text', settings.fastpic.thumb.thumbText);
  493.  
  494. formData.append('thumb_size', settings.fastpic.thumb.thumbSize);
  495. if (settings.fastpic.thumb.thumbSizeVertical) {
  496. formData.append('check_thumb_size_vertical', '1');
  497. }
  498.  
  499. // Добавляем настройки изображения
  500. if (settings.fastpic.image.origResize.enabled) {
  501. formData.append('check_orig_resize', '1');
  502. formData.append('res_select', settings.fastpic.image.origResize.resSelect);
  503. formData.append('orig_resize', settings.fastpic.image.origResize.customSize);
  504. } else {
  505. // Явно отключаем изменение размера
  506. formData.append('check_orig_resize', '0');
  507. formData.append('res_select', '0');
  508. formData.append('orig_resize', '0');
  509. }
  510.  
  511. if (settings.fastpic.image.origRotate.enabled) {
  512. formData.append('check_orig_rotate', '1');
  513. formData.append('orig_rotate', settings.fastpic.image.origRotate.value);
  514. }
  515.  
  516. if (settings.fastpic.image.optimization.enabled) {
  517. formData.append('check_optimization', 'on');
  518. formData.append('jpeg_quality', settings.fastpic.image.optimization.jpegQuality);
  519. }
  520.  
  521. if (settings.fastpic.image.poster) {
  522. formData.append('check_poster', 'on');
  523. }
  524.  
  525. formData.append('submit', 'Загрузить');
  526.  
  527. const response = await new Promise((resolve, reject) => {
  528. GM_xmlhttpRequest({
  529. method: 'POST',
  530. url: 'https://fastpic.org/upload?api=1',
  531. data: formData,
  532. onload: (response) => resolve(response),
  533. onerror: (error) => reject(error)
  534. });
  535. });
  536.  
  537. return parseFastPicResponse(response.responseText);
  538. }
  539.  
  540. // Функция для загрузки на New.FastPic
  541. async function uploadToNewFastPic(files) {
  542. const formData = new FormData();
  543. for (let i = 0; i < files.length; i++) {
  544. formData.append(`file${i + 1}`, files[i]);
  545. }
  546.  
  547. // Добавляем параметры New FastPic
  548. formData.append('uploading', files.length.toString());
  549.  
  550. // Логика работы с альбомами
  551. if (settings.newfastpic.useExistingAlbum && settings.newfastpic.selectedAlbumId) {
  552. // Используем существующий альбом - передаем только upload_id
  553. formData.append('upload_id', settings.newfastpic.selectedAlbumId);
  554. } else if (settings.newfastpic.albumName) {
  555. // Создаем новый альбом - передаем upload_id + album_name
  556. // Для новой галереи можно использовать любой upload_id или не передавать вовсе
  557. formData.append('album_name', settings.newfastpic.albumName);
  558. }
  559.  
  560. formData.append('fp', 'not-loaded');
  561.  
  562. // Добавляем настройки превью
  563. formData.append('check_thumb', settings.newfastpic.thumb.checkThumb);
  564. formData.append('thumb_text', settings.newfastpic.thumb.thumbText);
  565. formData.append('thumb_size', settings.newfastpic.thumb.thumbSize);
  566. formData.append('check_thumb_size_vertical', settings.newfastpic.thumb.thumbSizeVertical.toString());
  567.  
  568. // Добавляем настройки изображения
  569. formData.append('check_orig_resize', settings.newfastpic.image.origResize.enabled.toString());
  570. if (settings.newfastpic.image.origResize.enabled) {
  571. formData.append('orig_resize', settings.newfastpic.image.origResize.customSize);
  572. }
  573. formData.append('check_resize_frontend', settings.newfastpic.image.resizeFrontend.toString());
  574.  
  575. formData.append('check_optimization', settings.newfastpic.image.optimization.enabled.toString());
  576. if (settings.newfastpic.image.optimization.enabled) {
  577. formData.append('jpeg_quality', settings.newfastpic.image.optimization.jpegQuality);
  578. }
  579.  
  580. formData.append('check_poster', settings.newfastpic.image.poster.toString());
  581. formData.append('delete_after', settings.newfastpic.deleteAfter);
  582.  
  583. const response = await new Promise((resolve, reject) => {
  584. GM_xmlhttpRequest({
  585. method: 'POST',
  586. url: 'https://new.fastpic.org/v2upload/',
  587. data: formData,
  588. onload: (response) => resolve(response),
  589. onerror: (error) => reject(error)
  590. });
  591. });
  592.  
  593. const data = JSON.parse(response.responseText);
  594. // console.log('Server response:', response.responseText);
  595. // console.log('New FastPic parsed data:', data);
  596.  
  597. // Преобразуем URL в абсолютные с помощью URL API
  598. const BASE_URL = 'https://new.fastpic.org';
  599. const makeAbsolute = url => url ? (url.startsWith('http') ? url : new URL(url, BASE_URL).href) : url;
  600.  
  601. // Применяем преобразование ко всем URL
  602. const links = {
  603. direct: makeAbsolute(data.direct_link),
  604. thumb: makeAbsolute(data.thumb_link),
  605. view: makeAbsolute(data.view_link),
  606. album: makeAbsolute(data.album_link)
  607. };
  608.  
  609. // Определяем URL для сессии (альбом > сессия > просмотр)
  610. const sessionUrl = links.album ||
  611. (data.session_id ? `${BASE_URL}/session/${data.session_id}` : links.view);
  612.  
  613. return {
  614. results: [{
  615. imagePath: links.direct,
  616. thumbPath: links.thumb,
  617. viewUrl: links.view,
  618. albumUrl: links.album,
  619. sessionId: data.session_id
  620. }],
  621. sessionUrl: sessionUrl
  622. };
  623. }
  624.  
  625. // Функция для загрузки на ImgBB
  626. async function uploadToImgBB(file) {
  627. if (!settings.imgbb.apiKey) {
  628. throw new Error(`Требуется API ключ ImgBB`);
  629. }
  630.  
  631. const base64 = await new Promise((resolve, reject) => {
  632. const reader = new FileReader();
  633. reader.onload = () => resolve(reader.result.split(',')[1]);
  634. reader.onerror = reject;
  635. reader.readAsDataURL(file);
  636. });
  637.  
  638. const formData = new FormData();
  639. formData.append('image', base64);
  640.  
  641. // Передаем имя файла только если включена опция
  642. if (settings.imgbb.useOriginalFilename) {
  643. formData.append('name', file.name);
  644. }
  645.  
  646. // Добавляем параметр только если выбран срок хранения
  647. if (settings.imgbb.expiration) {
  648. formData.append('expiration', settings.imgbb.expiration);
  649. }
  650.  
  651. const response = await new Promise((resolve, reject) => {
  652. GM_xmlhttpRequest({
  653. method: 'POST',
  654. url: `https://api.imgbb.com/1/upload?key=${settings.imgbb.apiKey}`,
  655. data: formData,
  656. onload: (response) => resolve(response),
  657. onerror: (error) => reject(error)
  658. });
  659. });
  660.  
  661. const data = JSON.parse(response.responseText);
  662. if (!data.success) {
  663. throw new Error(data.error?.message || 'Ошибка загрузки на ImgBB');
  664. }
  665.  
  666. // console.log('Server response:', response.responseText);
  667. // console.log('Parsed data:', data);
  668.  
  669. return data.data;
  670. }
  671.  
  672. // Функция для загрузки на ImageBam
  673. async function uploadToImageBam(file) {
  674. // Получаем XSRF-токен
  675. const sessionResponse = await new Promise(resolve => {
  676. GM_xmlhttpRequest({
  677. method: 'GET',
  678. url: 'https://www.imagebam.com',
  679. headers: { 'Accept': 'text/html' },
  680. onload: resolve
  681. });
  682. });
  683.  
  684. const xsrfToken = sessionResponse.responseHeaders.match(/XSRF-TOKEN=([^;]+)/)?.[1];
  685. if (!xsrfToken) throw new Error('Не удалось получить XSRF-токен');
  686.  
  687. // Формируем данные для создания сессии
  688. let data = `thumbnail_size=${settings.imagebam.thumbnailSize}&content_type=${settings.imagebam.contentType}&comments_enabled=false`;
  689.  
  690. // Логика для галерей
  691. if (settings.imagebam.galleryEnabled) {
  692. data += '&gallery=true';
  693.  
  694. // Если используется существующая галерея
  695. if (settings.imagebam.useExistingGallery && settings.imagebam.selectedGalleryToken) {
  696. data += `&gallery_token=${settings.imagebam.selectedGalleryToken}&gallery_title=`;
  697. }
  698. // Если создается новая галерея
  699. else if (settings.imagebam.galleryTitle) {
  700. data += `&gallery_token=&gallery_title=${encodeURIComponent(settings.imagebam.galleryTitle)}`;
  701. }
  702. }
  703.  
  704. // Создаем сессию
  705. const uploadSessionResponse = await new Promise(resolve => {
  706. GM_xmlhttpRequest({
  707. method: 'POST',
  708. url: 'https://www.imagebam.com/upload/session',
  709. headers: {
  710. 'Content-Type': 'application/x-www-form-urlencoded',
  711. 'X-XSRF-TOKEN': decodeURIComponent(xsrfToken),
  712. 'Cookie': `XSRF-TOKEN=${xsrfToken}`,
  713. 'Accept': 'application/json'
  714. },
  715. data: data,
  716. onload: resolve
  717. });
  718. });
  719.  
  720. const sessionData = JSON.parse(uploadSessionResponse.responseText);
  721. if (!sessionData.session) throw new Error('Ошибка создания сессии: отсутствует параметр session');
  722.  
  723. // Загружаем файл
  724. const formData = new FormData();
  725. formData.append('data', sessionData.data);
  726. formData.append('files[0]', file);
  727.  
  728. const uploadResponse = await new Promise(resolve => {
  729. GM_xmlhttpRequest({
  730. method: 'POST',
  731. url: `https://www.imagebam.com/upload?session=${sessionData.session}`,
  732. headers: {
  733. 'X-XSRF-TOKEN': decodeURIComponent(xsrfToken),
  734. 'Cookie': `XSRF-TOKEN=${xsrfToken}`
  735. },
  736. data: formData,
  737. onload: resolve
  738. });
  739. });
  740.  
  741. const uploadResult = JSON.parse(uploadResponse.responseText);
  742. if (!uploadResult.success) throw new Error('Ошибка загрузки');
  743.  
  744. // Получаем BB-код
  745. const completeResponse = await new Promise(resolve => {
  746. GM_xmlhttpRequest({
  747. method: 'GET',
  748. url: uploadResult.success,
  749. headers: {
  750. 'X-XSRF-TOKEN': decodeURIComponent(xsrfToken),
  751. 'Cookie': `XSRF-TOKEN=${xsrfToken}`
  752. },
  753. onload: resolve
  754. });
  755. });
  756.  
  757. // Ищем BB-код в input'ах
  758. const doc = new DOMParser().parseFromString(completeResponse.responseText, 'text/html');
  759. const bbcode = Array.from(doc.querySelectorAll('input[type="text"]'))
  760. .map(input => input.value)
  761. .find(value => value.includes('[URL=') && value.includes('_t.'));
  762.  
  763. if (!bbcode) throw new Error('Не удалось найти BB-код');
  764.  
  765. // Извлекаем URL
  766. const urlMatch = bbcode.match(/\[URL=([^\]]+)\]/);
  767. const imgMatch = bbcode.match(/\[IMG\]([^\]]+)\[\/IMG\]/);
  768.  
  769. if (!urlMatch || !imgMatch) throw new Error('Неправильный формат BB-кода');
  770.  
  771. return {
  772. url: imgMatch[1].replace('_t.', '.'),
  773. url_viewer: urlMatch[1],
  774. thumb: { url: imgMatch[1] },
  775. session: sessionData.session
  776. };
  777. }
  778.  
  779. // Функция для загрузки изображений
  780. async function uploadImages(files) {
  781. switch (settings.uploadService) {
  782. case 'fastpic':
  783. return await uploadToFastPic(files);
  784.  
  785. case 'newfastpic':
  786. return await uploadToNewFastPic(files);
  787.  
  788. case 'imgbb':
  789. const imgbbResults = [];
  790. for (const file of files) {
  791. const result = await uploadToImgBB(file);
  792. imgbbResults.push(result);
  793. }
  794. return { results: imgbbResults };
  795.  
  796. case 'imagebam':
  797. const imagebamResults = [];
  798. for (const file of files) {
  799. const result = await uploadToImageBam(file);
  800. imagebamResults.push(result);
  801. }
  802. return { results: imagebamResults };
  803.  
  804. default:
  805. throw new Error('Неподдерживаемый сервис загрузки');
  806. }
  807. }
  808.  
  809. // Общая функция обработки загрузки изображений
  810. async function handleImageUpload(files) {
  811. if (files.length === 0) return;
  812.  
  813. // Проверка форматов и размера
  814. const allowedFormats = ['image/gif', 'image/jpeg', 'image/png', 'image/webp', 'image/bmp'];
  815. const maxFileSizes = {
  816. 'fastpic': 25 * 1024 * 1024, // 25MB
  817. 'newfastpic': 25 * 1024 * 1024, // 25MB
  818. 'imgbb': 32 * 1024 * 1024, // 32MB
  819. 'imagebam': 16 * 1024 * 1024,
  820. };
  821. const maxFiles = {
  822. 'fastpic': 30,
  823. 'newfastpic': 30,
  824. 'imgbb': 30,
  825. 'imagebam': 30,
  826. };
  827.  
  828. const maxFileSize = maxFileSizes[settings.uploadService];
  829. const maxFileCount = maxFiles[settings.uploadService];
  830.  
  831. // Фильтруем файлы по формату и размеру
  832. const validFiles = Array.from(files).filter(file => {
  833. if (!allowedFormats.includes(file.type)) {
  834. showNotification(`Файл ${file.name} имеет неподдерживаемый формат. Разрешены: gif, jpeg, png, webp, bmp`);
  835. return false;
  836. }
  837. if (file.size > maxFileSize) {
  838. showNotification(`Файл ${file.name} превышает максимальный размер в ${Math.floor(maxFileSize / 1024 / 1024)}MB`);
  839. return false;
  840. }
  841. return true;
  842. });
  843.  
  844. if (validFiles.length === 0) {
  845. showNotification('Нет подходящих файлов для загрузки');
  846. return;
  847. }
  848.  
  849. if (validFiles.length > maxFileCount) {
  850. showNotification(`Можно загрузить максимум ${maxFileCount} файлов одновременно`);
  851. return false;
  852. }
  853.  
  854. // Создаем индикатор прогресса
  855. const progressDiv = document.createElement('div');
  856. progressDiv.className = 'fastpicext-upload-progress';
  857. document.body.appendChild(progressDiv);
  858.  
  859. const textarea = findTextarea();
  860. if (!textarea) return;
  861.  
  862. // Сохраняем позицию курсора
  863. const cursorPos = textarea.selectionStart;
  864. let formattedCode = '';
  865.  
  866. try {
  867. progressDiv.textContent = `Загрузка ${validFiles.length} изображений...`;
  868.  
  869. const { results: images, sessionUrl: uploadSessionUrl } = await uploadImages(validFiles);
  870.  
  871. // Получаем sessionUrl в зависимости от сервиса
  872. let sessionUrl = null;
  873.  
  874. // Для FastPic берем sessionUrl из ответа
  875. if (settings.uploadService === 'fastpic') {
  876. sessionUrl = images[0]?.sessionUrl;
  877. }
  878.  
  879. // Для ImageBam формируем ссылку на сессию
  880. else if (settings.uploadService === 'imagebam') {
  881. sessionUrl = `https://www.imagebam.com/upload/complete?session=${images[0]?.session}`;
  882. }
  883.  
  884. // Для New FastPic используем sessionUrl полученный при загрузке
  885. else if (settings.uploadService === 'newfastpic') {
  886. sessionUrl = uploadSessionUrl;
  887. }
  888.  
  889. // Для ImgBB используем url_viewer
  890. else if (settings.uploadService === 'imgbb') {
  891. sessionUrl = images[0]?.url_viewer;
  892. }
  893.  
  894. // Формируем код для всех изображений
  895. let formattedCode = images.map(image => formatCode(image)).join(' ');
  896.  
  897. // Вставляем formattedCode в текстовое поле
  898. const textBefore = textarea.value.substring(0, cursorPos);
  899. const textAfter = textarea.value.substring(cursorPos);
  900. textarea.value = textBefore + formattedCode + textAfter;
  901. textarea.selectionStart = textarea.selectionEnd = cursorPos + formattedCode.length;
  902.  
  903. showNotification(`Успешно загружено ${images.length} изображений`, 15000, sessionUrl);
  904. } catch (error) {
  905. console.error('Ошибка при загрузке изображений:', error);
  906. showNotification(`Ошибка при загрузке изображений: ${error.message}`, 5000);
  907. } finally {
  908. setTimeout(() => progressDiv.remove(), 3000);
  909. }
  910. }
  911.  
  912. // Создание меню настроек
  913. function createSettingsMenu() {
  914. const site = getCurrentSite();
  915. if (!site) return;
  916.  
  917. const menuButton = document.createElement('input');
  918. menuButton.type = 'button';
  919. menuButton.value = 'Настройки FastPic Upload';
  920. menuButton.id = 'fastpicext-settings-btn';
  921. menuButton.style.cssText = 'margin: 0 5px;';
  922.  
  923. // стили
  924. if (site === '4pda') {
  925. menuButton.className = 'zbtn zbtn-default';
  926. } else if (site === 'nnmclub') {
  927. menuButton.className = 'input mainoption';
  928. }
  929.  
  930. // Добавление кнопки в зависимости от сайта
  931. switch(site) {
  932. case 'rutracker':
  933. const rutrackerNav = document.querySelector('#ped-submit-buttons');
  934. if (rutrackerNav) {
  935. rutrackerNav.appendChild(menuButton);
  936. }
  937. break;
  938.  
  939. case 'tapochek':
  940. const tapochekNav = document.querySelector('.mrg_4.tCenter');
  941. if (tapochekNav) {
  942. tapochekNav.appendChild(menuButton);
  943. }
  944. break;
  945.  
  946. case 'nnmclub':
  947. const nnmNav = document.querySelector('td.row2[align="center"][valign="middle"][style*="padding: 6px"]');
  948. if (nnmNav) {
  949. nnmNav.appendChild(menuButton);
  950. }
  951. break;
  952.  
  953. case '4pda':
  954. const pdaNav = document.querySelector('.dfrms.text-center') || document.querySelector('div[style*="margin-top:3px"]');
  955. if (pdaNav) {
  956. pdaNav.appendChild(menuButton);
  957. }
  958. break;
  959. }
  960.  
  961. menuButton.addEventListener('click', showSettingsDialog);
  962. }
  963.  
  964. // Создание диалога настроек
  965. function showSettingsDialog(e) {
  966. if (e) e.preventDefault();
  967.  
  968. const dialog = document.createElement('div');
  969. dialog.className = 'fastpicext-settings-dialog';
  970. dialog.innerHTML = `
  971. <style>
  972. .fastpicext-settings-dialog {
  973. position: fixed;
  974. top: 50%;
  975. left: 50%;
  976. transform: translate(-50%, -50%);
  977. background: #fff;
  978. padding: 20px;
  979. border-radius: 8px;
  980. box-shadow: 0 2px 10px rgba(0,0,0,0.3);
  981. z-index: 10000;
  982. min-width: 300px;
  983. max-height: 80vh;
  984. overflow-y: auto;
  985. }
  986. .fastpicext-settings-dialog h3 {
  987. margin-top: 0;
  988. margin-bottom: 15px;
  989. }
  990. .fastpicext-settings-dialog .setting-group {
  991. margin-bottom: 15px;
  992. padding: 10px;
  993. border: 1px solid #ddd;
  994. border-radius: 4px;
  995. }
  996. .fastpicext-settings-dialog label {
  997. display: block;
  998. margin-bottom: 5px;
  999. }
  1000. .fastpicext-settings-dialog select,
  1001. .fastpicext-settings-dialog input[type="text"] {
  1002. width: 100%;
  1003. padding: 5px;
  1004. margin-bottom: 10px;
  1005. }
  1006. .fastpicext-settings-dialog .buttons {
  1007. text-align: right;
  1008. margin-top: 15px;
  1009. display: flex;
  1010. justify-content: space-between;
  1011. }
  1012. .fastpicext-settings-dialog .right-buttons {
  1013. display: flex;
  1014. gap: 10px;
  1015. }
  1016. .fastpicext-settings-dialog button.reset {
  1017. background-color: #fff;
  1018. color: black;
  1019. border: none;
  1020. padding: 5px 5px;
  1021. border-radius: 4px;
  1022. cursor: pointer;
  1023. }
  1024. .fastpicext-settings-dialog button.reset:hover {
  1025. background-color: #fff;
  1026. }
  1027. .fastpicext-overlay {
  1028. position: fixed;
  1029. top: 0;
  1030. left: 0;
  1031. right: 0;
  1032. bottom: 0;
  1033. background: rgba(0,0,0,0.5);
  1034. z-index: 9999;
  1035. }
  1036. .service-settings {
  1037. display: none;
  1038. }
  1039. .service-settings.active {
  1040. display: block;
  1041. }
  1042. </style>
  1043. <h3>Настройки FastPic Upload</h3>
  1044. <div class="setting-group">
  1045. <label>Сервис загрузки:</label>
  1046. <select id="uploadService">
  1047. <option value="fastpic">FastPic</option>
  1048. <option value="newfastpic">New FastPic</option>
  1049. <option value="imgbb">ImgBB</option>
  1050. <option value="imagebam">ImageBam</option>
  1051. </select>
  1052. </div>
  1053.  
  1054. <!-- Настройки FastPic -->
  1055. <div id="fastpic-settings" class="service-settings setting-group">
  1056. <h4>Настройки FastPic</h4>
  1057.  
  1058. <!-- Existing code format setting -->
  1059. <label>Формат кода:</label>
  1060. <select id="fastpic-codeFormat">
  1061. <option value="direct">Прямая ссылка</option>
  1062. <option value="bb_thumb">BB-код (превью)</option>
  1063. <option value="bb_full">BB-код (большое изображение)</option>
  1064. <option value="html_thumb">HTML</option>
  1065. <option value="markdown_thumb">Markdown</option>
  1066. </select>
  1067.  
  1068. <!-- Настройки превью -->
  1069. <div class="setting-group">
  1070. <h5>Настройки превью</h5>
  1071. <label>Тип превью:</label>
  1072. <select id="fastpic-checkThumb">
  1073. <option value="size">Размер</option>
  1074. <option value="text">Текст</option>
  1075. <option value="filename">Имя файла</option>
  1076. <option value="no">Без надписи</option>
  1077. </select>
  1078. <div id="fastpic-thumbText-container">
  1079. <label>Текст превью:</label>
  1080. <input type="text" id="fastpic-thumbText" value="Увеличить">
  1081. </div>
  1082. <label>Размер превью (px):</label>
  1083. <input type="number" id="fastpic-thumbSize" min="1" max="999" value="150">
  1084. <label>
  1085. <input type="checkbox" id="fastpic-thumbSizeVertical">
  1086. по высоте
  1087. </label>
  1088. </div>
  1089.  
  1090. <!-- Настройки изображения -->
  1091. <div class="setting-group">
  1092. <h5>Настройки изображения</h5>
  1093.  
  1094. <!-- Изменение размера -->
  1095. <label>
  1096. <input type="checkbox" id="fastpic-origResizeEnabled">
  1097. Уменьшить
  1098. </label>
  1099. <div id="fastpic-resizeOptions" style="margin-left: 20px;">
  1100. <label>Предустановленный размер:</label>
  1101. <select id="fastpic-resSelect">
  1102. <option value="150">150px</option>
  1103. <option value="320">320px</option>
  1104. <option value="500">500px</option>
  1105. <option value="640">640px</option>
  1106. <option value="800">800px</option>
  1107. </select>
  1108. <label>Размер по вертикали (px):</label>
  1109. <input type="number" id="fastpic-customSize" value="500">
  1110. </div>
  1111.  
  1112. <!-- Настройки поворота -->
  1113. <label>
  1114. <input type="checkbox" id="fastpic-origRotateEnabled">
  1115. Повернуть
  1116. </label>
  1117. <div id="fastpic-rotateOptions" style="margin-left: 20px;">
  1118. <select id="fastpic-origRotate">
  1119. <option value="0">0°</option>
  1120. <option value="90">90° по часовой</option>
  1121. <option value="180">180°</option>
  1122. <option value="270">90° против часовой</option>
  1123. </select>
  1124. </div>
  1125.  
  1126. <!-- Оптимизация -->
  1127. <label>
  1128. <input type="checkbox" id="fastpic-optimizationEnabled">
  1129. Оптимизировать в JPEG
  1130. </label>
  1131. <div id="fastpic-optimizationOptions" style="margin-left: 20px;">
  1132. <label>Качество JPEG (0-99):</label>
  1133. <input type="number" id="fastpic-jpegQuality" min="0" max="99" value="85">
  1134. </div>
  1135.  
  1136. <!-- Настройки постера -->
  1137. <label>
  1138. <input type="checkbox" id="fastpic-poster">
  1139. Постер
  1140. </label>
  1141. </div>
  1142. </div>
  1143.  
  1144. <!-- Настройки New.FastPic -->
  1145. <div id="newfastpic-settings" class="service-settings setting-group">
  1146. <h4>Настройки New FastPic</h4>
  1147.  
  1148. <!-- Existing code format setting -->
  1149. <label>Формат кода:</label>
  1150. <select id="newfastpic-codeFormat">
  1151. <option value="direct">Прямая ссылка</option>
  1152. <option value="bb_thumb">BB-код (превью)</option>
  1153. <option value="bb_full">BB-код (большое изображение)</option>
  1154. <option value="html_thumb">HTML</option>
  1155. <option value="markdown_thumb">Markdown</option>
  1156. </select>
  1157.  
  1158. <!-- Настройки превью -->
  1159. <div class="setting-group">
  1160. <h5>Настройки превью</h5>
  1161. <label>Тип превью:</label>
  1162. <select id="newfastpic-checkThumb">
  1163. <option value="size">Размер</option>
  1164. <option value="text">Текст</option>
  1165. <option value="filename">Имя файла</option>
  1166. <option value="no">Без надписи</option>
  1167. </select>
  1168. <div id="newfastpic-thumbText-container">
  1169. <label>Текст превью:</label>
  1170. <input type="text" id="newfastpic-thumbText" value="Увеличить">
  1171. </div>
  1172. <label>Размер превью (px):</label>
  1173. <input type="number" id="newfastpic-thumbSize" min="1" max="999" value="150">
  1174. <label>
  1175. <input type="checkbox" id="newfastpic-thumbSizeVertical">
  1176. по высоте
  1177. </label>
  1178. </div>
  1179.  
  1180. <!-- Настройки изображения -->
  1181. <div class="setting-group">
  1182. <h5>Настройки изображения</h5>
  1183.  
  1184. <!-- Изменение размера -->
  1185. <label>
  1186. <input type="checkbox" id="newfastpic-origResizeEnabled">
  1187. Уменьшить
  1188. </label>
  1189. <div id="newfastpic-resizeOptions" style="margin-left: 20px;">
  1190. <label>Размер (px):</label>
  1191. <input type="number" id="newfastpic-customSize" value="1200">
  1192. </div>
  1193.  
  1194. <!-- Уменьшать в браузере -->
  1195. <label>
  1196. <input type="checkbox" id="newfastpic-resizeFrontend">
  1197. Уменьшать в браузере
  1198. </label>
  1199.  
  1200. <!-- Оптимизация -->
  1201. <label>
  1202. <input type="checkbox" id="newfastpic-optimizationEnabled">
  1203. Оптимизировать в JPEG
  1204. </label>
  1205. <div id="newfastpic-optimizationOptions" style="margin-left: 20px;">
  1206. <label>Качество JPEG (0-99):</label>
  1207. <input type="number" id="newfastpic-jpegQuality" min="0" max="99" value="80">
  1208. </div>
  1209.  
  1210. <!-- Постер -->
  1211. <label>
  1212. <input type="checkbox" id="newfastpic-poster">
  1213. Постер
  1214. </label>
  1215. </div>
  1216.  
  1217. <!-- Удаление -->
  1218. <div class="setting-group">
  1219. <h5>Автоудаление</h5>
  1220. <select id="newfastpic-deleteAfter">
  1221. <option value="0">Не удалять</option>
  1222. <option value="1">Через 1 минуту</option>
  1223. <option value="5">Через 5 минут</option>
  1224. <option value="10">Через 10 минут</option>
  1225. <option value="60">Через 1 час</option>
  1226. <option value="1440">Через 1 день</option>
  1227. </select>
  1228. </div>
  1229.  
  1230. <!-- Настройки альбомов для New FastPic -->
  1231. <div class="setting-group">
  1232. <h5>Альбомы</h5>
  1233.  
  1234. <!-- Выбор между новым и существующим альбомом -->
  1235. <div>
  1236. <label>
  1237. <input type="radio" name="newfastpic-album-type" value="new" id="newfastpic-new-album"> Создать новый альбом
  1238. </label>
  1239. </div>
  1240. <div id="newfastpic-new-album-options" style="margin-left: 20px; margin-top: 5px;">
  1241. <label>Название альбома:</label>
  1242. <input type="text" id="newfastpic-albumName" placeholder="Введите название альбома">
  1243. </div>
  1244.  
  1245. <div style="margin-top: 10px;">
  1246. <label>
  1247. <input type="radio" name="newfastpic-album-type" value="existing" id="newfastpic-existing-album"> Использовать существующий альбом
  1248. </label>
  1249. </div>
  1250. <div id="newfastpic-existing-album-options" style="margin-left: 20px; margin-top: 5px;">
  1251. <label>Выберите альбом:</label>
  1252. <select id="newfastpic-selectedAlbum">
  1253. <option value="">Сначала обновите список альбомов</option>
  1254. </select>
  1255. <button type="button" id="newfastpic-refresh-albums" style="margin-top: 5px; padding: 5px 10px;">
  1256. 🔄 Обновить список альбомов
  1257. </button>
  1258. <div id="newfastpic-albums-status" style="margin-top: 5px; font-size: 12px; color: #666;"></div>
  1259. </div>
  1260. </div>
  1261. </div>
  1262.  
  1263. <!-- Настройки ImgBB -->
  1264. <div id="imgbb-settings" class="service-settings setting-group">
  1265. <h4>Настройки ImgBB</h4>
  1266. <label><a href="https://api.imgbb.com/" target="_blank">API ключ ImgBB</a>:</label>
  1267. <input type="text" id="imgbb-apiKey" placeholder="Введите API ключ ImgBB">
  1268. <label>Формат кода:</label>
  1269. <select id="imgbb-codeFormat">
  1270. <option value="viewer_link">Ссылка на просмотр</option>
  1271. <option value="direct">Прямая ссылка</option>
  1272. <option value="html_image">HTML-код изображения</option>
  1273. <option value="html_full_linked">HTML-код полноразмерного со ссылкой</option>
  1274. <option value="html_medium_linked">HTML-код среднего размера со ссылкой</option>
  1275. <option value="html_thumb_linked">HTML-код миниатюры со ссылкой</option>
  1276. <option value="bb_full">BB-код полноразмерного</option>
  1277. <option value="bb_full_linked">BB-код полноразмерного со ссылкой</option>
  1278. <option value="bb_medium_linked">BB-код среднего размера со ссылкой</option>
  1279. <option value="bb_thumb_linked">BB-код миниатюры со ссылкой</option>
  1280. </select>
  1281. <label>Срок хранения:</label>
  1282. <select id="imgbb-expiration">
  1283. <option value="" selected="">Никогда не удалять</option>
  1284. <option value="60">1 минута</option>
  1285. <option value="300">5 минут</option>
  1286. <option value="900">15 минут</option>
  1287. <option value="1800">30 минут</option>
  1288. <option value="3600">1 час</option>
  1289. <option value="10800">3 часа</option>
  1290. <option value="21600">6 часов</option>
  1291. <option value="43200">12 часов</option>
  1292. <option value="86400">1 день</option>
  1293. <option value="172800">2 дня</option>
  1294. <option value="259200">3 дня</option>
  1295. <option value="345600">4 дня</option>
  1296. <option value="432000">5 дней</option>
  1297. <option value="518400">6 дней</option>
  1298. <option value="604800">1 неделя</option>
  1299. <option value="1209600">2 недели</option>
  1300. <option value="1814400">3 недели</option>
  1301. <option value="2592000">1 месяц</option>
  1302. <option value="5184000">2 месяца</option>
  1303. <option value="7776000">3 месяца</option>
  1304. <option value="10368000">4 месяца</option>
  1305. <option value="12960000">5 месяцев</option>
  1306. <option value="15552000">6 месяцев</option>
  1307. </select>
  1308. <label>
  1309. <input type="checkbox" id="imgbb-useOriginalFilename"> Оригинальное имя файла
  1310. </label>
  1311. </div>
  1312.  
  1313. <!-- Настройки ImageBam -->
  1314. <div id="imagebam-settings" class="service-settings setting-group">
  1315. <h4>Настройки ImageBam</h4>
  1316. <label>Формат кода:</label>
  1317. <select id="imagebam-codeFormat">
  1318. <option value="direct">Прямая ссылка</option>
  1319. <option value="bb_thumb">BB-код (превью)</option>
  1320. <option value="html_thumb">HTML-код</option>
  1321. </select>
  1322. <label>Размер превью:</label>
  1323. <select id="imagebam-thumbnailSize">
  1324. <option value="1">100x100 (small)</option>
  1325. <option value="2">180x180 (standard)</option>
  1326. <option value="3">250x250 (large)</option>
  1327. <option value="4">300x300 (extra large)</option>
  1328. </select>
  1329. <label>Тип контента:</label>
  1330. <select id="imagebam-contentType">
  1331. <option value="sfw">Family Safe Content</option>
  1332. <option value="nsfw">Adult Content</option>
  1333. </select>
  1334.  
  1335. <!-- Настройки галерей для ImageBam -->
  1336. <div>
  1337. <label>
  1338. <input type="checkbox" id="imagebam-galleryEnabled"> Использовать галерею
  1339. </label>
  1340. </div>
  1341. <div id="imagebam-gallery-options" style="display:none; margin-left: 20px; margin-top: 10px;">
  1342. <!-- Выбор между новой и существующей галереей -->
  1343. <div>
  1344. <label>
  1345. <input type="radio" name="imagebam-gallery-type" value="new" id="imagebam-new-gallery"> Создать новую галерею
  1346. </label>
  1347. </div>
  1348. <div id="imagebam-new-gallery-options" style="margin-left: 20px; margin-top: 5px;">
  1349. <label>Название галереи:</label>
  1350. <input type="text" id="imagebam-galleryTitle" placeholder="Введите название галереи">
  1351. </div>
  1352.  
  1353. <div style="margin-top: 10px;">
  1354. <label>
  1355. <input type="radio" name="imagebam-gallery-type" value="existing" id="imagebam-existing-gallery"> Использовать существующую галерею
  1356. </label>
  1357. </div>
  1358. <div id="imagebam-existing-gallery-options" style="margin-left: 20px; margin-top: 5px;">
  1359. <label>Выберите галерею:</label>
  1360. <select id="imagebam-selectedGallery">
  1361. <option value="">Сначала обновите список галерей</option>
  1362. </select>
  1363. <button type="button" id="imagebam-refresh-galleries" style="margin-top: 5px; padding: 5px 10px;">
  1364. 🔄 Обновить список галерей
  1365. </button>
  1366. <div id="imagebam-galleries-status" style="margin-top: 5px; font-size: 12px; color: #666;"></div>
  1367. </div>
  1368. </div>
  1369. </div>
  1370.  
  1371. <div class="buttons">
  1372. <button class="reset" id="resetSettings">Сброс</button>
  1373. <div class="right-buttons">
  1374. <button id="cancelSettings">Отмена</button>
  1375. <button id="saveSettings">Сохранить</button>
  1376. </div>
  1377. </div>
  1378. `;
  1379.  
  1380. const overlay = document.createElement('div');
  1381. overlay.className = 'fastpicext-overlay';
  1382. document.body.appendChild(overlay);
  1383. document.body.appendChild(dialog);
  1384.  
  1385. overlay.addEventListener('click', (e) => {
  1386. // Проверяем, что клик был именно по оверлею, а не по диалогу
  1387. if (e.target === overlay) {
  1388. dialog.remove();
  1389. overlay.remove();
  1390. }
  1391. });
  1392.  
  1393. // Предотвращение закрытия при клике по самому диалогу
  1394. dialog.addEventListener('click', (e) => {
  1395. e.stopPropagation();
  1396. });
  1397.  
  1398. // Заполняем текущими настройками
  1399. dialog.querySelector('#uploadService').value = settings.uploadService;
  1400.  
  1401. // Обработчик кнопки сброса
  1402. dialog.querySelector('#resetSettings').addEventListener('click', () => {
  1403. // Сохраняем API ключ ImgBB
  1404. const apiKey = settings.imgbb.apiKey;
  1405. // Сохраняем списки галерей/альбомов
  1406. const availableGalleries = settings.imagebam.availableGalleries;
  1407. const availableAlbums = settings.newfastpic.availableAlbums;
  1408.  
  1409. // Сохраняем текущий выбранный сервис
  1410. const currentService = dialog.querySelector('#uploadService').value;
  1411.  
  1412. // Сбрасываем настройки к значениям по умолчанию
  1413. settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS));
  1414.  
  1415. // Восстанавливаем сохраненные данные
  1416. settings.imgbb.apiKey = apiKey;
  1417. settings.imagebam.availableGalleries = availableGalleries;
  1418. settings.newfastpic.availableAlbums = availableAlbums;
  1419.  
  1420. // Восстанавливаем текущий сервис
  1421. settings.uploadService = currentService;
  1422.  
  1423. // Сохраняем обновленные настройки
  1424. saveSettings();
  1425.  
  1426. // Перезагружаем диалог с сохранением текущего сервиса
  1427. dialog.remove();
  1428. overlay.remove();
  1429. showSettingsDialog(new Event('click'));
  1430.  
  1431. // Показываем уведомление
  1432. showNotification('Настройки сброшены до значений по умолчанию');
  1433. });
  1434.  
  1435. // <-- Настройки FastPic -->
  1436. const fastpicSettings = settings.fastpic;
  1437. // codeFormat
  1438. dialog.querySelector('#fastpic-codeFormat').value = fastpicSettings.codeFormat;
  1439.  
  1440. // Настройки thumb
  1441. dialog.querySelector('#fastpic-checkThumb').value = fastpicSettings.thumb.checkThumb;
  1442. dialog.querySelector('#fastpic-thumbText').value = fastpicSettings.thumb.thumbText;
  1443. dialog.querySelector('#fastpic-thumbSize').value = fastpicSettings.thumb.thumbSize;
  1444. dialog.querySelector('#fastpic-thumbSizeVertical').checked = fastpicSettings.thumb.thumbSizeVertical;
  1445.  
  1446. // Настройки image.origResize
  1447. dialog.querySelector('#fastpic-origResizeEnabled').checked = fastpicSettings.image.origResize.enabled;
  1448. dialog.querySelector('#fastpic-resSelect').value = fastpicSettings.image.origResize.resSelect;
  1449. dialog.querySelector('#fastpic-customSize').value = fastpicSettings.image.origResize.customSize;
  1450.  
  1451. // Настройки origRotate
  1452. dialog.querySelector('#fastpic-origRotateEnabled').checked = fastpicSettings.image.origRotate.enabled;
  1453. dialog.querySelector('#fastpic-origRotate').value = fastpicSettings.image.origRotate.value;
  1454.  
  1455. // Настройки image.optimization
  1456. dialog.querySelector('#fastpic-optimizationEnabled').checked = fastpicSettings.image.optimization.enabled;
  1457. dialog.querySelector('#fastpic-jpegQuality').value = fastpicSettings.image.optimization.jpegQuality;
  1458.  
  1459. // Настройки постера
  1460. dialog.querySelector('#fastpic-poster').checked = fastpicSettings.image.poster;
  1461.  
  1462. // Управление видимостью опций изменения размера
  1463. const resizeOptions = dialog.querySelector('#fastpic-resizeOptions');
  1464. resizeOptions.style.display = fastpicSettings.image.origResize.enabled ? 'block' : 'none';
  1465. dialog.querySelector('#fastpic-origResizeEnabled').addEventListener('change', (e) => {
  1466. resizeOptions.style.display = e.target.checked ? 'block' : 'none';
  1467. });
  1468.  
  1469. // Управление видимостью опций поворота
  1470. const rotateOptions = dialog.querySelector('#fastpic-rotateOptions');
  1471. rotateOptions.style.display = fastpicSettings.image.origRotate.enabled ? 'block' : 'none';
  1472. dialog.querySelector('#fastpic-origRotateEnabled').addEventListener('change', (e) => {
  1473. rotateOptions.style.display = e.target.checked ? 'block' : 'none';
  1474. });
  1475.  
  1476. // Управление видимостью опций оптимизации
  1477. const optimizationOptions = dialog.querySelector('#fastpic-optimizationOptions');
  1478. optimizationOptions.style.display = fastpicSettings.image.optimization.enabled ? 'block' : 'none';
  1479. dialog.querySelector('#fastpic-optimizationEnabled').addEventListener('change', (e) => {
  1480. optimizationOptions.style.display = e.target.checked ? 'block' : 'none';
  1481. });
  1482.  
  1483. // Обработчик видимости типа превью
  1484. const thumbTypeSelect = dialog.querySelector('#fastpic-checkThumb');
  1485. const thumbTextContainer = dialog.querySelector('#fastpic-thumbText-container');
  1486. function updateThumbTextVisibility() {
  1487. thumbTextContainer.style.display = thumbTypeSelect.value === 'text' ? 'block' : 'none';
  1488. }
  1489. thumbTypeSelect.addEventListener('change', updateThumbTextVisibility);
  1490. updateThumbTextVisibility(); // Устанавливаем начальное состояние
  1491.  
  1492. // Синхронизация предустановленного и пользовательского размера
  1493. const resSelect = dialog.querySelector('#fastpic-resSelect');
  1494. const customSize = dialog.querySelector('#fastpic-customSize');
  1495. // Обработчик изменения предустановленного размера
  1496. resSelect.addEventListener('change', (e) => {
  1497. customSize.value = e.target.value;
  1498. });
  1499. // Начальная синхронизация при открытии диалога
  1500. customSize.value = resSelect.value;
  1501.  
  1502.  
  1503. // <-- Настройки New.FastPic -->
  1504. const newfastpicSettings = settings.newfastpic;
  1505. dialog.querySelector('#newfastpic-codeFormat').value = newfastpicSettings.codeFormat;
  1506.  
  1507. // Настройки thumb
  1508. dialog.querySelector('#newfastpic-checkThumb').value = newfastpicSettings.thumb.checkThumb;
  1509. dialog.querySelector('#newfastpic-thumbText').value = newfastpicSettings.thumb.thumbText;
  1510. dialog.querySelector('#newfastpic-thumbSize').value = newfastpicSettings.thumb.thumbSize;
  1511. dialog.querySelector('#newfastpic-thumbSizeVertical').checked = newfastpicSettings.thumb.thumbSizeVertical;
  1512.  
  1513. // Настройки image.origResize
  1514. dialog.querySelector('#newfastpic-origResizeEnabled').checked = newfastpicSettings.image.origResize.enabled;
  1515. dialog.querySelector('#newfastpic-customSize').value = newfastpicSettings.image.origResize.customSize;
  1516. dialog.querySelector('#newfastpic-resizeFrontend').checked = newfastpicSettings.image.resizeFrontend;
  1517.  
  1518. // Настройки image.optimization
  1519. dialog.querySelector('#newfastpic-optimizationEnabled').checked = newfastpicSettings.image.optimization.enabled;
  1520. dialog.querySelector('#newfastpic-jpegQuality').value = newfastpicSettings.image.optimization.jpegQuality;
  1521.  
  1522. // Настройки постера
  1523. dialog.querySelector('#newfastpic-poster').checked = newfastpicSettings.image.poster;
  1524.  
  1525. // Настройки удаления
  1526. dialog.querySelector('#newfastpic-deleteAfter').value = newfastpicSettings.deleteAfter;
  1527.  
  1528. // Настройки альбома (старое поле)
  1529. dialog.querySelector('#newfastpic-albumName').value = newfastpicSettings.albumName;
  1530.  
  1531. // НОВЫЕ настройки альбомов
  1532. const albumNewRadio = dialog.querySelector('#newfastpic-new-album');
  1533. const albumExistingRadio = dialog.querySelector('#newfastpic-existing-album');
  1534. const newAlbumOptions = dialog.querySelector('#newfastpic-new-album-options');
  1535. const existingAlbumOptions = dialog.querySelector('#newfastpic-existing-album-options');
  1536. const refreshAlbumsBtn = dialog.querySelector('#newfastpic-refresh-albums');
  1537. const albumsStatus = dialog.querySelector('#newfastpic-albums-status');
  1538. const selectedAlbumSelect = dialog.querySelector('#newfastpic-selectedAlbum');
  1539.  
  1540. // Управление видимостью опций нового/существующего альбома
  1541. function updateAlbumTypeOptions() {
  1542. newAlbumOptions.style.display = albumNewRadio.checked ? 'block' : 'none';
  1543. existingAlbumOptions.style.display = albumExistingRadio.checked ? 'block' : 'none';
  1544. }
  1545.  
  1546. albumNewRadio.addEventListener('change', updateAlbumTypeOptions);
  1547. albumExistingRadio.addEventListener('change', updateAlbumTypeOptions);
  1548.  
  1549. // Устанавливаем начальные значения
  1550. if (settings.newfastpic.useExistingAlbum) {
  1551. albumExistingRadio.checked = true;
  1552. } else {
  1553. albumNewRadio.checked = true;
  1554. }
  1555.  
  1556. updateAlbumTypeOptions();
  1557.  
  1558. // Заполняем список существующих альбомов
  1559. function populateAlbums() {
  1560. selectedAlbumSelect.innerHTML = '<option value="">Выберите альбом...</option>';
  1561. settings.newfastpic.availableAlbums.forEach(album => {
  1562. const option = document.createElement('option');
  1563. option.value = album.id;
  1564. option.textContent = `${album.title}`;
  1565. if (album.id === settings.newfastpic.selectedAlbumId) {
  1566. option.selected = true;
  1567. }
  1568. selectedAlbumSelect.appendChild(option);
  1569. });
  1570.  
  1571. albumsStatus.textContent = settings.newfastpic.availableAlbums.length > 0
  1572. ? `Найдено ${settings.newfastpic.availableAlbums.length} альбомов`
  1573. : 'Альбомов не найдено';
  1574. }
  1575.  
  1576. // Кнопка обновления списка альбомов
  1577. refreshAlbumsBtn.addEventListener('click', async () => {
  1578. refreshAlbumsBtn.disabled = true;
  1579. refreshAlbumsBtn.textContent = '🔄 Загрузка...';
  1580. albumsStatus.textContent = 'Получение списка альбомов...';
  1581.  
  1582. try {
  1583. const albums = await updateNewFastPicAlbums();
  1584. populateAlbums();
  1585. showNotification(`Обновлен список альбомов: найдено ${albums.length} альбомов`);
  1586. } catch (error) {
  1587. albumsStatus.textContent = 'Ошибка получения альбомов';
  1588. showNotification('Ошибка при получении списка альбомов');
  1589. } finally {
  1590. refreshAlbumsBtn.disabled = false;
  1591. refreshAlbumsBtn.textContent = '🔄 Обновить список альбомов';
  1592. }
  1593. });
  1594.  
  1595. // Заполняем список при открытии диалога
  1596. populateAlbums();
  1597.  
  1598. // Обработчики видимости опций
  1599. const updateNewFastpicOptionsVisibility = () => {
  1600. const resizeOptions = dialog.querySelector('#newfastpic-resizeOptions');
  1601. const optimizationOptions = dialog.querySelector('#newfastpic-optimizationOptions');
  1602.  
  1603. resizeOptions.style.display = dialog.querySelector('#newfastpic-origResizeEnabled').checked ? 'block' : 'none';
  1604. optimizationOptions.style.display = dialog.querySelector('#newfastpic-optimizationEnabled').checked ? 'block' : 'none';
  1605. };
  1606.  
  1607. // Обработчик видимости типа превью
  1608. const thumbNewFastpicTypeSelect = dialog.querySelector('#newfastpic-checkThumb');
  1609. const thumbNewFastpicTextContainer = dialog.querySelector('#newfastpic-thumbText-container');
  1610. function updateNewFastpicThumbTextVisibility() {
  1611. thumbNewFastpicTextContainer.style.display = thumbNewFastpicTypeSelect.value === 'text' ? 'block' : 'none';
  1612. }
  1613. thumbNewFastpicTypeSelect.addEventListener('change', updateNewFastpicThumbTextVisibility);
  1614. updateNewFastpicThumbTextVisibility(); // Устанавливаем начальное состояние
  1615.  
  1616. dialog.querySelector('#newfastpic-origResizeEnabled').addEventListener('change', updateNewFastpicOptionsVisibility);
  1617. dialog.querySelector('#newfastpic-optimizationEnabled').addEventListener('change', updateNewFastpicOptionsVisibility);
  1618. updateNewFastpicOptionsVisibility();
  1619.  
  1620.  
  1621. // <-- Настройки ImgBB -->
  1622. dialog.querySelector('#imgbb-apiKey').value = settings.imgbb.apiKey;
  1623. dialog.querySelector('#imgbb-codeFormat').value = settings.imgbb.codeFormat;
  1624. dialog.querySelector('#imgbb-expiration').value = settings.imgbb.expiration;
  1625. dialog.querySelector('#imgbb-useOriginalFilename').checked = settings.imgbb.useOriginalFilename;
  1626.  
  1627.  
  1628. // <-- Настройки ImageBam -->
  1629. dialog.querySelector('#imagebam-codeFormat').value = settings.imagebam.codeFormat;
  1630. dialog.querySelector('#imagebam-thumbnailSize').value = settings.imagebam.thumbnailSize;
  1631. dialog.querySelector('#imagebam-contentType').value = settings.imagebam.contentType;
  1632. dialog.querySelector('#imagebam-galleryEnabled').checked = settings.imagebam.galleryEnabled;
  1633. dialog.querySelector('#imagebam-galleryTitle').value = settings.imagebam.galleryTitle;
  1634.  
  1635. // НОВЫЕ настройки галерей
  1636. const galleryCheckbox = dialog.querySelector('#imagebam-galleryEnabled');
  1637. const galleryOptions = dialog.querySelector('#imagebam-gallery-options');
  1638. const newGalleryRadio = dialog.querySelector('#imagebam-new-gallery');
  1639. const existingGalleryRadio = dialog.querySelector('#imagebam-existing-gallery');
  1640. const newGalleryOptions = dialog.querySelector('#imagebam-new-gallery-options');
  1641. const existingGalleryOptions = dialog.querySelector('#imagebam-existing-gallery-options');
  1642. const refreshGalleriesBtn = dialog.querySelector('#imagebam-refresh-galleries');
  1643. const galleriesStatus = dialog.querySelector('#imagebam-galleries-status');
  1644. const selectedGallerySelect = dialog.querySelector('#imagebam-selectedGallery');
  1645.  
  1646. // Управление видимостью опций галерей
  1647. galleryCheckbox.addEventListener('change', () => {
  1648. galleryOptions.style.display = galleryCheckbox.checked ? 'block' : 'none';
  1649. });
  1650.  
  1651. // Управление видимостью опций новой/существующей галереи
  1652. function updateGalleryTypeOptions() {
  1653. newGalleryOptions.style.display = newGalleryRadio.checked ? 'block' : 'none';
  1654. existingGalleryOptions.style.display = existingGalleryRadio.checked ? 'block' : 'none';
  1655. }
  1656.  
  1657. newGalleryRadio.addEventListener('change', updateGalleryTypeOptions);
  1658. existingGalleryRadio.addEventListener('change', updateGalleryTypeOptions);
  1659.  
  1660. // Устанавливаем начальные значения
  1661. if (settings.imagebam.useExistingGallery) {
  1662. existingGalleryRadio.checked = true;
  1663. } else {
  1664. newGalleryRadio.checked = true;
  1665. }
  1666.  
  1667. galleryOptions.style.display = settings.imagebam.galleryEnabled ? 'block' : 'none';
  1668. updateGalleryTypeOptions();
  1669.  
  1670. // Заполняем список существующих галерей
  1671. function populateGalleries() {
  1672. selectedGallerySelect.innerHTML = '<option value="">Выберите галерею...</option>';
  1673. settings.imagebam.availableGalleries.forEach(gallery => {
  1674. const option = document.createElement('option');
  1675. option.value = gallery.token;
  1676. option.textContent = `${gallery.title} (${gallery.token})`;
  1677. if (gallery.token === settings.imagebam.selectedGalleryToken) {
  1678. option.selected = true;
  1679. }
  1680. selectedGallerySelect.appendChild(option);
  1681. });
  1682.  
  1683. galleriesStatus.textContent = settings.imagebam.availableGalleries.length > 0
  1684. ? `Найдено ${settings.imagebam.availableGalleries.length} галерей`
  1685. : 'Галерей не найдено';
  1686. }
  1687.  
  1688. // Кнопка обновления списка галерей
  1689. refreshGalleriesBtn.addEventListener('click', async () => {
  1690. refreshGalleriesBtn.disabled = true;
  1691. refreshGalleriesBtn.textContent = '🔄 Загрузка...';
  1692. galleriesStatus.textContent = 'Получение списка галерей...';
  1693.  
  1694. try {
  1695. const galleries = await updateImageBamGalleries();
  1696. populateGalleries();
  1697. showNotification(`Обновлен список галерей: найдено ${galleries.length} галерей`);
  1698. } catch (error) {
  1699. galleriesStatus.textContent = 'Ошибка получения галерей';
  1700. showNotification('Ошибка при получении списка галерей');
  1701. } finally {
  1702. refreshGalleriesBtn.disabled = false;
  1703. refreshGalleriesBtn.textContent = '🔄 Обновить список галерей';
  1704. }
  1705. });
  1706.  
  1707. // Заполняем список при открытии диалога
  1708. populateGalleries();
  1709.  
  1710. // Управление видимостью настроек сервисов
  1711. const updateServiceSettings = () => {
  1712. const service = dialog.querySelector('#uploadService').value;
  1713. document.querySelectorAll('.service-settings').forEach(el => {
  1714. el.classList.remove('active');
  1715. });
  1716. dialog.querySelector(`#${service}-settings`).classList.add('active');
  1717. };
  1718.  
  1719. dialog.querySelector('#uploadService').addEventListener('change', updateServiceSettings);
  1720. updateServiceSettings();
  1721.  
  1722. // Обработчики кнопок
  1723. dialog.querySelector('#cancelSettings').addEventListener('click', () => {
  1724. overlay.remove();
  1725. dialog.remove();
  1726. });
  1727.  
  1728.  
  1729. // <-- Настройки сохранения -->
  1730. dialog.querySelector('#saveSettings').addEventListener('click', () => {
  1731. settings.uploadService = dialog.querySelector('#uploadService').value;
  1732.  
  1733. // Сохраняем настройки FastPic
  1734. settings.fastpic = {
  1735. codeFormat: dialog.querySelector('#fastpic-codeFormat').value,
  1736. thumb: {
  1737. checkThumb: dialog.querySelector('#fastpic-checkThumb').value,
  1738. thumbText: dialog.querySelector('#fastpic-thumbText').value,
  1739. thumbSize: dialog.querySelector('#fastpic-thumbSize').value,
  1740. thumbSizeVertical: dialog.querySelector('#fastpic-thumbSizeVertical').checked
  1741. },
  1742. image: {
  1743. origResize: {
  1744. enabled: dialog.querySelector('#fastpic-origResizeEnabled').checked,
  1745. resSelect: dialog.querySelector('#fastpic-resSelect').value,
  1746. customSize: dialog.querySelector('#fastpic-customSize').value
  1747. },
  1748. origRotate: {
  1749. enabled: dialog.querySelector('#fastpic-origRotateEnabled').checked,
  1750. value: dialog.querySelector('#fastpic-origRotate').value
  1751. },
  1752. optimization: {
  1753. enabled: dialog.querySelector('#fastpic-optimizationEnabled').checked,
  1754. jpegQuality: dialog.querySelector('#fastpic-jpegQuality').value
  1755. },
  1756. poster: dialog.querySelector('#fastpic-poster').checked
  1757. }
  1758. };
  1759.  
  1760. // Сохраняем настройки New FastPic
  1761. settings.newfastpic = {
  1762. codeFormat: dialog.querySelector('#newfastpic-codeFormat').value,
  1763. thumb: {
  1764. checkThumb: dialog.querySelector('#newfastpic-checkThumb').value,
  1765. thumbText: dialog.querySelector('#newfastpic-thumbText').value,
  1766. thumbSize: dialog.querySelector('#newfastpic-thumbSize').value,
  1767. thumbSizeVertical: dialog.querySelector('#newfastpic-thumbSizeVertical').checked
  1768. },
  1769. image: {
  1770. origResize: {
  1771. enabled: dialog.querySelector('#newfastpic-origResizeEnabled').checked,
  1772. customSize: dialog.querySelector('#newfastpic-customSize').value
  1773. },
  1774. resizeFrontend: dialog.querySelector('#newfastpic-resizeFrontend').checked,
  1775. optimization: {
  1776. enabled: dialog.querySelector('#newfastpic-optimizationEnabled').checked,
  1777. jpegQuality: dialog.querySelector('#newfastpic-jpegQuality').value
  1778. },
  1779. poster: dialog.querySelector('#newfastpic-poster').checked
  1780. },
  1781. deleteAfter: dialog.querySelector('#newfastpic-deleteAfter').value,
  1782. albumName: dialog.querySelector('#newfastpic-albumName').value,
  1783. useExistingAlbum: dialog.querySelector('#newfastpic-existing-album').checked,
  1784. selectedAlbumId: dialog.querySelector('#newfastpic-selectedAlbum').value,
  1785. availableAlbums: settings.newfastpic.availableAlbums // Сохраняем текущий список
  1786. };
  1787.  
  1788. // Сохраняем настройки ImgBB
  1789. settings.imgbb = {
  1790. apiKey: dialog.querySelector('#imgbb-apiKey').value,
  1791. codeFormat: dialog.querySelector('#imgbb-codeFormat').value,
  1792. expiration: dialog.querySelector('#imgbb-expiration').value,
  1793. useOriginalFilename: dialog.querySelector('#imgbb-useOriginalFilename').checked
  1794. };
  1795.  
  1796. // Сохраняем настройки ImageBam
  1797. settings.imagebam = {
  1798. codeFormat: dialog.querySelector('#imagebam-codeFormat').value,
  1799. thumbnailSize: dialog.querySelector('#imagebam-thumbnailSize').value,
  1800. contentType: dialog.querySelector('#imagebam-contentType').value,
  1801. galleryEnabled: dialog.querySelector('#imagebam-galleryEnabled').checked,
  1802. galleryTitle: dialog.querySelector('#imagebam-galleryTitle').value,
  1803. useExistingGallery: dialog.querySelector('#imagebam-existing-gallery').checked,
  1804. selectedGalleryToken: dialog.querySelector('#imagebam-selectedGallery').value,
  1805. availableGalleries: settings.imagebam.availableGalleries // Сохраняем текущий список
  1806. };
  1807.  
  1808. saveSettings();
  1809. overlay.remove();
  1810. dialog.remove();
  1811.  
  1812. showNotification('Настройки сохранены');
  1813. });
  1814. }
  1815.  
  1816. // Функция настройки кнопки загрузки
  1817. function setupUploadButton() {
  1818. const site = getCurrentSite();
  1819. if (!site) return;
  1820.  
  1821. // Создаем input для файлов
  1822. const input = document.createElement('input');
  1823. input.type = 'file';
  1824. input.multiple = true;
  1825. input.accept = 'image/*';
  1826. input.style.display = 'none';
  1827. document.body.appendChild(input);
  1828.  
  1829. // Находим существующую кнопку загрузки изображений или добавляем новую
  1830. let uploadButton;
  1831. switch(site) {
  1832. case 'rutracker':
  1833. uploadButton = document.querySelector('#load-pic-btn');
  1834. break;
  1835.  
  1836. case 'tapochek':
  1837. // Создаем новую кнопку для tapochek
  1838. uploadButton = document.createElement('input');
  1839. uploadButton.type = 'button';
  1840. uploadButton.value = 'Загрузить картинку';
  1841. uploadButton.style.cssText = 'margin: 0 5px;';
  1842. const tapochekNav = document.querySelector('.mrg_4.tCenter');
  1843. if (tapochekNav) {
  1844. // Вставляем в начало
  1845. tapochekNav.insertBefore(uploadButton, tapochekNav.firstChild);
  1846. }
  1847. break;
  1848.  
  1849. case 'nnmclub':
  1850. // Создаем новую кнопку для nnmclub
  1851. uploadButton = document.createElement('input');
  1852. uploadButton.type = 'button';
  1853. uploadButton.value = 'Загрузить картинку';
  1854. uploadButton.className = 'input mainoption'; // Используем стили nnmclub
  1855. uploadButton.style.cssText = 'margin: 0 5px;';
  1856. const nnmNav = document.querySelector('td.row2[align="center"][valign="middle"][style*="padding: 6px"]');
  1857. if (nnmNav) {
  1858. // Вставляем в начало
  1859. nnmNav.insertBefore(uploadButton, nnmNav.firstChild);
  1860. }
  1861. break;
  1862.  
  1863. case '4pda':
  1864. // Создаем новую кнопку для 4pda
  1865. uploadButton = document.createElement('input');
  1866. uploadButton.type = 'button';
  1867. uploadButton.value = 'Загрузить картинку';
  1868. uploadButton.className = 'zbtn zbtn-default'; // Используем стили 4pda
  1869. uploadButton.style.cssText = 'margin: 0 5px;';
  1870. const pdaNav = document.querySelector('.dfrms.text-center') || document.querySelector('div[style*="margin-top:3px"]');
  1871. if (pdaNav) {
  1872. // Вставляем в начало
  1873. pdaNav.insertBefore(uploadButton, pdaNav.firstChild);
  1874. }
  1875. break;
  1876. }
  1877.  
  1878. if (uploadButton) {
  1879. uploadButton.onclick = (e) => {
  1880. e.preventDefault();
  1881. input.click();
  1882. };
  1883.  
  1884. // Добавляем обработчик выбора файлов
  1885. input.addEventListener('change', async (e) => {
  1886. const files = Array.from(e.target.files).filter(file => file.type.startsWith('image/'));
  1887. if (files.length > 0) {
  1888. await handleImageUpload(files);
  1889. }
  1890. input.value = ''; // Сброс input для возможности повторной загрузки тех же файлов
  1891. });
  1892. }
  1893. }
  1894.  
  1895. // Настройка drag&drop для textarea
  1896. function setupDragAndDrop() {
  1897. const textarea = findTextarea();
  1898. if (!textarea) return;
  1899.  
  1900. textarea.addEventListener('dragover', (e) => {
  1901. e.preventDefault();
  1902. e.stopPropagation();
  1903. textarea.style.border = '2px dashed #4a90e2';
  1904. });
  1905.  
  1906. textarea.addEventListener('dragleave', (e) => {
  1907. e.preventDefault();
  1908. e.stopPropagation();
  1909. textarea.style.border = '';
  1910. });
  1911.  
  1912. textarea.addEventListener('drop', async (e) => {
  1913. e.preventDefault();
  1914. e.stopPropagation();
  1915. textarea.style.border = '';
  1916.  
  1917. const files = Array.from(e.dataTransfer.files).filter(file => file.type.startsWith('image/'));
  1918. if (files.length > 0) {
  1919. await handleImageUpload(files);
  1920. }
  1921. });
  1922. }
  1923.  
  1924. // Настройка вставки из буфера обмена
  1925. function setupClipboardPaste() {
  1926. const textarea = findTextarea();
  1927. if (!textarea) return;
  1928.  
  1929. textarea.addEventListener('paste', async (e) => {
  1930. // Получаем данные из буфера обмена
  1931. const clipboardData = e.clipboardData || window.clipboardData;
  1932.  
  1933. // Сначала проверяем наличие файлов (важно для Firefox)
  1934. if (clipboardData.files && clipboardData.files.length > 0) {
  1935. const imageFiles = Array.from(clipboardData.files).filter(file => file.type.startsWith('image/'));
  1936.  
  1937. if (imageFiles.length > 0) {
  1938. e.preventDefault(); // Останавливаем стандартную вставку текста
  1939. await handleImageUpload(imageFiles);
  1940. return;
  1941. }
  1942. }
  1943.  
  1944. // Проверяем наличие изображений в буфере обмена (для Chrome и других браузеров)
  1945. const items = clipboardData.items;
  1946. if (items) {
  1947. // Ищем изображения среди элементов буфера
  1948. const imageItems = Array.from(items)
  1949. .filter(item => item.kind === 'file' && item.type.startsWith('image/'))
  1950. .map(item => item.getAsFile());
  1951.  
  1952. if (imageItems.length > 0) {
  1953. e.preventDefault(); // Останавливаем стандартную вставку текста
  1954. await handleImageUpload(imageItems);
  1955. }
  1956. }
  1957. });
  1958. }
  1959.  
  1960. // Функция сохранения настроек
  1961. function saveSettings() {
  1962. GM_setValue('fastpicextSettings', settings);
  1963. }
  1964.  
  1965. // Добавляем пункт меню в Tampermonkey
  1966. function registerTampermonkeyMenu() {
  1967. if (typeof GM_registerMenuCommand !== 'undefined') {
  1968. GM_registerMenuCommand('Настройки FastPic Upload', showSettingsDialog);
  1969. }
  1970. }
  1971.  
  1972. // Инициализация скрипта
  1973. function initializeScript() {
  1974. addScriptStyles();
  1975. createSettingsMenu();
  1976. setupUploadButton();
  1977. setupDragAndDrop();
  1978. setupClipboardPaste();
  1979. registerTampermonkeyMenu();
  1980. }
  1981.  
  1982. // Загрузка сохраненных настроек с глубоким слиянием
  1983. let settings = deepMerge(DEFAULT_SETTINGS, GM_getValue('fastpicextSettings', {}));
  1984.  
  1985. // Вызов инициализации
  1986. initializeScript();
  1987. })();