Kemono File Ripper

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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);
    })();