burning series enhancer

Wechselt automatisch zum Vivo-Tab auf burning series enhancer und öffnet Vivo. Wahlweise wird die Video-URL kopiert und der Tab geschlossen oder das Video startet automatisch. Wenn aktiviert, startet das nächste Video, sobald ein Vivo-Video beendet wurde.

Від 12.08.2021. Дивіться остання версія.

// ==UserScript==
// @icon           https://bs.to/favicon.ico
// @name           burning series enhancer
// @author         xtrars
// @description    Wechselt automatisch zum Vivo-Tab auf burning series enhancer und öffnet Vivo. Wahlweise wird die Video-URL kopiert und der Tab geschlossen oder das Video startet automatisch. Wenn aktiviert, startet das nächste Video, sobald ein Vivo-Video beendet wurde.
// @description:en Automatically switches to the Vivo tab on burning series enhancer and opens Vivo. Optionally copies the video url and closes the tab or starts the video automatically. If enabled, the next video starts as soon as a vivo video is finished.
// @version        4.0
// @run-at         document-start
// @license        GPL-3.0-or-later
// @namespace      https://greasyforks.org/users/140785
// @grant          GM_setValue
// @grant          GM_getValue
// @grant          GM_setClipboard
// @grant          GM_addValueChangeListener
// @grant          GM_removeValueChangeListener

// @include        https://bs.to/*
// @include        https://burningseries.co/*
// @include        https://burningseries.sx/*
// @include        https://burningseries.vc/*
// @include        https://burningseries.ac/*
// @include        https://burningseries.cx/*
// @include        https://vivo.sx/*
// @include        https://vivo.st/*
// @include        https://vupload.com/*

// @include        https://*.vivo.sx/*
// @include        https://*.vivo.st/*
// @include        https://*.megaupload.to/*
// ==/UserScript==

class BaseHandler {
    // thanks to xZaheer (https://greasyforks.org/de/scripts/400669-burningseries-autoplay/code)
    waitForElement(selector) {
        return new Promise(async resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));

            }

            const observer = new MutationObserver(mutations => {
                if (document.querySelector(selector)) {
                    resolve(document.querySelector(selector));
                    observer.disconnect();
                }
            });
            let waitForElementInterval = setInterval(() => {
                if(document.body) {
                    observer.observe(document.body, {
                        childList: true,
                        subtree: true,
                    });
                    clearInterval(waitForElementInterval);
                }

            }, 20);

        });
    }

    hasUrl(aSelector) {
        let isAvailable = true;
        for (let selector of aSelector) {
            isAvailable = document['location']['href'].search(selector) !== -1;
            if (!isAvailable) {
                return false;
            }
        }
        return true;
    }

    reload(iDelay = 300) {
        setTimeout(() => {
            window.location.reload();
        }, iDelay);
    }
}

class BurningSeriesHandler extends BaseHandler {
    initGMVariables() {
        if (typeof GM_getValue('bActivateEnhancer') === "undefined") {
            GM_setValue('bActivateEnhancer', false);
        }
        if (typeof GM_getValue('bAutoplayNextEpisode') === "undefined") {
            GM_setValue('bAutoplayNextEpisode', true);
        }
        if (typeof GM_getValue('bAutoplayVivoVideo') === "undefined") {
            GM_setValue('bAutoplayVivoVideo', true);
        }
        if (typeof GM_getValue('bAutoplayNextSeason') === "undefined") {
            GM_setValue('bAutoplayNextSeason', true);
        }
        if (typeof GM_getValue('bAutoplayRandomEpisode') === "undefined") {
            GM_setValue('bAutoplayRandomEpisode', false);
        }
    }

    hasAnotherHoster() {
        return this.hasUrl([/^https:\/\/(bs.to|burningseries.[a-z]{2,3})\/.*[0-9]{1,2}\/[0-9]{1,2}\-.*\/[a-z]*\/(?!Vivo|Vupload).*/g]);
    }

    isEpisode() {
        return this.hasUrl([/^https:\/\/(bs.to|burningseries.[a-z]{2,3})/g, /[0-9]{1,2}\/[0-9]{1,2}\-/g]);
    }

    async clickPlay() {
        let playerElem = await this.waitForElement('section.serie .hoster-player').catch(() => {});
        let iNumberOfClicks = 0;
        let clickInterval = setInterval(() => {
            if(playerElem) {
                if (document.querySelector('section.serie .hoster-player > a') ||
                    document.querySelector('iframe[title="recaptcha challenge"]') ||
                    iNumberOfClicks > 120) {
                    clearInterval(clickInterval);
                }
                iNumberOfClicks++;
                let clickEvent = new Event('click');
                clickEvent.which = 1;
                clickEvent.pageX = 6;
                clickEvent.pageY = 1;
                playerElem.dispatchEvent(clickEvent);
            }

        }, 500);
    }

    playNextEpisodeIfVideoEnded(bSetEvent = true) {
        if(!bSetEvent) {
            GM_removeValueChangeListener('isLocalVideoEnded');
            GM_setValue('isLocalVideoEnded', false);
            return;
        }
        GM_addValueChangeListener('isLocalVideoEnded', () => {
            if (GM_getValue('isLocalVideoEnded')) {
                GM_setValue('isLocalVideoEnded', false);
                if (GM_getValue('bAutoplayRandomEpisode')) {
                    let oRandomEpisode = document.querySelector('#sp_right > a');
                    document['location'].replace(oRandomEpisode.href);
                } else {
                    let oNextEpisode = document.querySelector('.serie .frame ul li[class^="e"].active ~ li:not(.disabled) a');
                    if (oNextEpisode) {
                        document['location'].replace(oNextEpisode.href);
                    } else if (GM_getValue('bAutoplayNextSeason')) {
                        GM_setValue('clickFirstSeason', true);
                        let oNextSeason = document.querySelector('.serie .frame ul li[class^="s"].active ~ li:not(.disabled) a');
                        document['location'].replace(oNextSeason);
                    }
                }
            }
        });

    }

    async buildButton() {
        const style = document.createElement('style');
        style.innerHTML = `
  :root {
  --inner-pl: 14px;
  --inner-bc-before: #2FB536;
  --inner-bc-after: #12A6F6;
  --color: white;
}

  @keyframes shake {
  10%, 90% {transform: translate3d(-.5px, 0, 0);}
  20%, 80% {transform: translate3d(1px, 0, 0);}
  30%, 50%, 70% {transform: translate3d(-2px, 0, 0);}
  40%, 60% {transform: translate3d(2px, 0, 0);}
}
.onoffswitch {
    position: relative; width: 350px;
    -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
}
.onoffswitch-checkbox {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}
.onoffswitch-label {
    width: 350px;
    display: block;
    overflow: hidden;
    cursor: pointer;
    border: 2px solid transparent;
    border-radius: 20px;
}
.onoffswitch-inner {
    display: block; width: 200%; margin-left: -100%;
    transition: margin 0.3s ease-in 0s;
}
.onoffswitch-inner:before, .onoffswitch-inner:after {
    display: block; float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
    font-size: 10px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
    box-sizing: border-box;
}
.onoffswitch-switch {
    display: block;
    width: 23px; margin: 3.5px;
    background: #FFFFFF;
    position: absolute;
    top: 0;
    bottom: 2px;
    right: 314px;
    border: 2px solid #999999;
    border-radius: 20px;
    transition: all 0.3s ease-in 0s;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
    margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
    right: 0px;
}

#xtrars-btn {
   position: absolute;
   bottom: 70px;
   right: 18px;
   background: #12a6f6;
   border-radius: 50%;
   width: 70px;
   height: 70px;
   line-height: 81px;
   text-align: center;
   cursor: pointer;
   animation: shake 1s ease 1s 1 normal;
}
#xtrars-btn:hover {
    cursor: auto;
}
#xtrars-menu {
   right: 4;
}
#xtrars-btn-icon {
   color: var(--color);
}
.onoffswitch-inner.autoplay:before {
    padding-left: var(--inner-pl);
    background-color: var(--inner-bc-before);
    color: var(--color);
    text-align: start;
    content: "Vivo Autoplay aktiviert";
}
.onoffswitch-inner.autoplay:after {
    padding-right: var(--inner-pl);
    background-color: var(--inner-bc-after);
    color: var(--color);
    text-align: end;
    content: "Video-URL wird in die Zwischenablage kopiert";
}
.onoffswitch-inner.auto-next-episode:before {
    padding-left: var(--inner-pl);
    background-color: var(--inner-bc-before);
    color: var(--color);
    text-align: start;
    content: "Nächste Episode wird automatisch abgespielt";
}
.onoffswitch-inner.auto-next-episode:after {
    padding-right: var(--inner-pl);
    background-color: var(--inner-bc-after);
    color: var(--color);
    text-align: end;
    content: "Nächste Episode wird manuell abgespielt";
}
.onoffswitch-inner.enable-enhancer:before {
    padding-left: var(--inner-pl);
    background-color: var(--inner-bc-before);
    color: var(--color);
    text-align: start;
    content: "burning series enhancer aktiviert";
}
.onoffswitch-inner.enable-enhancer:after {
    padding-right: var(--inner-pl);
    background-color: var(--inner-bc-after);
    color: var(--color);
    text-align: end;
    content: "burning series enhancer deaktiviert";
}
.onoffswitch-inner.auto-next-season:before {
    padding-left: var(--inner-pl);
    background-color: var(--inner-bc-before);
    color: var(--color);
    text-align: start;
    content: "Nächste Staffel wird automatisch abgespielt";
}
.onoffswitch-inner.auto-next-season:after {
    padding-right: var(--inner-pl);
    background-color: var(--inner-bc-after);
    color: var(--color);
    text-align: end;
    content: "Nächste Staffel wird manuell abgespielt";
}
.onoffswitch-inner.auto-random-episode:before {
    padding-left: var(--inner-pl);
    background-color: var(--inner-bc-before);
    color: var(--color);
    text-align: start;
    content: "Nächste Episode wird abgespielt";
}
.onoffswitch-inner.auto-random-episode:after {
    padding-right: var(--inner-pl);
    background-color: var(--inner-bc-after);
    color: var(--color);
    text-align: end;
    content: "Nächste Episode wird zufällig abgespielt";
}
.disabled:after {
    background-color: #D3D3D3 !important;
}
.disabled:before {
    background-color: #D3D3D3 !important;
}

  `;
        document.head.appendChild(style);

        const button = document.createElement("div");
        button['innerHTML'] = '<div id="xtrars-btn">' +
            '<i id="xtrars-btn-icon" class="fas fa-cogs fa-2x"></i>' +
            '</div>';
        button['style'] = 'position: relative; height: 0;'
        await this.waitForElement('.infos').catch(() => {});
        document.getElementsByClassName('infos')[0].appendChild(button);

        const menu = document.createElement("div");
        menu['innerHTML'] = '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner enable-enhancer"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner auto-next-episode"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner auto-random-episode"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner auto-next-season"></span><span class="onoffswitch-switch"></span></label></div>' +
            '<div class="onoffswitch"><input type="checkbox" name="onoffswitch" class="onoffswitch-checkbox xtrars-onoffswitch"><label class="onoffswitch-label" for="xtrars-onoffswitch"><span class="onoffswitch-inner autoplay"></span><span class="onoffswitch-switch"></span></label></div>';
        menu['style'] = 'display: none;';
        menu['id'] = 'xtrars-menu';
        document.getElementById('xtrars-btn').appendChild(menu);

        this.initEvents();
    }

    initEvents() {
        let button = document.getElementById('xtrars-btn');
        let menu = document.getElementById('xtrars-menu');
        let activateEnhancer = document.querySelectorAll('.xtrars-onoffswitch + label')[0];
        let autoplayNextEpisode = document.querySelectorAll('.xtrars-onoffswitch + label')[1];
        let autoplayRandomEpisode = document.querySelectorAll('.xtrars-onoffswitch + label')[2];
        let autoplayNextSeason = document.querySelectorAll('.xtrars-onoffswitch + label')[3];
        let autoplayVivoVideo = document.querySelectorAll('.xtrars-onoffswitch + label')[4];

        activateEnhancer.previousSibling.checked = GM_getValue('bActivateEnhancer');
        autoplayNextEpisode.previousSibling.checked = GM_getValue('bAutoplayNextEpisode');
        autoplayRandomEpisode.previousSibling.checked = !GM_getValue('bAutoplayRandomEpisode');
        autoplayNextSeason.previousSibling.checked = GM_getValue('bAutoplayNextSeason');
        autoplayVivoVideo.previousSibling.checked = GM_getValue('bAutoplayVivoVideo');


        button.addEventListener('mouseover', () => {
            menu['style'] = 'display: block; position: absolute; bottom: 0; line-height: normal;'
            document.getElementById('xtrars-btn')['style'] = 'background: transparent; border-radius: 0; width: 355px; right: -130px; height: 185px; bottom: 20px';
            document.getElementById('xtrars-btn-icon')['style'] = 'color: transparent;';
        });

        button.addEventListener('mouseleave', () => {
            menu['style'] = 'display: none;';
            document.getElementById('xtrars-btn')['style'] = '';
            document.getElementById('xtrars-btn-icon')['style'] = '';
        });


        this.manageButtonState(activateEnhancer, autoplayNextEpisode, autoplayRandomEpisode, autoplayNextSeason, autoplayVivoVideo);

        activateEnhancer.addEventListener('click', () => {
            activateEnhancer.previousSibling.checked = !activateEnhancer.previousSibling.checked;
            GM_setValue('bActivateEnhancer', activateEnhancer.previousSibling.checked);
            this.manageButtonState(activateEnhancer, autoplayNextEpisode, autoplayRandomEpisode, autoplayNextSeason, autoplayVivoVideo);
            this.reload();
        });


        autoplayNextEpisode.addEventListener('click', () => {
            if (!autoplayNextEpisode.childNodes[0].classList.contains('disabled')) {
                autoplayNextEpisode.previousSibling.checked = !autoplayNextEpisode.previousSibling.checked;
                GM_setValue('bAutoplayNextEpisode', autoplayNextEpisode.previousSibling.checked);
                this.manageButtonState(activateEnhancer, autoplayNextEpisode, autoplayRandomEpisode, autoplayNextSeason, autoplayVivoVideo);
            }
        });

        autoplayRandomEpisode.addEventListener('click', () => {
            if (!autoplayRandomEpisode.childNodes[0].classList.contains('disabled')) {
                autoplayRandomEpisode.previousSibling.checked = !autoplayRandomEpisode.previousSibling.checked;
                GM_setValue('bAutoplayRandomEpisode', !autoplayRandomEpisode.previousSibling.checked);
                this.manageButtonState(activateEnhancer, autoplayNextEpisode, autoplayRandomEpisode, autoplayNextSeason, autoplayVivoVideo);
            }
        });

        autoplayNextSeason.addEventListener('click', () => {
            if (!autoplayNextSeason.childNodes[0].classList.contains('disabled')) {
                autoplayNextSeason.previousSibling.checked = !autoplayNextSeason.previousSibling.checked;
                GM_setValue('bAutoplayNextSeason', autoplayNextSeason.previousSibling.checked);
            }
        });

        autoplayVivoVideo.addEventListener('click', () => {
            if (!autoplayVivoVideo.childNodes[0].classList.contains('disabled')) {
                autoplayVivoVideo.previousSibling.checked = !autoplayVivoVideo.previousSibling.checked;
                GM_setValue('bAutoplayVivoVideo', autoplayVivoVideo.previousSibling.checked);
            }
        });
    }

    manageButtonState(activateEnhancer, autoplayNextEpisode, autoplayRandomEpisode, autoplayNextSeason, autoplayVivoVideo) {
        if (!activateEnhancer.previousSibling.checked) {
            this.disableAutoplay(autoplayVivoVideo);
            this.disableAutoplay(autoplayNextEpisode);
            this.disableAutoplay(autoplayNextSeason);
            this.disableAutoplay(autoplayRandomEpisode);
        } else {
            this.enableAutoplay(autoplayNextEpisode);

            if (autoplayNextEpisode.previousSibling.checked) {
                this.enableAutoplay(autoplayRandomEpisode);
                this.disableAutoplay(autoplayVivoVideo);

                if(!autoplayRandomEpisode.previousSibling.checked) {
                    this.disableAutoplay(autoplayNextSeason);
                } else {
                    this.enableAutoplay(autoplayNextSeason);
                }

            } else {
                this.disableAutoplay(autoplayRandomEpisode);
                this.disableAutoplay(autoplayNextSeason);
                this.enableAutoplay(autoplayVivoVideo);
            }
        }
    }

    disableAutoplay(oElement) {
        if (oElement.childNodes[0].classList.contains('auto-next-episode')) {
            this.playNextEpisodeIfVideoEnded(false);
        }
        oElement.childNodes[0].classList.add('disabled');
    }

    enableAutoplay(oElement) {
        oElement.childNodes[0].classList.remove('disabled');
    }
}

class StreamingHandler extends BaseHandler {
    applyShortcuts(videoElement) {
        document.addEventListener('keydown', function(e) {
            if (!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
                switch(e.keyCode){
                    case 37: // Left
                        e.preventDefault();
                        videoElement.currentTime -= 10;
                        break;
                    case 38: // Up
                        e.preventDefault();
                        if (videoElement.volume < 0.9) {
                            videoElement.volume += 0.1;
                        } else {
                            videoElement.volume = 1;
                        }
                        break;
                    case 39: // Right
                        e.preventDefault();
                        videoElement.currentTime += 10;
                        break;
                    case 40: // Down
                        e.preventDefault();
                        if (videoElement.volume > 0.1) {
                            videoElement.volume -= 0.1;
                        } else {
                            videoElement.volume = 0;
                        }
                        break;
                    case 70: // F
                        if (!document.fullscreenElement) {
                            videoElement.requestFullscreen().catch(() => {
                                video.style.width = "100%";
                                video.style.height = "100%";
                                document.body.style.margin = "0px";
                            });
                        } else {
                            document.exitFullscreen();
                        }
                        break;
                    case 75: // K
                        if(videoElement.paused) {
                            videoElement.play();
                        }
                        else {
                            videoElement.pause();
                        }
                        break;
                }
            }
        }, true);
    }

    async handleStreamBehavior(sSrc) {
        if (GM_getValue('bAutoplayVivoVideo') || GM_getValue('bAutoplayNextEpisode')) {
            window['location'].replace(sSrc);
        } else {
            await navigator.clipboard.writeText(sSrc).catch(() => {
                GM_setClipboard(sSrc);
            });
            window.close();
        }
    }


    async isStreamingHoster(regex, selector) {
        return this.hasUrl([regex]) && await this.waitForElement(selector).catch(() => {});
    }

    async handleStreamingHoser(aHoster) {
        for (let oHoster of aHoster) {
            if(await this.isStreamingHoster(oHoster['regex'], oHoster['selector'])) {
                let video = await this.waitForElement(oHoster['selector'] + oHoster['detailSelector']).catch(() => {});
                this.handleStreamBehavior(video['src']);
            }
        }
        this.handleLocalVideo();
    }

    async handleLocalVideo() {
        let video;
        if (video = await this.waitForElement('html > head + body > video').catch(() => {})) {
            this.applyShortcuts(video);
            GM_setValue('isLocalVideoEnded', false);
            video.requestFullscreen().catch(() => {
                video.style.width = "100%";
                video.style.height = "100%";
                document.body.style.margin = "0px";
            });
            video.onended = () => {
                GM_setValue('isLocalVideoEnded', true);
                window.close();
            }
        }
    }
}

(async function() {
    'use strict';

    let bsHandler = new BurningSeriesHandler();
    let streamingHandler = new StreamingHandler();

    if (GM_getValue('clickFirstSeason')) {
        GM_setValue('clickFirstSeason', false);
        let sSelector = '.serie > .episodes > tbody > tr:first-child > td:first-child > a:first-child';
        await bsHandler.waitForElement(sSelector);
        document['location'].replace(document.querySelector(sSelector));
    }

    bsHandler.initGMVariables();

    if (bsHandler.isEpisode()) {
        if (GM_getValue('bActivateEnhancer') && !bsHandler.hasAnotherHoster() && !bsHandler.hasUrl(['/Vivo']) && !bsHandler.hasUrl(['/Vupload'])) {
            document['location'].replace(document['location']['href'] + '/Vivo');
        }
        await bsHandler.buildButton();

        if (GM_getValue('bActivateEnhancer') && !bsHandler.hasAnotherHoster() && (bsHandler.hasUrl(['/Vivo']) || bsHandler.hasUrl(['/Vupload']))) {
            if (GM_getValue('bAutoplayNextEpisode')) {
                bsHandler.playNextEpisodeIfVideoEnded();
            }

            if(GM_getValue('bActivateEnhancer')){
                bsHandler.clickPlay();
            }
        }
    }

    if (GM_getValue('bActivateEnhancer')) {
        // add new hoster here
        let aHoster = [
            {
                selector: 'video:not(#player)',
                regex: /^https:\/\/vivo.[a-z]{2,3}\//g,
                detailSelector: ' > source'
            },
            {
                selector: '#vjsplayer_html5_api',
                regex: /^https:\/\/vupload.[a-z]{2,3}\//g,
                detailSelector: '[src]',
            },
        ];

        streamingHandler.handleStreamingHoser(aHoster);
    }

})();
长期地址
遇到问题?请前往 GitHub 提 Issues。