buhwp31gjs
445 total views, 1 views today
buhwp31gjs
445 total views, 1 views today
hichannel 更改了 token ,現在只需要一個 token 和一個 expires ,因為沒有實際利益 Hoyo 本人也沒在使用這個收聽功能,因此停止維護
—
—
先看一下可播放的實際案例網址
1 |
http://radio-hichannel.cdn.hinet.net/live/pool/hich-ra000073/ra-hls/index.m3u8?token1=ICzRSfp8Qy6wVuIDVIlOLg&token2=cSVWYP2kBp49hiIha41Y-A&expire1=1462880161&expire2=1462908961 |
解讀
—
參考資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#!/usr/bin/env python import time import base64 import hashlib import urllib import urlparse import urllib2 import subprocess from collections import OrderedDict def gen_token(path, timestamp, ip_addr, token_ord): const_str = 'radio@himediaservice#t' cat_str = path + str(timestamp) + ip_addr + const_str + str(token_ord) hashed = hashlib.md5(unicode(cat_str)).digest() b64_md5hash = base64.b64encode(hashed) return b64_md5hash.replace('+', '-').replace('/', '_').replace('=', '') def build_url(): base_url = 'http://radio-hichannel.cdn.hinet.net/' ip_addr = urllib2.urlopen('http://ipinfo.io/ip').read().rstrip() path = '/live/pool/hich-ra000009/ra-hls/' expire1 = int(time.time()) expire2 = expire1 + (60 * 60 * 8) params = OrderedDict([ ('token1', gen_token(path, expire1, ip_addr, 1)), ('token2', gen_token(path, expire2, ip_addr, 2)), ('expire1', expire1), ('expire2', expire2) ]) params_str = urllib.urlencode(params) return urlparse.urljoin(base_url, path + 'index.m3u8') + '?' + params_str def check_url_alive(url): try: urllib2.urlopen(url) return True except urllib2.HTTPError: return False def ffplay(source): _prefix_url = "ffmpeg://{}".format(source) print "HIT RETURN TO QUIT\n" subprocess.call(['mplayer', '-msglevel', 'all=-1', '-cache', '1024', _prefix_url]) def main(): is_alive = None while is_alive != True: url = build_url() is_alive = check_url_alive(url) try: ffplay(url) except KeyboardInterrupt: exit() if __name__ == '__main__': main() |
寫 PHP 的人轉到 python 會遇到的問題有
好好研究一下網頁是如何運作,追到了 radio_proxy.swf 這個檔案,網頁的播放最後就是由這個 Flash 檔案生成播放路徑,這時又要尋找「反組譯」黑科技
可惜的是,閃客 Sothink SWF Decompile 解不開,大概也有反制黑科技的技術吧
還好這種東西有線上版可以用!?…
不知道當時哪來的腦洞想到上網找 online 版,不過結果是美好的,至少是解開有看到類似 ActionScript 的輸出了。
下面是有關產生 token 的片段程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
public function buildPlayUrl(clientIP:String, url:String):String{ if ((clientIP == null)){ } else { (clientIP == null); }; if ((clientIP == null)){ return ("null ip or url"); }; var pos:int = url.lastIndexOf("/"); if (pos >= 0){ } else { throw (new ArgumentError(("error stream url format:" + url))); }; this.debug(("build token => client ip:" + clientIP)); var len:int = url.length; var location:String = url.substr((pos + 1), len).toLowerCase(); var path:String = (("/live/pool/hich-" + location) + "/ra-hls/"); var now:Date = new Date(); now.time = (now.time + ((1000 * 60) * 5)); var expire1:String = this.toSecTimeStr(now.time); now.time = (now.time + (((1000 * 60) * 60) * 8)); var expire2:String = this.toSecTimeStr(now.time); var key1:String = (this.KEYS[0] + "1"); var key2:String = (this.KEYS[0] + "2"); var token1:String = this.buildToken(path, expire1.toString(), clientIP, key1); var token2:String = this.buildToken(path, expire2.toString(), clientIP, key2); var mp4Url:String = ((this.DOMAIN[0] + path) + "index.m3u8"); return (StringUtil.substitute("{0}?token1={1}&token2={2}&expire1={3}&expire2={4}", mp4Url, token1, token2, expire1.toString(), expire2.toString())); } private function toSecTimeStr(timeMillis:Number):String{ var secs:Number = int((timeMillis / 1000)); return (secs.toString()); } private function buildToken(path:String, expire:String, ip:String, key:String):String{ var sec:String = (((path + expire) + ip) + key); var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(sec); var md5:MD5 = new MD5(); var hash:ByteArray = md5.hash(bytes); var token:String = Base64.encodeByteArray(hash); var pat1:RegExp = /\+/gi; var pat2:RegExp = /\//gi; var pat3:RegExp = /=/gi; token = token.replace(pat1, "-").replace(pat2, "_").replace(pat3, ""); return (token); } |
基本上和 python 架構是一樣的,只是反組譯有少東西,最關鍵的 KEYS 沒有解開,只好回頭繼續研究 python
後來證實 python 有關 token 的演算法是正確的
—
token1 字串組成
1 |
path + expire timestamp + IP + Key + 1 |
token2 字串組成
1 |
path + expire timestamp + IP + Key + 2 |
token1 範例,token2 只要將最後的 1 改成 2 即可
1 |
/live/pool/hich-ra000073/ra-hls/146295177759.125.207.208radio@himediaservice#t1 |
然後拿去 md5() 再跑 base64_encode() 就可以得到 Token 了
—
index.m3u8 內容
1 2 3 4 5 6 7 |
http://radio-hichannel.cdn.hinet.net/live/pool/hich-ra000036/ra-hls/index.m3u8?token1=IKGH3KBN_Fy5_mAvsgaibQ&token2=Af9RZuEDZ_yK9TIBmIuFTw&expire1=1462943429&expire2=1462972229 #EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=135680,CODECS="avc1.100.30,mp4a.40.2",RESOLUTION=362x242 hich-ra000036-video=0-audio=128000.m3u8?token1=IKGH3KBN_Fy5_mAvsgaibQ&token2=Af9RZuEDZ_yK9TIBmIuFTw&expire1=1462943429&expire2=1462972229 |
—
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?php header("Content-Type: text/html; charset=utf-8"); date_default_timezone_set("Asia/Taipei"); set_time_limit(0); ini_set('error_reporting',E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); function gen_token( $path, $timestamp, $ip_addr, $token_ord ) { $const_str = 'radio@himediaservice#t'; $cat_str = $path . $timestamp . $ip_addr . $const_str . $token_ord; $hashed = md5($cat_str, true); $b64_md5hash = base64_encode($hashed); return str_replace( '=', '', str_replace( '/', '_', str_replace( '+', '-', $b64_md5hash))); } $path = '/live/pool/hich-ra000018/ra-hls/'; $expire1 = time()+ (60 * 60 * 2); //$expire1 = 1462951777; $expire2 = $expire1 + (60 * 60 * 12); //$expire2 = 1462980577; $ip = $_SERVER['REMOTE_ADDR']; // 執行環境對外的 IP $token1 = gen_token($path, $expire1, $ip, 1); $token2 = gen_token($path, $expire2, $ip, 2); // $m3u8 就是播放路徑 $m3u8 = 'http://radio-hichannel.cdn.hinet.net/live/pool/hich-ra000018/ra-hls/index.m3u8?token1='. $token1 .'&token2=' . $token2 . '&expire1='. $expire1 .'&expire2='. $expire2; //echo $m3u8; $Filename = date('YmdHi00'); exec('/usr/bin/ffmpeg -i "'. $m3u8 .'" -t 01:00:00 /tmp/mp3/'. $Filename .'.mp3 &'); |
—
—
3,008 total views, 5 views today
Full web stack No browser required
—
安裝
參考至
—
取得 ajax 後的完整原始碼
參考至
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
"use strict"; // 单个资源等待时间,避免资源加载后还需要加载其他资源 var resourceWait = 500; var resourceWaitTimer; // 最大等待时间 var maxWait = 5000; var maxWaitTimer; // 资源计数 var resourceCount = 0; // PhantomJS WebPage模块 var page = require('webpage').create(); // NodeJS 系统模块 var system = require('system'); // 从CLI中获取第二个参数为目标URL var url = system.args[1]; // 设置PhantomJS视窗大小 page.viewportSize = { width: 1280, height: 1014 }; // 获取镜像 var capture = function(errCode){ // 外部通过stdout获取页面内容 console.log(page.content); // 清除计时器 clearTimeout(maxWaitTimer); // 任务完成,正常退出 phantom.exit(errCode); }; // 资源请求并计数 page.onResourceRequested = function(req){ resourceCount++; clearTimeout(resourceWaitTimer); }; // 资源加载完毕 page.onResourceReceived = function (res) { // chunk模式的HTTP回包,会多次触发resourceReceived事件,需要判断资源是否已经end if (res.stage !== 'end'){ return; } resourceCount--; if (resourceCount === 0){ // 当页面中全部资源都加载完毕后,截取当前渲染出来的html // 由于onResourceReceived在资源加载完毕就立即被调用了,我们需要给一些时间让JS跑解析任务 // 这里默认预留500毫秒 resourceWaitTimer = setTimeout(capture, resourceWait); } }; // 资源加载超时 page.onResourceTimeout = function(req){ resouceCount--; }; // 资源加载失败 page.onResourceError = function(err){ resourceCount--; }; // 打开页面 page.open(url, function (status) { if (status !== 'success') { phantom.exit(1); } else { // 当改页面的初始html返回成功后,开启定时器 // 当到达最大时间(默认5秒)的时候,截取那一时刻渲染出来的html maxWaitTimer = setTimeout(function(){ capture(2); }, maxWait); } }); |
實際使用
1 |
phantomjs spider.js http://www.bocelife.tw |
—
497 total views, no views today