您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Embeds a "Download" button before each file element and starts downloading and saving it to your computer, can use constant to change replace kemono image server if standart not work.
// ==UserScript== // @name Kemono FIX+Download // @namespace GPT // @version 1.1.2 // @description Embeds a "Download" button before each file element and starts downloading and saving it to your computer, can use constant to change replace kemono image server if standart not work. // @description:ru Встраивает кнопку Download перед каждым элементом с файлами и запускает скачивание с сохранением на компьютер, так же имеет константу для смены сервера изображений если стандартный не работает. // @author Wizzergod // @icon https://www.google.com/s2/favicons?sz=64&domain=kemono.su // @homepageURL https://greasyfork.org/ru/users/712283-wizzergod // @grant GM_download // @grant GM_xmlhttpRequest // @grant GM_info // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM.xmlHttpRequest // @grant GM_getResourceUrl // @grant GM.openInTab // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @grant GM_notification // @match *://kemono.su/* // @match *://kemono.party/* // @match *://coomer.su/* // @match *://*.patreon.com/* // @match *://*.fanbox.cc/* // @match *://*.pixiv.net/* // @match *://*.discord.com/* // @match *://*.fantia.jp/* // @match *://*.boosty.to/* // @match *://*.dlsite.com/* // @match *://*.gumroad.com/* // @match *://*.subscribestar.com/* // @match *://*.subscribestar.adult/* // @match *://*.onlyfans.com/* // @match *://*.candfans.jp/* // @connect kemono.party // @connect kemono.su // @connect coomer.su // @run-at document-idle // @license MIT // ==/UserScript== (function () { 'use strict'; // Константы для включения/выключения функций let ENABLE_IMAGE_REPLACEMENT = 0; // 1 - включено, 0 - выключено let ENABLE_URL_REPLACEMENT = 0; // 1 - включено, 0 - выключено const ENABLE_MUTATION_OBSERVER = 1; // 1 - включено, 0 - выключено // Дебаунс функция для оптимизации MutationObserver let debounceTimeout; const debounce = (callback, delay = 300) => { clearTimeout(debounceTimeout); debounceTimeout = setTimeout(callback, delay); }; // Core configuration constants const CONSTANTS = { API_BASE_URL: "https://kemono.su/api/v1", GRID_GAP: "16px", GRID_MAX_WIDTH: "1600px", GRID_MIN_COLUMN_WIDTH: "250px", IMAGE_BASE_URL: "https://img.kemono.su/thumbnail", SELECTORS: { GRID: ".card-list__items", POST_CARD: ".post-card", POST_IMAGE: ".post-card__image", }, SUPPORTED_IMAGE_EXTENSIONS: [ ".bmp", ".gif", ".jpeg", ".jpg", ".png", ".webp", ], }; // Стили для кнопок и контейнеров GM_addStyle(` .download-container { padding: 7px; text-align: center; border: none; width: 100%; display: inline-block; justify-content: center; transform: translate(0%, -110%); } .download-button { padding: 1px 7%; background-color: #4CAF50; background-position: center; background-repeat: no-repeat; color: white; border: solid rgba(128,128,128,.7) .125em; cursor: pointer; font-size: 14px; display: inline-block; min-width: 80%; width: 98%; text-align: center; border-radius: 5px; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.57), -3px -3px rgba(0, 0, 0, .1) inset; transition: transform 0.3s ease, background-color 0.3s ease; justify-content: center; font-size: 20px; opacity: 0.8; } .download-button:hover { background-color: #4C9CAF; } .post__thumbnail { max-width: 10%; min-width: 20%; height: auto; cursor: pointer; padding: 5px; border: 5px; display: inline-flex; flex-direction: column; flex-wrap: wrap; align-content: center; justify-content: space-between; align-items: center; margin-top: -3.5%; } .post__thumbnail img:hover { transform: scale(1.1); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); transition: transform 0.3s ease, background-color 0.3s ease; } .post__thumbnail img { cursor: pointer; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); transition: transform 0.3s ease, background-color 0.3s ease; border: solid rgba(128,128,128,.7) .125em; } .post-card__image-container { max-width: 100%; height: auto; } .post__files { display: flex; padding: 50px; border: 5px; max-width: 100%; min-width: 70%; flex-direction: row; flex-wrap: wrap; align-content: center; justify-content: center; align-items: center; } [data-testid='tracklist-row'] .newButtonClass { position: absolute; top: 50%; right: calc(100% + 10px); transform: translateY(-50%); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); } .post__content p img, .post__content h2 img, .post__content h3 img { width: 20%; cursor: pointer; border: solid rgba(128,128,128,.7) .125em; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); margin: 5px auto; border-radius: 10px; transition: transform 0.3s ease, background-color 0.3s ease; } .post__content p img:hover, .post__content h2 img:hover, .post__content h3 img:hover { transform: translate(50%) scale(1.5); /* используем translate для точного центрирования */ z-index: 9999; /* картинка будет выше всех других элементов */ } .post__content p { padding: 1px 7%; margin: 5px; } /* Чтобы иконка не перекрывала текст ссылки */ a.post__attachment-link { position: relative; } .post__nav-links { position: sticky; /* Задает прилипание элемента */ top: 0; /* Фиксирует элемент сверху на странице */ z-index: 100; /* Устанавливает уровень слоя для избегания перекрытия другими элементами */ background-color: #000000ba; /* Опционально, чтобы фоновый цвет скрывал элементы позади */ border: solid rgba(128,128,128,.7) .125em; border-radius: 10px; /* border-radius: 10px 10px 0 0; */ width:99%; top: 5px; /* Учитывает отступ сверху */ margin: 5px auto; /* Вертикальный отступ 5px и центрирование по горизонтали */ } `); // Функция для получения имени файла из URL (используя параметр f, если он есть) const getFileName = (url) => { return new URLSearchParams(new URL(url).search).get('f') || url.split('/').pop().split('?')[0]; }; // Функция для скачивания файла const downloadFile = (url) => { const fileName = getFileName(url); GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', onload: (response) => { const link = document.createElement('a'); link.href = URL.createObjectURL(response.response); link.download = fileName; link.click(); URL.revokeObjectURL(link.href); }, onerror: (error) => { console.error('Ошибка загрузки файла:', error); }, }); }; // Функция для асинхронной загрузки изображения с оригинальным именем const downloadImage = async (url) => { try { const fileName = getFileName(url); // Получаем имя файла const response = await fetch(url); const blob = await response.blob(); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = fileName; // Используем имя файла из URL или параметра f link.click(); URL.revokeObjectURL(link.href); } catch (error) { console.error('Ошибка загрузки файла:', error); } }; // Функция для создания кнопки Download const createDownloadButton = (onClickCallback) => { const button = document.createElement('button'); button.textContent = 'Download'; button.className = 'download-button'; button.addEventListener('click', onClickCallback); return button; }; // Добавление кнопки к миниатюрам const addDownloadButtonsToThumbnails = () => { document.querySelectorAll('.post__thumbnail').forEach((thumbnail) => { const link = thumbnail.querySelector('a.fileThumb'); if (!link || thumbnail.querySelector('.download-container')) return; const downloadContainer = document.createElement('div'); downloadContainer.className = 'download-container'; const button = createDownloadButton((event) => { event.preventDefault(); downloadFile(link.href); }); downloadContainer.appendChild(button); thumbnail.appendChild(downloadContainer); }); }; // Добавление кнопок сразу при загрузке addDownloadButtonsToThumbnails(); // Отслеживание изменений в DOM const observer = new MutationObserver(() => { addDownloadButtonsToThumbnails(); }); observer.observe(document.body, { childList: true, subtree: true, }); // Функция для замены атрибутов src и data-src изображений function replaceImageSources() { const imageLinks = document.querySelectorAll('.post__files .post__thumbnail a.fileThumb.image-link'); imageLinks.forEach((link) => { const imgElement = link.querySelector('img'); if (imgElement) { const originalSrc = link.href; const dataSrc = imgElement.getAttribute('data-src'); if (ENABLE_IMAGE_REPLACEMENT === 1) { imgElement.setAttribute('src', originalSrc); imgElement.setAttribute('data-src', originalSrc); } } }); } // Функция для замены URL для миниатюр function replaceThumbnailURLs() { const imageLinks = document.querySelectorAll('.post__files .post__thumbnail a.fileThumb.image-link, .post-card__image-container'); imageLinks.forEach((link) => { const imgElement = link.querySelector('img'); if (imgElement) { let src = imgElement.getAttribute('src'); let dataSrc = imgElement.getAttribute('data-src'); if (ENABLE_URL_REPLACEMENT === 1) { if (src && src.includes('img.kemono.su/thumbnail/data/')) { src = src.replace('img.kemono.su/thumbnail/data/', 'n3.kemono.su/data/'); imgElement.setAttribute('src', src); } if (dataSrc && dataSrc.includes('img.kemono.su/thumbnail/data/')) { dataSrc = dataSrc.replace('img.kemono.su/thumbnail/data/', 'n3.kemono.su/data/'); imgElement.setAttribute('data-src', dataSrc); } } } }); } // Регистрируем команды меню для включения/выключения функций GM_registerMenuCommand('Включить замену изображений', () => { ENABLE_IMAGE_REPLACEMENT = 1; replaceImageSources(); }, 'r'); GM_registerMenuCommand('Отключить замену изображений', () => { ENABLE_IMAGE_REPLACEMENT = 0; }, 'r'); // Проверка и добавление кнопок addDownloadButtonsToThumbnails(); // Используем MutationObserver с дебаунсом для отслеживания изменений в DOM if (ENABLE_MUTATION_OBSERVER) { const observer = new MutationObserver(() => { debounce(() => { if (ENABLE_IMAGE_REPLACEMENT === 1) replaceImageSources(); if (ENABLE_URL_REPLACEMENT === 1) replaceThumbnailURLs(); }); }); observer.observe(document.body, { childList: true, subtree: true }); } // Если включены замены, выполняем их сразу после загрузки if (ENABLE_IMAGE_REPLACEMENT === 1) { replaceImageSources(); } if (ENABLE_URL_REPLACEMENT === 1) { replaceThumbnailURLs(); } })();