Tampermonkey / tampermonkey

Tampermonkey is the most popular userscript manager, with over 10 million users. It's available for Chrome, Microsoft Edge, Safari, Opera Next, and Firefox.
GNU General Public License v3.0
4.38k stars 431 forks source link

stop working tampermonkey on website player.pl #1180

Closed morarz closed 3 years ago

morarz commented 3 years ago

(Please fill out the issue template with your details)

Expected Behavior

downloading video from website

Actual Behavior

script not showing download button to download movie, in other webiste work fine, stop working on plater.pl

Specifications

Script

(Please give an example of the script if applicable.)

// ==UserScript==
// ==UserScript==
// @name           Skrypt umożliwiający pobieranie materiałów ze znanych serwisów VOD.
// @version        7.2.4
// @description    Skrypt służący do pobierania materiałów ze znanych serwisów VOD.
//                 Działa poprawnie tylko z rozszerzeniem Tampermonkey.
//                 Cześć kodu pochodzi z:
//                 miniskrypt.blogspot.com,
//                 miniskrypt.hubaiitv.pl
// @author         Przmus, zacny
// @namespace      http://www.ipla.tv/
// @source         https://github.com/zacny/voddownloader
// @include        /^https://(vod|cyfrowa)\.tvp\.pl/video/.*$/
// @include        /^https?://.*\.tvp.(pl|info)/sess/TVPlayer2/embed.*$/
// @include        /^https?://((?!wiadomosci).)*\.tvp\.pl/\d{6,}/.*$/
// @include        https://www.tvpparlament.pl/sess/*
// @include        https://www.ipla.tv/*
// @include        https://player.pl/*
// @include        https://*.cda.pl/*
// @include        https://vod.pl/*
// @include        https://redir.atmcdn.pl/*
// @include        https://*.redcdn.pl/file/o2/redefine/partner/*
// @include        https://partner.ipla.tv/embed/*
// @include        https://wideo.wp.pl/*
// @include        https://ninateka.pl/*
// @include        https://www.arte.tv/*/videos/*
// @include        https://pulsembed.eu/*
// @include        https://tv-trwam.pl/local-vods/*
// @exclude        http://www.tvp.pl/sess/*
// @exclude        /^https?://(bialystok|gorzow|krakow|olsztyn|rzeszow|wroclaw|bydgoszcz|katowice|lublin|opole|szczecin|gdansk|kielce|lodz|poznan|warszawa)\.tvp.\pl/.*$/
// @exclude        /^https?://.*\.vod\.tvp\.pl/\d{6,}/.*$/
// @exclude        https://www.cda.pl/iframe/*
// @grant          GM_getResourceText
// @grant          GM_xmlhttpRequest
// @grant          GM_download
// @grant          GM_setClipboard
// @grant          GM_info
// @connect        tvp.pl
// @connect        getmedia.redefine.pl
// @connect        distro.redefine.pl
// @connect        player-api.dreamlab.pl
// @connect        api.arte.tv
// @connect        b2c.redefine.pl
// @connect        player.pl
// @connect        api-trwam.app.insysgo.pl
// @run-at         document-end
// @require        https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @require        https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js
// @require        https://cdnjs.cloudflare.com/ajax/libs/platform/1.3.5/platform.min.js
// @require        https://gitcdn.xyz/cdn/zacny/voddownloader/4b17a120f521eaddf476d6e8fe3be152d506f244/lib/js/mdb-with-waves-patch.js
// @require        https://gitcdn.xyz/cdn/kapetan/jquery-observe/ca67b735bb3ae8d678d1843384ebbe7c02466c61/jquery-observe.js
// @resource       buttons_css https://raw.githubusercontent.com/zacny/voddownloader/master/lib/css/voddownloader-buttons.css
// @resource       content_css https://raw.githubusercontent.com/zacny/voddownloader/master/lib/css/voddownloader-content.css
// ==/UserScript==

(function vodDownloader($, platform, Waves) {
    'use strict';

    var Exception = (function(error, templateParams) {
        this.error = error;
        this.templateParams = Array.isArray(templateParams) ? templateParams : [templateParams];
    });

    var Tool = (function(Tool) {
        Tool.deleteParametersFromUrl = function(url){
            return decodeURIComponent(url.replace(/\?.*/,''));
        };

        Tool.getUrlParameter = function(paramName, url){
            var results = new RegExp('[\?&]' + paramName + '=([^&#]*)').exec(url);
            if (results==null) {
                return null;
            }
            return decodeURIComponent(results[1]) || 0;
        };

        Tool.formatConsoleMessage = function(message, params){
            console.log.apply(this, $.merge([message], params));
        };

        var removeNotAllowedFileNameChars = function(input){
            var regexp = new RegExp('[\/:*?"<>|]+', 'g');
            return input.replace(regexp, '').replace(/\s+/g, ' ');
        };

        Tool.downloadFile = function(fileUrl, title){
            var extension = Tool.deleteParametersFromUrl(fileUrl.split('.').pop());
            var movieTitle = (title !== undefined && title !== '' ) ? title : 'nieznany';
            var name = removeNotAllowedFileNameChars(movieTitle) + '.' + extension;
            GM_download(fileUrl, name);
        };

        Tool.template = function(templates, ...keys){
            return (function(...values) {
                var dict = values[values.length - 1] || {};
                var result = [templates[0]];
                keys.forEach(function(key, i) {
                    var value = Number.isInteger(key) ? values[key] : dict[key];
                    result.push(value, templates[i + 1]);
                });
                return result.join('');
            });
        };

        Tool.getRealUrl = function(){
            var topUrl = window.sessionStorage.getItem(config.storage.topWindowLocation);
            return topUrl !== null ? topUrl : window.location.href;
        };

        Tool.isTopWindow = function(){
            return window.top === window.self;
        };

        Tool.pad = function(number, characters){
            return(1e15+number+"").slice(-characters)
        };

        Tool.mapDescription = function(data){
            var defaults = $.extend({}, config.description.defaults);
            var sourceDescriptions = config.description.sources[data.source] || {};
            var descriptionVariant = sourceDescriptions[data.key] || {};
            return $.extend(true, defaults, data, descriptionVariant);
        };

        return Tool;
    }(Tool || {}));

    const config = {
        attempts: 10,
        attemptTimeout: 1500,
        storage: {
            doNotWarn: 'voddownloader.doNotwarnIfIncorrectPluginSettingsDetected',
            topWindowLocation: 'voddownloader.topWindowLocation'
        },
        urlParamPattern: '#',
        urlParamDefaultKey: 'videoId',
        urlPartPattern: '~',
        include: {
            fontawesome: {
                id: 'fontawesome',
                css: 'https://use.fontawesome.com/releases/v5.8.2/css/all.css'
            },
            bootstrap: {
                id: 'bootstrap',
                css: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css'
            },
            mdb: {
                id: 'mdb',
                css: 'https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.8.2/css/mdb.min.css',
            }
        },
        error: {
            id: {
                caption: 'Nie udało się odnaleźć idetyfikatora.',
                template: Tool.template`Algorytm rozpoznawania identyfikatora wideo na stronie: ${0} \
                    zakończył się niepowodzeniem. Może to oznaczać błąd skryptu lub zmiany w portalu.`,
            },
            tvnId: {
                caption: 'Nie udało się odnaleźć idetyfikatora.',
                template: Tool.template`Algorytm rozpoznawania identyfikatora wideo na stronie: ${0} \
                    zakończył się niepowodzeniem.\nJeżeli jest to główna strona programu oznacza to, \
                    że nie udało się odnaleźć identyfikatora ostatniego odcinka. Wejdź na stronę odcinka \
                    i spróbuj ponownie.\nMoże to również oznaczać błąd skryptu lub zmiany w portalu.`,
            },
            call: {
                caption: 'Błąd pobierania informacji o materiale.',
                template: Tool.template`Wystąpił błąd w wykonaniu skryptu w kroku: ${0} na stronie: ${1} \
                    Zgłoś problem autorom skryptu.`,
            },
            noSource: {
                caption: 'Nie udało się odnaleźć metadanych tego materiału.',
                template: Tool.template`Materiał ze strony ${0} nie posiada zdefiniowanych metadanych potrzebnych do \
                    działania skryptu lub są one nieprawidłowe.\n
                    Może to oznaczać, że nie jest to materiał publicznie dostępny, nie posiada zdefiniowanych źródeł lub nie \
                    mogą one zostać wyświetlone w przeglądarce bez dodatkowego oprogramowania albo jest to materiał \
                    umieszczony w płatnej strefie.`,
                type: 'info'
            },
            timeout: {
                caption: 'Zbyt długi czas odpowiedzi.',
                template: Tool.template`Dla kroku: ${0} na stronie "${1}" nie dotarły \
                    informacje zwrotne.\nPrzypuszczalnie jest to problem sieciowy. Spróbuj ponownie za jakiś czas.`
            },
            noParent: {
                caption: 'Brak zakładki ze stroną główną.',
                template: Tool.template`Została zamknięta zakładka ze stroną na której został uruchomiony skrypt. \
                        Ta zakładka nie może przez to działać poprawnie. Otwórz ponownie stronę główną: \n ${0} \n
                        by przywrócić prawidłowe funkcjonowanie skryptu.`
            }
        },
        description: {
            defaults: {
                index: 99,
                language: 'polski',
                audio:  'MPEG AAC'
            },
            sources: {
                IPLA: {
                    '1080p': {video: 'H264 MPEG-4 AVC, 4011 kb/s, 1920x1080, 25fps, 16:9', index: 1},
                    '720p': {video: 'H264 MPEG-4 AVC, 1672 kb/s, 1280x720, 25fps, 16:9', index: 2},
                    '576p': {video: 'H264 MPEG-4 AVC, 1175 kb/s, 1024x576, 25fps, 16:9', index: 3},
                    '384p': {video: 'H264 MPEG-4 AVC, 256 kb/s, 484x272, 25fps, 16:9', index: 4}
                },
                WP: {
                    HQ: {video: 'H264 MPEG-4 AVC, 1804 kb/s, 1280x720, 24fps, 16:9', index: 1},
                    LQ: {video: 'H264 MPEG-4 AVC, 616 kb/s, 640x360, 24fps, 16:9', index: 2}
                },
                TVN: {
                    'HD': {video: 'H264 MPEG-4 AVC, 2776 kb/s, 1280x720, 25fps, 16:9', index: 1},
                    'Bardzo wysoka': {video: 'H264 MPEG-4 AVC, 1786 kb/s, 1280x720, 25fps, 16:9', index: 2},
                    'Wysoka': {video: 'H264 MPEG-4 AVC, 1191 kb/s, 720x576, 25fps, 5:4', index: 3},
                    'Standard': {video: 'H264 MPEG-4 AVC, 794 kb/s, 720x576, 25fps, 5:4', index: 4},
                    'Średnia': {video: 'H264 MPEG-4 AVC, 596 kb/s, 640x480, 25fps, 4:3', index: 5},
                    'Niska': {video: 'H264 MPEG-4 AVC, 417 kb/s, 512x384, 25fps, 4:3', index: 6},
                    'Bardzo niska': {video: 'H264 MPEG-4 AVC, 238 kb/s, 320x240, 25fps, 4:3', index: 7}
                },
                VOD: {
                    '1080':{video: 'H264 MPEG-4 AVC, 1920x1080, 25fps, 16:9', index: 1},
                    '720': {video: 'H264 MPEG-4 AVC, 1280x720, 25fps, 16:9', index: 2},
                    '576': {video: 'H264 MPEG-4 AVC, 1024x576, 25fps, 16:9', index: 3},
                    '480': {video: 'H264 MPEG-4 AVC, 854x480, 25fps, 16:9', index: 4},
                    '360': {video: 'H264 MPEG-4 AVC, 640x360, 25fps, 16:9', index: 5},
                    '240': {video: 'H264 MPEG-4 AVC, 426x240, 25fps, 16:9', index: 6}
                },
                TVP: {
                    '9100000': {video: 'H264 MPEG-4 AVC, 21030 kb/s, 1920x1080, 25fps, 16:9', index: 1},
                    '5420000': {video: 'H264 MPEG-4 AVC, 9875 kb/s, 1280x720, 25fps, 16:9', index: 2},
                    '2850000': {video: 'H264 MPEG-4 AVC, 4661 kb/s, 960x540, 25fps, 16:9', index: 3},
                    '1750000': {video: 'H264 MPEG-4 AVC, 1782 kb/s, 800x450, 25fps, 16:9', index: 4},
                    '1500000': {video: 'H264 MPEG-4 AVC, 1487 kb/s, 720x404, 25fps, 16:9', index: 5},
                    '1250000': {video: 'H264 MPEG-4 AVC, 1255 kb/s, 640x360, 25fps, 16:9', index: 6},
                    '820000': {video: 'H264 MPEG-4 AVC, 809 kb/s, 480x270, 25fps, 16:9', index: 7},
                    '590000': {video: 'H264 MPEG-4 AVC, 581 kb/s, 398x224, 25fps, 199:112', index: 8}
                },
                ARTE: {
                    '2200': {video: 'H264 MPEG-4 AVC,  2438 kb/s, 1280x720, 25fps, 16:9', index: 1},
                    '1500': {video: 'H264 MPEG-4 AVC,  1619 kb/s, 720x406, 25fps, 360:203', index: 2},
                    '800': {video: 'H264 MPEG-4 AVC,  805 kb/s, 640x360, 25fps, 16:9', index: 3},
                    '300': {video: 'H264 MPEG-4 AVC,  357 kb/s, 384x216, 25fps, 16:9', index: 4}
                },
                NINATEKA: {
                    'video/mp4': {video: 'H264 MPEG-4 AVC,  900 kb/s, 640x360, 25fps, 16:9', index: 1},
                    'application/x-mpegURL': {description: "H264 MPEG-4 video stream with multiple resolutions", format: "HLS", index: 2},
                    'application/dash+xml': {description: "MPEG-DASH video stream with multiple resolutions", format: "MPD", index: 3},
                },
                CDA: {
                    '1080p': {video: 'H264 MPEG-4 AVC, 1920x1080, 16:9', index: 1},
                    '720p': {video: 'H264 MPEG-4 AVC, 1280x720, 16:9', index: 2},
                    '480p': {video: 'H264 MPEG-4 AVC, 854x480, 427:240', index: 3},
                    '360p': {video: 'H264 MPEG-4 AVC, 640x360, 16:9', index: 4},
                },
                TRWAM: {
                    '3': {video: 'H264 MPEG-4 AVC, 640x360, 16:9', index: 1},
                    '2': {description: "H264 MPEG-4 video stream with multiple resolutions", format: "HLS", index: 2},
                    '9': {description: "MPEG-DASH video stream with multiple resolutions", format: "MPD", index: 3},
                }
            }
        }
    };

    var Step = (function(properties){
        var step = {
            urlTemplateParts: [],
            urlTemplate: '',
            before: function(input){return input},
            after: function (output) {return output},
            resultUrlParams: function (input, template) {
                var urlParams = {};
                $.each(input, function (key, value) {
                    template = template.replace(new RegExp(config.urlParamPattern + key,'g'), value);
                    urlParams[key] = value;
                });

                return {
                    url: template,
                    urlParams: urlParams
                };
            },
            resolveUrl: function (input, partIndex) {
                return this.resultUrlParams(input, this.resolveUrlParts(partIndex));
            },
            isRemote: function(){
                return this.urlTemplate.length > 0;
            },
            method: 'GET',
            headers: {},
            responseType: 'json',
            retryErrorCodes: [],
            methodParam: function(){return {}},
            resolveUrlParts: function(partIndex){
                if(this.urlTemplateParts.length){
                    return this.urlTemplate.replace(config.urlPartPattern, this.urlTemplateParts[partIndex]);
                }

                return this.urlTemplate;
            }
        };

        return $.extend(true, step, properties);
    });

    var Notification = (function(Notification) {
        var create = function(title, bodyContent, special) {
            var specialContentClasses = special ? ' special-color white-text' : '';
            var content = $('<div>').addClass('toast notification' + specialContentClasses).attr('role', 'alert')
                .attr('aria-live', 'assertive').attr('aria-atomic', 'true')
                .attr('name', special ? 'special' : 'normal').attr('data-delay', '5000');
            var header = $('<div>').addClass('toast-header special-color-dark white-text');
            var warnIcon = $('<i>').addClass('fas fa-exclamation-triangle pr-2');
            var notificationTitle = $('<strong>').addClass('mr-auto').text(title);
            var time = $('<small>').text(new Date().toLocaleTimeString());
            var close = $('<button>').attr('type', 'button').addClass('ml-2 mb-1 close white-text')
                .attr('data-dismiss', 'toast').attr('aria-label', 'Close')
                .append($('<span>').attr('aria-hidden', 'true').text('\u00D7'));

            if(special){
                header.append(warnIcon);
                content.attr('data-autohide', 'false');
            }
            header.append(notificationTitle).append(time).append(close);
            var body = $('<div>').addClass('toast-body notification-body').append(bodyContent);

            content.append(header).append(body);
            return content;
        };

        Notification.show = function(options, w){
            options = options || {};
            var special = false;
            if (options.hasOwnProperty('special')) {
                special = options.special;
            }
            if(!options.hasOwnProperty('title') || !options.hasOwnProperty('content')){
                return;
            }

            var rootElement = $(w.document.body);
            var notification = create(options.title, options.content, special);
            $('#notification-container', rootElement).append(notification);
            $('.toast', rootElement).toast('show');
            $('.toast', rootElement).on('hidden.bs.toast', function (){
                $.each($(this), function(index, value) {
                    var element = $(value);
                    element.remove();
                });
            })
        };

        return Notification;
    }(Notification || {}));

    var PluginSettingsDetector = (function(PluginSettingsDetector){
        var prepareWarningNotification = function(w) {
            var bodyContent = $('<div>')
                .append('Twój dodatek ma nieprawidłowe ustawienia, przez co nie możesz korzystać z opcji ')
                .append('bezpośredniego pobierania plików. Możesz skorygować je w następujący sposób:');
            var list = $('<ol>').addClass('m-0')
                .append($('<li>').text('Otwórz Panel sterowania Tampermonkey i kliknij ustawienia.'))
                .append($('<li>').text('Ogólne > Tryb konfiguracji > Expert'))
                .append($('<li>').text('Pobieranie BETA > Tryb pobierania > API przeglądarki'))
                .append($('<li>').text('Zapisz ustawienia, a jeżeli przeglądarka zapyta o możliwość zarządzania' +
                    ' pobieranymi plikami, należy się zgodzić'));
            bodyContent.append(list).append(createButton(w));
            var options = {title: 'Wykryto problem', content: bodyContent, special: true};
            Notification.show(options, w);
        };

        var createButton = function(w){
            return $('<button>').attr('type', 'button').addClass('btn btn-dark btn-sm m-1 pl-3 pr-3')
                .append($('<i>').addClass('fas pr-1 fa-window-close')).append('Nie pokazuj więcej').click(function(){
                    var rootElement = $(w.document.body);
                    w.localStorage.setItem(config.storage.doNotWarn, true);
                    $('.toast.special-color', rootElement).toast('hide');
                    setTimeout(function(){
                        $('.toast.special-color', rootElement).remove();
                    }, 1000);
                });
        };

        var disableDownload = function(w){
            var rootElement = $(w.document.body);
            $('.fa-save', rootElement).closest('button').attr('disabled', true);
        };

        PluginSettingsDetector.detect = function(w){
            var downloadMode = GM_info.downloadMode;
            if(downloadMode !== 'browser'){
                disableDownload(w);
                var value = w.localStorage.getItem(config.storage.doNotWarn);
                if(value !== 'true'){
                    prepareWarningNotification(w);
                }
            }
        };
        return PluginSettingsDetector;
    }(PluginSettingsDetector || {}));

    var DomTamper = (function(DomTamper){

        DomTamper.injectStyle = function(w, name){
            var head = $(w.document.head);
            if(!head.find('style[name="' + name + '"]').length){
                var styleElement = $('<style>').attr('type', 'text/css')
                    .attr('name', name).text((GM_getResourceText(name)));
                head.append(styleElement);
            }
        };

        DomTamper.injectStylesheet = function (w, setting) {
            var head = $(w.document.head);
            if(!head.find('link[name="' + setting.id + '"]').length){
                var stylesheet = $('<link>').attr('name', setting.id).attr('type', 'text/css').attr('rel', 'stylesheet')
                    .attr('href', setting.css);
                head.append(stylesheet);
            }
        };

        var prepareHead = function(w){
            DomTamper.injectStylesheet(w, config.include.fontawesome);
            DomTamper.injectStylesheet(w, config.include.bootstrap);
            DomTamper.injectStylesheet(w, config.include.mdb);
            DomTamper.injectStyle(w, 'content_css');
        };

        var createLinks = function(w, additionalClass){
            var links = [
                {
                    url: 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RWX4EUR77CMKU',
                    icon: 'fa-hand-holding-usd',
                    tooltip: 'dotacje'
                },
                {
                    url: 'https://greasyfork.org/pl/scripts/6049-skrypt-umo%C5%BCliwiaj%C4%85cy-pobieranie-' +
                        'materia%C5%82%C3%B3w-ze-znanych-serwis%C3%B3w-vod/feedback',
                    icon: 'fa-comments',
                    tooltip: 'problemy, komentarze'
                },
                {
                    url: 'https://github.com/zacny/voddownloader/issues',
                    icon: 'fa-bug',
                    tooltip: 'zgłoś błąd'
                }
            ];
            var container = $('<div>').addClass('links-position');
            links.forEach(function(link){
                var button = $('<button>').attr('type', 'button').attr('title', link.tooltip)
                    .addClass('btn btn-sm m-1 p-2').addClass(additionalClass)
                    .append($('<i>').addClass('fas').addClass(link.icon).addClass('fa-2x'));
                button.click(function(){
                    w.open(link.url);
                });
                container.append(button);
            });
            return container;
        };

        var prepareBody = function(w, pageContent, detection) {
            appendOrReplace(w, pageContent);
            attachWaveEffect(w, pageContent);
            if(detection) {
                PluginSettingsDetector.detect(w);
            }
        };

        var appendOrReplace = function (w, pageContent) {
            var body = $(w.document.body);
            if(body.children().length > 0){
                body.children(":first").replaceWith(pageContent);
            }
            else {
                body.append(pageContent);
            }
        };

        var attachWaveEffect = function(w, pageContent){
            var buttons = pageContent.find('.btn:not(.btn-flat), .btn-floating');
            Waves.attach(buttons, ['waves-light']);
            Waves.init({}, w);
        };

        DomTamper.handleError = function(exception, w){
            if(w === undefined){
                w = window.open();
            }

            prepareHead(w);
            var errorData = getErrorData(exception);
            var pageContent = $('<div>').addClass('page-content');
            pageContent.append(createErrorContent(errorData));
            pageContent.append(createLinks(w, errorData.type === 'error' ?
                'btn-danger' : 'special-color white-text'));
            prepareBody(w, pageContent);
        };

        var getErrorData = function(exception){
            var type = 'error';
            var caption = 'Niespodziewany błąd';
            var message = 'Natrafiono na niespodziewany błąd: ' + exception;
            if(exception.error){
                message = exception.error.template.apply(this, exception.templateParams).replace(/\n/g, '<br/>');
                caption = exception.error.caption;
                type = exception.error.type !== undefined ? exception.error.type : 'error';
            }

            return {
                message: linkify(message),
                caption: caption,
                type: type
            }
        };

        var linkify = function(text) {
            var linkDetectionRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
            return text.replace(linkDetectionRegex, function(url) {
                return '<u><a class="text-white" href="' + url + '">' + url + '</a></u>';
            });
        };

        var createErrorContent = function(errorData){
            var typeClass = errorData.type === 'error' ? 'bg-danger' : 'bg-dark';
            var card = $('<div>').addClass('card text-white mb-3').addClass(typeClass);
            var cardHeader = $('<div>').addClass('card-header')
                .text('Niestety natrafiono na problem, który uniemożliwił dalsze działanie');
            var cardBody = $('<div>').addClass('card-body')
                .append($('<h5>').addClass('card-title').text(errorData.caption))
                .append($('<div>').addClass('card-text text-white mb-3').append(errorData.message))
                .append($('<div>').addClass('card-text text-white')
                    .append('Informacje o systemie: ').append(platform.description))
                .append($('<div>').addClass('card-text text-white')
                    .append('Wersja pluginu: ').append(GM_info.version))
                .append($('<div>').addClass('card-text text-white')
                    .append('Wersja skryptu: ').append(GM_info.script.version));
            card.append(cardHeader).append(cardBody);
            return card;
        };

        DomTamper.removeButton = function(properties){
            $(properties.injection.selector).find('#' + properties.injection.id).remove();
        };

        DomTamper.createButton = function(properties){
            DomTamper.removeButton(properties);
            var element = properties.inject();
            element.bind('click', properties.click);
            $(properties.injection.selector).append(element);
        };

        DomTamper.createLoader = function(w){
            prepareHead(w);
            var pageContent = $('<div>').addClass('page-content');
            pageContent.append(createLoaderContent());
            pageContent.append(createLinks(w, 'special-color white-text'));
            prepareBody(w, pageContent);
            Unloader.init(w);
        };

        var createLoaderContent = function(){
            var card = $('<div>').addClass('card text-white bg-dark');
            var cardHeader = $('<div>').addClass('card-header').text('Poczekaj trwa wczytywanie danych...');
            var cardBody = $('<div>').addClass('card-body');
            var bodyContainer = $('<div>').addClass('d-flex justify-content-center m-3');
            var spinner = $('<div>').addClass('spinner-border spinner-size').attr('role', 'status')
                .append($('<span>').addClass('sr-only').text('Loading...'));
            cardBody.append(bodyContainer.append(spinner));
            card.append(cardHeader).append(cardBody);

            return card;
        };

        var setWindowTitle = function(data, w){
            var head = $(w.document.head);
            var title = head.find('title');
            if(title.length) {
                title.text(data.title);
            }
            else {
                head.append($('<title>').text(data.title));
            }
        };

        DomTamper.createDocument = function(data, w){
            prepareHead(w);
            setWindowTitle(data, w);
            var pageContent = $('<div>').addClass('page-content');
            pageContent.append(Accordion.create(w, data));
            pageContent.append(createLinks(w, 'special-color white-text'));
            pageContent.append(createNotificationContainer());
            prepareBody(w, pageContent, true);
            Unloader.init(w);
            Accordion.bindActions(w, data);
        };

        var createNotificationContainer = function(){
            return $('<div>').attr('id', 'notification-container')
                .attr('aria-live', 'polite').attr('aria-atomic', 'true').addClass('notification-container');
        };

        return DomTamper;
    }(DomTamper || {}));

    var HistoryTamper = (function(HistoryTamper){
        HistoryTamper.onLocationChange = function(locationChangeCallback){
            history.pushState = ( f => function pushState(){
                var ret = f.apply(this, arguments);
                window.dispatchEvent(new Event('pushstate'));
                window.dispatchEvent(new Event('locationchange'));
                return ret;
            })(history.pushState);

            history.replaceState = ( f => function replaceState(){
                var ret = f.apply(this, arguments);
                window.dispatchEvent(new Event('replacestate'));
                window.dispatchEvent(new Event('locationchange'));
                return ret;
            })(history.replaceState);

            window.addEventListener('popstate',()=>{
                window.dispatchEvent(new Event('locationchange'))
            });

            window.addEventListener('locationchange', function(){
                locationChangeCallback();
            });
        };

        return HistoryTamper;
    }(HistoryTamper || {}));

    var Accordion = (function(Accordion) {
        Accordion.create = function(w, data){
            var mainCardTitle = $('<div>').addClass('card-header').text(data.title);

            var accordion = $('<div>').addClass('accordion md-accordion').attr('id', 'accordion')
                .attr('role', 'tablist').attr('aria-multiselectable', 'true');

            createCards(accordion, data);

            var mainCardBody = $('<div>').addClass('card-body p-0').append(accordion);
            return $('<div>').addClass('card').append(mainCardTitle).append(mainCardBody);
        };

        var createCards = function(accordion, data) {
            for(var key in data.cards) {
                var card = createCard({
                    card: data.cards[key],
                    key: key,
                    title: data.title
                });
                accordion.append(card);
            }
        };

        var createCard = function(data){
            var accordionCard = $('<div>').addClass('border border-top-0');
            var content = $('<div>').addClass('card-body pt-0');

            var badgeClass = 'badge-light';
            var textMuted = 'text-muted';
            if(data.card.items.length > 0){
                badgeClass = 'badge-danger';
                textMuted = 'text-dark';
                content.append(createCardContent(data));
            }

            var icon = $('<i>').addClass('fas').addClass(data.card.icon).addClass('pr-2');
            var badge = $('<span>').addClass('badge mr-3 float-right').addClass(badgeClass)
                .text(data.card.items.length);
            var cardTitle = $('<h6>').addClass('mb-0').addClass(textMuted).append(icon).append(badge)
                .append($('<span>').text(data.card.label));
            var link = $('<a>').append(cardTitle);
            var cardHeader = $('<div>').addClass('ml-3 p-2').attr('role', 'tab').attr('id', data.key).append(link);

            var cardBody = $('<div>').addClass('collapse').attr('role', 'tabpanel')
                .attr('aria-labelledby', data.key).append(content);
            if(data.card.collapse){
                cardBody.addClass('show');
            }

            accordionCard.append(cardHeader);
            accordionCard.append(cardBody);
            return accordionCard;
        };

        var createCardContent = function(data){
            var table = $('<table>').addClass('table table-bordered table-striped btn-table');
            var tbody = $('<tbody>');
            table.append(tbody);
            createRows(tbody, data);

            return table;
        };

        var createRows = function(tableBody, data){
            data.card.items.forEach(function(item) {
                tableBody.append(createRow({
                    item: item,
                    info: data.card.info,
                    title: data.title,
                    actions: data.card.actions
                }));
            });
        };

        var createRow = function(data){
            var actions = $('<td>').attr('scope', 'row').addClass('action-row-' + data.actions.length);
            data.actions.forEach(function(action){
                actions.append(createButton(action, data));
            });

            var description = $('<td>').html(createDescriptionHtml(data));
            return $('<tr>').append(actions).append(description);
        };

        var createDescriptionHtml = function(data){
            var descriptionHtml = $('<div>');

            createDescription(data).forEach(function(item, idx, array){
                descriptionHtml.append($('<b>').text(item.desc + ': '))
                    .append($('<span>').text(item.value));
                if(idx !== array.length - 1) {//not last
                    descriptionHtml.append($('<span>').text(', '));
                }
            });
            return descriptionHtml;
        };

        var itemExist = function(data, info){
            return data.item.hasOwnProperty(info.name) && data.item[info.name] != null
        };

        var createDescription = function(data){
            var description = [];
            data.info.forEach(function(info){
                if (itemExist(data, info)) {
                    description.push({
                        desc: info.desc,
                        value: data.item[info.name]
                    });
                }
            });
            return description;
        };

        var createButton = function(action, data){
            return $('<button>').attr('type', 'button').attr('data-url', data.item.url).attr('data-title', data.title)
                .addClass('btn btn-dark btn-sm m-1 pl-3 pr-3')
                .append($('<i>').addClass('fas pr-1').addClass(action.icon)).append(action.label);
        };

        Accordion.bindActions = function(w, data){
            cardActions(w, data);
            buttonActions(w);
        };

        var cardActions = function(w, data){
            for(var key in data.cards) {
                var cardHeader = $(w.document.body).find('#' + key);
                var disabled = cardHeader.find('h6.text-muted');
                if(disabled.length){
                    disabled.addClass('cursor-normal');
                }
                else {
                    $(w.document.body).find('#' + key).click(function() {
                        var id = $(this).attr('id');
                        $(w.document.body).find('div[aria-labelledby="' + id + '"]').toggle();
                    });
                }
            }
        };

        var buttonActions = function(w){
            getButton(w, '.fa-clone').click(function(){ copyActionClick($(this), w) });
            getButton(w, '.fa-film').click(function(){ openActionClick($(this), w) });
            getButton(w, '.fa-download').click(function(){ downloadActionClick($(this), w) });
        };

        var getButton = function(w, iconClass){
            return $(w.document.body).find(iconClass).parent();
        };

        var downloadActionClick = function (element, w) {
            var options = {title: 'Rozpoczęto pobieranie pliku', content: element.attr('data-title')};
            Tool.downloadFile(element.attr('data-url'), element.attr('data-title'));
            Notification.show(options, w);
        };

        var copyActionClick = function (element, w) {
            GM_setClipboard(element.attr('data-url'));
            var options = {title: 'Kopiowanie', content: 'Skopiowano do schowka'};
            Notification.show(options, w);
        };

        var openActionClick = function (element, w) {
            w.open(element.attr('data-url'));
        };

        return Accordion;
    }(Accordion || {}));

    var Executor = (function(Executor){
        var execute = function(service, options, w){
            var setup = setupStep(service, options);
            logStepInfo(options, setup);
            if(setup.isRemote()){
                 executeAsync(service, setup, options, w);
            }
            else {
                callback(service, options, w);
            }
        };

        var executeAsync = function(service, setup, options, w){
            var chain = options.chainNames[options.chainIndex];
            var chainStep = chain + '[' + options.stepIndex + ']';
            var exceptionParams = [chainStep, Tool.getRealUrl()];
            var requestParams = {
                method: setup.method,
                headers: setup.headers,
                url: setup.resolveUrl.url,
                data: JSON.stringify(setup.methodParam()),
                responseType: setup.responseType,
                onload: function(data) {
                    var currentStep = getCurrentStep(service, options);
                    if(retryPossible(currentStep, options, data.status)){
                        execute(service, options, w);
                    }
                    else {
                        if(setup.responseType === 'jsonp'){
                            var match = data.responseText.match(/callback\(([\s\S]*?)\);/);
                            if(match && match[1] && !match[1].startsWith('null')){
                                setStepResult(options, {async: JSON.parse(match[1])});
                            }
                        }
                        else {
                            setStepResult(options, {async: data.response || {}});
                        }
                        callback(service, options, w);
                    }
                },
                onerror: function(data){
                    DomTamper.handleError(new Exception(config.error.call, exceptionParams), w);
                },
                ontimeout: function(){
                    DomTamper.handleError(new Exception(config.error.timeout, exceptionParams), w);
                }
            };
            GM_xmlhttpRequest(requestParams);
        };

        var retryPossible = function(step, options, status){
            return step.retryErrorCodes.indexOf(status) >= 0 && step.urlTemplateParts[options.retries++];
        };

        var logStepInfo = function(options, setup){
            var chain = options.chainNames[options.chainIndex];
            var step = chain + '[' + options.stepIndex + ']';
            var stepParams = $.isEmptyObject(setup.methodParam()) ? '' : JSON.stringify(setup.methodParam());
            var params = [
                'color:green', options.retries+1, 'color:black', ':',
                'color:blue', step,  'color:red', setup.isRemote() ? setup.method : '---',
                'color:black;font-weight: bold', setup.isRemote() ? setup.resolveUrl.url : '---', 'color:magenta', stepParams
            ];
            Tool.formatConsoleMessage('%c%s%c%s%c%s%c %s %c %s %c%s', params);
        };

        var setupStep = function(service, options){
            var currentStep = getCurrentStep(service, options);
            beforeStep(currentStep, options);
            var setup = $.extend(true, {}, currentStep);
            if(currentStep.isRemote()) {
                setup.resolveUrl = currentStep.resolveUrl(getStepResult(options).before, options.retries);
            }
            return setup;
        };

        var getCurrentStep = function(service, options){
            var chain = options.chainNames[options.chainIndex];
            var steps = service.chains[chain];
            return steps[options.stepIndex];
        };

        var beforeStep = function(currentStep, options){
            var stepOutput = currentStep.before(getStepResult(options, true).after || {}, getStepResult(options, true));
            if(currentStep.isRemote()){
                if(typeof stepOutput === 'string' || typeof stepOutput == 'number'){
                    var result = stepOutput;
                    stepOutput = {};
                    stepOutput[config.urlParamDefaultKey] = result;
                }
            }
            setStepResult(options, {before: stepOutput});
        };

        var getStepResult = function(options, previous){
            var chain = options.chainNames[options.chainIndex];
            if(!options.results){
                options.results = {};
            }
            if(!options.results[chain]){
                options.results[chain] = [];
            }
            if(!options.results[chain][options.stepIndex]){
                options.results[chain].push({});
            }
            var stepIndex = previous && options.stepIndex > 0 ? options.stepIndex - 1 : options.stepIndex;
            return options.results[chain][stepIndex];
        };

        var setStepResult = function(options, object){
            var chain = options.chainNames[options.chainIndex];
            options.results[chain][options.stepIndex] = $.extend(true, getStepResult(options), object);
        };

        var hasNextStep = function(service, options){
            var chain = options.chainNames[options.chainIndex];
            var steps = service.chains[chain];
            return steps.length - 1 > options.stepIndex;
        };

        var hasNextChain = function(service, options){
            return options.chainNames.length - 1 > options.chainIndex;
        };

        var pushChain = function(service, options){
            if(hasNextChain(service, options)){
                options.chainIndex += 1;
                options.stepIndex = 0;
                return true;
            }
            return false;
        };

        var pushStep = function(service, options) {
            if(hasNextStep(service, options)){
                options.stepIndex += 1;
                return true;
            }
            return false;
        };

        var afterStep = function(service, options) {
            var currentStep = getCurrentStep(service, options);
            var previousResult = currentStep.isRemote() ? getStepResult(options).async : getStepResult(options).before;
            var output = currentStep.after(previousResult || {}, getStepResult(options));
            options.retries = 0;
            setStepResult(options, {after: output});
        };

        var callback = function(service, options, w){
            try {
                afterStep(service, options);
                if(pushStep(service, options) || pushChain(service, options)) {
                    return Promise.resolve().then(
                        Executor.chain(service, options, w)
                    );
                }
                else {
                    return Promise.resolve().then(
                        service.onDone(options.results, w)
                    );
                }
            }
            catch(e){
                DomTamper.handleError(e, w);
            }
        };

        Executor.chain = function(service, options, w){
            try {
                if(w === undefined){
                    w = window.open();
                    DomTamper.createLoader(w, service);
                }

                execute(service, options, w);
            }
            catch(e){
                DomTamper.handleError(e, w);
            }
        };

        return Executor;
    }(Executor || {}));

    function Configurator(properties){
        var service = {
            observer: {
                anchor: undefined,
                mode: 'added',
                selector: undefined
            },
            injection: {
                selector: properties.observer.selector,
                id: 'direct-download',
                class: '',
            },
            cardsData: {
                title: '',
                cards: {
                    videos: {
                        icon: 'fa-video', label: 'Video', collapse: true, items: [],
                        info: [
                            {name: 'video', desc: 'video'},
                            {name: 'audio', desc: 'audio'},
                            {name: 'language', desc: 'wersja językowa'}
                        ],
                        actions: [
                            {label: 'Pobierz', icon: 'fa-download'},
                            {label: 'Kopiuj', icon: 'fa-clone'},
                            {label: 'Otwórz', icon: 'fa-film'}
                        ]
                    },
                    subtitles: {
                        icon: 'fa-file-alt', label: 'Napisy', collapse: false, items: [],
                        info: [
                            {name: 'description', desc: 'opis'},
                            {name: 'format', desc: 'format'}
                        ],
                        actions: [
                            {label: 'Pobierz', icon: 'fa-download'}
                        ]
                    },
                    streams: {
                        icon: 'fa-stream', label: 'Strumienie', collapse: false, items: [],
                        info: [
                            {name: 'description', desc: 'opis'},
                            {name: 'format', desc: 'format'}
                        ],
                        actions: [
                            {label: 'Kopiuj', icon: 'fa-clone'}
                        ]
                    }
                }
            },
            chains: {
                videos: []
            },
            chainSelector: function(){
                return ['videos'];
            },
            formatter: function(data){
                data.cards['videos'].items.sort(function (a, b) {
                    return a.index - b.index;
                });
                data.cards['subtitles'].items.sort(function (a, b) {
                    return ('' + a.format).localeCompare(b.format);
                });
                data.cards['streams'].items.sort(function (a, b) {
                    return ('' + a.format).localeCompare(b.format);
                });
            },
            aggregate: function(data){
                var aggregatedData = $.extend(true, {}, service.cardsData);
                var chains = service.chainSelector();
                chains.forEach(function(chain){
                    var extend = data[chain][data[chain].length - 1].after;
                    $.extend(true, aggregatedData, extend);
                });
                return aggregatedData;
            },
            onDone: function(data, w) {
                var aggregatedData = service.aggregate(data);
                service.formatter(aggregatedData);
                DomTamper.createDocument(aggregatedData, w);
            },
            ready: function(){
                return $(service.observer.selector).length > 0;
            },
            click: function(){
                var chainNames = service.chainSelector();
                Executor.chain(service, {
                    stepIndex: 0,
                    chainIndex: 0,
                    retries: 0,
                    chainNames: chainNames
                });
            },
            inject: function(){
                var icon1 = $('<i>').addClass('fa fa-circle fa-stack-2x video_button_circle');
                var icon2 = $('<i>').addClass('fa fa-video fa-stack-1x fa-inverse');
                var span = $('<span>').addClass('fa-stack fa-1x').append(icon1).append(icon2);
                var div = $('<div>')
                    .attr('id', service.injection.id).attr('title', 'informacje o wideo')
                    .append(span).addClass('video_button')
                    .addClass(service.injection.class);
                $(service.observer.selector).hover(() => div.show(), () => div.hide());
                return div;
            },
        };

        return $.extend(true, service, properties);
    }

    var Detector = (function(conf) {
        var configuration = conf;

        var logObservation = function(){
            var observer = configuration.properties.observer;
            var color = configuration.properties.ready() ? 'color:green' : 'color:red';
            var anchor = observer.anchor ? observer.anchor + '->' : '';
            var params = [color, anchor + observer.selector, 'color:black'];
            Tool.formatConsoleMessage('[%c%s%c]', params);
        };

        this.observe = function(){
            var observer = configuration.properties.observer;
            if(configuration.properties.ready()){
                logObservation();
                configuration.successCallback();
            }
            else {
                $(observer.anchor).observe(observer.mode, observer.selector, function(record) {
                    logObservation();
                    configuration.successCallback();
                });
            }
        };
    });

    var ElementDetector = (function(ElementDetector){
        ElementDetector.detect = function(properties, callback){
            var detector = new Detector({
                properties: properties,
                successCallback: callback
            });
            detector.observe();
        };

        return ElementDetector;
    }(ElementDetector || {}));

    var Unloader = (function(Unloader) {
        var win;
        var url;

        Unloader.init = function(w){
            win = w;
            url = Tool.getRealUrl();
            $(window).bind('beforeunload', function(){
                if(!win.closed) {
                    DomTamper.handleError(new Exception(config.error.noParent, url), win);
                }
            });
        };

        return Unloader;
    }(Unloader || {}));

    var MessageReceiver = (function(MessageReceiver) {
        var win;
        var origin;
        var callbackFunction;
        var alreadyConfirmed = false;
        var alreadyPosted = false;

        var receiveMessage = function(event, callback){
            if (event.origin !== origin) {
                return;
            }

            var data = parseJSON(event.data);
            if($.isEmptyObject(data)){
                return;
            }
            if(data.confirmation){
                alreadyConfirmed = true;
            }
            else {
                data.confirmation = true;
                if(!alreadyPosted) {
                    window.removeEventListener('message', callbackFunction);
                    alreadyPosted = true;
                    postMessage(data);
                    callback(data);
                }
            }
        };

        var parseJSON = function(json) {
            if (typeof json == 'object')
                return {};
            try {
                return JSON.parse(json);
            }
            catch(e) {
                return {};
            }
        };

        var postMessage = function(data){
            data = JSON.stringify(data);
            win.postMessage(data, '*');
        };

        MessageReceiver.awaitMessage = function(object, callback){
            initCommunication(object, callback);
        };

        var initCommunication = function(object, callback){
            callbackFunction = function(e){
                receiveMessage(e, callback);
            };
            window.addEventListener('message', callbackFunction);
            win = getProperty(object, 'windowReference');
            origin = getProperty(object, 'origin');
        };

        var getProperty = function(object, prop){
            if(object.hasOwnProperty(prop)){
                return object[prop];
            }
        };

        MessageReceiver.postUntilConfirmed = function(object){
            initCommunication(object);
            isMessageConfirmed(config.attempts, getProperty(object, 'message'))
        };

        var isMessageConfirmed = function(attempt, message){
            if (alreadyConfirmed || attempt <= 0) {
                return Promise.resolve().then(function(){
                    window.removeEventListener('message', callbackFunction);
                    if(attempt <= 0){
                        console.warn("Nie udało się przekazać adresu z okna głównego.");
                    }
                });
            } else if(attempt > 0){
                attempt = attempt-1;
                postMessage(message);
                return Promise.resolve().then(
                    setTimeout(isMessageConfirmed, config.attemptTimeout, attempt, message)
                );
            }
        };

        return MessageReceiver;
    }(MessageReceiver || {}));

    var Common = (function(Common) {
        Common.grabIplaSubtitlesData = function(data){
            var items = [];
            var subtitles = (((data.result || {}).mediaItem || {}).displayInfo || {}).subtitles || [];
            subtitles.forEach(function(subtitle) {
                items.push({
                    url: subtitle.src,
                    description: subtitle.name,
                    format: subtitle.format
                })
            });
            return {
                cards: {subtitles: {items: items}}
            };
        };

        Common.run = function(properties){
            HistoryTamper.onLocationChange(function () {
                DomTamper.removeButton(properties);
            });
            ElementDetector.detect(properties, function () {
                DomTamper.createButton(properties);
            });
        };

        Common.createProperties = function(anchor, selector, mode) {
            return {
                observer: {
                    anchor: anchor,
                    mode: mode ? mode : 'added',
                    selector: selector,
                },
                ready: function() {
                    return $(this.observer.selector).length > 0;
                }
            };
        };

        return Common;
    }(Common || {}));

    var TVP = (function() {
        var dataAttributeParser = function() {
            var src = $(properties.observer.selector).attr('data-video-id');
            if(src !== undefined){
                return {
                    videoId: src.split("/").pop()
                };
            }

            return urlForwardParser();
        };

        var urlForwardParser = function() {
            var urlMatch = window.location.href.match(/^https?:\/\/.*\.tvp\..*\/(\d{6,})\/.*$/);
            if(urlMatch && urlMatch[1]){
                return urlMatch[1];
            }

            return urlParameterParser();
        };
        var urlParameterParser = function(){
            var ids = [
                Tool.getUrlParameter('ID', window.location.href),
                Tool.getUrlParameter('object_id', window.location.href)
            ];
            var id = ids.find(nonNull);
            if(id){
                return id;
            }

            throw new Exception(config.error.id, window.location.href);
        };

        var nonNull = function (id) {
            return id !== null;
        };

        var properties = new Configurator({
            observer: {
                selector: '#JS-TVPlayer2-Wrapper, .player-video-container, #tvplayer, #Player'
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplate: 'https://tvp.pl/pub/stat/videofileinfo?video_id=#videoId',
                        before: function (input) {
                            return dataAttributeParser();
                        },
                        after: function (input, result) {
                            return getRealVideoId(input, result.before.videoId);
                        }
                    }),
                    new Step({
                        urlTemplate: 'https://vod.tvp.pl/sess/TVPlayer2/api.php?id=#videoId&@method=getTvpConfig' +
                            '&@callback=callback',
                        responseType: 'jsonp',
                        after: function(input){
                            return grapVideoData(input);
                        }
                    })
                ]
            }
        });

        var getRealVideoId = function(json, videoId){
            var videoId = (json || {}).copy_of_object_id !== undefined ?
                json.copy_of_object_id : videoId;
            return {
                videoId: videoId
            };
        };

        var grapVideoData = function(data){
            var items = [];
            var subtitlesItems = [];
            var info = ((data || {}).content || {}).info || {};
            var files = ((data || {}).content || {}).files || [];
            var subtitles = ((data || {}).content || {}).subtitles || [];
            var files = removeUnsupportedVideoFormats(files);
            if(files.length) {
                files.forEach(function (file) {
                    var videoDesc = file.quality.bitrate;
                    items.push(Tool.mapDescription({
                        source: 'TVP',
                        key: videoDesc,
                        video: videoDesc,
                        url: file.url
                    }));
                });
                subtitles.forEach(function(subtitle) {
                    var extension = subtitle.type;
                    subtitlesItems.push({
                        url: 'https:' + subtitle.url,
                        format: extension,
                        description: subtitle.lang
                    })
                });

                return {
                    title: (info.title != null ? info.title : '') + (info.subtitle != null ? ' ' + info.subtitle : ''),
                    cards: {
                        videos: {items: items},
                        subtitles: {items: subtitlesItems}
                    }
                }
            }
            throw new Exception(config.error.noSource, window.location.href);
        };

        var removeUnsupportedVideoFormats = function(files){
            var result = [];
            files.forEach(function (file) {
                if (file['type'] === 'any_native') {
                    result.push(file);
                }
            });
            return result;
        };

        this.setup = function(){
            Common.run(properties);
        };
    });

    var TVN = (function() {
        var properties = new Configurator({
            observer: {
                anchor: 'body',
                selector: 'div.cover-buttons > a[href="#"], #player-container, div.custom-alert-inner-wrapper'
            },
            injection: {
                selector: 'div.right-side'
            },
            inject: function(){
                var icon = $('<i>').addClass('fas fa-video');
                var button = $('<div>')
                    .attr('id', properties.injection.id).attr('title', 'pobierz video')
                    .append(icon).addClass('btn btn-login');
                return button;
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplate: 'http://player.pl/api/?platform=ConnectedTV&terminal=Panasonic&format=json' +
                            '&authKey=064fda5ab26dc1dd936f5c6e84b7d3c2&v=3.1&m=getItem&id=#videoId',
                        before: function(input){
                            return idParser();
                        },
                        after: function(output) {
                            return grabVideoData(output);
                        }
                    })
                ]
            },
        });

        var idParser = function(){
            var watchingNow = $('.watching-now').closest('.embed-responsive').find('.embed-responsive-item');
            if(watchingNow.length > 0){
                return watchingNow.attr('href').split(',').pop();
            }

            return episodeIdParser();
        };

        var episodeIdParser = function () {
            var match = window.location.href.match(/odcinki,(\d+)\/.*,(\d+)/);
            if(match && match[2]){
                return match[2];
            }

            return serialIdParser();
        };

        var serialIdParser = function () {
            var match = window.location.href.match(/odcinki,(\d+)/);
            if(match && match[1]){
                throw new Exception(config.error.tvnId, Tool.getRealUrl());
            }

            return vodIdParser();
        };

        var vodIdParser = function(){
            var match = window.location.href.match(/,(\d+)/);
            if(match && match[1]){
                return match[1];
            }

            throw new Exception(config.error.tvnId, Tool.getRealUrl());
        };

        var grabVideoData = function(data){
            var items = [];
            var main = ((data.item || {}).videos || {}).main || {};
            var video_content = main.video_content || {};
            if(main.video_content_license_type !== 'WIDEVINE' && video_content && video_content.length > 0){
                $.each(video_content, function( index, value ) {
                    items.push(Tool.mapDescription({
                        source: 'TVN',
                        key: value.profile_name,
                        video: value.profile_name,
                        url: value.url
                    }));
                });

                return {
                    title: getTitle(data),
                    cards: {videos: {items: items}}
                }
            }
            throw new Exception(config.error.noSource, Tool.getRealUrl());
        };

        var getTitle = function(data){
            var episode = data.item.episode ? 'E'+Tool.pad(data.item.episode, 3) : '';
            var season = data.item.season != null ? 'S'+Tool.pad(data.item.season, 2) : '';
            var serie_title = data.item.serie_title != null ? data.item.serie_title : '';
            var episodeTitle = data.item.title ? ' ' + data.item.title : '';
            var seasonAndEpisode = season + episode;

            return serie_title + (seasonAndEpisode !== '' ? ' - ' + seasonAndEpisode : '') +
                (episodeTitle !== '' ? ' - ' + episodeTitle : '');
        };

        var inVodFrame = function(){
            var regexp = new RegExp('https:\/\/player\.pl(.*)');
            var match = regexp.exec(window.location.href);
            if(match[1]) {
                window.sessionStorage.setItem(config.storage.topWindowLocation, 'https://vod.pl' + match[1]);
            }
        };

        this.setup = function(){
            if(!Tool.isTopWindow() && $('#app').length) {
                inVodFrame();
            }

            Common.run(properties);
        };
    });

    var IPLA = (function() {
        var properties = new Configurator({
            observer: {
                anchor: 'app-root',
                selector: 'div.player-wrapper, div.promo-box:visible, div.player-error-presentation:visible'
            },
            chainSelector: function(){
                return ['videos', 'subtitles'];
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplateParts: [
                          'ua=www_iplatv_html5/12345',
                          'ua=mipla_ios/122'
                        ],
                        urlTemplate: 'https://getmedia.redefine.pl/vods/get_vod/?cpid=1&~&media_id=#videoId',
                        retryErrorCodes: [404],
                        before: function (input) {
                            return grabVideoIdFromUrl();
                        },
                        after: function(data){
                            return grabVideoData(data);
                        }
                    })
                ],
                subtitles: [
                    new Step({
                        urlTemplate: 'https://b2c.redefine.pl/rpc/navigation/',
                        method: 'POST',
                        methodParam: function(){
                            return getParamsForSubtitles();
                        },
                        after: Common.grabIplaSubtitlesData
                    })
                ]
            }
        });

        var grabVideoData = function(data){
            var items = [];
            var vod = data.vod || {};
            if(vod.copies && vod.copies.length > 0 && !vod.drm){
                $.each(vod.copies, function( index, value ) {
                    var videoDesc = value.quality_p + ', ' + value.bitrate;
                    items.push(Tool.mapDescription({
                        source: 'IPLA',
                        key: value.quality_p,
                        video: videoDesc,
                        url: value.url
                    }));
                });
                return {
                    title: vod.title,
                    cards: {videos: {items: items}}
                }
            }
            throw new Exception(config.error.noSource, Tool.getRealUrl());
        };

        var getParamsForSubtitles = function(){
            var mediaId = grabVideoIdFromUrl();
            return {
                jsonrpc: "2.0",
                id: 1,
                method: "prePlayData",
                params: {
                    userAgentData: {
                        application: "firefox",
                        portal: "ipla"
                    },
                    cpid: 1,
                    mediaId: mediaId
                }
            }
        };

        this.setup = function(){
            Common.run(properties);
        };

        var matchingId = function(input, failureAction){
            input = input ? input : '';
            var match = matchingHexId(input);
            if(!match){
                match = matchingDecId(input);
            }
            return match ? match : failureAction();
        };

        var matchingHexId = function(input){
            var match = input.match(/[0-9a-f]{32}/);
            if(match && match[0]) {
                return match[0];
            }

            return null;
        };

        var matchingDecId = function(input) {
            var match = input.match(/([\d]+)?(\?.*)$/);
            if(match && match[1]) {
                return match[1];
            }

            return null;
        };

        var grabVideoIdFromUrl = function(){
            return matchingId(location.href, grabVideoIdFromWatchingNowElement);
        };

        var grabVideoIdFromWatchingNowElement = function(){
            return matchingId($('div.vod-image-wrapper__overlay').closest('a').attr('href'), grabVideoIdFromHtmlElement);
        };

        var grabVideoIdFromHtmlElement = function(){
            var frameSrc = $('app-commercial-wallpaper iframe:first-child').attr('src');
            if(frameSrc !== undefined) {
                return Tool.getUrlParameter('vid', frameSrc);
            }
            throw new Exception(config.error.id, Tool.getRealUrl());
        };
    });

    var VOD = (function() {
        var properties = new Configurator({
            observer: {
                selector: '#v_videoPlayer'
            },
            injection: {
                class: 'right_margin'
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplate: 'https://player-api.dreamlab.pl/?body[id]=#videoId&body[jsonrpc]=2.0' +
                            '&body[method]=get_asset_detail&body[params][ID_Publikacji]=#videoId' +
                            '&body[params][Service]=vod.onet.pl&content-type=application/jsonp' +
                            '&x-onet-app=player.front.onetapi.pl&callback=',
                        before: function (input) {
                            return idParser();
                        },
                        after: function (output) {
                            return grabVideoData(output);
                        }
                    })
                ]
            }
        });

        var idParser = function () {
            var id = $(".mvp").attr('id');
            if(id !== undefined){
                return id.match(/mvp:(.+)/)[1];
            }

            return parseFromJS();
        };

        var parseFromJS = function(){
            var scripts = $('script[type="text/javascript"]').filter(':not([src])');
            for (var i = 0; i < scripts.length; i++) {
                var match = $(scripts[i]).text().match(/\"mvpId\"\s*:\s*\"(\d+\.\d+)\"/);
                if(match && match[1]){
                    return match[1];
                }
            }

            throw new Exception(config.error.id, Tool.getRealUrl());
        };

        var grabVideoData = function (data) {
            var items = [];
            var subtitlesItems = [];
            var video = (((data.result || new Array())[0] || {}).formats || {}).wideo || {};
            var meta = ((data.result || new Array())[0] || {}).meta || {};
            var subtitles = meta.subtitles || [];
            var videoData = video['mp4-uhd'] && video['mp4-uhd'].length > 0 ? video['mp4-uhd'] : video['mp4'];
            if(videoData && videoData.length > 0){
                videoData.forEach(function(value) {
                    var videoDesc = value.vertical_resolution + ', ' + value.video_bitrate;
                    items.push(Tool.mapDescription({
                        source: 'VOD',
                        key: value.vertical_resolution,
                        video: videoDesc,
                        url: value.url
                    }));
                });

                subtitles.forEach(function(subtitle) {
                    var extension = subtitle.name.split('.').pop();
                    subtitlesItems.push({
                        url: subtitle.url,
                        format: extension,
                        description: subtitle.name
                    })
                });

                return {
                    title: meta.title,
                    cards: {
                        videos: {items: items},
                        subtitles: {items: subtitlesItems}
                    }
                }
            }
            throw new Exception(config.error.noSource, Tool.getRealUrl());
        };

        var iplaDetected = function(){
            return $('#v_videoPlayer div.pulsembed_embed').length > 0;
        };

        var workWithSubService = function(){
            var src = 'https://pulsembed.eu';
            var frameSelector = 'iframe[src^="' + src + '"]';
            var properties = Common.createProperties('div.pulsembed_embed', frameSelector);

            ElementDetector.detect(properties, function () {
                MessageReceiver.postUntilConfirmed({
                    windowReference: $(frameSelector).get(0).contentWindow,
                    origin: src,
                    message: {
                        location: window.location.href
                    }
                });
            });
        };

        this.setup = function(){
            if(iplaDetected()) {
                workWithSubService();
            }
            else if(Tool.isTopWindow()){
                Common.run(properties);
            }
        };
    });

    var VOD_IPLA = (function() {
        var properties = new Configurator({
            observer: {
                anchor: 'body',
                selector: '#player-wrapper, #playerContainer'
            },
            injection: {
                class: 'left_margin'
            },
            chainSelector: function(){
                return ['videos', 'subtitles'];
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplate: 'https://distro.redefine.pl/partner_api/v1/2yRS5K/media/#media_id/vod/player_data?' +
                            'dev=pc&os=linux&player=html&app=firefox&build=12345',
                        before: function (input) {
                            return {media_id: idParser()};
                        },
                        after: function(data){
                            return grabVideoData(data);
                        }
                    })
                ],
                subtitles: [
                    new Step({
                        after: function (input) {
                            return Common.grabIplaSubtitlesData(getJson());
                        }
                    })
                ]
            }
        });

        var grabVideoData = function(data){
            var items = [];
            var displayInfo = (data.mediaItem || {}).displayInfo || {};
            var mediaSources = ((data.mediaItem || {}).playback || {}).mediaSources || {};
            var videos = $.grep(mediaSources, function(source) {
                return source.accessMethod === 'direct';
            });
            if(videos && videos.length > 0){
                $.each(videos, function( index, value ) {
                    items.push(Tool.mapDescription({
                        source: 'IPLA',
                        key: value.quality,
                        video: value.quality,
                        url: value.url
                    }));
                });
                return {
                    title: displayInfo.title,
                    cards: {videos: {items: items}}
                }
            }
            throw new Exception(config.error.noSource, Tool.getRealUrl());
        };

        var getJson = function(){
            var match = $('script:not(:empty)').text().match(/(window\.CP\.embedSetup\()(.*)\);/);
            if(match) {
                var jsonObject = JSON.parse(match[2]);
                return JSON.parse(jsonObject[0].media);
            }

            return {};
        };

        var idParser = function(){
            try {
                if($('#player-wrapper').length > 0) {
                    return (((getJson() || {}).result || {}).mediaItem || {}).id;
                }
                else if($('#playerContainer').length > 0){
                    return getMediaId();
                }
            }
            catch(e){
                throw new Exception(config.error.id, Tool.getRealUrl());
            }
        };

        var getMediaId = function(){
            var match = $('script:not(:empty)').text().match(/mediaId: "(\w+)",/);
            return match[1];
        };

        this.setup = function(){
            var callback = function(data) {
                window.sessionStorage.setItem(config.storage.topWindowLocation, data.location);
                Common.run(properties);
            };
            MessageReceiver.awaitMessage({
                origin: 'https://pulsembed.eu',
                windowReference: window.parent
            }, callback);
        };
    });

    var WP = (function() {
        var properties = new Configurator({
            observer: {
                anchor: 'body',
                selector: 'div.npp-container'
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplate: 'https://wideo.wp.pl/player/mid,#videoId,embed.json',
                        before: function (input) {
                            return idParser();
                        },
                        after: function (output) {
                            return grabVideoData(output);
                        }
                    })
                ]
            }
        });

        var idParser = function () {
            try {
                var id = window.location.href.match(/^(.*)-(\d+)v$/)[2];
                //__NEXT_DATA__ is a variable on page
                return __NEXT_DATA__.props.initialPWPState.material[id].mid;
            }
            catch(e){
                throw new Exception(config.error.id, window.location.href);
            }
        };

        var grabVideoData = function(data){
            var items = [];
            var urls = (data.clip || {}).url || {};
            if(urls && urls.length > 0){
                $.each(urls, function( index, value ) {
                    if(value.type === 'mp4@avc'){
                        var videoDesc = value.quality + ', ' + value.resolution;
                        items.push(Tool.mapDescription({
                            source: 'WP',
                            key: value.quality,
                            video: videoDesc,
                            url: value.url
                        }));
                    }
                });
                return {
                    title: data.clip.title,
                    cards: {videos: {items: items}}
                }
            }
            throw new Exception(config.error.noSource, window.location.href);
        };

        this.setup = function(){
            Common.run(properties);
        };
    });

    var CDA = (function() {
        var properties = new Configurator({
            observer: {
                selector: '.pb-player-content'
            },
            injection: {
                class: 'right_margin'
            },
            chains: {
                videos: [
                    new Step({
                        before: function(input){
                            return getDestinationUrl();
                        },
                        after: function (input) {
                            return grabVideoData(input);
                        }
                    })
                ]
            }
        });

        var getDestinationUrl = function(){
            var url = $("video.pb-video-player").attr('src');
            if(url !== undefined){
                if(!url.match(/blank\.mp4/)){
                    return url;
                }
                else if(l !== undefined){
                    return l;
                }
            }
            throw new Exception(config.error.id, window.location.href);
        };

        var grabVideoData = function(data){
            var items = [];
            var title = $('meta[property="og:title"]');
            var quality = $('.quality-btn-active');
            var videoDesc = quality.length > 0 ? quality.text() : '-';
            items.push(Tool.mapDescription({
                source: 'CDA',
                key: videoDesc,
                video: videoDesc,
                audio: '-',
                url: data
            }));
            return {
                title: title.length > 0 ? title.attr('content').trim() : 'brak danych',
                cards: {videos: {items: items}}
            };
        };

        this.setup = function(){
            Common.run(properties);
        };
    });

    var NINATEKA = (function() {
        var properties = new Configurator({
            observer: {
                selector: '#videoPlayer, #player'
            },
            chains: {
                videos: [
                    new Step({
                        before: function(input){
                            return getVideoUrls();
                        },
                        after: function (input) {
                            return grabVideoData(input);
                        }
                    })
                ]
            }
        });

        var grabVideoData = function(sources){
            var videoItems = [];
            var streamItems = [];
            var title = $('meta[name="title"]').attr('content').trim();
            if(sources && sources.length > 0){
                $.each(sources, function(i, v ) {
                    if(sources[i].type && sources[i].type.match(/mp4/g)){
                        videoItems.push(Tool.mapDescription({
                            source: 'NINATEKA',
                            key: v.type,
                            url: v.src
                        }));
                    }
                    else if(sources[i].type && (sources[i].type.match(/dash\+xml/g) || sources[i].type.match(/mpegURL/g))){
                        streamItems.push(Tool.mapDescription({
                            source: 'NINATEKA',
                            key: v.type,
                            url: v.src
                        }));
                    }
                });
                return {
                    title: title.length > 0 ? title : 'brak danych',
                    cards: {
                        videos: {items: videoItems},
                        streams: {items: streamItems}
                    }
                }
            }
            throw new Exception(config.error.noSource, window.location.href);
        };

        var getVideoUrls = function(){
            var videoPlayer = $('#videoPlayer').data('player-setup');
            var sources = (videoPlayer || {}).sources || [];
            if(sources.length == 0){
                var scripts = $('script[type="text/javascript"]').filter(':not([src])');
                for (var i = 0; i < scripts.length; i++) {
                    var match = $(scripts[i]).text().match(/fn_\S+\(playerOptionsWithMainSource,\s*\d+\)\.sources/g);
                    if(match && match[0]){
                        sources = eval(match[0]);
                        break;
                    }
                }
            }
            return sources;
        };

        this.setup = function(){
            Common.run(properties);
        };
    });

    var ARTE = (function() {
        var properties = new Configurator({
            observer: {
                anchor: 'div.video-thumbnail',
                selector: 'div.avp-player'
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplate: 'https://api.arte.tv/api/player/v1/config/#langCode/#videoId',
                        before: function (input) {
                            return idParser();
                        },
                        after: function (output) {
                            return grabVideoData(output);
                        }
                    })
                ]
            },
            formatter: function(data) {
                data.cards['videos'].items.sort(function (a, b) {
                    return a.index - b.index;
                });

                var sortingOrder = {'POL': 1};
                data.cards['videos'].items.sort(function (a, b) {
                    var aLangOrder = sortingOrder[a.langCode] ? sortingOrder[a.langCode] : -1,
                        bLangOrder = sortingOrder[b.langCode] ? sortingOrder[b.langCode] : -1;
                    return bLangOrder - aLangOrder;

                });
            }
        });

        var detectLanguage = function() {
            var regexp = new RegExp('https:\/\/www.arte\.tv\/(\\w{2})\/');
            var match = regexp.exec(window.location.href);
            return match[1];
        };

        var detectVideoId = function(){
            var regexp = new RegExp('https:\/\/www.arte\.tv\/\\w{2}\/videos\/([\\w-]+)\/');
            var match = regexp.exec(window.location.href);
            return match[1];
        };

        var idParser = function() {
            try {
                return {
                    videoId: detectVideoId(),
                    langCode: detectLanguage()
                };
            }
            catch(e){
                throw new Exception(config.error.id, window.location.href);
            }
        };

        var grabVideoData = function(data){
            var items = [];
            var title = (((data || {}).videoJsonPlayer || {}).eStat || {}).streamName || '';
            var streams = ((data || {}).videoJsonPlayer || {}).VSR || {};
            if(streams){
                Object.keys(streams).filter(function(k, i) {
                    return k.startsWith("HTTPS");
                }).forEach(function(k) {
                    var stream = streams[k];
                    var videoDesc = stream.width + 'x' + stream.height + ', ' + stream.bitrate;
                    items.push(Tool.mapDescription({
                        source: 'ARTE',
                        key: stream.bitrate,
                        video: videoDesc,
                        langCode: stream.versionShortLibelle,
                        language: stream.versionLibelle,
                        url: stream.url
                    }));
                });
                return {
                    title: title,
                    cards: {videos: {items: items}}
                }
            }
            throw new Exception(config.error.noSource, window.location.href);
        };

        this.setup = function(){
            Common.run(properties);
        };

    });

    var TV_TRWAM = (function() {
        var properties = new Configurator({
            observer: {
                anchor: '#ipott',
                mode: 'removed',
                selector: 'div[data-name="playerWindowPlace"]'
            },
            injection: {
                class: 'left_margin'
            },
            chains: {
                videos: [
                    new Step({
                        urlTemplate: 'https://api-trwam.app.insysgo.pl/v1/Tile/GetTiles',
                        headers: {'Content-Type': 'application/json'},
                        method: 'POST',
                        methodParam: function(){
                            return getParamsForVideo();
                        },
                        after: function(json) {
                            return getCodename(json);
                        }
                    }),
                    new Step({
                        urlTemplate: 'https://api-trwam.app.insysgo.pl/v1/Player/AcquireContent?platformCodename=www&' +
                            'codename=#codename',
                        after: function(output) {
                            return grabData(output);
                        }
                    })
                ]
            }
        });

        var grabVideoIdFromUrl = function(input){
            var match = input.match(/\/(vod\.[\d]+)$/);
            if(match && match[1]) {
                return match[1];
            }

            throw new Exception(config.error.id, Tool.getRealUrl());
        };

        var getParamsForVideo = function(){
            var mediaId = grabVideoIdFromUrl(window.location.href);
            return {
                platformCodename: "www",
                tilesIds:[mediaId]
            }
        };

        var getCodename = function(json){
            var tile = (json.Tiles || [])[0] || {};
            return {
                title: tile.Title || {},
                codename: tile.Codename || {}
            };
        };

        var grabData = function(data){
            var streams = (((data || {}).MediaFiles || [])[0] || {}).Formats || [];
            var videoItems = grabVideoData(streams);
            var streamItems = grabStreamData(streams);
            if(videoItems.length > 0 || streamItems.length > 0){
                return {
                    cards: {
                        videos: {items: videoItems},
                        streams: {items: streamItems}
                    }
                }
            }
            throw new Exception(config.error.noSource, window.location.href);
        };

        var grabVideoData = function(streams){
            var items = [];
            $.each(streams, function( index, value ) {
                if(value.Type === 3){
                    items.push(Tool.mapDescription({
                        source: 'TRWAM',
                        key: value.Type,
                        url: value.Url
                    }));
                }
            });
            return items;
        };

        var grabStreamData = function(streams){
            var items = [];
            var types = [2, 9];
            $.each(streams, function( index, value ) {
                if($.inArray(value.Type, types) > -1){
                    items.push(Tool.mapDescription({
                        source: 'TRWAM',
                        key: value.Type,
                        url: value.Url
                    }));
                }
            });
            return items;
        }

        this.setup = function(){
            Common.run(properties);
        };

    });

    var VOD_FRAME = (function() {
        this.setup = function(){
            var callback = function(data) {
                var srcArray = ['https://redir.atmcdn.pl', 'https://partner.ipla.tv'];
                setupDetector(srcArray, data);
            };
            MessageReceiver.awaitMessage({
                origin: 'https://vod.pl',
                windowReference: window.parent
            }, callback);
        };

        var setupDetector = function(srcArray, data){
            var selectors = createArrySelectors(srcArray);
            var multiSelector = createMultiSelector(selectors);
            var properties = Common.createProperties('div.iplaContainer', multiSelector);

            ElementDetector.detect(properties, function() {
                selectors.forEach(function(element){
                    if($(element.frameSelector).length > 0){
                        MessageReceiver.postUntilConfirmed({
                            windowReference: $(element.frameSelector).get(0).contentWindow,
                            origin: element.src,
                            message: {
                                location: data.location
                            }
                        });
                    }
                });
            });
        };

        var createArrySelectors = function(srcArray){
            return jQuery.map(srcArray, function(src) {
                return {
                    src: src,
                    frameSelector: 'iframe[src^="' + src + '"]'
                }
            });
        };

        var createMultiSelector = function(selectors){
            return $.map(selectors, function(src){
                return src.frameSelector
            }).join(', ');
        }
    });

    var Starter = (function(Starter) {
        var sources = [
            {objectName: 'TVP', urlPattern: new RegExp(
                    '^https:\/\/(vod|cyfrowa)\.tvp\.pl\/video\/.*$|' +
                    '^https?:\/\/.*\.tvp\.(pl|info)\/sess\/TVPlayer2\/embed.*$|' +
                    '^https?:\/\/((?!wiadomosci).)*\.tvp\.pl\/\\d{6,}\/.*$|' +
                    '^https?:\/\/w{3}\.tvpparlament\.pl\/sess\/.*'
                )
            },
            {objectName: 'TVN', urlPattern: new RegExp('^https:\/\/(?:w{3}\.)?(?:tvn)?player\.pl\/')},
            {objectName: 'CDA', urlPattern: new RegExp('^https:\/\/.*\.cda\.pl\/')},
            {objectName: 'VOD', urlPattern: new RegExp('^https:\/\/vod.pl\/')},
            {objectName: 'VOD_IPLA', urlPattern: new RegExp(
                    '^https:\/\/partner\.ipla\.tv\/embed\/|' +
                    '^https:\/\/.*\.redcdn\.pl\/file\/o2\/redefine\/partner\/'
                )
            },
            {objectName: 'IPLA', urlPattern: new RegExp('^https:\/\/w{3}\.ipla\.tv\/')},
            {objectName: 'WP', urlPattern: new RegExp('^https:\/\/wideo\.wp\.pl\/')},
            {objectName: 'NINATEKA', urlPattern: new RegExp('^https:\/\/ninateka.pl\/')},
            {objectName: 'ARTE', urlPattern: new RegExp('^https:\/\/w{3}\.arte\.tv\/.*\/videos\/')},
            {objectName: 'VOD_FRAME', urlPattern: new RegExp('^https:\/\/pulsembed\.eu\/')},
            {objectName: 'TV_TRWAM', urlPattern: new RegExp('^https:\/\/tv-trwam\.pl\/local-vods\/')}
        ];

        Starter.start = function() {
            sources.some(function(source){
                if(source.urlPattern.exec(location.href)){
                    console.info('voddownloader: context: ' + source.objectName + ', url: ' + location.href);
                    var object = eval('new ' + source.objectName + '()');
                    object.setup();
                    return true;
                }
            });
        };

        return Starter;
    }(Starter || {}));

    $(document).ready(function(){
        DomTamper.injectStylesheet(window, config.include.fontawesome);
        DomTamper.injectStyle(window, 'buttons_css');
        Starter.start();
    });

}).bind(this)(jQuery, platform, Waves);
// ==/UserScript==
jb222 commented 3 years ago

Better ask the developer.

derjanb commented 3 years ago

Better ask the developer.

Correct. If there is anything wrong with Tampermonkey, then he'll come back to me.