SirTony
3/11/2015 - 11:30 PM

A simple userscript to enable direct-downloading of content on fakku.net

A simple userscript to enable direct-downloading of content on fakku.net

// ==UserScript==
// @name         FAKKU! Download
// @namespace    https://github.com/SirTony
// @version      1.8
// @description  Enables direct-downloads on FAKKU.net
// @author       Tony J. Ellis
// @include      /^https?:\/\/(?:www\.)?fakku\.net\/(?:manga|doujinshi)/
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js
// @require      https://raw.githubusercontent.com/Stuk/jszip-utils/master/dist/jszip-utils.min.js
// @require      https://raw.githubusercontent.com/Stuk/jszip/master/vendor/FileSaver.js
// @grant        GM_xmlhttpRequest
// ==/UserScript==

// This code is PUBLIC DOMAIN and may be used by anyone for any purpose.

const contentRegex = /^https?:\/\/(?:www\.)?fakku\.net\/(manga|doujinshi)\/(.*?)(?:\/|$)/;
const button       = "<a class=\"button light\" id=\"download-btn\"><i id=\"dl-btn-icon\" class=\"fa fa-arrow-down\"></i> <span id=\"text\">Download</span></a>";

if( !contentRegex.test( window.location.href ) )
    return;

var inProgress = false;

function download() {
    if( inProgress )
        return;
    else
        inProgress = true;
    
    const match           = window.location.href.match( contentRegex );
    const contentCategory = match[1];
    const contentName     = match[2];
    const apiUrl          = "https://api.fakku.net/" + contentCategory + "/" + contentName + "/read";
    const button          = $( "#download-btn #text" );
    const icon            = $( "#download-btn #dl-btn-icon" );
    
    button.error = function() {
        this.css( "font-weight", "bold" );
        this.text( "Failed" );
        icon.removeClass( "fa-spinner fa-spin" );
        icon.addClass( "fa-exclamation-triangle" );
    }
    
    button.reset = function() {
        this.css( {
            "font-style":  "normal",
            "font-weight": "bold"
        } );

        this.text( "Download" );
        icon.removeClass( "fa-spinner fa-spin fa-exclamation-triangle" );
        icon.addClass( "fa-arrow-down" );
    }
    
    button.progress = function( complete, total ) {
        this.text( ( ( complete / total ) * 100.0 ).toFixed( 0 ) + "% (" + complete + "/" + total + ")" );   
    }
    
    button.css( {
        "font-style":  "italic",
        "font-weight": "normal"
    } );
    
    button.text( "Downloading..." );
    icon.addClass( "fa-spinner fa-spin" );
    
    GM_xmlhttpRequest( {
        method: "GET",
        url:    apiUrl,
        
        onload: function( response ) {
            if( response.status !== 200 ) {
                button.error();
                return;
            }
            
            var json = null;
            
            try {
                json = JSON.parse( response.response );
            }
            catch( e ) {
                button.error();
                return;
            }
            
            const zip   = new JSZip();
            const total = json.content.content_pages;
            
            var completed = 0;
            
            button.progress( 0, total );
            
            for( var i = 1; i <= total; ++i ) {
                var stop    = false;
                var url     = json.pages[i].image;
                var request = {
                    method: "GET",
                    url:    url,
                    responseType: "arraybuffer",
                    
                    onload: function( response ) {
                        if( stop || response.status !== 200 ) {
                            button.error();
                            return;
                        }
                        
                        zip.file( response.finalUrl.split( "/" ).pop(), response.response, { binary: true } );
                        ++completed;
                        
                        button.progress( completed, total );
                        
                        if( completed === total ) {
                            var payload = zip.generate( { type: "blob" } );
                            var artists = json.content.content_artists.map( function( x ) { return x.attribute; } ).join( ", " );
                            var series  = json.content.content_series.map( function( x ) { return x.attribute; } ).join( ", " );
                            var name    = "[" + artists + "][" + series + "]" + json.content.content_name;
                            
                            saveAs( payload, name + ".zip" );
                            button.reset();
                            inProgress = false;
                        }
                    },
                    
                    onerror: function( e ) {
                        stop = true;
                        button.error();
                    }
                };
                
                if( stop ) break;
                GM_xmlhttpRequest( request );
            }
        },
        
        onerror: function( e ) {
            button.error();
        }
    } );
}

// This will only be disabled on books you haven't purchased
// so as to prevent the script from downloading zip files
// with empty images in them.
if( $( "textarea[name=comment]" ).attr( "disabled" ) !== "disabled" ) {
    $( button ).prependTo( ".content-options" );
    $( "#download-btn" ).click( download );
}