Kemono FIX+Download

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.

目前為 2024-12-04 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Kemono FIX+Download
// @namespace    GPT
// @version      1.0.9
// @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
// @match        *://kemono.su/*
// @match        *://kemono.party/*
// @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);
    };

    // Добавляем стиль для кнопки Download и контейнера
    GM_addStyle(`
        .download-container {
            margin-top: 2px;
            padding: 5px;
            background-position: center;
            background-repeat: no-repeat;
            text-align: center;
            border: none;
            width: 100%;
            display: inline-block;
            transition: transform 0.3s ease, background-color 0.3s ease;
            justify-content: center;
            font-size: 30px;
            opacity: 0.9;
            transform: translate(0%, -110%);
        }

        .download-button {
            padding: 1px 7%;
            background-color: #4CAF50;
            background-position: center;
            background-repeat: no-repeat;
            color: white;
            border: none;
            cursor: pointer;
            font-size: 14px;
            display: inline-block;
            min-width: 100px;
            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;
}

.post-card__image-container {
	max-width: 100%;
	height: auto;
}
.post__files {
    display: flex;
    padding: 5px;
    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);
    }
    `);


    // Функция для асинхронной загрузки изображения
    const downloadImage = async (url) => {
        try {
            const response = await fetch(url);
            const blob = await response.blob();
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = url.split('/').pop().split('?')[0];
            link.click();
            URL.revokeObjectURL(link.href);
        } catch (error) {
            console.error('Ошибка загрузки файла:', error);
        }
    };

    // Функция для замены атрибутов 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);
                    }
                }
            }
        });
    }

    // Функция для создания кнопки Download
    function createDownloadButton(onClickCallback) {
        const button = document.createElement('button');
        button.textContent = 'Download';
        button.classList.add('download-button');
        button.addEventListener('click', onClickCallback);
        return button;
    }

    // Функция для добавления кнопок Download
    function addDownloadButtons() {
        try {
            const fileElements = document.querySelectorAll('.post__files .post__thumbnail a.fileThumb.image-link');
            if (!fileElements.length) throw new Error('Элементы не найдены');
            fileElements.forEach((fileElement) => {
                if (fileElement.parentElement.querySelector('.download-container')) {
                    return;
                }
                const downloadContainer = document.createElement('div');
                downloadContainer.classList.add('download-container');
                const downloadButton = createDownloadButton((event) => {
                    event.preventDefault();
                    const fileUrl = fileElement.href;
                    downloadImage(fileUrl);
                });
                downloadContainer.appendChild(downloadButton);
                fileElement.parentElement.appendChild(downloadContainer);
            });
        } catch (error) {
            console.error('Ошибка при добавлении кнопок загрузки:', error);
        }
    }

    // Двойная проверка добавления кнопок с таймаутом
    function checkAndAddButtons() {
        addDownloadButtons();
        setTimeout(() => {
            addDownloadButtons();
        }, 500);
    }

    // Регистрируем команды меню для включения/выключения функций
    GM_registerMenuCommand('Включить замену изображений', () => {
        ENABLE_IMAGE_REPLACEMENT = 1;
        replaceImageSources();
    }, 'r');

    GM_registerMenuCommand('Отключить замену изображений', () => {
        ENABLE_IMAGE_REPLACEMENT = 0;
    }, 'r');

    // Вызываем проверку и добавление кнопок
    checkAndAddButtons();

    // Используем 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();
    }

})();