Kemono File Ripper

Rips all images and videos from Kemono and downloads them as a zip

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Kemono File Ripper
// @namespace    Tampermonkey/Violentmonkey Scripts
// @version      1.2.1
// @description  Rips all images and videos from Kemono and downloads them as a zip
// @author       Selentia
// @icon         https://raw.githubusercontent.com/Selentia-IX/kemono-file-ripper/main/kr-png.png
// @match        https://kemono.party/*
// @match        https://kemono.cr/*
// @copyright    2025+, Selentia (https://github.com/Selentia-IX/kemono-file-ripper)
// @license      GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @grant        none
// @description  Rips all the static images and videos and files from Kemono and downloads them as a zip
// ==/UserScript==

/* global JSZip, saveAs, jQuery */

(function waitForLibs() {
    if (typeof JSZip === 'undefined' || typeof saveAs === 'undefined' || typeof jQuery === 'undefined') {
        setTimeout(waitForLibs, 100);
        return;
    }

    (function($) {
        'use strict';

        function sanitizeString(str) {
            return str.replace(/[\\/:*?"<>|]/g, '_').trim();
        }

        function showLoadingBar(percent) {
            let bar = $('#kemono-download-loading');
            if (!bar.length) {
                bar = $('<div id="kemono-download-loading">')
                    .css({
                        position: 'fixed',
                        top: '0',
                        left: '0',
                        width: '100%',
                        height: '5px',
                        background: '#eee',
                        zIndex: 99999
                    })
                    .append($('<div id="kemono-download-progress">').css({
                        height: '100%',
                        width: '0%',
                        background: '#ff9500'
                    }));
                $('body').append(bar);
            }
            $('#kemono-download-progress').css('width', percent + '%');
        }

        function hideLoadingBar() {
            $('#kemono-download-loading').remove();
        }

        async function downloadKemonoMedia() {
            const zip = new JSZip();
            let mediaLinks = [];

            $('.post__thumbnail a.fileThumb').each(function() {
                const url = $(this).attr('href');
                const filename = $(this).attr('download') || url.split('/').pop().split('?')[0];
                mediaLinks.push({url, filename});
            });

            $('.post__video').each(function() {
                const url = $(this).attr('src');
                if (url) {
                    let filename = url.split('/').pop().split('?')[0];
                    const att = $('.post__attachment-link[href*="' + filename + '"]');
                    if (att.length) filename = att.attr('download') || filename;
                    mediaLinks.push({url, filename});
                }
            });

            $('.post__attachment-link').each(function() {
                const url = $(this).attr('href');
                const filename = $(this).attr('download') || url.split('/').pop().split('?')[0];
                if (!mediaLinks.some(m => m.filename === filename)) {
                    mediaLinks.push({url, filename});
                }
            });
            // filters duplicates (feel free to remove it if it conflicts with the file you want)
            mediaLinks = mediaLinks.filter((media, idx, arr) =>
                arr.findIndex(m => m.filename === media.filename) === idx
            );

            const total = mediaLinks.length;
            let completed = 0;

            const title = sanitizeString($('.post__title span').first().text() || 'Kemono_Post');
            const creator = sanitizeString($('.post__user-name').first().text() || 'Unknown_Creator');
            const zipName = `${title} by ${creator}.zip`;

            if (total === 0) {
                alert('No images or videos found!');
                return;
            }

            showLoadingBar(0);

            let saveHandler = saveAs;
            let usePicker = false;
            if ('showSaveFilePicker' in window) {
                usePicker = true;
            }

            const fetchPromises = mediaLinks.map(media =>
                fetch(media.url)
                    .then(response => {
                        if (!response.ok) throw new Error('Network error');
                        return response.blob();
                    })
                    .then(blob => {
                        const index = mediaLinks.indexOf(media) + 1;
                        const ext = media.filename.includes('.') ? media.filename.split('.').pop() : '';
                        const baseName = title !== 'Kemono_Post' ? title : 'file';
                        const newFilename = `${sanitizeString(baseName)}_${index}${ext ? '.' + ext : ''}`;
                        zip.file(newFilename,  blob);
                    })
                    .catch(() => {
                        alert(`Failed to download ${media.filename}`);
                    })
                    .finally(() => {
                        completed++;
                        showLoadingBar(Math.round((completed / total) * 100));
                    })
            );

            await Promise.all(fetchPromises);

            zip.generateAsync({type:"blob"}).then(async function(content) {
                hideLoadingBar();
                if (usePicker) {
                    try {
                        const handle = await window.showSaveFilePicker({
                            suggestedName: zipName,
                            types: [{
                                description: 'Zip Files',
                                accept: {'application/zip': ['.zip']}
                            }]
                        });
                        const writable = await handle.createWritable();
                        await writable.write(content);
                        await writable.close();
                    } catch (e) {
                        saveHandler(content, zipName);
                    }
                } else {
                    saveHandler(content, zipName);
                }
            });
        }

        function insertDownloadButton() {
            if ($('#kemono-download-btn').length) return;
            const actions = $('.post__actions');
            if (!actions.length) return;
            const btn = $('<button id="kemono-download-btn">')
                .text('Download Media')
                .css({
                    marginLeft: '10px',
                    padding: '5px 10px',
                    background: '#ff9500',
                    color: '#fff',
                    border: 'none',
                    borderRadius: '5px',
                    cursor: 'pointer'
                })
                .click(function() {
                    $(this).prop('disabled', true);
                    downloadKemonoMedia().finally(() => {
                        $(this).prop('disabled', false);
                    });
                });
            actions.append(btn);
        }

        $(document).ready(function() {
            insertDownloadButton();
        });

        const observer = new MutationObserver(() => {
            insertDownloadButton();
        });
        observer.observe(document.body, {childList: true, subtree: true});
        })(jQuery);
    })();