dc165015 / xsnvshen

// ==UserScript==
// @name         xsnvshen
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Extract the original full size images from xsnvshen.com. Press space to start/stop slideshow, drag any extracted image to hide it and double click to reload it.
// @author       dc
// @match        https://www.xsnvshen.com/album/*
// @grant        none
// @license      MIT
// @updateURL    https://openuserjs.org/meta/dc165015/xsnvshen.meta.js
// @downloadURL  https://openuserjs.org/install/dc165015/xsnvshen.user.js
// @copyright    2019, dc165015 (https://openuserjs.org/users/dc165015)
// ==/UserScript==

(function () {
    'use strict';

    let bigImgsCount = 0;
    let isBoardShowed = false;
    let slidesHandler, slidesInerval = 1000;
    let title = document.title;
    let $ = window.jQuery;
    let $thumbsList = $("div.showlists");
    let $thumbs = $thumbsList.find("img");
    let $slidesPanel = $("div.workContentWrapper");
    let $board = $('<div class="top-board"></div>');

    // show lists mode
    $('a#showlists').click();

    setTriggerLink();
    setStyle();
    setOpDivs();
    setHotkey();

    function setTriggerLink() {
        let $button = $("div.show-topmbx > a").last();
        $button.addClass('trigger-link');
        $button.attr('href', '#');
        $button.text('[展示全部原图]');
        $button.click(() => extractAllBigImgs(isBoardShowed = true));
    }

    function setStyle() {
        let style = document.createElement('STYLE');
        style.type = 'text/css';
        style.innerHTML = `
        .op {
            background-color: white;
                width: 100%;
                height: 80px;
                display: flex;
                position: absolute;
            }

            .op-hide {
                opacity: 0;
            }

            .op-show {
                opacity: 0.5;
            }

            .op:hover {
                opacity: 0.5;
            }

            .op-select {
                bottom: 0;
            }

            .op-remove {
                top: 0;
            }

            .op .op-button {
                display: block;
                color: rgba(255,0,0,0.5);
                font-size: 30px;
                font-weight: bolder;
                margin: auto auto;
            }

            .top-board {
                background-color: black;
                position: absolute;
                min-height: 100%;
                width: 100%;
                z-index: 10000;
                overflow: auto;
                flex-direction: row;
                flex-wrap: nowrap;
            }

            .full-img-box {
                display: block;
                position: relative;
                width: 100%;
                height: 100vh;
                margin-bottom: 1px;
            }

            .full-img {
                position: absolute;
                top: 0;
                bottom: 0;
                left: 0;
                right: 0;
                margin: auto;
                max-width: 100%;
                max-height: 100%;
            }

            .trigger-link {
                float: right;
                color: blue !important;
            }
        `;
        document.head.append(style);
    }

    function setOpDivs() {
        let OP_REMOVE = 'remove',
            OP_REMOVED = 'removed',
            OP_SELECT = 'select',
            OP_SELECTED = 'selected';

        $thumbs.each((n, img) => {
            setOpSelect(img);
            setOpRemove(img);
        });

        function setOpSelect(img) {
            let $img = $(img);
            let $div = $createOpDiv();
            $div.addClass('op-select');
            $img.after($div);

            img.opSelect = (is) => {
                if (is == void 0) is = !img.isSelected;
                if (is) {
                    $div.show();
                    $div.label(OP_SELECTED);
                    img.isSelected = true;
                    img.opRemove(false);
                } else {
                    $div.hide();
                    $div.label(OP_SELECT);
                    img.isSelected = false;
                }
            };
            img.opSelect(false);
            $div.click(() => img.opSelect());
        }

        function setOpRemove(img) {
            let $img = $(img);
            let $div = $createOpDiv();
            $div.addClass('op-remove');
            $img.before($div);

            img.opRemove = (is) => {
                if (is == void 0) is = !img.isRemoved;
                if (is) {
                    $div.show();
                    $div.label(OP_REMOVED);
                    img.isRemoved = true;
                    img.opSelect(false);
                } else {
                    $div.hide();
                    $div.label(OP_REMOVE);
                    img.isRemoved = false;
                }
            };
            img.opRemove(false);
            $div.click(() => img.opRemove());
        }

        function $createOpDiv() {
            let $div = $('<div class="op"></div>');
            $div.hide = () => {
                $div.removeClass('op-show');
                $div.addClass('op-hide');
            };
            $div.show = () => {
                $div.removeClass('op-hide');
                $div.addClass('op-show');
            };
            $div.label = (text = '') => {
                $div.html(`<span class="op-button">${text.toUpperCase()}</span>`);
            }
            return $div;
        }
    }

    function setHotkey() {
        document.addEventListener('keydown', (e) => {
            if (e.ctrlKey && e.key == '`') {
                window.scrollTo(0, 0);
                extractAllBigImgs(isBoardShowed = !isBoardShowed);
            } else if (isBoardShowed) {
                doKeyActions(e);
            }

            function doKeyActions(e) {
                e.stopPropagation();
                e.preventDefault();
                if (e.key == 'ArrowDown') {
                    scrollToImgBox('down');
                } else if (e.key == 'ArrowUp') {
                    scrollToImgBox('up');
                } else if (e.key == ' ') {
                    doSlides();
                } else if (e.key == 'Escape') {
                    reset();
                }
            }

            function reset() {
                extractAllBigImgs(isBoardShowed = false);
                stopSlides();
                $thumbs.each((i, img) => {
                    img.opSelect(false);
                    img.opRemove(false);
                });
            }
        });

        function scrollToImgBox() {
            let boxes = getBoxes(),
                current = getCurrentImgBoxInView(boxes);

            if (current) {
                current = getNextReadyImgBox(current);
                //$(current).fadeOut(0).fadeIn(400);
                current && current.scrollIntoView(true);
            }

            function getNextReadyImgBox(current){
                let next = current, img, didGoThrough = false;
                do {
                    next = next.nextElementSibling;
                    if (!next) {
                        if (didGoThrough) break;
                        next = boxes[0];
                        didGoThrough = true;
                    }
                    img = next.children[0];
                } while(!(img.complete || img.readyState));

                return next || boxes[0];
            }

            function getBoxes() {
                return $board.find('.full-img-box') || [];
            }

            function getCurrentImgBoxInView(boxes = []) {
                let current;
                for (let i = 0; i < boxes.length; i++) {
                    current = boxes[i];
                    if (isInView(current)) break;
                };
                return current;

                function isInView(el) {
                    return el.style.display != 'none' && window.scrollY >= el.offsetTop && window.scrollY <= el.offsetTop + el.scrollHeight;
                }
            }
        }

        function doSlides() {
            if (slidesHandler) {
                stopSlides();
            } else {
                startSlides();
            }
        }

        function stopSlides() {
            clearInterval(slidesHandler);
            slidesHandler = void 0;
        }

        function startSlides() {
            if (isBoardShowed) {
                scrollToImgBox();
                slidesHandler = setTimeout(startSlides, slidesInerval);
            }
        }
    }

    function extractAllBigImgs(doExtract = true) {
        if (doExtract) {
            $(document.body).prepend($board);
            startExtract();
        } else {
            $board.detach();
            $board.children().detach();
        }
        removeThumbsListAndSlidesPanel(doExtract);

        function startExtract() {
            let $chosenThumbs = getChosen$Thumbs();
            let i = bigImgsCount = $chosenThumbs.length;
            while (i-- > 0) {
                try {
                    showBigImgBy($chosenThumbs[i]);
                } catch (e) {}
            }

            function getChosen$Thumbs() {
                let selected = [];
                let unremoved = [];
                $thumbs.each((i, img) => {
                    let $thumb = $(img);
                    if (img.isSelected) selected.push($thumb);
                    if (!img.isRemoved) unremoved.push($thumb);
                });
                return selected.length ? selected : unremoved;
            }

            function showBigImgBy($thumb, $box, retryNo = 0) {
                let $loadedImgBox, $img, $loadedBigImgBoxes = new Map();

                if (!$box && ($loadedImgBox = $loadedBigImgBoxes.get($thumb))) {
                    $board.prepend($loadedImgBox);
                } else {
                    $box = $box || createBigImgWithBox($thumb);
                    $img = $box.find('img');
                    $img.length && initImg();
                }

                function createBigImgWithBox($thumb) {
                    let $img = $('<img/>').addClass("full-img");
                    let $box = $('<div></div>').addClass("full-img-box");
                    $box.append($img);
                    $loadedBigImgBoxes.set($thumb, $box);
                    listenDblClick();
                    listenDrag();
                    return $box;

                    function listenDblClick() {
                        $box.dblclick((e) => {
                            reload$img($img);
                        });

                        function reload$img($img) {
                            let src = $img.attr('src');
                            let index = src.indexOf('?');
                            src = index == -1 ? src : src.substring(0, src.indexOf('?'));
                            setImgSrc($img, src);

                        }
                    }

                    function listenDrag() {
                        $box[0].ondrag = (e) => {
                            $thumb[0].opRemove();
                            $box.detach();
                            bigImgsCount--;
                        };
                    }
                }

                function initImg() {
                    let src = getBigImgSrcFrom($thumb);
                    setImgSrc($img, src);
                    setImgId();

                    return new Promise((resolve, reject) => {
                        var img = $img[0];
                        $board.prepend($box);

                        $img.load(() => {
                            $img.css('display', 'block');
                            updateTitle();
                            resolve();
                        });

                        img.onerror = img.onsuspend = img.onstalled = img.onemptied = () => {
                            retryImg($thumb, src, $img, ++retryNo);
                            reject(retryNo);
                        };
                    });

                    function getBigImgSrcFrom($thumb) {
                        return $thumb.data('original').replace(/thumb_600x900\//, '');
                    }

                    function setImgId() {
                        $img.attr('id', 'big' + $thumb.attr('id').match(/\d+/));
                    }

                    function updateTitle() {
                        let loadedCount = $board.find('img[style*="display: block;"]').length;
                        document.title = loadedCount != bigImgsCount ? `${loadedCount}/${bigImgsCount}|${title}` : title;
                    }

                    function retryImg($thumb, src, $img, retryNo) {
                        if (retryNo < 10) {
                            setTimeout(() => {
                                console.warn(`retry to load img(${src}) the ${retryNo} time.`);
                                showBigImgBy($thumb, $img, retryNo);
                            }, 10000 * retryNo * retryNo);
                        }
                    }
                }
            }
        }

        function removeThumbsListAndSlidesPanel(doRemove = true) {
            if (doRemove) {
                $slidesPanel.detach();
                $thumbsList.detach();
            } else {
                $('.swp-tit').after($slidesPanel, $thumbsList);
            }
        }
    }

    function setImgSrc($img, src) {
        // src = src + '?' + Date.now();
        $img.attr('src', src);
    }
})();