您需要先安装一个扩展,例如 篡改猴、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. Improved version: download buttons, optimized MutationObserver, simplified and accelerated and optimized code.
// ==UserScript== // @name Kemono FIX+Download // @namespace GPT // @version 1.2.3 // @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. Improved version: download buttons, optimized MutationObserver, simplified and accelerated and optimized code. // @description:ru Встраивает кнопку Download перед каждым элементом с файлами и запускает скачивание с сохранением на компьютер, так же имеет константу для смены сервера изображений если стандартный не работает. Улучшенная версия: кнопки загрузки, оптимизированный MutationObserver, упрощённый и ускоренный и оптимизированный код. // @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'; const ENABLE_IMAGE_REPLACEMENT = 0; const ENABLE_URL_REPLACEMENT = 0; const ENABLE_MUTATION_OBSERVER = 1; const CONFIG = { INCLUDE_AUTHOR: 0, INCLUDE_SERVICE: 0, INCLUDE_USER: 0, INCLUDE_POSTID: 0 }; const SELECTORS = { THUMBNAILS: '.post__thumbnail', FILE_LINK: 'a.fileThumb', IMAGE_LINK: '.post__thumbnail a.fileThumb.image-link' }; const debounce = (callback, delay = 300) => { clearTimeout(debounce._t); debounce._t = setTimeout(callback, delay); }; const getMeta = name => document.querySelector(`meta[name="${name}"]`)?.content || null; const sanitize = str => str.replace(/[\\/:"*?<>|]+/g, '_'); const getFileName = url => { try { const u = new URL(url); const f = u.searchParams.get('f'); return sanitize(f || u.pathname.split('/').pop()); } catch { return sanitize(url.split('/').pop()); } }; const getPostData = () => { const path = location.pathname.split('/').filter(Boolean); return { service: getMeta('service') || path[0], user: getMeta('user') || (path[1] === 'user' ? path[2] : null), postid: getMeta('id') || (path[3] === 'post' ? path[4] : null) }; }; const getAuthor = () => { const el = document.querySelector('a.post__user-name.fancy-link--kemono'); return el ? sanitize(el.textContent.trim()) : null; }; const makeFileName = (original) => { const data = getPostData(); const parts = []; if (CONFIG.INCLUDE_AUTHOR && getAuthor()) parts.push(getAuthor()); if (CONFIG.INCLUDE_SERVICE && data.service) parts.push(data.service); if (CONFIG.INCLUDE_USER && data.user) parts.push(data.user); if (CONFIG.INCLUDE_POSTID && data.postid) parts.push(data.postid); parts.push(original); return parts.join('_'); }; const download = (url) => { const fileName = makeFileName(getFileName(url)); GM_xmlhttpRequest({ method: 'GET', url, responseType: 'blob', onload: res => { const blobUrl = URL.createObjectURL(res.response); const a = document.createElement('a'); a.href = blobUrl; a.download = fileName; document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(blobUrl); a.remove(); }, 200); }, onerror: () => alert('Ошибка при скачивании файла') }); }; const createButton = (url) => { const btn = document.createElement('button'); btn.className = 'download-button'; btn.textContent = 'Download'; btn.onclick = e => { e.preventDefault(); download(url); }; return btn; }; const insertButtons = () => { document.querySelectorAll(SELECTORS.THUMBNAILS).forEach(thumbnail => { if (thumbnail.querySelector('.download-container')) return; const link = thumbnail.querySelector(SELECTORS.FILE_LINK) || thumbnail.querySelector('a[href*="/download/"]'); if (!link) return; const container = document.createElement('div'); container.className = 'download-container'; container.appendChild(createButton(link.href)); thumbnail.appendChild(container); }); }; const addStyles = () =>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); 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; width:99%; top: 5px; margin: 5px auto; } `); const init = () => { addStyles(); insertButtons(); if (ENABLE_MUTATION_OBSERVER && window.MutationObserver) { new MutationObserver(() => debounce(insertButtons, 200)) .observe(document.body, {childList: true, subtree: true}); } }; init(); })();