Skip to content

Instantly share code, notes, and snippets.

@intact
Last active November 5, 2017 09:30
Show Gist options
  • Save intact/3d6af5f361c1a44fd878 to your computer and use it in GitHub Desktop.
Save intact/3d6af5f361c1a44fd878 to your computer and use it in GitHub Desktop.
Daisuki plugin for livestreamer
import base64
import json
import random
import re
import time
try:
from Crypto import Random
from Crypto.Cipher import AES, PKCS1_v1_5
from Crypto.PublicKey import RSA
from Crypto.Util import number
HAS_CRYPTO = True
except ImportError as e:
HAS_CRYPTO = False
crypto_import_exception = str(e)
from livestreamer.compat import urljoin, urlparse, urlunparse
from livestreamer.exceptions import PluginError
from livestreamer.plugin import Plugin
from livestreamer.plugin.api import http, validate
from livestreamer.plugin.api.utils import parse_json
from livestreamer.stream import HLSStream
HDCORE_VERSION="3.2.0"
_url_re = re.compile(r"https?://www.daisuki.net/[^/]+/[^/]+/anime/watch\..+")
_flashvars_re = re.compile(r"var\s+flashvars\s*=\s*{([^}]*?)};", re.DOTALL)
_flashvar_re = re.compile(r"""(['"])(.*?)\1\s*:\s*(['"])(.*?)\3""")
_clientlibs_re = re.compile(r"""<script.*?src=(['"])(.*?/clientlibs_anime_watch.*?\.js)\1""")
_schema = validate.Schema(
validate.union({
"flashvars": validate.all(
validate.transform(_flashvars_re.search),
validate.get(1),
validate.transform(_flashvar_re.findall),
validate.map(lambda v: (v[1], v[3])),
validate.transform(dict),
{
"s": validate.text,
"country": validate.text,
"init": validate.text,
validate.optional("ss_id"): validate.text,
validate.optional("mv_id"): validate.text,
validate.optional("device_cd"): validate.text,
validate.optional("ss1_prm"): validate.text,
validate.optional("ss2_prm"): validate.text,
validate.optional("ss3_prm"): validate.text
}
),
"clientlibs": validate.all(
validate.transform(_clientlibs_re.search),
validate.get(2),
validate.text
)
})
)
_language_schema = validate.Schema(
validate.xml_findtext("./country_code")
)
_init_schema = validate.Schema(
{
"rtn": validate.all(
validate.text
)
},
validate.get("rtn")
)
def aes_encrypt(key, plaintext):
plaintext = plaintext.encode("utf-8")
aes = AES.new(key, AES.MODE_CBC, number.long_to_bytes(0, AES.block_size))
if len(plaintext) % AES.block_size != 0:
plaintext += b"\0" * (AES.block_size - len(plaintext) % AES.block_size)
return base64.b64encode(aes.encrypt(plaintext))
def aes_decrypt(key, ciphertext):
aes = AES.new(key, AES.MODE_CBC, number.long_to_bytes(0, AES.block_size))
plaintext = aes.decrypt(base64.b64decode(ciphertext))
plaintext = plaintext.strip(b"\0")
return plaintext.decode("utf-8")
def rsa_encrypt(key, plaintext):
pubkey = RSA.importKey(key)
cipher = PKCS1_v1_5.new(pubkey)
return base64.b64encode(cipher.encrypt(plaintext))
def get_public_key(cache, url):
headers = {}
cached = cache.get("clientlibs")
if cached and cached["url"] == url:
headers["If-Modified-Since"] = cached["modified"]
script = http.get(url, headers=headers)
if cached and script.status_code == 304:
return cached["pubkey"]
modified = script.headers.get("Last-Modified", "")
match = re.search(r"""\"(-----BEGIN PUBLIC KEY-----[^"]*?-----END PUBLIC KEY-----)\"""", script.text, re.DOTALL)
if match is None:
return None
pubkey = match.group(1).replace("\\n", "\n")
cache.set("clientlibs", dict(url=url, modified=modified, pubkey=pubkey))
return pubkey
class Daisuki(Plugin):
@classmethod
def can_handle_url(cls, url):
return _url_re.match(url)
def _get_streams(self):
if not HAS_CRYPTO:
raise PluginError("pyCrypto needs to be installed (exception: {0})".format(crypto_import_exception))
page = http.get(self.url, schema=_schema)
if not page:
return
pubkey_pem = get_public_key(self.cache, urljoin(self.url, page["clientlibs"]))
if not pubkey_pem:
raise PluginError("Unable to get public key")
flashvars = page["flashvars"]
params = {
"cashPath":int(time.time()*1000)
}
res = http.get(urljoin(self.url, flashvars["country"]), params=params)
if not res:
return
language = http.xml(res, schema=_language_schema)
api_params = {}
for key in ("ss_id", "mv_id", "device_cd", "ss1_prm", "ss2_prm", "ss3_prm"):
if flashvars.get(key, ""):
api_params[key] = flashvars[key]
aeskey = number.long_to_bytes(random.getrandbits(8*32), 32)
params = {
"s": flashvars["s"],
"c": language,
"e": self.url,
"d": aes_encrypt(aeskey, json.dumps(api_params)),
"a": rsa_encrypt(pubkey_pem, aeskey)
}
res = http.get(urljoin(self.url, flashvars["init"]), params=params)
if not res:
return
rtn = http.json(res, schema=_init_schema)
if not rtn:
return
init_data = parse_json(aes_decrypt(aeskey, rtn))
parsed = urlparse(init_data["play_url"])
if parsed.scheme != "https" or not parsed.path.startswith("/i/") or not parsed.path.endswith("/master.m3u8"):
return
hlsstream_url = init_data["play_url"]
if "caption_url" in init_data:
self.logger.info("Subtitles: {0}".format(init_data["caption_url"]))
return HLSStream.parse_variant_playlist(self.session, hlsstream_url)
__plugin__ = Daisuki
@AlexWFreitas
Copy link

I am having trouble using this plugin since I am receiving the regional site for my country instead of the normal expected site.

Example of url :

http://www.daisuki.net/br/en/anime/watch.ONEPUNCHMAN.12517.html

@intact
Copy link
Author

intact commented Feb 23, 2016

@otaru1921 try new version

@zaockle
Copy link

zaockle commented Mar 15, 2016

im having issues with this plugin. when I have the script in the right location, python 2.7 installed, and pycrypto 2.6 for python 2.7 properly installed, but when I copy-paste any daisuki url thru livestreamer, I get an error of "pyCrypto needs to be installed (exception: cannot import name Random)". Not sure what I am doing wrong. Is there a known version that works better than another?

@intact
Copy link
Author

intact commented Mar 15, 2016

@zaockle Livestreamer contains incomplete pyCrypto

chrippa/livestreamer#199 (comment)

Copy link

ghost commented Apr 7, 2016

got the same error as zaockle tried the fix above now i get

pyCrypto needs to be installed (exception: cannot import name winrandom)

@intact
Copy link
Author

intact commented Apr 7, 2016

You need compiled pyCrypto library. You can extract Crypto folder from pycrypto-2.6.1.win32-py2.7.exe (you can use p7zip)

Copy link

ghost commented Apr 7, 2016

would the pre-compiled files from here work?
http://www.voidspace.org.uk/python/modules.shtml#pycrypto

I have windows 10 64bit.
python 2.7.5 64bit

@intact
Copy link
Author

intact commented Apr 7, 2016

@a4840639
Copy link

a4840639 commented Apr 8, 2016

Is is possible to seek? I tried --player-passthrough hls with no luck, mpv and VLC simply does not run.

@intact
Copy link
Author

intact commented Apr 8, 2016

Seeking is not supported for Akamai streams.

@lae65888
Copy link

lae65888 commented Jul 8, 2016

Daisuki has a new web-player and this plugin can't get the streams anymore, could you update the pligin?
apparently has the same flashvars with some little changes and probably a different location of the RSA Public Key

@intact
Copy link
Author

intact commented Jul 8, 2016

Try new version :)

@lae65888
Copy link

lae65888 commented Jul 8, 2016

Thanks! now is working again
Also apparently all new streams are restricted with a login system for premium users only

@a4840639
Copy link

a4840639 commented Jul 8, 2016

+1 for premium streams

@rs3mk
Copy link

rs3mk commented Jul 10, 2016

C:\livestreamer-v1.12.2>livestreamer http://www.daisuki.net/us/en/anime/watch.TalesofZestiriatheX.13475.html 1080p -o 1080p.ts
[cli][info] Found matching plugin daisuki for URL http://www.daisuki.net/us/en/anime/watch.TalesofZestiriatheX.13475.html
error: Unable to validate response text: Unable to validate union 'flashvars': 'NoneType' object has no attribute '__getitem__'

@intact
Copy link
Author

intact commented Jul 10, 2016

Tales of Zestiria the X is Premium only in US.

@rs3mk
Copy link

rs3mk commented Jul 10, 2016

It is possible to login daisuki premium?

@intact
Copy link
Author

intact commented Jul 10, 2016

No

@Yuuki2012
Copy link

Works with older animes, but recent ones it gives an error:
error: Unable to validate response text: Unable to validate union 'flashvars': 'NoneType' object has no attribute 'getitem'

@intact
Copy link
Author

intact commented Jan 8, 2017

All episodes (but Youtube trailers) works for me (some animes can be geolocked for me).

@Vectaryon
Copy link

Vectaryon commented Jan 14, 2017

Hi, i'm getting the "PyCrypto needs to be installed (exception : No module named winrandom)" when i try to run it.

I did delete Delete Crypto.Cipher._AES.pyd in Livestreamer directory

Then Delete Crypto in Livestreamer\library.zip

then Copy Crypto folder from pycrypto-2.6.1.win32-py2.7.exe

I did theses steps and now i'm getting this error, any idea please?

I'm pretty new & bad to all this stuff tho.

@Vectaryon
Copy link

Nevermind i fixed it, its because i put the Crypto folder in the livestreamer folder AND in library.zip, once i deleted the one on library, this error got fixed....just to shot me another one.

Now i'm getting this : "UnicodeDecodeError: 'ascii" codec can't decode byte 0x92 in position 64: ordiinal no in range<128>"

error

@intact
Copy link
Author

intact commented Jan 14, 2017

Works for me (Windows 8.1, Livestreamer 1.12.2, pycrypto-2.6.1.win32-py2.7)
ls3

@Vectaryon
Copy link

I had pycrypto 2.6 and not 2.6.1 apparently, at least i uninstalled everything & did something clean with 2.6.1 instead & its now working, thanks!

@starchivore
Copy link

I just changed the code a little bit from:

from livestreamer.compat import urljoin, urlparse, urlunparse
from livestreamer.exceptions import PluginError
from livestreamer.plugin import Plugin
from livestreamer.plugin.api import http, validate
from livestreamer.plugin.api.utils import parse_json
from livestreamer.stream import HLSStream

To:

from streamlink.compat import urljoin, urlparse, urlunparse
from streamlink.exceptions import PluginError
from streamlink.plugin import Plugin
from streamlink.plugin.api import http, validate
from streamlink.plugin.api.utils import parse_json
from streamlink.stream import HLSStream

And it's working for my PC (Windows Vista SP2, Streamlink 0.3.2, pycryptodome-3.4.5) with Python 2.7 as shown below:
8panbmp

Is there a way to get that working for either Python 3.5 or 3.6 by any chance?
ozvwsus

Same deal with Python 3.5:
image

Portable version of Streamlink is embedded with Python 3.5.2 and that's why I would like to switch from Python 2.7 to Python 3.5 accordingly:

https://github.com/streamlink/streamlink-portable/blob/master/Streamlink%20for%20Windows%20(Compiled)/Files/Resources/python-3.5.2-embed-win32.zip

Thank you very much and have a great day.

@intact
Copy link
Author

intact commented Feb 15, 2017

Try new version

@starchivore
Copy link

That's totally awesome, it's working great with WinPython 3.5.2.3 so I'm thrilled and thank you again for your help.

I just asked them to look into the portable version of Streamlink so let's see how it goes:

beardypig/streamlink-portable#7

@zodman
Copy link

zodman commented Mar 17, 2017

if the error:

error: Unable to validate response text: Unable to validate union 'flashvars': 'NoneType' object has no attribute 'getitem'

showed its because flashvars its not present ... check with

curl url | grep -i flashvars

It works on with mexico IP address.

the plugin directly not use proxy settings so --http-proxy not works. livestreamer use requests and it had support to HTTP_PROXY HTTPS_PROXY by enviroment so run:

HTTP_PROXY="" HTTPS_PROXY= livestreamer <args>

and works! yes you need https and http proxy

@starchivore
Copy link

Now they're only streaming Dragon Ball Super for free and here's the direct link to the latest episode 114

http://motto.daisuki.net/framewatch/embed/embedDRAGONBALLSUPERUniverseSurvivalsaga/P2e/760/428

Main page:

http://motto.daisuki.net/information/

Could this plugin be updated with a few quick tweaks? Thanks a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment