Another new server in urlparser: typical strategies - oe-mirrors/e2iplayer GitHub Wiki
Hoster pages rarely show the stream url in plain text. These are the tricks you meet most often, and the tools E2iPlayer has against them. They work the same in a resolver (libs/urlparser.py) and in a host.
Always start with the debug log: printDBG(data) shows what the box really received - and the browser's developer tools (Network tab) show which request finally returns the .m3u8/.mp4. Then work backwards from that request.
1. Two kinds of link
Many hosters have a download page and a player page for the same video, e.g. https://hoster.com/f/1f13jq and https://hoster.com/e/1f13jq. The player page (/e/, /embed/, /v/) is the one with the stream - turn the other one into it first:
url = baseUrl.replace('/f/', '/e/')
2. Escaped strings
Code like this hides text in \xNN and \uNNNN escapes:
y["_\x70op"]=["s\x69\x74\u0065Id",3365713],["h\u0074\u0074ps:\u002f/u\x74h..."](/oe-mirrors/e2iplayer/wiki/"s\x69\x74\u0065Id",3365713],["h\u0074\u0074ps:\u002f/u\x74h...")
This helper decodes both kinds and works on Python 2 and 3:
from Plugins.Extensions.IPTVPlayer.libs.e2ijson import loads as json_loads
from Plugins.Extensions.IPTVPlayer.p2p3.manipulateStrings import ensure_str
def jsUnescape(text):
# \xNN and \uNNNN escapes of a JavaScript string -> characters
return re.sub(r'\\x([0-9a-fA-F]{2})|\\u([0-9a-fA-F]{4})',
lambda m: ensure_str(json_loads('"\\u%s"' % (m.group(2) or '00' + m.group(1)))), text)
jsUnescape(code) turns the line above into y["_pop"]=["siteId",3365713],["https://uth..."](/oe-mirrors/e2iplayer/wiki/"siteId",3365713],["https://uth...").
[!NOTE]
unicode_escape()inlibs/urlparserhelper.pydecodes only\uNNNNand returnsbyteson Python 3 - wrap it inensure_str()if you use it. The oldtext.decode('unicode-escape')works only on Python 2.
Other encodings you meet: base64 (atob("...") in the JavaScript) → b64Decode(text) from tools/iptvtools.py (adds missing padding, returns a string; binary=True for bytes), URL encoding → urllib_unquote from p2p3.UrlLib, HTML entities → self.cleanHtmlStr() or ph.clean_html().
3. Packed JavaScript
Very common on JWPlayer hosters:
eval(function(p,a,c,k,e,d){ ... }('2.8="//3-4.5.6/i/7.f";2.o="//3-4.5.6/a/7.b?3=c&e=d";...',25,25,'||MDCore|s|delivery4|...'.split('|'),0,{}))
This is Dean Edwards' packer: the real code is in the first string, the words in the list at the end. It does not need a JavaScript engine - libs/jsunpack.py unpacks it in Python:
from Plugins.Extensions.IPTVPlayer.libs.jsunpack import get_packed_data
if 'function(p,a,c,k,e' in data:
data = get_packed_data(data) # all packed blocks of the page, unpacked
The unpacked code then contains sources:[{file:"..."}], MDCore.wurl="..." or similar, which a regular expression finds. The generic parserJWPLAYER in urlparser.py does exactly this - read it as an example.
4. When code has to run
Some players compute the url with their own JavaScript (string shuffling, small ciphers). If it can't be rewritten in Python with reasonable effort, E2iPlayer can run it:
from Plugins.Extensions.IPTVPlayer.tools.e2ijs import js_execute
jscode = 'var key = %s;\n%s\nprint(decode(key));' % (keyFromPage, decodeFunctionFromPage)
ret = js_execute(jscode, {'timeout_sec': 20})
if ret['sts'] and ret['code'] == 0:
streamUrl = ret['data'].strip()
js_execute()uses QuickJS (/usr/bin/qjs) when it is installed, otherwise duktape (/usr/bin/duk), which the E2iPlayer installer offers. It returns{'sts': ..., 'code': exit code, 'data': output}.- The script has no browser around it: no
window,document,atob, no network. Define what it needs (var window = {};) and give it the data as variables. Its result is what it writes withprint(...). - It costs time on a box (a process per call) - use it for the small decode step, not for whole pages.
5. Tokens, cookies and headers
A stream url that works in the browser and gives 403 on the box usually misses a header. Check the request in the Network tab and give the stream exactly what the browser sent:
streamUrl = urlparser.decorateUrl(streamUrl, {'User-Agent': HTTP_HEADER['User-Agent'], 'Referer': 'https://hoster.com/', 'Origin': 'https://hoster.com'})
- signed urls (
?token=...&expires=...) are bound to the IP and often to the User-Agent of the request that got them - resolve them with the same UA the player will use, and resolve them only when the user starts the video (need_resolve: 1), never when the list is built - a cookie the hoster set on the player page goes along with
'Cookie': ...in the meta - some hosters check the order of requests (player page → API → playlist) - repeat the browser's sequence
6. What not to do
- no fixed sleeps to "wait for the ad", no requests to ad or tracking domains
- no own crypto libraries -
libs/crypto,libs/pyaesandlibs/aesgcmare there for AES - if the hoster needs a solved captcha for every video, it belongs to the captcha helpers (see Other example of host: config options, cookies, cloudflare protection and captchas), not to a resolver that fakes a browser
Based on the original text by Maxbambi, thank you!