[bib] RequestUpload

Facilitates uploading requests by adding an upload link to request page sidebar and populating the upload form with info available on the page.

当前为 2014-11-06 提交的版本,查看 最新版本

// ==UserScript==
// @name            [bib] RequestUpload
// @namespace       varb
// @version         0.6
// @description     Facilitates uploading requests by adding an upload link to request page sidebar and populating the upload form with info available on the page.
// @include         /^https?://bibliotik.org/(request|torrent)s/\d+/
// @include         /^https?://bibliotik.org/upload/\w+/
// @require         https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_deleteValue
// @grant           GM_addStyle
// @grant           GM_xmlhttpRequest
// @license         WTFPL Version 2; http://www.wtfpl.net/txt/copying/
// ==/UserScript==

var FORMATS = {'MP3':1,'PDF':2,'CBR':3,'DJVU':4,'CBZ':5,'CHM':6,'FLAC':10,'SPX':13,'TXT':14,'EPUB':15,'MOBI':16,'M4A':17,'M4B':18,'AZW3':21};
var LANGS = {'English':1,'German':2,'French':3,'Spanish':4,'Italian':5,'Latin':6,'Japanese':7,'Swedish':8,'Norwegian':9,'Dutch':12,'Russian':13,'Portuguese':14,'Danish':15,'Korean':16,'Chinese':17,'Polish':18,'Arabic':19,'Irish':20,'Greek':21,'Turkish':22,'Hungarian':23,'Thai':24,'Indonesian':25,'Bulgarian':26};

$(document).ready(function () {

    console.log('requp: initialized');

    var page = location.pathname.split('/')[1];
    switch (page) {
        case 'requests':
            onReqPage();
            break;
        case 'upload':
            onUploadPage();
            break;
        case 'torrents':
            onTorrentPage();
            break;
        default:
            console.log('requp: unhandled page ' + page);
    }

});

function onReqPage() {
    var category, lang, overdrive = false, req = {};
    var reqid = location.pathname.split('/').slice(-1);
    var reqdetails = $('#requestDetails').text();

    if ($('#filled').length)
        return;

    var _match = reqdetails.match(/(?:(\w+),\s)?(\w+)\scategory/);
    if (!_match) {
        console.log('requp: cannot establish category');
        return;
    }
    lang = _match[1];
    category = _match[2];

    // add upload link to req page sidebar
    var uppageurl = location.origin + '/upload/' + category.toLowerCase() + '?reqid=' + reqid;
    $('<a/>', {id: 'upreq', href: uppageurl, text: 'Upload Request'})
        .appendTo('#sidebar ul:first').wrap('<li></li>').after('<span></span>')
        .on('click', function (e) {
            var $self = $(this);

            if (!overdrive) {
                GM_setValue(reqid, JSON.stringify(req));
                return;
            }

            e.preventDefault();
            $self.next().addClass('loading');
            fetchODListing()
                .then(parseODListing)
                .then(updateISBN)
                .done(function (oddata) {
                    GM_setValue(reqid, JSON.stringify($.extend(req, oddata)));
                    location.href = $self.attr('href');
                })
                .fail(function (reason) {
                    console.error('requp: ' + reason);
                    if (confirm('Failed to acquire OverDrive listing.\nProceed to upload page?'))
                        location.href = $self.attr('href');
                    $self.next().removeClass('loading');
                });
        });

    // save fill request form
    req.fillform = $('#TorrentIdField').parents('form').find(':hidden').serializeArray();

    // collect reusable info
    var $description = $('#description');
    req.title = $('h1').text().match(/\/\s*(.+?)(?:\s\[Retail\]$|$)/)[1];
    req.publishers = $('#published a').map(function () { return this.text }).toArray();
    req.tags = $('#details_tags a').map(function () {
        if (this.text.indexOf('overdrive') === -1)
            return this.text;
        overdrive = true;
    }).toArray();
    req.overdrive = overdrive;
    req.retail = reqdetails.indexOf('Retail') !== -1;
    req.lang = lang;
    req.format = $('#requestDetails strong').text().split('/');
    var $authors = $('#creatorlist');
    req.authors = $authors.find('a').map(function () { return this.text }).toArray();
    if (category === 'Comics') {
        req.artists = $authors.next('#creatorlist').find('a')
                              .map(function () { return this.text }).toArray();
    }
    var _isbnmatch = $description.text().match(/Overdrive Listing \((\d+)\)/);
    req.isbn = _isbnmatch ? _isbnmatch[1] : '';

    function fetchODListing() {
        var dfr = new $.Deferred();
        var odurl = $('a:contains(Overdrive Listing)').attr('href');
        if (!odurl)
            return dfr.reject('could not find od listing url').promise();

        GM_xmlhttpRequest({
            method: 'GET',
            url: odurl,
            onload: function (res) { dfr.resolve(res.responseText) },
            onerror: function (res) {
                dfr.reject('failed fetching od listing: ' + res.status + ' ' + res.statusText);
            }
        });
        return dfr.promise();
    }

    function parseODListing(page) {
        var dfr = new $.Deferred(), $page = $(page), oddata = {}, ed;

        try {
            var subt = $('h2.subtitle', $page).contents()[0];
            subt = subt && subt.textContent.trim() ? ': ' + subt.textContent.trim() : '';
            oddata.title = $('h1.title', $page).text().trim() + subt;
            // because innerHTML doesn't behave identically across implementations
            oddata._odid = page.match(/meta.+?od:id.+?content=\"([-\w]+)\"/)[1];
            oddata._formats = $('.meta-accordion span:contains(Format)', $page).next().text();
            oddata.pubdate = $('dt:contains(Publication Date)', $page).next().text();
            oddata.desc = $('.description', $page).contents().slice(0, -4).toArray()
                .reduce(function (p, c) { return p + stripNode(c) }, '').trim();
            var $auth = $('.meta-accordion span:contains(Creators)', $page).next().find('dd');
            if ($auth.length)
                oddata.authors = $auth.map(function () { return this.textContent }).toArray();
            if (ed = $('dt:contains(Edition)', $page).next().text()) {
                var suffix = [null, 'st', 'nd', 'rd'];
                oddata.title += ' (' + ed + (ed.match(/([^1]|\b)[123]$/)
                                ? suffix[ed.substr(-1)] : 'th') + ' Edition)';
            }
        } catch (e) {
            return dfr.reject('failed parsing od listing: ' + e.message).promise();
        }

        return dfr.resolve(oddata).promise();

        function stripNode(n) {
            if (n.nodeType === 3)
                return n.textContent;
            if (n.nodeType === 1)
                switch (n.nodeName) {
                    case 'P':
                        if (n.textContent.trim())
                            return '\n' + stripChildNodes(n) + '\n';
                        break;
                    case 'BR':
                        return '\n';
                    case 'I':
                    case 'EM':
                        return '[i]' + stripChildNodes(n) + '[/i]';
                    case 'B':
                    case 'STRONG':
                        return '[b]' + stripChildNodes(n) + '[/b]';
                    case 'UL':
                        return '\n[ul]' + stripChildNodes(n) + '[/ul]';
                    case 'OL':
                        return '\n[ol]' + stripChildNodes(n) + '[/ol]';
                    case 'LI':
                        return '[*]' + stripChildNodes(n) + '\n';
                }
            return '';

            function stripChildNodes(n) {
                return $(n.childNodes).toArray()
                    .reduce(function (p, c) { return p + stripNode(c) }, '');
            }
        }
    }

    function updateISBN(oddata) {
        var dfr = new $.Deferred(), requests = [];

        // look up isbn when one isn't available or when multiple formats are requested
        if (req.isbn && req.format.length === 1)
            return dfr.resolve(oddata).promise();

        var formatcodes = {WMA: 25, MP3: 425, EPUB: 410, PDF: 50, Kindle: 420};
        for (var format in formatcodes) {
            // skip if not available or requested
            var requested = format === 'Kindle' ? $(req.format).is(['MOBI','AZW3'])
                                                : $(req.format).is([format]);
            if (oddata._formats.indexOf(format) === -1 || !requested) continue;
            requests.push(isbnRequest(oddata._odid, format, formatcodes[format]));
        }

        $.when.apply($, requests).done(function (/* {format:'', isbn:''}|null, ... */) {
            oddata.formatisbn = {};
            for (var i = 0; i < arguments.length; i++) {
                if (!arguments[i]) continue;
                oddata.formatisbn[arguments[i].format] = arguments[i].isbn;
            }
            // for other formats
            oddata.isbn = req.isbn || oddata.formatisbn.EPUB || oddata.formatisbn.PDF;
            dfr.resolve(oddata);
        }).fail(function (reason) {
            // no need to propagate the error
            console.error('requp: ' + reason);
            dfr.resolve(oddata);
        });

        return dfr.promise();

        function isbnRequest(odid, format, code) {
            var dfr = new $.Deferred();

            GM_xmlhttpRequest({
                method: 'GET',
                url: 'http://www.contentreserve.com/TitleInfo.asp?ID={'+odid+'}&Format='+code,
                onload: function (res) {
                    var $page = $(res.responseText, null);
                    var isbn = $('td:contains(ISBN):last', $page).next().text().trim();
                    dfr.resolve(isbn ? {format: format, isbn: isbn} : null);
                },
                onerror: function (res) {
                    dfr.reject('failed looking up isbn: ' + res.status + ' ' + res.statusText);
                }
            });

            return dfr.promise();
        }
    }
}

function onUploadPage() {
    var reqid, _match;
    if (_match = location.search.match(/reqid=(\w+)/))
        reqid = _match[1];
    else
        return;
    var _req = GM_getValue(reqid);
    if (!_req) {
        console.log('requp: no data available');
        return;
    }
    var req = JSON.parse(_req);
    GM_deleteValue(reqid);

    // populate upload form fields
    $('#TitleField').val(req.title);
    $('#TagsField').val(req.tags.join(', '));
    $('#AuthorsField').val(req.authors.join(', '));
    if (req.artists && req.artists.length)
        $('#ArtistsField').val(req.artists.join(', '));
    if (req.format.length === 1)
        $('#FormatField').val(FORMATS[req.format[0]]);
    $('#PublishersField').val(req.publishers.join(', '));
    $('#LanguageField').val(LANGS[req.lang]);
    $('#RetailField').prop('checked', req.retail);
    $('#IsbnField').val(req.isbn);
    $('#DescriptionField').val(req.desc);
    $('#YearField').val(req.pubdate);
    if (req.formatisbn) {
        $('#FormatField').on('change', function () {
            var format = $(this).find(':selected').text();
            $('#IsbnField').val(req.formatisbn[format] || req.isbn);
        });
    }

    // save fill request form again for the torrent page
    $('#TitleField').parents('form').on('submit', function (e) {
        if (! (this.IsbnField.value && this.FormatField.value !== 'noformat')) {
            alert('Either ISBN or Format is empty.');
            e.preventDefault();
        }

        var key = this.IsbnField.value
            + $(this.FormatField).find(':selected').text();
        GM_setValue(key, JSON.stringify({ id: reqid, fillform: req.fillform }));
    });
}

function onTorrentPage() {
    var _match, _req;
    if (_match = $('#details_content_info').text().match(/^\s*(\w+).+?\((\d+)\)/)) {
        var key = _match[2] + _match[1]; // isbn + format
        if (_req = GM_getValue(key)) {
            GM_deleteValue(key);
        } else {
            console.log('requp: no linked request');
            return;
        }
    } else {
        console.log('requp: failed to parse isbn/format');
        return;
    }

    var req = JSON.parse(_req);
    var torrid = location.pathname.split('/').slice(-1);
    req.fillform.push({name: 'TorrentIdField', value: torrid});

    // add fill request link
    $('<a/>', {href: '/requests/' + req.id, text: 'Fill Request', id: 'fillreq'})
        .on('click', function (e) {
            var $self = $(this);
            e.preventDefault();
            $self.next().addClass('loading').text('');
            $.post(this.href, $.param(req.fillform)).done(function () {
                location.href = this.url;
            }).fail(function (xhr) {
                $self.next().removeClass('loading').text(xhr.status + ' ' + xhr.statusText);
                console.error('requp: failed to fill request');
            });
        }).appendTo('#sidebar ul').wrap('<li></li>').after('<span></span>');
}

GM_addStyle('#fillreq+span,#upreq+span{margin-left:10px;font-size:0.8em;}.loading{height:12px;width:12px;background-repeat:no-repeat;display:inline-block;background-image:url(data:image/gif;base64,R0lGODlhDAAMAIQAAAQCBIyKjERCRCQiJNze3BQSFPTy9DQ2NHx+fBwaHPz6/AwKDJyanExOTOzq7Dw+PAQGBCwqLOTi5BQWFPT29Dw6PBweHPz+/JyenFRWVP///wAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCAAaACwAAAAADAAMAAAFQ6CmKcxTTA+jiJpzAHB8OBr1xnhFMXgPYA9fg0DICAo+msZgQeIgFNElEuwhVoGUD2Cx/BQ3X2Xl8s1YpMdkIsCsNCEAIfkECQgAEwAsAAAAAAwADACEBAIElJKUPDo85OLkHB4cZGZkFBIUREJE/Pr8nJqc7OrsHBocPD48NDY0bGpsFBYUREZEnJ6c7O7s////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUDgNCEJYzxMgoiT0gBw3CjjG98CEtw8UPY3EzD2MPAgA0kBgOINRIjHIcGjiQgRhA1WWAVyrS3gJLCOSg9pVhQCACH5BAkIABIALAAAAAAMAAwAhAQCBJSWlDQ2NOzq7ERGRAwODMzKzDw+PJyenPz6/BQWFAQGBJyanDw6PExOTBQSFERCRPz+/P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVCoCQlwfEoB5OI0iAAcCwM4xvfDXnvAHPwNxMwpngMYajYwWD4wSCMGK21gCESr8JqVADkWq+AKACYsRIMiMMBuYpCACH5BAkIABUALAAAAAAMAAwAhAQCBIyOjDw6POTi5BweHGRmZPz6/BQSFExKTOzq7JyanERCRBwaHDw+POTm5DQ2NGxqbPz+/BQWFOzu7JyenP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVCYFUZSnNIjWKIVfIAcPwk4xvfAnnvQMnfpl9McgAUJoPFDiVZVRy7hYIREU12FMMjEDEUcCsXoyijiQyUhUSyyIpCACH5BAkIABQALAAAAAAMAAwAhAQCBJSSlDQ2NMzKzBQSFERCROzq7JyanAwODDw+PBwaHPz6/AQGBJSWlDw6PMzOzBQWFExKTJyenPz+/P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAIEUtTUJAybGIlCEAcCwY4xvfDgkgzB0ficbCkPABEhHWwAhJipY+VGBY9BUOOyNMsrAZcy2vjCZaACGQAlcUAgAh+QQJCAAUACwAAAAADAAMAIQEAgSUkpQ8Ojzk4uQcHhxkZmT08vQUEhREQkScmpzs6uwcGhw8Pjzk5uQ0NjRsamz8+vwUFhRERkScnpz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQiBFQQlzREwCiZTiEBEgA44yOsFYzIAABQSWgQcoRVaUAdEEKCgGEmLkQJQyqjxEAjubQBxcH6UBJtZYJEYkgvCKQgAh+QQJCAAYACwAAAAADAAMAIQEAgSEgoQ0NjTc3twUEhT08vRMTkwMCgycmpw8Pjzs6uwcGhz8+vwEBgSEhoQ8Ojzk4uQUFhRUUlQMDgycnpxEQkTs7uz8/vz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAFRSCGMUgiCAnCiJgiAAHDOICgjO+xjhPwkABAwyKyHIKlIMAwgBiUCYJyqoxIqdNIAjutILhKCuPF/bXIUxuLlIhEKmJRCAAh+QQJCAASACwAAAAADAAMAIQEAgSUlpQ8Ojzk4uQkIiRsamwUEhREQkT8+vycnpzs6uwcGhycmpw8Pjw0NjQUFhRERkTs7uz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQqAkIUFjPA2DiJLiADDwEI4yvnGxBgIZwzbRItH4AQYixONgMB4GioKsafyhqr8DAxtLIHDVXgscq7EQjMZj6RWFAAA7);}');