· 6 years ago · Oct 09, 2019, 02:00 PM
1// ==UserScript==
2// @name Skrypt umożliwiający pobieranie materiałów ze znanych serwisów VOD.
3// @version 6.4.0
4// @description Skrypt służący do pobierania materiałów ze znanych serwisów VOD.
5// Działa poprawnie tylko z rozszerzeniem Tampermonkey.
6// Cześć kodu pochodzi z:
7// miniskrypt.blogspot.com,
8// miniskrypt.hubaiitv.pl
9// @author Przmus, zacny
10// @namespace http://www.ipla.tv/
11// @source https://github.com/zacny/voddownloader
12// @include https://vod.tvp.pl/video/*
13// @include /^https://(bialystok|katowice|lodz|rzeszow|bydgoszcz|kielce|olsztyn|szczecin|gdansk|krakow|opole|warszawa|gorzow|lublin|poznan|wroclaw).tvp.pl/\d{6,}/
14// @include https://cyfrowa.tvp.pl/video/*
15// @include https://www.ipla.tv/*
16// @include https://player.pl/*
17// @include https://*.cda.pl/*
18// @include https://vod.pl/*
19// @include https://redir.atmcdn.pl/*
20// @include https://*.redcdn.pl/file/o2/redefine/partner/*
21// @include https://partner.ipla.tv/embed/*
22// @include https://video.wp.pl/*
23// @include https://ninateka.pl/*
24// @include https://www.arte.tv/*/videos/*
25// @include https://pulsembed.eu/*
26// @exclude http://www.tvp.pl/sess/*
27// @exclude https://www.cda.pl/iframe/*
28// @grant GM_getResourceText
29// @grant GM_xmlhttpRequest
30// @grant GM_download
31// @grant GM_setClipboard
32// @grant GM_info
33// @connect tvp.pl
34// @connect getmedia.redefine.pl
35// @connect distro.redefine.pl
36// @connect player-api.dreamlab.pl
37// @connect api.arte.tv
38// @connect b2c.redefine.pl
39// @run-at document-end
40// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
41// @require https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js
42// @require https://cdnjs.cloudflare.com/ajax/libs/platform/1.3.5/platform.min.js
43// @require https://gitcdn.xyz/cdn/zacny/voddownloader/4b17a120f521eaddf476d6e8fe3be152d506f244/lib/js/mdb-with-waves-patch.js
44// @resource buttons_css https://raw.githubusercontent.com/zacny/voddownloader/master/lib/css/voddownloader-buttons.css
45// @resource content_css https://raw.githubusercontent.com/zacny/voddownloader/master/lib/css/voddownloader-content.css
46// ==/UserScript==
47
48(function vodDownloader($, platform, Waves) {
49 'use strict';
50
51 var Exception = (function(error, templateParams) {
52 this.error = error;
53 this.templateParams = Array.isArray(templateParams) ? templateParams : [templateParams];
54 });
55
56 var Format = (function(data) {
57 this.bitrate = null;
58 this.format = null;
59 $.extend(true, this, data);
60 });
61
62 var Tool = (function(Tool) {
63 Tool.deleteParametersFromUrl = function(url){
64 return decodeURIComponent(url.replace(/\?.*/,''));
65 };
66
67 Tool.getUrlParameter = function(paramName, url){
68 var results = new RegExp('[\?&]' + paramName + '=([^&#]*)').exec(url);
69 if (results==null) {
70 return null;
71 }
72 return decodeURIComponent(results[1]) || 0;
73 };
74
75 Tool.formatConsoleMessage = function(message, params){
76 console.log.apply(this, $.merge([message], params));
77 };
78
79 Tool.downloadFile = function(fileUrl, title){
80 var extension = Tool.deleteParametersFromUrl(fileUrl.split('.').pop());
81 var movieTitle = (title !== undefined && title !== '' ) ? title : 'nieznany';
82 var name = movieTitle + '.' + extension;
83 GM_download(fileUrl, name);
84 };
85
86 Tool.template = function(templates, ...keys){
87 return (function(...values) {
88 var dict = values[values.length - 1] || {};
89 var result = [templates[0]];
90 keys.forEach(function(key, i) {
91 var value = Number.isInteger(key) ? values[key] : dict[key];
92 result.push(value, templates[i + 1]);
93 });
94 return result.join('');
95 });
96 };
97
98 Tool.getRealUrl = function(){
99 var topUrl = window.sessionStorage.getItem(config.storage.topWindowLocation);
100 return topUrl !== null ? topUrl : window.location.href;
101 };
102
103 Tool.isTopWindow = function(){
104 return window.top === window.self;
105 };
106
107 return Tool;
108 }(Tool || {}));
109
110 const config = {
111 attempts: 10,
112 attemptTimeout: 1500,
113 storage: {
114 doNotWarn: 'voddownloader.doNotwarnIfIncorrectPluginSettingsDetected',
115 topWindowLocation: 'voddownloader.topWindowLocation'
116 },
117 include: {
118 fontawesome: {
119 id: 'fontawesome',
120 css: 'https://use.fontawesome.com/releases/v5.8.2/css/all.css'
121 },
122 bootstrap: {
123 id: 'bootstrap',
124 css: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css'
125 },
126 mdb: {
127 id: 'mdb',
128 css: 'https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.8.2/css/mdb.min.css',
129 }
130 },
131 error: {
132 id: {
133 caption: 'Nie udało się odnaleźć idetyfikatora.',
134 template: Tool.template`Algorytm rozpoznawania identyfikatora wideo na stronie: ${0} \
135 zakończył się niepowodzeniem. Może to oznaczać błąd skryptu.`,
136 },
137 tvnId: {
138 caption: 'Nie udało się odnaleźć idetyfikatora.',
139 template: Tool.template`Algorytm rozpoznawania identyfikatora wideo na stronie: ${0} \
140 zakończył się niepowodzeniem.\nJeżeli jest to główna strona programu oznacza to, \
141 że nie udało się odnaleźć identyfikatora ostatniego odcinka. Wejdź na stronę odcinka \
142 i spróbuj ponownie.\nMoże to również oznaczać błąd skryptu.`,
143 },
144 call: {
145 caption: 'Błąd pobierania informacji o materiale.',
146 template: Tool.template`Wystąpił błąd w wykonaniu skryptu w kroku: ${0} na stronie: ${1} \
147 Zgłoś problem autorom skryptu.`,
148 },
149 noSource: {
150 caption: 'Nie udało się odnaleźć źródeł do materiału.',
151 template: Tool.template`Materiał ze strony ${0} nie posiada zdefiniowanych źródeł, które mogłyby zostać \
152 wyświetlone. \nMoże to oznaczać, że nie jest on publicznie dostępny, dostępne źródła nie mogą zostać \
153 wyświetlone w przeglądarce bez dodatkowego oprogramowania lub jest umieszczony w płatnej strefie.`,
154 type: 'info'
155 },
156 timeout: {
157 caption: 'Zbyt długi czas odpowiedzi.',
158 template: Tool.template`Dla kroku: ${0} na stronie "${1}" nie dotarły \
159 informacje zwrotne.\nPrzypuszczalnie jest to problem sieciowy. Spróbuj ponownie za jakiś czas.`
160 },
161 noParent: {
162 caption: 'Brak zakładki ze stroną główną.',
163 template: Tool.template`Została zamknięta zakładka ze stroną na której został uruchomiony skrypt. \
164 Ta zakładka nie może przez to działać poprawnie. Otwórz ponownie stronę główną: \n ${0} \n
165 by przywrócić prawidłowe funkcjonowanie skryptu.`
166 }
167 }
168 };
169
170
171 var Step = (function(properties){
172 var step = {
173 urlTemplate: '',
174 beforeStep: function(input){return input},
175 afterStep: function (output) {return output},
176 resolveUrl: function (input) {
177 var url = this.urlTemplate;
178 var urlParams = {};
179 $.each(input, function (key, value) {
180 url = url.replace(new RegExp('#'+key,'g'), value);
181 urlParams[key] = value;
182 });
183
184 return {
185 url: url,
186 urlParams: urlParams
187 };
188 },
189 isRemote: function(){
190 return this.urlTemplate.length > 0;
191 },
192 method: 'GET',
193 methodParam: function(){return {}}
194 };
195
196 return $.extend(true, step, properties);
197 });
198
199 var Notification = (function(Notification) {
200 var create = function(title, bodyContent, special) {
201 var specialContentClasses = special ? ' special-color white-text' : '';
202 var content = $('<div>').addClass('toast notification' + specialContentClasses).attr('role', 'alert')
203 .attr('aria-live', 'assertive').attr('aria-atomic', 'true')
204 .attr('name', special ? 'special' : 'normal').attr('data-delay', '5000');
205 var header = $('<div>').addClass('toast-header special-color-dark white-text');
206 var warnIcon = $('<i>').addClass('fas fa-exclamation-triangle pr-2');
207 var notificationTitle = $('<strong>').addClass('mr-auto').text(title);
208 var time = $('<small>').text(new Date().toLocaleTimeString());
209 var close = $('<button>').attr('type', 'button').addClass('ml-2 mb-1 close white-text')
210 .attr('data-dismiss', 'toast').attr('aria-label', 'Close')
211 .append($('<span>').attr('aria-hidden', 'true').text('\u00D7'));
212
213 if(special){
214 header.append(warnIcon);
215 content.attr('data-autohide', 'false');
216 }
217 header.append(notificationTitle).append(time).append(close);
218 var body = $('<div>').addClass('toast-body notification-body').append(bodyContent);
219
220 content.append(header).append(body);
221 return content;
222 };
223
224 Notification.show = function(options, w){
225 options = options || {};
226 var special = false;
227 if (options.hasOwnProperty('special')) {
228 special = options.special;
229 }
230 if(!options.hasOwnProperty('title') || !options.hasOwnProperty('content')){
231 return;
232 }
233
234 var rootElement = $(w.document.body);
235 var notification = create(options.title, options.content, special);
236 $('#notification-container', rootElement).append(notification);
237 $('.toast', rootElement).toast('show');
238 $('.toast', rootElement).on('hidden.bs.toast', function (){
239 $.each($(this), function(index, value) {
240 var element = $(value);
241 element.remove();
242 });
243 })
244 };
245
246 return Notification;
247 }(Notification || {}));
248
249 var PluginSettingsDetector = (function(PluginSettingsDetector){
250 var prepareWarningNotification = function(w) {
251 var bodyContent = $('<div>')
252 .append('Twój dodatek ma nieprawidłowe ustawienia, przez co nie możesz korzystać z opcji ')
253 .append('bezpośredniego pobierania plików. Możesz skorygować je w następujący sposób:');
254 var list = $('<ol>').addClass('m-0')
255 .append($('<li>').text('Otwórz Panel sterowania Tampermonkey i kliknij ustawienia.'))
256 .append($('<li>').text('Ogólne > Tryb konfiguracji > Expert'))
257 .append($('<li>').text('Pobieranie BETA > Tryb pobierania > API przeglądarki'))
258 .append($('<li>').text('Zapisz ustawienia, a jeżeli przeglądarka zapyta o możliwość zarządzania' +
259 ' pobieranymi plikami, należy się zgodzić'));
260 bodyContent.append(list).append(createButton(w));
261 var options = {title: 'Wykryto problem', content: bodyContent, special: true};
262 Notification.show(options, w);
263 };
264
265 var createButton = function(w){
266 return $('<button>').attr('type', 'button').addClass('btn btn-dark btn-sm m-1 pl-3 pr-3')
267 .append($('<i>').addClass('fas pr-1 fa-window-close')).append('Nie pokazuj więcej').click(function(){
268 var rootElement = $(w.document.body);
269 w.localStorage.setItem(config.storage.doNotWarn, true);
270 $('.toast.special-color', rootElement).toast('hide');
271 setTimeout(function(){
272 $('.toast.special-color', rootElement).remove();
273 }, 1000);
274 });
275 };
276
277 var disableDownload = function(w){
278 var rootElement = $(w.document.body);
279 $('.fa-save', rootElement).closest('button').attr('disabled', true);
280 };
281
282 PluginSettingsDetector.detect = function(w){
283 var downloadMode = GM_info.downloadMode;
284 if(downloadMode !== 'browser'){
285 disableDownload(w);
286 var value = w.localStorage.getItem(config.storage.doNotWarn);
287 if(value !== 'true'){
288 prepareWarningNotification(w);
289 }
290 }
291 };
292 return PluginSettingsDetector;
293 }(PluginSettingsDetector || {}));
294
295 var DomTamper = (function(DomTamper){
296
297 DomTamper.injectStyle = function(w, name){
298 var head = $(w.document.head);
299 if(!head.find('style[name="' + name + '"]').length){
300 var styleElement = $('<style>').attr('type', 'text/css')
301 .attr('name', name).text((GM_getResourceText(name)));
302 head.append(styleElement);
303 }
304 };
305
306 var injectStylesheet = function (w, setting) {
307 var head = $(w.document.head);
308 if(!head.find('link[name="' + setting.id + '"]').length){
309 var stylesheet = $('<link>').attr('name', setting.id).attr('type', 'text/css').attr('rel', 'stylesheet')
310 .attr('href', setting.css);
311 head.append(stylesheet);
312 }
313 };
314
315 var prepareHead = function(w){
316 injectStylesheet(w, config.include.fontawesome);
317 injectStylesheet(w, config.include.bootstrap);
318 injectStylesheet(w, config.include.mdb);
319 DomTamper.injectStyle(w, 'content_css');
320 };
321
322 var createLinks = function(w, additionalClass){
323 var links = [
324 {
325 url: 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=RWX4EUR77CMKU',
326 icon: 'fa-hand-holding-usd',
327 tooltip: 'dotacje'
328 },
329 {
330 url: 'https://greasyfork.org/pl/scripts/6049-skrypt-umo%C5%BCliwiaj%C4%85cy-pobieranie-' +
331 'materia%C5%82%C3%B3w-ze-znanych-serwis%C3%B3w-vod/feedback',
332 icon: 'fa-comments',
333 tooltip: 'problemy, komentarze'
334 },
335 {
336 url: 'https://github.com/zacny/voddownloader/issues',
337 icon: 'fa-bug',
338 tooltip: 'zgłoś błąd'
339 }
340 ];
341 var container = $('<div>').addClass('links-position');
342 links.forEach(function(link){
343 var button = $('<button>').attr('type', 'button').attr('title', link.tooltip)
344 .addClass('btn btn-sm m-1 p-2').addClass(additionalClass)
345 .append($('<i>').addClass('fas').addClass(link.icon).addClass('fa-2x'));
346 button.click(function(){
347 w.open(link.url);
348 });
349 container.append(button);
350 });
351 return container;
352 };
353
354 var prepareBody = function(w, pageContent, detection) {
355 appendOrReplace(w, pageContent);
356 attachWaveEffect(w, pageContent);
357 if(detection) {
358 PluginSettingsDetector.detect(w);
359 }
360 };
361
362 var appendOrReplace = function (w, pageContent) {
363 var body = $(w.document.body);
364 if(body.children().length > 0){
365 body.children(":first").replaceWith(pageContent);
366 }
367 else {
368 body.append(pageContent);
369 }
370 };
371
372 var attachWaveEffect = function(w, pageContent){
373 var buttons = pageContent.find('.btn:not(.btn-flat), .btn-floating');
374 Waves.attach(buttons, ['waves-light']);
375 Waves.init({}, w);
376 };
377
378 DomTamper.handleError = function(exception, w){
379 if(w === undefined){
380 w = window.open();
381 }
382
383 prepareHead(w);
384 var errorData = getErrorData(exception);
385 var pageContent = $('<div>').addClass('page-content');
386 pageContent.append(createErrorContent(errorData));
387 pageContent.append(createLinks(w, errorData.type === 'error' ?
388 'btn-danger' : 'special-color white-text'));
389 prepareBody(w, pageContent);
390 };
391
392 var getErrorData = function(exception){
393 var type = 'error';
394 var caption = 'Niespodziewany błąd';
395 var message = 'Natrafiono na niespodziewany błąd: ' + exception;
396 if(exception.error){
397 message = exception.error.template.apply(this, exception.templateParams).replace(/\n/g, '<br/>');
398 caption = exception.error.caption;
399 type = exception.error.type !== undefined ? exception.error.type : 'error';
400 }
401
402 return {
403 message: linkify(message),
404 caption: caption,
405 type: type
406 }
407 };
408
409 var linkify = function(text) {
410 var linkDetectionRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
411 return text.replace(linkDetectionRegex, function(url) {
412 return '<u><a class="text-white" href="' + url + '">' + url + '</a></u>';
413 });
414 };
415
416 var createErrorContent = function(errorData){
417 var typeClass = errorData.type === 'error' ? 'bg-danger' : 'bg-dark';
418 var card = $('<div>').addClass('card text-white mb-3').addClass(typeClass);
419 var cardHeader = $('<div>').addClass('card-header')
420 .text('Niestety natrafiono na problem, który uniemożliwił dalsze działanie');
421 var cardBody = $('<div>').addClass('card-body')
422 .append($('<h5>').addClass('card-title').text(errorData.caption))
423 .append($('<div>').addClass('card-text text-white mb-3').append(errorData.message))
424 .append($('<div>').addClass('card-text text-white')
425 .append('Informacje o systemie: ').append(platform.description))
426 .append($('<div>').addClass('card-text text-white')
427 .append('Wersja pluginu: ').append(GM_info.version));
428 card.append(cardHeader).append(cardBody);
429 return card;
430 };
431
432 DomTamper.createButton = function(properties){
433 properties.wrapper.get().find('#'+properties.button.id).remove();
434 var button = $('<input>').attr('id', properties.button.id).attr('type', 'button')
435 .attr('style', properties.button.style).attr('value', 'Pobierz video').addClass(properties.button.class);
436 button.bind('click', properties.button.click);
437 properties.wrapper.get().append(button);
438 };
439
440 DomTamper.createLoader = function(w){
441 prepareHead(w);
442 var pageContent = $('<div>').addClass('page-content');
443 pageContent.append(createLoaderContent());
444 pageContent.append(createLinks(w, 'special-color white-text'));
445 prepareBody(w, pageContent);
446 Unloader.init(w);
447 };
448
449 var createLoaderContent = function(){
450 var card = $('<div>').addClass('card text-white bg-dark');
451 var cardHeader = $('<div>').addClass('card-header').text('Poczekaj trwa wczytywanie danych...');
452 var cardBody = $('<div>').addClass('card-body');
453 var bodyContainer = $('<div>').addClass('d-flex justify-content-center m-3');
454 var spinner = $('<div>').addClass('spinner-border spinner-size').attr('role', 'status')
455 .append($('<span>').addClass('sr-only').text('Loading...'));
456 cardBody.append(bodyContainer.append(spinner));
457 card.append(cardHeader).append(cardBody);
458
459 return card;
460 };
461
462 var setWindowTitle = function(data, w){
463 var head = $(w.document.head);
464 var title = head.find('title');
465 if(title.length) {
466 title.text(data.title);
467 }
468 else {
469 head.append($('<title>').text(data.title));
470 }
471 };
472
473 DomTamper.createDocument = function(data, w){
474 prepareHead(w);
475 setWindowTitle(data, w);
476 var pageContent = $('<div>').addClass('page-content');
477 pageContent.append(Accordion.create(w, data));
478 pageContent.append(createLinks(w, 'special-color white-text'));
479 pageContent.append(createNotificationContainer());
480 prepareBody(w, pageContent, true);
481 Unloader.init(w);
482 Accordion.bindActions(w, data);
483 };
484
485 var createNotificationContainer = function(){
486 return $('<div>').attr('id', 'notification-container')
487 .attr('aria-live', 'polite').attr('aria-atomic', 'true').addClass('notification-container');
488 };
489
490 return DomTamper;
491 }(DomTamper || {}));
492
493 var Accordion = (function(Accordion) {
494 Accordion.create = function(w, data){
495 var mainCardTitle = $('<div>').addClass('card-header').text(data.title);
496
497 var accordion = $('<div>').addClass('accordion md-accordion').attr('id', 'accordion')
498 .attr('role', 'tablist').attr('aria-multiselectable', 'true');
499
500 createCards(accordion, data);
501
502 var mainCardBody = $('<div>').addClass('card-body p-0').append(accordion);
503 return $('<div>').addClass('card').append(mainCardTitle).append(mainCardBody);
504 };
505
506 var createCards = function(accordion, data) {
507 for(var key in data.cards) {
508 var card = createCard({
509 card: data.cards[key],
510 key: key,
511 title: data.title
512 });
513 accordion.append(card);
514 }
515 };
516
517 var createCard = function(data){
518 var accordionCard = $('<div>').addClass('border border-top-0');
519 var content = $('<div>').addClass('card-body pt-0');
520
521 var badgeClass = 'badge-light';
522 var textMuted = 'text-muted';
523 if(data.card.items.length > 0){
524 badgeClass = 'badge-danger';
525 textMuted = 'text-dark';
526 content.append(createCardContent(data));
527 }
528
529 var icon = $('<i>').addClass('fas').addClass(data.card.icon).addClass('pr-2');
530 var badge = $('<span>').addClass('badge mr-3 float-right').addClass(badgeClass)
531 .text(data.card.items.length);
532 var cardTitle = $('<h6>').addClass('mb-0').addClass(textMuted).append(icon).append(badge)
533 .append($('<span>').text(data.card.label));
534 var link = $('<a>').append(cardTitle);
535 var cardHeader = $('<div>').addClass('ml-3 p-2').attr('role', 'tab').attr('id', data.key).append(link);
536
537 var cardBody = $('<div>').addClass('collapse').attr('role', 'tabpanel')
538 .attr('aria-labelledby', data.key).append(content);
539 if(data.card.collapse){
540 cardBody.addClass('show');
541 }
542
543 accordionCard.append(cardHeader);
544 accordionCard.append(cardBody);
545 return accordionCard;
546 };
547
548 var createCardContent = function(data){
549 var table = $('<table>').addClass('table table-bordered table-striped btn-table');
550 var tbody = $('<tbody>');
551 table.append(tbody);
552 createRows(tbody, data);
553
554 return table;
555 };
556
557 var createRows = function(tableBody, data){
558 data.card.items.forEach(function(item) {
559 tableBody.append(createRow({
560 item: item,
561 info: data.card.info,
562 title: data.title,
563 actions: data.card.actions
564 }));
565 });
566 };
567
568 var createRow = function(data){
569 var actions = $('<td>').attr('scope', 'row').addClass('action-row-' + data.actions.length);
570 data.actions.forEach(function(action){
571 actions.append(createButton(action, data));
572 });
573
574 var description = $('<td>').html(createDescriptionHtml(data));
575 return $('<tr>').append(actions).append(description);
576 };
577
578 var createDescriptionHtml = function(data){
579 var descriptionHtml = $('<div>');
580
581 createDescription(data).forEach(function(item, idx, array){
582 descriptionHtml.append($('<b>').text(item.desc + ': '))
583 .append($('<span>').text(item.value));
584 if(idx !== array.length - 1) {//not last
585 descriptionHtml.append($('<span>').text(', '));
586 }
587 });
588 return descriptionHtml;
589 };
590
591 var itemExist = function(data, info){
592 return data.item.hasOwnProperty(info.name) && data.item[info.name] != null
593 };
594
595 var createDescription = function(data){
596 var description = [];
597 data.info.forEach(function(info){
598 if (itemExist(data, info)) {
599 description.push({
600 desc: info.desc,
601 value: data.item[info.name]
602 });
603 }
604 });
605 return description;
606 };
607
608 var createButton = function(action, data){
609 return $('<button>').attr('type', 'button').attr('data-url', data.item.url).attr('data-title', data.title)
610 .addClass('btn btn-dark btn-sm m-1 pl-3 pr-3')
611 .append($('<i>').addClass('fas pr-1').addClass(action.icon)).append(action.label);
612 };
613
614 Accordion.bindActions = function(w, data){
615 cardActions(w, data);
616 buttonActions(w);
617 };
618
619 var cardActions = function(w, data){
620 for(var key in data.cards) {
621 var cardHeader = $(w.document.body).find('#' + key);
622 var disabled = cardHeader.find('h6.text-muted');
623 if(disabled.length){
624 disabled.addClass('cursor-normal');
625 return;
626 }
627
628 $(w.document.body).find('#' + key).click(function() {
629 var id = $(this).attr('id');
630 $(w.document.body).find('div[aria-labelledby="' + id + '"]').toggle();
631 });
632 }
633 };
634
635 var buttonActions = function(w){
636 getButton(w, '.fa-clone').click(function(){ copyActionClick($(this), w) });
637 getButton(w, '.fa-film').click(function(){ openActionClick($(this), w) });
638 getButton(w, '.fa-download').click(function(){ downloadActionClick($(this), w) });
639 };
640
641 var getButton = function(w, iconClass){
642 return $(w.document.body).find(iconClass).parent();
643 };
644
645 var downloadActionClick = function (element, w) {
646 var options = {title: 'Rozpoczęto pobieranie pliku', content: element.attr('data-title')};
647 Tool.downloadFile(element.attr('data-url'), element.attr('data-title'));
648 Notification.show(options, w);
649 };
650
651 var copyActionClick = function (element, w) {
652 GM_setClipboard(element.attr('data-url'));
653 var options = {title: 'Kopiowanie', content: 'Skopiowano do schowka'};
654 Notification.show(options, w);
655 };
656
657 var openActionClick = function (element, w) {
658 w.open(element.attr('data-url'));
659 };
660
661 return Accordion;
662 }(Accordion || {}));
663
664 var Executor = (function(Executor){
665 var execute = function(service, options, w){
666 var setup = setupStep(service, options);
667 logStepInfo(options, setup);
668 if(setup.isRemote){
669 executeAsync(service, setup, options, w);
670 }
671 else {
672 options.temporaryData = {};
673 callback(service, options, w);
674 }
675 };
676
677 var executeAsync = function(service, setup, options, w){
678 var chain = options.chainNames[options.chainIndex];
679 var chainStep = chain + '[' + options.stepIndex + ']';
680 var exceptionParams = [chainStep, Tool.getRealUrl()];
681 var requestParams = {
682 method: setup.method,
683 url: setup.resolveUrl.url,
684 data: JSON.stringify(setup.methodParam),
685 responseType: 'json',
686 onload: function(data) {
687 options.temporaryData = data.response || {};
688 callback(service, options, w);
689 },
690 onerror: function(){
691 DomTamper.handleError(new Exception(config.error.call, exceptionParams), w);
692 },
693 ontimeout: function(){
694 DomTamper.handleError(new Exception(config.error.timeout, exceptionParams), w);
695 }
696 };
697 GM_xmlhttpRequest(requestParams);
698 };
699
700 var logStepInfo = function(options, setup){
701 var chain = options.chainNames[options.chainIndex];
702 var step = chain + '[' + options.stepIndex + ']';
703 var stepParams = $.isEmptyObject(setup.methodParam) ? '' : JSON.stringify(setup.methodParam);
704 var params = [
705 'color:blue', step, 'color:red', setup.isRemote ? setup.method : '---',
706 'color:black;font-weight: bold', setup.resolveUrl.url, 'color:magenta', stepParams
707 ];
708 Tool.formatConsoleMessage('%c%s%c %s %c %s %c%s', params);
709 };
710
711 var setupStep = function(service, options){
712 var currentStep = getCurrentStep(service, options);
713 var result = currentStep.beforeStep(options.temporaryData);
714 if(typeof result === 'string' || typeof result == 'number'){
715 result = {
716 videoId: result
717 }
718 }
719 if(options.urlParams){
720 $.extend(true, options.urlParams, result);
721 }
722 else {
723 options.urlParams = result;
724 }
725
726 return {
727 resolveUrl: currentStep.resolveUrl(options.urlParams),
728 method: currentStep.method,
729 methodParam: currentStep.methodParam(),
730 isRemote: currentStep.isRemote()
731 };
732 };
733
734 var getCurrentStep = function(service, options){
735 var chain = options.chainNames[options.chainIndex];
736 var steps = service.asyncChains[chain];
737 return steps[options.stepIndex];
738 };
739
740 var hasNextStep = function(service, options){
741 var chain = options.chainNames[options.chainIndex];
742 var steps = service.asyncChains[chain];
743 return steps.length - 1 > options.stepIndex;
744 };
745
746 var hasNextChain = function(service, options){
747 return options.chainNames.length - 1 > options.chainIndex;
748 };
749
750 var setChainResult = function(options){
751 var chain = options.chainNames[options.chainIndex];
752 if(!options.hasOwnProperty('results')){
753 options.results = {};
754 }
755 var chainResult = options.results;
756 chainResult[chain] = options.temporaryData;
757 options.temporaryData = {};
758 };
759
760 var pushChain = function(service, options){
761 setChainResult(options);
762 if(hasNextChain(service, options)){
763 options.chainIndex += 1;
764 options.stepIndex = 0;
765 return true;
766 }
767 return false;
768 };
769
770 var pushStep = function(service, options) {
771 if(hasNextStep(service, options)){
772 options.stepIndex += 1;
773 return true;
774 }
775 return false;
776 };
777
778 var afterStep = function(service, options) {
779 var currentStep = getCurrentStep(service, options);
780 var output = currentStep.afterStep(options.temporaryData);
781 options.temporaryData = output;
782 };
783
784 var callback = function(service, options, w){
785 try {
786 afterStep(service, options);
787 if(pushStep(service, options) || pushChain(service, options)) {
788 return Promise.resolve().then(
789 Executor.chain(service, options, w)
790 );
791 }
792 else {
793 return Promise.resolve().then(
794 service.onDone(options.results, w)
795 );
796 }
797 }
798 catch(e){
799 DomTamper.handleError(e, w);
800 }
801 };
802
803 Executor.chain = function(service, options, w){
804 try {
805 if(w === undefined){
806 w = window.open();
807 DomTamper.createLoader(w);
808 }
809
810 execute(service, options, w);
811 }
812 catch(e){
813 DomTamper.handleError(e, w);
814 }
815 };
816
817 return Executor;
818 }(Executor || {}));
819
820 function Configurator(properties){
821 var service = {
822 wrapper: {
823 selector: '',
824 get: function(){
825 return $(service.wrapper.selector);
826 },
827 exist: function(){
828 return $(service.wrapper.selector).length > 0;
829 }
830 },
831 button: {
832 id: 'direct-download',
833 style: '',
834 class: '',
835 click: function(){
836 var chainNames = service.chainSelector();
837 Executor.chain(service, {
838 stepIndex: 0,
839 chainIndex: 0,
840 chainNames: chainNames
841 });
842 }
843 },
844 cardsData: {
845 title: '',
846 cards: {
847 videos: {
848 icon: 'fa-video', label: 'Video', collapse: true, items: [],
849 info: [
850 {name: 'bitrate', desc: 'bitrate'},
851 {name: 'quality', desc: 'rozdzielczość'},
852 {name: 'langDesc', desc: 'wersja językowa'}
853 ],
854 actions: [
855 {label: 'Pobierz', icon: 'fa-download'},
856 {label: 'Kopiuj', icon: 'fa-clone'},
857 {label: 'Otwórz', icon: 'fa-film'}
858 ]
859 },
860 subtitles: {
861 icon: 'fa-file-alt', label: 'Napisy', collapse: false, items: [],
862 info: [
863 {name: 'description', desc: 'opis'},
864 {name: 'format', desc: 'format'},
865 ],
866 actions: [
867 {label: 'Pobierz', icon: 'fa-download'}
868 ]
869 }
870 }
871 },
872 asyncChains: {
873 videos: []
874 },
875 chainSelector: function(){
876 return ['videos'];
877 },
878 formatter: function(data){
879 data.cards['videos'].items.sort(function (a, b) {
880 return b.bitrate - a.bitrate;
881 });
882 data.cards['subtitles'].items.sort(function (a, b) {
883 return ('' + a.format).localeCompare(b.format);
884 });
885 },
886 aggregate: function(data){
887 var aggregatedData = {};
888 $.extend(true, aggregatedData, service.cardsData);
889 var chains = service.chainSelector();
890 chains.forEach(function(chain){
891 $.extend(true, aggregatedData, data[chain]);
892 });
893 return aggregatedData;
894 },
895 onDone: function(data, w) {
896 var aggregatedData = service.aggregate(data);
897 service.formatter(aggregatedData);
898 DomTamper.createDocument(aggregatedData, w);
899 }
900 };
901
902 return $.extend(true, service, properties);
903 }
904
905 var Detector = (function(conf) {
906 var configuration = conf;
907
908 var logMessage = function(attempt){
909 var color = configuration.logStyle || 'color:black;font-weight:bold';
910 var existColor = configuration.success() ? 'color:green' : 'color:red';
911 if(configuration.unlimited){
912 var params = [
913 existColor, configuration.target, 'color:black'
914 ];
915 Tool.formatConsoleMessage('[%c%s%c]', params);
916 }
917 else {
918 var params = [
919 'color:black', color, configuration.target, 'color:black',
920 existColor + ';font-weight:bold', attempt, 'color:black'
921 ];
922 Tool.formatConsoleMessage('%c[%c%s%c] [%c%s%c]', params);
923 }
924 };
925
926 var check = function(attempt){
927 logMessage(attempt);
928 if (configuration.success()) {
929 return Promise.resolve().then(
930 configuration.successCallback()
931 );
932 } else if(configuration.unlimited || attempt > 0){
933 attempt = attempt-1;
934 return Promise.resolve().then(
935 setTimeout(check, config.attemptTimeout, attempt)
936 );
937 }
938 };
939
940 this.detect = function() {
941 check(config.attempts);
942 };
943 });
944
945 var ChangeVideoDetector = (function(ChangeVideoDetector){
946 ChangeVideoDetector.run = function(videoChangeCallback) {
947 var detector = new Detector({
948 unlimited: true,
949 previousLocation: window.location.href,
950 target: 'video-change',
951 success: function(){
952 return this.previousLocation !== window.location.href
953 },
954 successCallback: videoChangeCallback
955 });
956 detector.detect();
957 };
958 return ChangeVideoDetector;
959 }(ChangeVideoDetector || {}));
960
961 var WrapperDetector = (function(WrapperDetector){
962 WrapperDetector.run = function(properties, videoChangeCallback) {
963 var detector = new Detector({
964 logStyle: 'color:orange',
965 target: properties.wrapper.selector,
966 success: properties.wrapper.exist,
967 successCallback: function(){
968 DomTamper.createButton(properties);
969 }
970 });
971 detector.detect();
972
973 if(typeof videoChangeCallback === "function"){
974 ChangeVideoDetector.run(videoChangeCallback);
975 }
976 };
977 return WrapperDetector;
978 }(WrapperDetector || {}));
979
980 var ElementDetector = (function(ElementDetector){
981 ElementDetector.detect = function(selector, callback){
982 var detector = new Detector({
983 logStyle: 'color:dodgerblue',
984 target: selector,
985 success: function(){
986 return $(this.target).length > 0;
987 },
988 successCallback: callback
989 });
990 detector.detect();
991 };
992
993 return ElementDetector;
994 }(ElementDetector || {}));
995
996 var Unloader = (function(Unloader) {
997 var win;
998 var url;
999
1000 Unloader.init = function(w){
1001 win = w;
1002 url = Tool.getRealUrl();
1003 $(window).bind('beforeunload', function(){
1004 if(!win.closed) {
1005 DomTamper.handleError(new Exception(config.error.noParent, url), win);
1006 }
1007 });
1008 };
1009
1010 return Unloader;
1011 }(Unloader || {}));
1012
1013 var MessageReceiver = (function(MessageReceiver) {
1014 var win;
1015 var origin;
1016 var callbackFunction;
1017 var alreadyConfirmed = false;
1018 var alreadyPosted = false;
1019
1020 var receiveMessage = function(event, callback){
1021 if (event.origin !== origin) {
1022 return;
1023 }
1024
1025 var data = JSON.parse(event.data);
1026 if(data.confirmation){
1027 alreadyConfirmed = true;
1028 }
1029 else {
1030 data.confirmation = true;
1031 if(!alreadyPosted) {
1032 window.removeEventListener('message', callbackFunction);
1033 alreadyPosted = true;
1034 postMessage(data);
1035 callback(data);
1036 }
1037 }
1038 };
1039
1040 var postMessage = function(data){
1041 data = JSON.stringify(data);
1042 win.postMessage(data, '*');
1043 };
1044
1045 MessageReceiver.awaitMessage = function(object, callback){
1046 initCommunication(object, callback);
1047 };
1048
1049 var initCommunication = function(object, callback){
1050 callbackFunction = function(e){
1051 receiveMessage(e, callback);
1052 };
1053 window.addEventListener('message', callbackFunction);
1054 win = getProperty(object, 'windowReference');
1055 origin = getProperty(object, 'origin');
1056 };
1057
1058 var getProperty = function(object, prop){
1059 if(object.hasOwnProperty(prop)){
1060 return object[prop];
1061 }
1062 };
1063
1064 MessageReceiver.postUntilConfirmed = function(object){
1065 initCommunication(object);
1066 isMessageConfirmed(config.attempts, getProperty(object, 'message'))
1067 };
1068
1069 var isMessageConfirmed = function(attempt, message){
1070 if (alreadyConfirmed || attempt <= 0) {
1071 return Promise.resolve().then(function(){
1072 window.removeEventListener('message', callbackFunction);
1073 if(attempt <= 0){
1074 console.warn("Nie udało się przekazać adresu z okna głównego.");
1075 }
1076 });
1077 } else if(attempt > 0){
1078 attempt = attempt-1;
1079 postMessage(message);
1080 return Promise.resolve().then(
1081 setTimeout(isMessageConfirmed, config.attemptTimeout, attempt, message)
1082 );
1083 }
1084 };
1085
1086 return MessageReceiver;
1087 }(MessageReceiver || {}));
1088
1089 var COMMON_SOURCE = (function(COMMON_SOURCE) {
1090 COMMON_SOURCE.grabIplaSubtitlesData = function(data){
1091 var items = [];
1092 var subtitles = (((data.result || {}).mediaItem || {}).displayInfo || {}).subtitles || [];
1093 subtitles.forEach(function(subtitle) {
1094 items.push(new Format({
1095 url: subtitle.src,
1096 description: subtitle.name,
1097 format: subtitle.format
1098 }))
1099 });
1100 return {
1101 cards: {subtitles: {items: items}}
1102 };
1103 };
1104
1105 COMMON_SOURCE.iplaFormatter = function(data){
1106 var videosRegexp = /^(\d+)p$/;
1107 data.cards['videos'].items.sort(function (a, b) {
1108 var qualityMatchA = a.quality.match(videosRegexp);
1109 var qualityMatchB = b.quality.match(videosRegexp);
1110 var qualityA = qualityMatchA && qualityMatchA[1] ? Number(qualityMatchA[1]) : 0;
1111 var qualityB = qualityMatchB && qualityMatchB[1] ? Number(qualityMatchB[1]) : 0;
1112 return qualityB - qualityA;
1113 });
1114 data.cards['subtitles'].items.sort(function (a, b) {
1115 return ('' + a.format).localeCompare(b.format);
1116 });
1117 };
1118
1119 COMMON_SOURCE.grabTvpVideoData = function(data){
1120 var items = [];
1121 if(data.status == 'OK' && data.formats !== undefined){
1122 $.each(data.formats, function( index, value ) {
1123 if(value.adaptive == false){
1124 items.push(new Format({
1125 bitrate: value.totalBitrate,
1126 url: value.url
1127 }));
1128 }
1129 });
1130 return {
1131 title: data.title,
1132 cards: {videos: {items: items}}
1133 }
1134 }
1135 throw new Exception(config.error.noSource, window.location.href);
1136 };
1137
1138 return COMMON_SOURCE;
1139 }(COMMON_SOURCE || {}));
1140 var VOD_TVP = (function() {
1141 var properties = new Configurator({
1142 wrapper: {
1143 selector: 'div.playerContainerWrapper'
1144 },
1145 button: {
1146 class: 'video-block__btn tvp_vod_downlaod_button',
1147 },
1148 asyncChains: {
1149 videos: [
1150 new Step({
1151 urlTemplate: 'https://tvp.pl/pub/stat/videofileinfo?video_id=#videoId',
1152 beforeStep: function (input) {
1153 return idParser();
1154 }
1155 }),
1156 new Step({
1157 urlTemplate: 'https://www.tvp.pl/shared/cdn/tokenizer_v2.php?object_id=#videoId',
1158 beforeStep: function (json) {
1159 return getRealVideoId(json);
1160 },
1161 afterStep: COMMON_SOURCE.grabTvpVideoData
1162 })
1163 ]
1164 }
1165 });
1166
1167 var idParser = function() {
1168 var src = $('div.playerContainer').attr('data-id');
1169 if(src !== undefined){
1170 return {
1171 videoId: src.split("/").pop()
1172 };
1173 }
1174
1175 throw new Exception(config.error.id, window.location.href);
1176 };
1177
1178 var getRealVideoId = function(json){
1179 var videoId = json.copy_of_object_id !== undefined ?
1180 json.copy_of_object_id : json.video_id;
1181 return {
1182 videoId: videoId
1183 };
1184 };
1185
1186 this.setup = function(){
1187 WrapperDetector.run(properties);
1188 };
1189 });
1190
1191 var CYF_TVP = (function() {
1192 var properties = new Configurator({
1193 wrapper: {
1194 selector: 'div.playerContainerWrapper'
1195 },
1196 button: {
1197 class: 'tvp_cyf_downlaod_button'
1198 },
1199 asyncChains: {
1200 videos: [
1201 new Step({
1202 urlTemplate: 'https://www.tvp.pl/shared/cdn/tokenizer_v2.php?object_id=#videoId',
1203 beforeStep: function (input) {
1204 return idParser();
1205 },
1206 afterStep: COMMON_SOURCE.grabTvpVideoData
1207 })
1208 ]
1209 }
1210 });
1211
1212 var idParser = function(){
1213 var src = $('iframe#JS-TVPlayer').attr('src');
1214 if(src !== undefined) {
1215 return src.split("/").pop();
1216 }
1217 else {
1218 var div = $('div.playerWidget');
1219 if(div !== undefined){
1220 return div.attr('data-video-id');
1221 }
1222 }
1223
1224 throw new Exception(config.error.id, window.location.href);
1225 };
1226
1227 this.setup = function(){
1228 WrapperDetector.run(properties);
1229 };
1230 });
1231
1232 var TVP_REG = (function() {
1233 var properties = new Configurator({
1234 wrapper: {
1235 selector: 'div.js-video'
1236 },
1237 button: {
1238 class: 'tvp_reg_download_button'
1239 },
1240 asyncChains: {
1241 videos: [
1242 new Step({
1243 urlTemplate: 'https://www.tvp.pl/shared/cdn/tokenizer_v2.php?object_id=#videoId',
1244 beforeStep: function (input) {
1245 return idParser();
1246 },
1247 afterStep: COMMON_SOURCE.grabTvpVideoData
1248 })
1249 ]
1250 }
1251 });
1252
1253 var idParser = function(){
1254 var dataId = $('div.js-video').attr('data-object-id');
1255 if(dataId != undefined) {
1256 return dataId;
1257 }
1258
1259 throw new Exception(config.error.id, window.location.href);
1260 };
1261
1262 this.setup = function(){
1263 WrapperDetector.run(properties);
1264 };
1265 });
1266
1267 var TVN = (function() {
1268 var properties = new Configurator({
1269 wrapper: {
1270 selector: '#player-container'
1271 },
1272 button: {
1273 class: 'btn btn-primary tvn_download_button'
1274 },
1275 asyncChains: {
1276 videos: [
1277 new Step({
1278 urlTemplate: '/api/?platform=ConnectedTV&terminal=Panasonic&format=json' +
1279 '&authKey=064fda5ab26dc1dd936f5c6e84b7d3c2&v=3.1&m=getItem&id=#videoId',
1280 beforeStep: function(input){
1281 return idParser();
1282 },
1283 afterStep: function(output) {
1284 return grabVideoData(output);
1285 }
1286 })
1287 ]
1288 },
1289 formatter: function(data){
1290 var sortingOrder = {
1291 'HD': 7,
1292 'Bardzo wysoka': 6,
1293 'Wysoka': 5,
1294 'Standard': 4,
1295 'Średnia': 3,
1296 'Niska': 2,
1297 'Bardzo niska': 1
1298 };
1299
1300 data.cards['videos'].items.sort(function (a, b) {
1301 return sortingOrder[b.quality] - sortingOrder[a.quality];
1302 });
1303 }
1304 });
1305
1306 var idParser = function(){
1307 var watchingNow = $('.watching-now').closest('.embed-responsive').find('.embed-responsive-item');
1308 if(watchingNow.length > 0){
1309 return watchingNow.attr('href').split(',').pop();
1310 }
1311
1312 return episodeIdParser();
1313 };
1314
1315 var episodeIdParser = function () {
1316 var match = window.location.href.match(/odcinki,(\d+)\/.*,(\d+)/);
1317 if(match && match[2]){
1318 return match[2];
1319 }
1320
1321 return serialIdParser();
1322 };
1323
1324 var serialIdParser = function () {
1325 var match = window.location.href.match(/odcinki,(\d+)/);
1326 if(match && match[1]){
1327 throw new Exception(config.error.tvnId, Tool.getRealUrl());
1328 }
1329
1330 return vodIdParser();
1331 };
1332
1333 var vodIdParser = function(){
1334 var match = window.location.href.match(/,(\d+)/);
1335 if(match && match[1]){
1336 return match[1];
1337 }
1338
1339 throw new Exception(config.error.tvnId, Tool.getRealUrl());
1340 };
1341
1342 var grabVideoData = function(data){
1343 var items = [];
1344 var main = ((data.item || {}).videos || {}).main || {};
1345 var video_content = main.video_content || {};
1346 if(main.video_content_license_type !== 'WIDEVINE' && video_content && video_content.length > 0){
1347 $.each(video_content, function( index, value ) {
1348 items.push(new Format({
1349 quality: value.profile_name,
1350 url: value.url
1351 }));
1352 });
1353
1354 return {
1355 title: getTitle(data),
1356 cards: {videos: {items: items}}
1357 }
1358 }
1359 throw new Exception(config.error.noSource, Tool.getRealUrl());
1360 };
1361
1362 var getTitle = function(data){
1363 var title = data.item.episode != null ? 'E'+data.item.episode : '';
1364 title = data.item.season != null ? 'S'+data.item.season + title : title;
1365 if(data.item.serie_title != null){
1366 title = data.item.serie_title + (title != '' ? ' - ' + title : '');
1367 }
1368 return title;
1369 };
1370
1371 var inVodFrame = function(){
1372 var regexp = new RegExp('https:\/\/player\.pl(.*)');
1373 var match = regexp.exec(window.location.href);
1374 if(match[1]) {
1375 window.sessionStorage.setItem(config.storage.topWindowLocation, 'https://vod.pl' + match[1]);
1376 }
1377 };
1378
1379 this.setup = function(){
1380 if(!Tool.isTopWindow()) {
1381 inVodFrame();
1382 }
1383
1384 WrapperDetector.run(properties, this.setup);
1385 };
1386 });
1387
1388 var IPLA = (function() {
1389 var properties = new Configurator({
1390 wrapper: {
1391 selector: 'div.player-wrapper:visible:first-child, div.promo-box:visible:first-child,' +
1392 ' div.player-error-presentation:visible:first-child'
1393 },
1394 button: {
1395 class: 'ipla_download_button'
1396 },
1397 chainSelector: function(){
1398 return ['videos', 'subtitles'];
1399 },
1400 asyncChains: {
1401 videos: [
1402 new Step({
1403 urlTemplate: 'https://getmedia.redefine.pl/vods/get_vod/?cpid=1' +
1404 '&ua=www_iplatv_html5/12345&media_id=#videoId',
1405 beforeStep: function (input) {
1406 return idParser();
1407 },
1408 afterStep: function(data){
1409 return grabVideoData(data);
1410 }
1411 })
1412 ],
1413 subtitles: [
1414 new Step({
1415 urlTemplate: 'https://b2c.redefine.pl/rpc/navigation/',
1416 method: 'POST',
1417 methodParam: function(){
1418 return getParamsForSubtitles();
1419 },
1420 afterStep: COMMON_SOURCE.grabIplaSubtitlesData
1421 })
1422 ]
1423 }
1424 });
1425
1426 var grabVideoData = function(data){
1427 var items = [];
1428 var vod = data.vod || {};
1429 if(vod.copies && vod.copies.length > 0){
1430 $.each(vod.copies, function( index, value ) {
1431 items.push(new Format({
1432 bitrate: value.bitrate,
1433 url: value.url,
1434 quality: value.quality_p
1435 }))
1436 });
1437 return {
1438 title: vod.title,
1439 cards: {videos: {items: items}}
1440 }
1441 }
1442 throw new Exception(config.error.noSource, Tool.getRealUrl());
1443 };
1444
1445 var getParamsForSubtitles = function(){
1446 var mediaId = idParser();
1447 return {
1448 jsonrpc: "2.0",
1449 id: 1,
1450 method: "prePlayData",
1451 params: {
1452 userAgentData: {
1453 application: "firefox",
1454 portal: "ipla"
1455 },
1456 cpid: 1,
1457 mediaId: mediaId
1458 }
1459 }
1460 };
1461
1462 var idParser = function(){
1463 var match = location.href.match(/[\a-z\d]{32}/);
1464 if(match && match[0]) {
1465 return match[0];
1466 }
1467
1468 return grabVideoIdFromWatchingNowElement();
1469 };
1470
1471 this.setup = function(){
1472 WrapperDetector.run(properties, this.setup);
1473 };
1474
1475 var grabVideoIdFromWatchingNowElement = function(){
1476 var href = $('div.vod-image-wrapper__overlay').closest('a').attr('href');
1477 if(href !== undefined){
1478 var match = href.match(/[\a-z\d]{32}/);
1479 if(match && match[0]){
1480 return match[0];
1481 }
1482 }
1483 return grabVideoIdFromHtmlElement();
1484 };
1485
1486 var grabVideoIdFromHtmlElement = function(){
1487 var frameSrc = $('app-commercial-wallpaper iframe:first-child').attr('src');
1488 if(frameSrc !== undefined) {
1489 return Tool.getUrlParameter('vid', frameSrc);
1490 }
1491
1492 throw new Exception(config.error.id, Tool.getRealUrl());
1493 };
1494 });
1495
1496 var VOD = (function() {
1497 var properties = new Configurator({
1498 wrapper: {
1499 selector: '#v_videoPlayer'
1500 },
1501 button: {
1502 class: 'vod_download_button'
1503 },
1504 asyncChains: {
1505 videos: [
1506 new Step({
1507 urlTemplate: 'https://player-api.dreamlab.pl/?body[id]=#videoId&body[jsonrpc]=2.0' +
1508 '&body[method]=get_asset_detail&body[params][ID_Publikacji]=#videoId' +
1509 '&body[params][Service]=vod.onet.pl&content-type=application/jsonp' +
1510 '&x-onet-app=player.front.onetapi.pl&callback=',
1511 beforeStep: function (input) {
1512 return idParser();
1513 },
1514 afterStep: function (output) {
1515 return grabVideoData(output);
1516 }
1517 })
1518 ]
1519 }
1520 });
1521
1522 var idParser = function () {
1523 var id = $(".mvp").attr('id');
1524 if(id !== undefined){
1525 return id.match(/mvp:(.+)/)[1];
1526 }
1527
1528 return parseFromJS();
1529 };
1530
1531 var parseFromJS = function(){
1532 var scripts = $('script[type="text/javascript"]').filter(':not([src])');
1533 for (var i = 0; i < scripts.length; i++) {
1534 var match = $(scripts[i]).text().match(/\"mvpId\"\s*:\s*\"(\d+\.\d+)\"/);
1535 if(match && match[1]){
1536 return match[1];
1537 }
1538 }
1539
1540 throw new Exception(config.error.id, Tool.getRealUrl());
1541 };
1542
1543 var grabVideoData = function (data) {
1544 var items = [];
1545 var subtitlesItems = [];
1546 var video = (((data.result || new Array())[0] || {}).formats || {}).wideo || {};
1547 var meta = ((data.result || new Array())[0] || {}).meta || {};
1548 var subtitles = meta.subtitles || [];
1549 var videoData = video['mp4-uhd'] && video['mp4-uhd'].length > 0 ? video['mp4-uhd'] : video['mp4'];
1550 if(videoData && videoData.length > 0){
1551 videoData.forEach(function(value) {
1552 items.push(new Format({
1553 quality: value.vertical_resolution,
1554 bitrate: value.video_bitrate,
1555 url: value.url
1556 }))
1557 });
1558
1559 subtitles.forEach(function(subtitle) {
1560 subtitlesItems.push(new Format({
1561 url: subtitle.url,
1562 description: subtitle.name
1563 }))
1564 });
1565
1566 return {
1567 title: meta.title,
1568 cards: {
1569 videos: {items: items},
1570 subtitles: {items: subtitlesItems}
1571 }
1572 }
1573 }
1574 throw new Exception(config.error.noSource, Tool.getRealUrl());
1575 };
1576
1577 var iplaDetected = function(){
1578 return $('#v_videoPlayer div.pulsembed_embed').length > 0;
1579 };
1580
1581 var workWithSubService = function(){
1582 var src = 'https://pulsembed.eu';
1583 var frameSelector = 'iframe[src^="' + src + '"]';
1584
1585 ElementDetector.detect(frameSelector, function () {
1586 MessageReceiver.postUntilConfirmed({
1587 windowReference: $(frameSelector).get(0).contentWindow,
1588 origin: src,
1589 message: {
1590 location: window.location.href
1591 }
1592 });
1593 });
1594 };
1595
1596 this.setup = function(){
1597 if(iplaDetected()) {
1598 workWithSubService();
1599 }
1600 else if(Tool.isTopWindow()){
1601 WrapperDetector.run(properties);
1602 }
1603 };
1604 });
1605
1606 var VOD_IPLA = (function() {
1607 var properties = new Configurator({
1608 wrapper: {
1609 selector: '#player-wrapper, #playerContainer'
1610 },
1611 button: {
1612 class: 'vod_ipla_downlaod_button'
1613 },
1614 chainSelector: function(){
1615 return ['videos', 'subtitles'];
1616 },
1617 asyncChains: {
1618 videos: [
1619 new Step({
1620 urlTemplate: 'https://distro.redefine.pl/partner_api/v1/2yRS5K/media/#media_id/vod/player_data?' +
1621 'dev=pc&os=linux&player=html&app=firefox&build=12345',
1622 beforeStep: function (input) {
1623 return {media_id: idParser()};
1624 },
1625 afterStep: function(data){
1626 return grabVideoData(data);
1627 }
1628 })
1629 ],
1630 subtitles: [
1631 new Step({
1632 afterStep: function (output) {
1633 return parseSubtitleData();
1634 }
1635 })
1636 ]
1637 },
1638 formatter: COMMON_SOURCE.iplaFormatter
1639 });
1640
1641 var grabVideoData = function(data){
1642 var items = [];
1643 var displayInfo = (data.mediaItem || {}).displayInfo || {};
1644 var mediaSources = ((data.mediaItem || {}).playback || {}).mediaSources || {};
1645 var videos = $.grep(mediaSources, function(source) {
1646 return source.accessMethod === 'direct';
1647 });
1648 if(videos && videos.length > 0){
1649 $.each(videos, function( index, value ) {
1650 items.push(new Format({
1651 url: value.url,
1652 quality: value.quality
1653 }))
1654 });
1655 return {
1656 title: displayInfo.title,
1657 cards: {videos: {items: items}}
1658 }
1659 }
1660 throw new Exception(config.error.noSource, Tool.getRealUrl());
1661 };
1662
1663 var getJson = function(){
1664 var match = $('script:not(:empty)').text().match(/(window\.CP\.embedSetup\()(.*)\);/);
1665 var jsonObject = JSON.parse(match[2]);
1666 return JSON.parse(jsonObject[0].media);
1667 };
1668
1669 var idParser = function(){
1670 try {
1671 if($('#player-wrapper').length > 0) {
1672 return (((getJson() || {}).result || {}).mediaItem || {}).id;
1673 }
1674 else if($('#playerContainer').length > 0){
1675 return getMediaId();
1676 }
1677 }
1678 catch(e){
1679 throw new Exception(config.error.id, Tool.getRealUrl());
1680 }
1681 };
1682
1683 var getMediaId = function(){
1684 var match = $('script:not(:empty)').text().match(/mediaId: "(\w+)",/);
1685 return match[1];
1686 };
1687
1688 var parseSubtitleData = function(){
1689 return COMMON_SOURCE.grabIplaSubtitlesData(getJson());
1690 };
1691
1692 this.setup = function(){
1693 var callback = function(data) {
1694 window.sessionStorage.setItem(config.storage.topWindowLocation, data.location);
1695 WrapperDetector.run(properties);
1696 };
1697 MessageReceiver.awaitMessage({
1698 origin: 'https://pulsembed.eu',
1699 windowReference: window.parent
1700 }, callback);
1701 };
1702 });
1703
1704 var WP = (function() {
1705 var properties = new Configurator({
1706 wrapper: {
1707 selector: '#Player0 > div'
1708 },
1709 button: {
1710 class: 'wp_download_button material__category'
1711 },
1712 asyncChains: {
1713 videos: [
1714 new Step({
1715 urlTemplate: 'https://video.wp.pl/player/mid,#videoId,embed.json',
1716 beforeStep: function (input) {
1717 return idParser();
1718 },
1719 afterStep: function (output) {
1720 return grabVideoData(output);
1721 }
1722 })
1723 ]
1724 }
1725 });
1726
1727 var idParser = function () {
1728 try {
1729 var id = window.location.href.match(/^(.*)-(\d+)v$/)[2];
1730 //__NEXT_DATA__ is a variable on page
1731 return __NEXT_DATA__.props.initialPWPState.material[id].mid;
1732 }
1733 catch(e){
1734 throw new Exception(config.error.id, window.location.href);
1735 }
1736 };
1737
1738 var grabVideoData = function(data){
1739 var items = [];
1740 var urls = (data.clip || {}).url || {};
1741 if(urls && urls.length > 0){
1742 $.each(urls, function( index, value ) {
1743 if(value.type === 'mp4@avc'){
1744 items.push(new Format({
1745 bitrate: value.quality,
1746 url: value.url,
1747 quality: value.resolution
1748 }));
1749 }
1750 });
1751
1752 return {
1753 title: data.clip.title,
1754 cards: {videos: {items: items}}
1755 }
1756 }
1757 throw new Exception(config.error.noSource, window.location.href);
1758 };
1759
1760 this.setup = function(){
1761 WrapperDetector.run(properties, this.setup);
1762 };
1763 });
1764
1765 var CDA = (function() {
1766 var properties = new Configurator({
1767 wrapper: {
1768 selector: '.pb-video-player-wrap'
1769 },
1770 button: {
1771 class: 'cda_download_button',
1772 click: function(){
1773 clickButton();
1774 }
1775 }
1776 });
1777
1778 var clickButton = function(){
1779 var w = window.open();
1780 try {
1781 var url = $("video.pb-video-player").attr('src');
1782 if(url !== undefined){
1783 if(!url.match(/blank\.mp4/)){
1784 prepareResult(url, w);
1785 }
1786 else if(l !== undefined){
1787 prepareResult(l, w);
1788 }
1789 else {
1790 throw new Exception(config.error.id, window.location.href);
1791 }
1792 }
1793 }catch(e){
1794 DomTamper.handleError(e, w);
1795 }
1796 };
1797
1798 var prepareResult = function(url, w) {
1799 var cardsData = properties.cardsData;
1800 var title = $('meta[property="og:title"]');
1801 var quality = $('.quality-btn-active');
1802 cardsData.title = title.length > 0 ? title.attr('content').trim() : 'brak danych';
1803 cardsData.cards['videos'].items = [new Format({
1804 url: url,
1805 quality: quality.length > 0 ? quality.text() : undefined
1806 })];
1807
1808 DomTamper.createDocument(cardsData, w);
1809 };
1810
1811 this.setup = function(){
1812 WrapperDetector.run(properties);
1813 };
1814 });
1815
1816 var NINATEKA = (function() {
1817 var properties = new Configurator({
1818 wrapper: {
1819 selector: '#videoPlayer, #player'
1820 },
1821 button: {
1822 class: 'ninateka_download_button',
1823 click: function(){
1824 clickButton();
1825 }
1826 }
1827 });
1828
1829 var prepareResult = function(url, w) {
1830 var title = $('meta[name="title"]');
1831 var cardsData = properties.cardsData;
1832 cardsData.title = title.length > 0 ? title.attr('content').trim() : 'brak danych';
1833 cardsData.cards['videos'].items = [new Format({
1834 url: url,
1835 quality: undefined
1836 })];
1837
1838 DomTamper.createDocument(cardsData, w);
1839 };
1840
1841 var getMp4Source = function(w, sources){
1842 for(var i = 0; i < sources.length; i++){
1843 if(sources[i].type && sources[i].type.match(/mp4/g)){
1844 prepareResult(sources[i].src, w);
1845 return;
1846 }
1847 }
1848
1849 throw new Exception(config.error.id, window.location.href);
1850 };
1851
1852 var clickButton = function(){
1853 var w = window.open();
1854 try {
1855 var videoPlayer = $('#videoPlayer').data('player-setup');
1856 var sources = (videoPlayer || {}).sources || {};
1857 if(sources.length > 0){
1858 getMp4Source(w, sources);
1859 }
1860 else {
1861 var scripts = $('script[type="text/javascript"]').filter(':not([src])');
1862 for (var i = 0; i < scripts.length; i++) {
1863 var match = $(scripts[i]).text().match(/fn_\S+\(playerOptionsWithMainSource,\s*\d+\)\.sources/g);
1864 if(match && match[0]){
1865 sources = eval(match[0]);
1866 getMp4Source(w, sources);
1867 break;
1868 }
1869 }
1870 }
1871 }catch(e){
1872 DomTamper.handleError(e, w);
1873 }
1874 };
1875
1876 this.setup = function(){
1877 WrapperDetector.run(properties);
1878 };
1879 });
1880
1881 var ARTE = (function() {
1882 var properties = new Configurator({
1883 wrapper: {
1884 selector: 'div.avp-player'
1885 },
1886 button: {
1887 class: 'arte_download_button',
1888 },
1889 asyncChains: {
1890 videos: [
1891 new Step({
1892 urlTemplate: 'https://api.arte.tv/api/player/v1/config/#langCode/#videoId',
1893 beforeStep: function (input) {
1894 return idParser();
1895 },
1896 afterStep: function (output) {
1897 return grabVideoData(output);
1898 }
1899 })
1900 ]
1901 },
1902 formatter: function(data) {
1903 data.cards['videos'].items.sort(function (a, b) {
1904 return b.bitrate - a.bitrate;
1905 });
1906 data.cards['videos'].items.sort(function (a, b) {
1907 var aLang = a.langCode, bLang = b.langCode;
1908 if(aLang !== 'POL' && bLang !== 'POL'){
1909 return ('' + a.langCode).localeCompare(b.langCode);
1910 }
1911 else if(aLang === 'POL' && bLang !== 'POL'){
1912 return -1;
1913 }
1914 else if(aLang !== 'POL' && bLang === 'POL'){
1915 return 1;
1916 }
1917 else {
1918 return 0;
1919 }
1920 });
1921 }
1922 });
1923
1924 var detectLanguage = function() {
1925 var regexp = new RegExp('https:\/\/www.arte\.tv\/(\\w{2})\/');
1926 var match = regexp.exec(window.location.href);
1927 return match[1];
1928 };
1929
1930 var detectVideoId = function(){
1931 var regexp = new RegExp('https:\/\/www.arte\.tv\/\\w{2}\/videos\/([\\w-]+)\/');
1932 var match = regexp.exec(window.location.href);
1933 return match[1];
1934 };
1935
1936 var idParser = function() {
1937 try {
1938 return {
1939 videoId: detectVideoId(),
1940 langCode: detectLanguage()
1941 };
1942 }
1943 catch(e){
1944 throw new Exception(config.error.id, window.location.href);
1945 }
1946 };
1947
1948 var grabVideoData = function(data){
1949 var items = [];
1950 var title = (((data || {}).videoJsonPlayer || {}).eStat || {}).streamName || '';
1951 var streams = ((data || {}).videoJsonPlayer || {}).VSR || {};
1952 if(streams){
1953 Object.keys(streams).filter(function(k, i) {
1954 return k.startsWith("HTTPS");
1955 }).forEach(function(k) {
1956 var stream = streams[k];
1957 items.push(new Format({
1958 bitrate: stream.bitrate,
1959 quality: stream.width + 'x' + stream.height,
1960 langCode: stream.versionShortLibelle,
1961 langDesc: stream.versionLibelle,
1962 url: stream.url
1963 }));
1964 });
1965 return {
1966 title: title,
1967 cards: {videos: {items: items}}
1968 }
1969 }
1970 throw new Exception(config.error.noSource, window.location.href);
1971 };
1972
1973 this.setup = function(){
1974 WrapperDetector.run(properties);
1975 };
1976
1977 });
1978
1979 var VOD_FRAME = (function() {
1980 this.setup = function(){
1981 var callback = function(data) {
1982 var srcArray = ['https://redir.atmcdn.pl', 'https://partner.ipla.tv'];
1983 setupDetector(srcArray, data);
1984 };
1985 MessageReceiver.awaitMessage({
1986 origin: 'https://vod.pl',
1987 windowReference: window.parent
1988 }, callback);
1989 };
1990
1991 var setupDetector = function(srcArray, data){
1992 var selectors = createArrySelectors(srcArray);
1993 var multiSelector = createMultiSelector(selectors);
1994
1995 ElementDetector.detect(multiSelector, function() {
1996 selectors.forEach(function(element){
1997 if($(element.frameSelector).length > 0){
1998 MessageReceiver.postUntilConfirmed({
1999 windowReference: $(element.frameSelector).get(0).contentWindow,
2000 origin: element.src,
2001 message: {
2002 location: data.location
2003 }
2004 });
2005 }
2006 });
2007 });
2008 };
2009
2010 var createArrySelectors = function(srcArray){
2011 return jQuery.map(srcArray, function(src) {
2012 return {
2013 src: src,
2014 frameSelector: 'iframe[src^="' + src + '"]'
2015 }
2016 });
2017 };
2018
2019 var createMultiSelector = function(selectors){
2020 return $.map(selectors, function(src){
2021 return src.frameSelector
2022 }).join(', ');
2023 }
2024 });
2025
2026 var Starter = (function(Starter) {
2027 var tvZones = [
2028 'bialystok', 'katowice', 'lodz', 'rzeszow', 'bydgoszcz', 'kielce', 'olsztyn', 'szczecin',
2029 'gdansk', 'krakow', 'opole', 'warszawa', 'gorzow', 'lublin', 'poznan', 'wroclaw'
2030 ];
2031
2032 var sources = [
2033 {objectName: 'VOD_TVP', urlPattern: /^https:\/\/vod\.tvp\.pl\/video\//},
2034 {objectName: 'CYF_TVP', urlPattern: /^https:\/\/cyfrowa\.tvp\.pl\/video\//},
2035 {objectName: 'TVP_REG', urlPattern: new RegExp('^https:\/\/(' + tvZones.join('|') + ')\.tvp\.pl\/\\d{6,}\/')},
2036 {objectName: 'TVN', urlPattern: /^https:\/\/(?:w{3}\.)?(?:tvn)?player\.pl\//},
2037 {objectName: 'CDA', urlPattern: /^https:\/\/.*\.cda\.pl\//},
2038 {objectName: 'VOD', urlPattern: /^https:\/\/vod.pl\//},
2039 {objectName: 'VOD_IPLA', urlPattern: /^https:\/\/partner\.ipla\.tv\/embed\/|^https:\/\/.*\.redcdn.pl\/file\/o2\/redefine\/partner\//},
2040 {objectName: 'IPLA', urlPattern: /^https:\/\/www\.ipla\.tv\//},
2041 {objectName: 'WP', urlPattern: /^https:\/\/video\.wp\.pl\//},
2042 {objectName: 'NINATEKA', urlPattern: /^https:\/\/ninateka.pl\//},
2043 {objectName: 'ARTE', urlPattern: /^https:\/\/www.arte.tv\/.*\/videos\//},
2044 {objectName: 'VOD_FRAME', urlPattern: /^https:\/\/pulsembed\.eu\//}
2045 ];
2046
2047 Starter.start = function() {
2048 sources.some(function(source){
2049 if(location.href.match(source.urlPattern)){
2050 var object = eval('new ' + source.objectName + '()');
2051 console.info('voddownloader: jQuery v' + $().jquery + ', context: ' + source.objectName);
2052 object.setup();
2053 return true;
2054 }
2055 });
2056 };
2057
2058 return Starter;
2059 }(Starter || {}));
2060
2061 $(document).ready(function(){
2062 DomTamper.injectStyle(window, 'buttons_css');
2063 Starter.start();
2064 });
2065
2066}).bind(this)(jQuery, platform, Waves);