1 (2018.10.27 23:20:22 отредактировано WendyH)

Тема: moonwalk

От WendyH пишет:

Если кто-то хочет поучаствовать в своевременном обновлении ключей декодирования для moonwalk, тот может это сделать вот на этой страничке:
https://hms.lostcut.net/moonup
За паролем для обновления данных обращаться ко мне в личку.



Доброго времени суток!
пытаюсь получить код плеера от moonwalk по URL=http://moonwalk.cc/video/5198ce6d3d85e288/iframe

+ открыть спойлер

Браузер все отдает корректно
когда пытаюсь делать curl из PHP

$user_agent = 'Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0';
$ch = curl_init(); 


$page = getPage("http://moonwalk.cc/video/5198ce6d3d85e288/iframe");
echo ($page);

function getPage($page){
  $header1[0] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
  $header1[] = "Accept-Encoding    gzip, deflate";
  $header1[] = "Accept-Language    ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3";
  $header1[] = "Connection keep-alive";
  $header1[] = "Host    moonwalk.cc";


    global $ch,$ssid,$user_agent;
    curl_setopt($ch, CURLOPT_URL, $page);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header1); 
    curl_setopt($ch, CURLOPT_COOKIE, '_ga=GA1.2.919505015.1448385507; remember_user_token=BAhbB1sGaQLuB0kiIiQyYSQxMCROVkhIZkpXNjJRM0JGTmxZMjNWV211BjoGRVQ
%3D--590f9da5e6c0e183c2bd5b77cdf724d44aedb9fa; _moon_session=Um5wbkZpNHlUNmx5K3V2UUo4a0tNakRHTE12U3B
hRlB3bE0zdGJLcEFlbkVPOWdDTkZoOG90RXMzZGVNVGhsczVCNWRDcllYdDFwYURadml4NFE3dHlYNGRqYnlZTFB4YTcxY3c1Zkl
BMnBGUWRJbjZwcGZ0cjVPNHFFalptU054ZFFVdTFabXE3M2lGaU9PZXc2QTBXbm5XekZ5bkRpUDRkT2FTYVd4RXpEWjFNWXVNZ1FpWklHdU00dlY4bkNrLS1oNDUrTW5JOG1MNzlkVmg1ZS9UdG9BPT0
%3D--1dbc2a96adcef7f863536b4d40a91e5a15c948ad; _gat=1');
    curl_setopt($ch, CURLOPT_REFERER, 'http://moonwalk.cc/video/5c650c58db6ad707/iframe');
    curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $res=curl_exec($ch);
    return $res;
}

возвращает 400 ошибку BAD request

Снифер в Браузере показывает заголовки запроса :

Accept                    text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding    gzip, deflate
Accept-Language    ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Connection            keep-alive
Cookie                   _ga=GA1.2.919505015.1448385507; remember_user_token=BAhbB1sGaQLuB0kiIiQyYSQxMCROVkhIZkpXNjJRM0JGTmxZMjNWV211BjoGRVQ
%3D--590f9da5e6c0e183c2bd5b77cdf724d44aedb9fa; _moon_session=Um5wbkZpNHlUNmx5K3V2UUo4a0tNakRHTE12U3B
hRlB3bE0zdGJLcEFlbkVPOWdDTkZoOG90RXMzZGVNVGhsczVCNWRDcllYdDFwYURadml4NFE3dHlYNGRqYnlZTFB4YTcxY3c1Zkl
BMnBGUWRJbjZwcGZ0cjVPNHFFalptU054ZFFVdTFabXE3M2lGaU9PZXc2QTBXbm5XekZ5bkRpUDRkT2FTYVd4RXpEWjFNWXVNZ1FpWklHdU00dlY4bkNrLS1oNDUrTW5JOG1MNzlkVmg1ZS9UdG9BPT0
%3D--1dbc2a96adcef7f863536b4d40a91e5a15c948ad; _gat=1
Host                              moonwalk.cc
User-Agent              Mozilla/5.0 (Windows NT 6.1; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0

Никаких дополнительных полей или параметров я не вижу... Мот мой снифер не показывает или я что -то делаю не так ?

2

Re: moonwalk

alsoft, у вас немного странный код.
HTTP заголовки должны передаваться в данном случае как "Имя заголовка: его значение" (через двоеточие).
Не все их нужно передавать как есть, которые показывает сниффер.
Например, Host - не нужно. Потому как он передаётся обязательно сам и указывается адрес, куда идёт запрос (это стандарт самого протокола HTTP).
Cookie - не нужно все значения, большинство значений передаёт служебные сам браузер. А их значения user_token и session временные.
Но и главная тут ошибка - это переносы строк в значении кук. Так нельзя.
Хотя подход примерно правильный. Из-за того, что мы не знаем, какие именно заголовки проверяются на стороне сервера, стараемся для начала как можно ближе к браузерным значениям делать запрос. Потом пробуя урезать и не указывать лишнее.

В общем, данный вопрос чисто по использованию curl в PHP.
Вот такой код сработает:

+ открыть спойлер
<?php

$user_agent = 'Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0';
$ch = curl_init(); 
$page = getPage("http://moonwalk.cc/video/5198ce6d3d85e288/iframe");
echo ($page);

function getPage($page){
  global $ch, $ssid, $user_agent;

  $header1[0] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
  $header1[] = "Accept-Encoding: gzip, deflate";
  $header1[] = "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3";

    curl_setopt($ch, CURLOPT_URL, $page);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header1); 
    curl_setopt($ch, CURLOPT_COOKIE, '');
    curl_setopt($ch, CURLOPT_REFERER, 'http://moonwalk.cc/video/5c650c58db6ad707/iframe');
    curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $res=curl_exec($ch);
    return gzdecode($res);
}

Заметьте gzdecode в конце. Без неё будут сжатые данные.

Но это всё ерунда. Если вы хотите получить на PHP ссылку на сам видео файл, то после получения содержимого страницы вы увидете в её коде волшебный блок: eval(function(p,a,c,k,e,r){e=function(c)...
Который, для успешного получения ссылки - нужно расшифровать, т.е. выполнить. А сделать это средством PHP, насколько я знаю, не удастся.

Но для получения кода самого плеера пойдёт.

Sony Bravia KDL-32CX523
Спасибо сказали: alsoft1

3

Re: moonwalk

Спасибо!  действительно с двоеточием я закопипастился как-то.... Оказалось, что для получения ссылки необходимо сделать всего один POST запрос
http://moonwalk.cc/sessions/create_session
и указать в качестве параметров 3 переменные
access_key
video_token
d_id
все они есть в ответе и с ними проблем нет вся фишка что в заголовке запроса должен быть указан еще один ключик ontent-Data, а вот он как раз и формируется когда отрабатывает шифрованный javascript на стороне пользователя. осталось расшифровать скрипт и выяснить его логику :)

итак собственно расшифровать не сложно

+ открыть спойлер
(function(g) {
    var h = {
        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        encode: function(e) {
            var t = "";
            var n, r, i, s, o, u, a;
            var f = 0;
            e = h._utf8_encode(e);
            while (f < e.length) {
                n = e.charCodeAt(f++);
                r = e.charCodeAt(f++);
                i = e.charCodeAt(f++);
                s = n >> 2;
                o = (n & 3) << 4 | r >> 4;
                u = (r & 15) << 2 | i >> 6;
                a = i & 63;
                if (isNaN(r)) {
                    u = a = 64
                } else if (isNaN(i)) {
                    a = 64
                }
                t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a)
            }
            return t
        },
        decode: function(e) {
            var t = "";
            var n, r, i;
            var s, o, u, a;
            var f = 0;
            e = e.replace(/[^A-Za-z0-9\+\/\=]/g, "");
            while (f < e.length) {
                s = this._keyStr.indexOf(e.charAt(f++));
                o = this._keyStr.indexOf(e.charAt(f++));
                u = this._keyStr.indexOf(e.charAt(f++));
                a = this._keyStr.indexOf(e.charAt(f++));
                n = s << 2 | o >> 4;
                r = (o & 15) << 4 | u >> 2;
                i = (u & 3) << 6 | a;
                t = t + String.fromCharCode(n);
                if (u != 64) {
                    t = t + String.fromCharCode(r)
                }
                if (a != 64) {
                    t = t + String.fromCharCode(i)
                }
            }
            t = h._utf8_decode(t);
            return t
        },
        _utf8_encode: function(e) {
            e = e.replace(/\r\n/g, "\n");
            var t = "";
            for (var n = 0; n < e.length; n++) {
                var r = e.charCodeAt(n);
                if (r < 128) {
                    t += String.fromCharCode(r)
                } else if (r > 127 && r < 2048) {
                    t += String.fromCharCode(r >> 6 | 192);
                    t += String.fromCharCode(r & 63 | 128)
                } else {
                    t += String.fromCharCode(r >> 12 | 224);
                    t += String.fromCharCode(r >> 6 & 63 | 128);
                    t += String.fromCharCode(r & 63 | 128)
                }
            }
            return t
        },
        _utf8_decode: function(e) {
            var t = "";
            var n = 0;
            var r = c1 = c2 = 0;
            while (n < e.length) {
                r = e.charCodeAt(n);
                if (r < 128) {
                    t += String.fromCharCode(r);
                    n++
                } else if (r > 191 && r < 224) {
                    c2 = e.charCodeAt(n + 1);
                    t += String.fromCharCode((r & 31) << 6 | c2 & 63);
                    n += 2
                } else {
                    c2 = e.charCodeAt(n + 1);
                    c3 = e.charCodeAt(n + 2);
                    t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
                    n += 3
                }
            }
            return t
        }
    };
    $(function() {
        $.ajaxSetup({
            beforeSend: function(a) {
                var b = h.encode("1464848139.7ee2a670f7378df41e1804c410b61e7d");
                a.setRequestHeader('Content-Data', b)
            }
        })
    });
    var j = function(a) {
        this._options = {
            checkOnLoad: false,
            resetOnEnd: false,
            loopCheckTime: 50,
            loopMaxNumber: 5,
            baitClass: 'pub_300x250 pub_300x250m pub_728x90 text-ad textAd text_ad text_ads text-ads text-ad-links',
            baitStyle: 'width: 1px !important; height: 1px !important; position: absolute !important; left: -10000px !important; top: -1000px !important;',
            debug: false
        };
        this._var = {
            version: '3.2.0',
            bait: null,
            checking: false,
            loop: null,
            loopNumber: 0,
            event: {
                detected: [],
                notDetected: []
            }
        };
        if (a !== undefined) {
            this.setOption(a)
        }
        var b = this;
        var c = function() {
            setTimeout(function() {
                if (b._options.checkOnLoad === true) {
                    if (b._options.debug === true) {
                        b._log('onload->eventCallback', 'A check loading is launched')
                    }
                    if (b._var.bait === null) {
                        b._creatBait()
                    }
                    setTimeout(function() {
                        b.check()
                    }, 1)
                }
            }, 1)
        };
        if (g.addEventListener !== undefined) {
            g.addEventListener('load', c, false)
        } else {
            g.attachEvent('onload', c)
        }
    };
    j.prototype._options = null;
    j.prototype._var = null;
    j.prototype._bait = null;
    j.prototype._log = function(a, b) {
        console.log('[FuckAdBlock][' + a + '] ' + b)
    };
    j.prototype.setOption = function(a, b) {
        if (b !== undefined) {
            var c = a;
            a = {};
            a[c] = b
        }
        for (var d in a) {
            this._options[d] = a[d];
            if (this._options.debug === true) {
                this._log('setOption', 'The option "' + d + '" he was assigned to "' + a[d] + '"')
            }
        }
        return this
    };
    j.prototype._creatBait = function() {
        var a = document.createElement('div');
        a.setAttribute('class', this._options.baitClass);
        a.setAttribute('style', this._options.baitStyle);
        this._var.bait = g.document.body.appendChild(a);
        this._var.bait.offsetParent;
        this._var.bait.offsetHeight;
        this._var.bait.offsetLeft;
        this._var.bait.offsetTop;
        this._var.bait.offsetWidth;
        this._var.bait.clientHeight;
        this._var.bait.clientWidth;
        if (this._options.debug === true) {
            this._log('_creatBait', 'Bait has been created')
        }
    };
    j.prototype._destroyBait = function() {
        g.document.body.removeChild(this._var.bait);
        this._var.bait = null;
        if (this._options.debug === true) {
            this._log('_destroyBait', 'Bait has been removed')
        }
    };
    j.prototype.check = function(a) {
        if (a === undefined) {
            a = true
        }
        if (this._options.debug === true) {
            this._log('check', 'An audit was requested ' + (a === true ? 'with a' : 'without') + ' loop')
        }
        if (this._var.checking === true) {
            if (this._options.debug === true) {
                this._log('check', 'A check was canceled because there is already an ongoing')
            }
            return false
        }
        this._var.checking = true;
        if (this._var.bait === null) {
            this._creatBait()
        }
        var b = this;
        this._var.loopNumber = 0;
        if (a === true) {
            this._var.loop = setInterval(function() {
                b._checkBait(a)
            }, this._options.loopCheckTime)
        }
        setTimeout(function() {
            b._checkBait(a)
        }, 1);
        if (this._options.debug === true) {
            this._log('check', 'A check is in progress ...')
        }
        return true
    };
    j.prototype._checkBait = function(a) {
        var b = false;
        if (this._var.bait === null) {
            this._creatBait()
        }
        if (g.document.body.getAttribute('abp') !== null || this._var.bait.offsetParent === null || this._var.bait.offsetHeight == 0 || this._var.bait.offsetLeft == 0 || this._var.bait.offsetTop == 0 || this._var.bait.offsetWidth == 0 || this._var.bait.clientHeight == 0 || this._var.bait.clientWidth == 0) {
            b = true
        }
        if (g.getComputedStyle !== undefined) {
            var c = g.getComputedStyle(this._var.bait, null);
            if (c.getPropertyValue('display') == 'none' || c.getPropertyValue('visibility') == 'hidden') {
                b = true
            }
        }
        if (this._options.debug === true) {
            this._log('_checkBait', 'A check (' + (this._var.loopNumber + 1) + '/' + this._options.loopMaxNumber + ' ~' + (1 + this._var.loopNumber * this._options.loopCheckTime) + 'ms) was conducted and detection is ' + (b === true ? 'positive' : 'negative'))
        }
        if (a === true) {
            this._var.loopNumber++;
            if (this._var.loopNumber >= this._options.loopMaxNumber) {
                this._stopLoop()
            }
        }
        if (b === true) {
            this._stopLoop();
            this._destroyBait();
            this.emitEvent(true);
            if (a === true) {
                this._var.checking = false
            }
        } else if (this._var.loop === null || a === false) {
            this._destroyBait();
            this.emitEvent(false);
            if (a === true) {
                this._var.checking = false
            }
        }
    };
    j.prototype._stopLoop = function(a) {
        clearInterval(this._var.loop);
        this._var.loop = null;
        this._var.loopNumber = 0;
        if (this._options.debug === true) {
            this._log('_stopLoop', 'A loop has been stopped')
        }
    };
    j.prototype.emitEvent = function(a) {
        if (this._options.debug === true) {
            this._log('emitEvent', 'An event with a ' + (a === true ? 'positive' : 'negative') + ' detection was called')
        }
        var b = this._var.event[(a === true ? 'detected' : 'notDetected')];
        for (var i in b) {
            if (this._options.debug === true) {
                this._log('emitEvent', 'Call function ' + (parseInt(i) + 1) + '/' + b.length)
            }
            if (b.hasOwnProperty(i)) {
                b[i]()
            }
        }
        if (this._options.resetOnEnd === true) {
            this.clearEvent()
        }
        return this
    };
    j.prototype.clearEvent = function() {
        this._var.event.detected = [];
        this._var.event.notDetected = [];
        if (this._options.debug === true) {
            this._log('clearEvent', 'The event list has been cleared')
        }
    };
    j.prototype.on = function(a, b) {
        this._var.event[(a === true ? 'detected' : 'notDetected')].push(b);
        if (this._options.debug === true) {
            this._log('on', 'A type of event "' + (a === true ? 'detected' : 'notDetected') + '" was added')
        }
        return this
    };
    j.prototype.onDetected = function(a) {
        return this.on(true, a)
    };
    j.prototype.onNotDetected = function(a) {
        return this.on(false, a)
    };
    g.FuckAdBlock = j;
    if (g.fuckAdBlock === undefined) {
        g.fuckAdBlock = new j({
            checkOnLoad: true,
            resetOnEnd: true
        })
    }
})(window);
if (typeof FuckAdBlock === 'undefined') {
    conditionDetected()
} else {
    fuckAdBlock.setOption({
        checkOnLoad: true,
        resetOnEnd: false
    });
    fuckAdBlock.onDetected(conditionDetected);
    fuckAdBlock.onNotDetected(conditionNotDetected)
}

function ALittleSecretGuys(c) {
    var d = document.createElement("img");
    d.height = "1px";
    d.width = "1px";
    d.style.position = 'absolute';
    d.style.top = '-9999px';
    d.style.left = '-9999px';
    d.setAttribute('class', 'adv banner');
    d.id = "ads-advimg-banner";
    d.src = "/upload/banners/banner.jpg";
    document.body.appendChild(d);
    setTimeout(function() {
        var a = document.getElementById("ads-advimg-banner");
        var b = $('#ads-advimg-banner').attr('class').length;
        if (a.style.display == "none" || a.style.display == "hidden" || a.style.visibility == "hidden" || b > 10) {
            c(true)
        } else {
            c(false)
        }
    }, 500)
}

function conditionNotDetected() {}

function conditionDetected() {
    condition_detected = true
}
console.log('ALittleSecretGuys', 1);
if (typeof ALittleSecretGuys === 'undefined') {
    console.log('ALittleSecretGuys', 2);
    conditionDetected()
} else {
    console.log('ALittleSecretGuys', 3);
    $(function() {
        ALittleSecretGuys(function(a) {
            console.log('ALittleSecretGuys', 4);
            if (a === true) {
                console.log('ALittleSecretGuys', 5);
                conditionDetected()
            } else {
                console.log('ALittleSecretGuys', 6);
                conditionNotDetected()
            }
        })
    })
}

нас интересует как раз вот это место где формируется  ключ а именно

var b = h.encode("1464848139.7ee2a670f7378df41e1804c410b61e7d");
                a.setRequestHeader('Content-Data', b)

4

Re: moonwalk

В общем-то да. Всё так.

alsoft пишет:

итак собственно расшифровать не сложно

Ну как, получилось это сделать на PHP? Поделитесь?

Sony Bravia KDL-32CX523

5

Re: moonwalk

да функцию то переписал конечно

+ открыть спойлер
function gethash($str){
     $t = "";
     $str=utf8_encode($str);
     $keyStr= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
            $f = 0;

            while ($f < strlen($str)) {
                $n = ord($str{$f++});
                $r = ord($str{$f++});
                $i = ord($str{$f++});
                $s = $n >> 2;
                $o = ($n & 3) << 4 | $r >> 4;
                $u = ($r & 15) << 2 | $i >> 6;
                $a = $i & 63;
                if ($r==0) {
                    $u = $a = 64;
                } else if ($i==0) {
                    $a = 64;
                }


                $t = $t.$keyStr{$s}.$keyStr{$o}.$keyStr{$u}.$keyStr{$a};

            }

            return $t;

и сервер мне отдает плейлист, только он не играется все равно:( пишет форбиден. где-то что-то еще....  :(
или в Хэше IP зашит, но я и с сервера пробовал все равно не пускает..... Пока думаю.

6 (2017.12.07 09:22:31 отредактировано WendyH)

Re: moonwalk

Вчера, вглядевшись в функцию encode, мне показалось что-то знакомое. Превращение трёх байт в четыре с записью цифро-буквенными символами...
Решил проверить и вправду - это же Base64Encode! Всего-то. Так что благодаря вам я разобрался, что этот eval на самом деле не так страшен.
Просто нужно вытащить значение Content-Data и закодировать его в Base64.

Поздравляю, вы написали функцию base64_encode своими руками (точнее переписали) и теперь тоже знаете, что это за зверь.

По поводу 403 шибки. Не совсем понял, вам сами ссылки после create_session отдаются уже или именно на этом этапе эта ошибка Forbitten?

Короче, для того чтобы POST запрос на create_session прошёл успешно, нужно, как вы уже знаете в HTTP заголовки добавить значение Content-Data и также указать, что запрос сформирован динамически скриптом в браузере - добавить заголовок "X-Requested-With: XMLHttpRequest".

+ После чего получаем JOSN со ссылками на разные типы потоков

{"manifest_f4m":"http://moonwalk.cc/video/5198ce6d3d85e288/manifest.f4m?cd=1&expired=1465248743&signature=0d2dd00f8eb38ab12f930035342cfa05","manifest_m3u8":"http://moonwalk.cc/video/5198ce6d3d85e288/index.m3u8?cd=1&expired=1465248743&signature=0d2dd00f8eb38ab12f930035342cfa05","manifest_dash":"http://moonwalk.cc/video/5198ce6d3d85e288/manifest.mpd?cd=1&expired=1465248743&signature=0d2dd00f8eb38ab12f930035342cfa05"}

И таки да, полученные ссылки на m3u8 или f4m поток, подписаны и привязаны к IP. Так что проиграть их на клиенте просто так не получится.
Но зато мы можем забрать содержимое этого плейлиста или манифеста на самом сервере и отдать пользователю. Как будто он скачал этот плейлист.

+ Вот какой у меня получился скрипт на PHP
<?php
ini_set("log_errors", 1); ini_set("error_log", $_SERVER['SCRIPT_FILENAME'].".log"); ini_set('error_reporting', E_ALL); ini_set("display_errors", 1);
$urlBase = "http://moonwalk.cc";

// Получение ссылки на видео c moonwalk в переданных параметрах, а также тип получаемого потока.
$url     = isset($_REQUEST['url'    ]) ? $_REQUEST['url' ] : ""    ; // moonwalk.cc iframe url
$type    = isset($_REQUEST['type'   ]) ? $_REQUEST['type'] : "m3u8"; // tyle of link (f4m, m3u8, dash)
$urlonly = isset($_REQUEST['urlonly']); // Флаг, сигнализирующий отдавать ссылку на плейлист, а не само его содержимое
$attacha = isset($_REQUEST['at'     ]); // Флаг, сигнализирующий отдавать плейлист как прикреплённый файл с расширением

if (!$url) die("No moonwalk iframe url in the parameters.");

$cookies = array();

// Установка HTTP заголовков
$headers = "Accept-Encoding: gzip, deflate\r\n" .
           "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n" .
           "Referer: " . $url . "\r\n" .
           "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36\r\n";

// Загружаем страницу iframe c moonwalk
$page = LoadPage($url, "GET", $headers);

// Добавляем HTTP заголовки для POST запроса
$headers .= "X-Requested-With: XMLHttpRequest\r\n" .
            "Origin: $urlBase\r\n";

// Поиск дополнительных HTTP заголовков, которые нужно установить
$data = GetRegexValue($page, "#VideoBalancer\((.*?)\);#is");
if (!$data) die("No VideoBalancer info in the loaded iframe.");
$options = JSDecode($data);

// Получение ссылки на js-скрипт, где есть список параметров POST запроса
$jsUrl = GetRegexValue($page, '#src="(.*?)"#');
if (!$jsUrl)  die("Not found js url in the loaded iframe.");

$jsData = LoadPage($urlBase . $jsUrl, "GET", $headers);

// Устанавливаем дополнительные заголовки и их значения
$data = GetRegexValue($jsData, "#headers:({.*?})#is");
if ($data) {
  $headersArr = JSDecode($data);
  foreach ($headersArr as $key => $value) {
    $val = $value;
    $var = GetRegexValue($val, "#this.options.(\w+)#");
    if ($var) $val = $options[$var];
    $headers .= $key . ": " . $val . "\r\n";
  }
}

// Получаем параметры POST запроса из js-скрипта
$data = GetRegexValue($jsData, "#var\s+\w+=(\{mw_key.*?\})#is");
if (!$data) die("POST parameters not found in loaded js.");

// Формируем данные для POST
$postData = JSDecode($data); $post = "";
foreach ($postData as $key => $value) {
  $val = $value;
  $var = GetRegexValue($val, "#this.options.(\w+)#");
  $tmp = GetRegexValue($val, "#(this.options.\w+)#");
  if ($var) $val = str_replace($tmp, $options[$var], $val);
  $var = GetRegexValue($val, "#\w+\.(\w+)#");
  if ($var && preg_match("#window\.".$var."\s*=\s*['\"](.*?)['\"]#", $page, $matches)) {
    $val = $matches[1];
  }
  $post .= $key . "=" . $val . "&";
}

$link = $urlBase . "/manifests/video/" . $options["video_token"] . "/all";

// Делаем POST запрос и получаем список ссылок на потоки
$data = LoadPage($link, "POST", $headers, $post);

if ($type=="json") die($data);

// Делаем из полученных json данных ассоциативный массив PHP
$answerObject = json_decode($data, TRUE);

// Получаем значение ссылки нужного типа потока (по-умолчанию: m3u8)
$link = "";
if (isset($answerObject["mans"])) $link = $answerObject["mans"]["manifest_".$type];

// Если ссылка с таким типом есть, получаем содержимое плейлиста/манифеста
if ($link) {
    if ($urlonly) 
        $data = $link;
    else {
//        if      ($type=="m3u8") header("Content-Type: application/vnd.apple.mpegurl");
//        else if ($type=="f4m" ) header("Content-Type: application/xml");
        $data = LoadPage($link, "GET", $headers);
        if ($attacha) {
            header("Content-Length: ".strlen($data));
            header("Content-Disposition: attachment; filename=play.$type");
        }
    }
} 

// Отдаём полученное
echo $data;

///////////////////////////////////////////////////////////////////////////////
// Получение страницы с указанными методом и заголовками
function LoadPage($url, $method, $headers, $data='') {
    global $cookies;

    // Если есть кукисы - добавляем их значения в HTTP заголовки
    $coo = "";
    foreach($cookies as $key => $val) $coo .= $key."=".urlencode($val)."; ";
    if ($coo) $headers .= "Cookie: $coo\r\n";

    $options = array();
    $options['http'] = array('method' => $method ,
                             'header' => $headers,
                             'content'=> $data   );
    $context = stream_context_create($options);
    $page    = file_get_contents($url, false, $context);
    // Перебираем HTTP заголовки ответа, чтобы установить кукис
    foreach($http_response_header as $c => $h) {
        if (stristr($h, 'content-encoding') and stristr($h, 'gzip')) {
            $page = gzdecode($page);
        } else if (preg_match('#^Set-Cookie:\s*([^;]+)#', $h, $matches)) {
            parse_str($matches[1], $tmp);
            $cookies += $tmp;
        }
    }
    return $page;
}

////////////////////////////////////////////////////////////////////
// Функция получения значения по указанному регулярному выражению
function GetRegexValue($text, $pattern, $group=1) {
    if (preg_match($pattern, $text, $matches))
        return $matches[$group];
    return "";
}

///////////////////////////////////////////////////////////////////////////////
// Функция получения массива из JS кода вместо json_decode
function JSDecode($data) {
  $data = str_replace("encodeURIComponent(", "", $data); // Убираем левые js команды
  $data = str_replace("'),", "',", $data);
  $data = str_replace("'", "\""  , $data); // Заменяем одинарные кавычки на кранированные обычные
  $data = str_replace(["\n","\r"], "", $data);                    // Убираем переносы строк
  $data = preg_replace('/([^\w"\.])(\w+)\s*:/','$1"$2":', $data); // Берём в кавычки имена
  $data = preg_replace('/("\w+")\s*:\s*([\w\.]+)/' ,'$1:"$2"', $data); // Берём в кавычки все значения
  $data = preg_replace('/(,\s*)(})/','$2', $data);                     // Убираем лишние пробелы
  $json = json_decode($data, true);
  return $json;
}

Пример использования:
httр://wonky.lostcut.net/moonwalk.php?type=m3u8&urlonly&url=http://moonwalk.cc/serial/f9cd5b01686a122360f758b5f15ca14d/iframe?season=4&episode=3

UPD 2017.03.08: Вышеприведенный скрипт может быть не актуален и больше не работать, был приведён только для примера на момент написания.

Sony Bravia KDL-32CX523
Спасибо сказали: alsoft, baat2

7

Re: moonwalk

Новые изменения в moonwalk
появился  в POST новый параметр X-CSRF-Token   пока не понимаю откуда он и как формируется :(

8

Re: moonwalk

alsoft пишет:

Новые изменения в moonwalk
появился  в POST новый параметр X-CSRF-Token   пока не понимаю откуда он и как формируется :(

Там просто вызываемый скрипт сменился с "/sessions/create_new" на "/sessions/new_session".
X-CSRF-Token у них был и есть (защита от межсайтовых запросов, но это для браузеров), но он на суть дела в данном случае не влияет.

Sony Bravia KDL-32CX523

9

Re: moonwalk

Как всегда кратко точно и поместу! Спасибо позавчера я был "не в форме"  ! :)))

10

Re: moonwalk

на moonwalk снова изменения :(
добавилось два параметра mw_pid= и mw_domain_id
но почему-то не возвращает ВООБЩЕ ничего тоетсь в ответ на  http://moonwalk.cc/sessions/new_session
пустая страница...непонятно куда копать....

11

Re: moonwalk

alsoft пишет:

на moonwalk снова изменения :(
...
пустая страница...непонятно куда копать....

Значит, ему не нравится то, что ему передали в запросе. Кроме параметров в запросе участвуют также HTTP заголовки.
Их посмотреть (так же как и передаваемые параметры) можно в браузере в инструментах разработчика на вкладке "Сеть" или "Network" при начале просмотра фильма в браузере. Найти переданный запрос и посмотреть что ему ещё такого нужно.

Добавили HTTP header

X-Data-Pool: Stream
+ открыть спойлер

https://hms.lostcut.net/misc.php?action=pun_attachment&amp;item=1317

Прикреплённые файлы сообщения

moonsniff.png 160.85 kb, скачивалось 58 раз, начиная с 2016.10.26

Sony Bravia KDL-32CX523

12

Re: moonwalk

Так Стоит я первым делом добавил :(

13 (2016.10.26 16:43:19 отредактировано alsoft)

Re: moonwalk

$headers .= "X-CSRF-Token: ".$csrf_token."\r\n";
$headers .= "X-Data-Pool: Stream\r\n";
$headers .= "X-Requested-With: XMLHttpRequest\r\n";
$headers .= "Referer: $url\r\n";
$headers .= "X-Compress: 1\r\n";
$headers .= "host: moonwalk.cc \r\n";
$headers .= "Connection: keep-alive\r\n";

У меня пишется еще какой-то Proxy-Authorization:  а вот как генерится я не могу понять

14

Re: moonwalk

У Вас конечно код намного красивее,  но разобрался  при  формировании строки для post оказалось  перевод строки "влез"
Сверил по байтно Ваш и свой мой оказался на байт длинее :(
Спасибо за помощь !! как всегда выручаете!

А.

15 (2016.12.01 10:52:33 отредактировано baat)

Re: moonwalk

опять защиту переделали... возвращает ролик, где мужик крадёт из магазина спиртное.... когда только успевают, 8 часов назад, всё работало...

WendyH, какие будут идеи?...

16 (2016.12.01 16:57:34 отредактировано WendyH)

Re: moonwalk

Обновил PHP скрипт...
P.S. А не...

Sony Bravia KDL-32CX523

17 (2016.12.01 17:05:00 отредактировано baat)

Re: moonwalk

на мужика с бутылками? так его не надо было обновлять для этого)))

надо отдать должное, чувство юмора у разработчиков есть)))))

крадет, но не проходит турникет (защиту)... символично... жестоко и задолбали они защиту менять, конечно, но улыбнуло...

18

Re: moonwalk

В общем, мне удалось у них стырить алкоголь!

И загрузка iframe и запрос в sessions/new_session работают: http://wonky.lostcut.net/moonwalk.php?u … c63/iframe

Теперь в заголовках Encoding-Pool не нужен, но теперь важны куки, которые нужно установить после получения iframe страницы.

Sony Bravia KDL-32CX523
Спасибо сказали: baat, bigmanalexey2

19 (2016.12.17 17:56:09 отредактировано Petro_Fesyuk)

Re: moonwalk

Здравствуйте. Недавно возникла проблема - почти все ФИЛЬМЫ(не сериалы) не грабятся. Ссылку на плейлист получаю, скачиваю, но отдать пользователю не могу. В консоли ошибка Allowed-Origin. Думал тут всё хорошо, но: http://wonky.lostcut.net/moonwalk.php?u … 460/iframe. Попробуйте проиграть что-то, например, в VLC. Надеюсь на вашу помощь.

20

Re: moonwalk

Petro_Fesyuk, ну значит всё. Теперь для таких фильмов нужно выполнять все действия и запрос на /sessions/new_session у пользователя. Теперь привязка к IP идёт не только при получении плейлиста, но и для получения видео-контента по этому плейлисту.

Sony Bravia KDL-32CX523

21

Re: moonwalk

Сам плейлист доступен. А на счёт ретрансляции - запиливал видео в скрипте, который потом брал iframe-ом. 0 результатов.

22

Re: moonwalk

Petro_Fesyuk пишет:

А на счёт ретрансляции - запиливал видео в скрипте, который потом брал iframe-ом.

Можно по-подробней?

Sony Bravia KDL-32CX523

23

Re: moonwalk

Сам модуль скачивает m3u8 файл и отдает ссылку php скрипту, который в свою очередь вмещает в себе тег <video> для мобильных устройств или uppod плеер для PC, где source - скаченный m3u8 файл.

24

Re: moonwalk

Не, так у вас работать не будет и это понятно. Я спрашивал насчёт поподробнее о "ретрансляции" и iframe. Как вы проверяли?

Sony Bravia KDL-32CX523

25

Re: moonwalk

А. Передавал ссылку на один из потоков и пробовал скачивать обычным file_get_contents() без указания header-ов.

26

Re: moonwalk

Petro_Fesyuk пишет:

А. Передавал ссылку на один из потоков и пробовал скачивать обычным file_get_contents() без указания header-ов.

Любой из потоков нормально скачивается даже через file_get_contents, если это делается там же, где и получали плейлист. Только на плеере это работать не будет, ибо это другой IP.

Sony Bravia KDL-32CX523

27

Re: moonwalk

baat пишет:

и опять ролик со спиртным, куки и параметры не прокатывают

Ну почему же не прокатывают. Если из браузера можно посмотреть фильм, значит и получить ссылку на поток тоже не невозможно.
В общем, тоже поднадоело, что у них часто меняются добавочные заголовки и параметры, поэтому в скрипте сделал так, чтобы он искал в HTML коде страницы устанавливаемые с помощью javascript значения.

1) Код в странице, который устанавливает заголовки (и они с постоянством меняются):

$.ajaxSetup({
    headers: {
      'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
      'X-Var-Document': 'String'
    }
  });

Сегодня это "X-Var-Document: String", вчера "X-Var-iframe: direct", позавчера "X-Var-iframe: forward" и проч.

2) Код, где устанавливаются параметры для POST запроса:

var session_params = {
    video_token: 'b5bd1b6ad9333687',
    content_type: 'movie',
    mw_key: '1152cb1dd4c4d544',
    mw_pid: 2502,
    p_domain_id: 355829,
    ad_attr: condition_detected ? 1 : 0,
    debug: false
  };

  session_params.varb1 = varb1;

И совсем в другом месте:

var varb1 = 'ac498dce15a1b1522edad6be67abfcc0';

Так вот сегодня этот параметр называется varb1, а вчера как-то по-другому.

В общем, в скрипте проще сделать поиск подобных штук в коде страницы, и выставлять их уже потом (а не жестко).
Там просто ищу блок ajaxSetup до символа "}" и в нём ищу по шаблону установленные имена и значения заголовков.
Также с session_params, ищем по шаблону все "session_params.<Название параметра> =", а потом к ним значения во всём коде.

Итого, на этот призрачный момент имеем следующие условия. Заголовок должен быть "X-Var-Document: String" и в POST параметрах должно присутствовать значение "varb1". Вот и все отличия от ранешней работы.

Sony Bravia KDL-32CX523
Спасибо сказали: baat1

28

Re: moonwalk

Здравстуйте, большое спасибо за скрипт. Не поможете в програмировании еще не силен только учусь как вытащить все сезоны и серии с плеера например http://online.the-cinema.tv/serial/1af6 … e?season=5 а сделать http://online.the-cinema.tv/serial/6d98 … ;episode=1 и так далее пока не переберет все спасибо еще раз

29

Re: moonwalk

bond пишет:

Здравстуйте, большое спасибо за скрипт. Не поможете в програмировании еще не силен только учусь как вытащить все сезоны и серии с плеера например http://online.the-cinema.tv/serial/1af6 … e?season=5 а сделать http://online.the-cinema.tv/serial/6d98 … ;episode=1 и так далее пока не переберет все спасибо еще раз

На каком языке программируете?

Достаточно загрузить страницу с сериалом с указанным заголовком Referer на этот же домен, чтобы получить страницу и в коде html найти список всех сезонов и серий к выбранному сезону.
Пример на C++Script:

+ открыть спойлер
  sHtml = HmsDownloadURL('http://online.the-cinema.tv/serial/1af6c3479f8179bff8212af26af8e273/iframe?season=5', 'Referer: http://online.the-cinema.tv/');
+ А вот так выглядит участок HTML кода в загруженной странице, который нам интересен

Кусок, где перечислены все сезоны:

<select name="season" id="season" style="width: 90px; margin-top: 3px;">
  <option value="1">Сезон 1</option>
  <option value="2">Сезон 2</option>
  <option value="3">Сезон 3</option>
  <option value="4">Сезон 4</option>
  <option selected="selected" value="5">Сезон 5</option>
  <option value="6">Сезон 6</option>
</select>

Список серий текущего сезона (номер которого был передан в ссылке для загрузки страницы):

<select name="episode" id="episode" style="width: 90px; margin-top: 3px;">
  <option value="1">Серия 1</option>
  <option value="2">Серия 2</option>
  <option value="3">Серия 3</option>
  <option value="4">Серия 4</option>
  <option value="5">Серия 5</option>
  <option value="6">Серия 6</option>
  <option value="7">Серия 7</option>
  <option value="8">Серия 8</option>
  <option value="9">Серия 9</option>
  <option value="10">Серия 10</option>
  <option value="11">Серия 11</option>
  <option value="12">Серия 12</option>
  <option value="13">Серия 13</option>
  <option value="14">Серия 14</option>
  <option value="15">Серия 15</option>
  <option selected="selected" value="16">Серия 16</option></select>

Кусок, где перечислены все варианты переводов:

<select name="translator" id="translator" style="width: 140px; margin-top: 3px;">
  <option selected="selected" value="1af6c3479f8179bff8212af26af8e273">Newstudio</option>
  <option value="f4e0a28b2b11d04cc241a73009fe4184">Первый канал</option>
  <option value="8af51260c22744cacc98cb5a11eef80c">Coldfilm</option>
</select>

Значит, нам нужно распарсить страницу: в цикле искать шаблон серии и создавать ссылки, вида <СсылкаНаВидео>?season=<НомерСезона>&episode=<НомерСерии>.

+ открыть спойлер
///////////////////////////////////////////////////////////////////////////////
// Создание списка серий сериала с Moonwalk.cc
void CreateMoonwallkLinks(string sLink) {
  String sHtml, sData, sSerie, sVal, sHeaders;
  int n, nEpisode, nSeason; TRegExpr RE; THmsScriptMediaItem Item, Folder = PodcastItem;
  
  sHeaders = 'Referer: '+sLink+'/\r\n'+
             'Accept-Encoding: gzip, deflate\r\n'+
             'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0\r\n';
  
  sHtml = HmsDownloadURL(sLink, sHeaders, true);

  // Если нашли в коде выбор эпизодов - значит это сериал
  if (HmsRegExMatch('<select[^>]+id="episode"(.*?)</select>', sHtml, '')) {
    // проверяем, в переданной ссылке для загрузки страницы, указан ли номер сезона?
    if (HmsRegExMatch('season=(\\d+)', sLink, sVal)) {
      nSeason = StrToInt(sVal); // Указан номер сезона
      HmsRegExMatch('<select[^>]+id="episode"(.*?)</select>', sHtml, sData);            // Вырезаем участок кода со списком эпизодов в sData
      RE = TRegExpr.Create('(<option[^>]+value="(.*?)".*?</option>)', PCRE_SINGLELINE); // Создаём объект TRegExpr для поиска шаблона
      if (RE.Search(sData)) do {                                                        // Цикл поиска шаблона
        sSerie = HmsHtmlToText(RE.Match(1));                                            // Получаем имя серии
        // Форматируем номер в два знака
        if (HmsRegExMatch("(\\d+)", sSerie, sVal)) {
          nEpisode = StrToInt(sVal);
          sSerie   = Format("%.2d %s", [nEpisode, ReplaceStr(sSerie, sVal, "")]);
        }
        Item = CreateMediaItem(Folder, sSerie, sLink+'&episode='+RE.Match(2), mpThumbnail, gsTime); // Создаём ссылку на серию с указанием номера эпизода в ссылке
      } while (RE.SearchAgain()); // Конец цикла поиска шаблона
      RE.Free();                  // Освобождаем созданный объект из памяти

    } else if (HmsRegExMatch('<select[^>]+id="season"(.*?)</select>', sHtml, sData)) {
      // В переданной ссылке не указано номер сезона, но есть список сезонов - создаём папки сезонов
      RE = TRegExpr.Create('(<option[^>]+value="(.*?)".*?</option>)', PCRE_SINGLELINE); // Создаём объект TRegExpr для поиска шаблона
      if (RE.Search(sData)) do {                                                        // Цикл поиска шаблона
        sSerie = HmsHtmlToText(RE.Match(1));                                            // Получаем имя Сезона
        Item = CreateFolder(Folder, sSerie, sLink+'?season='+RE.Match(2), mpThumbnail); // Создаём папку сезона, где в ссылке указываем номер сезона
      } while (RE.SearchAgain()); // Конец цикла поиска шаблона
      RE.Free();                  // Освобождаем созданный объект из памяти
    }

  } else {
    // Если НЕ нашли в коде выбор эпизодов - значит это фильм, просто создаём ссылку на видео
    CreateMediaItem(Folder, mpTitle, sLink, mpThumbnail, gsTime);
  }
}

Т.е. в общем случае - чтобы получить список сезонов: нам нужно выделить в отдельную переменную блок со списком сезонов и в цикле искать шаблон строки наименования сезона.

// В переменной sHtml - содержание загруженной страницы
HmsRegExMatch('<select[^>]+id="season"(.*?)</select>', sHtml, sData); // В переменной sData будет текст между <select id="season" и </select>, т.е. со списком сезонов
// Создаём объект TRegExpr для поиска шаблона c двумя группировками (группировка, это то, что попадает между скобок)
// 1) Первая группировка (первые скобки) - это всё то, что будет с <option value= до </option> включительно
// 2) Вторая группировка (вторые скобки) - это всё то, что будет между value=" и "
RE = TRegExpr.Create('(<option[^>]+value="(.*?)".*?</option>)', PCRE_SINGLELINE);
try {                             // Заключаем работу с объектом в блок try ... finally ...
  if (RE.Search(sData)) do {      // Цикл поиска шаблона
    sName = RE.Match(1)           // Получаем значение первой группировки
    sName = HmsHtmlToText(sName); // Преобразуем весь html код в простой текст (избавляемся от тегов) - получаем имя сезона
    sSeasonNum = RE.Match(2);     // Получаем значение второй группировки
    Folder = PodcastItem.AddFolder(sLink+'?season='+sSeasonNum); // Создаём папку с указанной ссылкой и номером сезона
    Folder[mpiTitle] = sName;     // Присваиваем наименование
  } while (RE.SearchAgain());     // Конец цикла поиска шаблона
} finally { RE.Free(); }          // Освобождаем созданный объект из памяти, что бы ни случилось

Подобным образом можно вытащить любую информацию: о списке серий, сезонов, вариантов озвучки.

Sony Bravia KDL-32CX523

30 (2017.02.11 10:42:15 отредактировано bond)

Re: moonwalk

програмирую на php Спасибо попробую переделать если получится :)