Repository: streamlink/streamlink
Pull request #136 (2016-11-07 10:27:14)

plugins.goodgame: restored DDOS protection cookie support
As per the discussion on #130 and [chrippa/livestreamer/#1518](https://github.com/chrippa/livestreamer/issues/1518) I have included @intact's patch to restore the DDOS protection cookie handling. 

codecov-io: ## [Current coverage](https://codecov.io/gh/streamlink/streamlink/pull/136?src=pr) is 33.68% (diff: 100%)
> Merging [#136](https://codecov.io/gh/streamlink/streamlink/pull/136?src=pr) into [master](https://codecov.io/gh/streamlink/streamlink/branch/master?src=pr) will not change coverage

```diff
@@             master       #136   diff @@
==========================================
  Files            40         40          
  Lines          5349       5349          
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
  Hits           1802       1802          
  Misses         3547       3547          
  Partials          0          0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [ec64ee2...725b932](https://codecov.io/gh/streamlink/streamlink/compare/ec64ee2a142535054d2256e26d35655c911559ed...725b932adf04abcbe8976c5bbbfe791e982c72bf?src=pr)
beardypig: @flijloku it would be good if you could test this out.
flijloku: @beardypig I replaced the plugin and checked his work on several streamers. Everything is fine.
beardypig: Awesome, thanks for that :)
flijloku: @beardypig Thank you! 

Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index d90da1ff..4cd211c0 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -12,6 +12,7 @@ is limited.
 =================== ==================== ===== ===== ===========================
 Name                URL(s)               Live  VOD   Notes
 =================== ==================== ===== ===== ===========================
+adultswim           adultswim.com        Yes   No    Streams may be geo-restricted, VOD streams are encrypted
 afreeca             afreecatv.com        Yes   No
 afreecatv           afreeca.tv           Yes   No
 aftonbladet         aftonbladet.se       Yes   Yes
diff --git a/src/streamlink/plugins/adultswim.py b/src/streamlink/plugins/adultswim.py
new file mode 100644
index 00000000..abe15b82
--- /dev/null
+++ b/src/streamlink/plugins/adultswim.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+import re
+from pprint import pprint
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, validate
+from streamlink.stream import HLSStream
+from streamlink.utils import parse_json
+
+
+class AdultSwim(Plugin):
+    _user_agent = (
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) "
+        "Chrome/43.0.2357.65 Safari/537.36")
+    API_URL = "http://www.adultswim.com/videos/api/v2/videos/{id}?fields=stream"
+
+    _url_re = re.compile(r"http://www\.adultswim\.com/videos/streams/(.*)")
+    _stream_data_re = re.compile(r".*AS_INITIAL_DATA = (\{.*?});.*", re.M | re.DOTALL)
+
+    _page_data_schema = validate.Schema({
+        u"streams": {
+            validate.text: {
+                u"stream": validate.text
+            }
+        }
+    })
+
+    _api_schema = validate.Schema({
+        u'status': u'ok',
+        u'data': {
+            u'stream': {
+                u'assets': [
+                    {
+                        u'url': validate.url()
+                    }
+                ]
+            }
+        }
+    })
+
+    @classmethod
+    def can_handle_url(cls, url):
+        match = AdultSwim._url_re.match(url)
+        return match is not None
+
+    def _get_streams(self):
+        # get the page
+        res = http.get(self.url, headers={"User-Agent": self._user_agent})
+        # find the big blob of stream info in the page
+        stream_data = self._stream_data_re.match(res.text)
+        stream_name = AdultSwim._url_re.match(self.url).group(1) or "live-stream"
+
+        if stream_data:
+            # parse the stream info as json
+            stream_info = parse_json(stream_data.group(1), schema=self._page_data_schema)
+            # get the stream ID
+            stream_id = stream_info[u"streams"][stream_name][u"stream"]
+
+            if stream_id:
+                api_url = self.API_URL.format(id=stream_id)
+
+                res = http.get(api_url, headers={"User-Agent": self._user_agent})
+                stream_data = http.json(res, schema=self._api_schema)
+
+                for asset in stream_data[u'data'][u'stream'][u'assets']:
+                    for n, s in HLSStream.parse_variant_playlist(self.session, asset[u"url"]).items():
+                        yield n, s
+
+            else:
+                self.logger.error("Couldn't find the stream ID for this stream: {}".format(stream_name))
+        else:
+            self.logger.error("Couldn't find the stream data for this stream: {}".format(stream_name))
+
+__plugin__ = AdultSwim
diff --git a/src/streamlink/plugins/goodgame.py b/src/streamlink/plugins/goodgame.py
index 75979026..14557b77 100644
--- a/src/streamlink/plugins/goodgame.py
+++ b/src/streamlink/plugins/goodgame.py
@@ -14,6 +14,7 @@ QUALITIES = {
 
 _url_re = re.compile("https://(?:www\.)?goodgame.ru/channel/(?P<user>\w+)")
 _stream_re = re.compile(r'var src = "([^"]+)";')
+_ddos_re = re.compile(r'document.cookie="(__DDOS_[^;]+)')
 
 class GoodGame(Plugin):
     @classmethod
@@ -31,6 +32,11 @@ class GoodGame(Plugin):
         }
         res = http.get(self.url, headers=headers)
 
+        match = _ddos_re.search(res.text)
+        if match:
+            headers["Cookie"] = match.group(1)
+            res = http.get(self.url, headers=headers)
+
         match = _stream_re.search(res.text)
         if not match:
             return


Test patch:
diff --git a/tests/test_plugin_adultswim.py b/tests/test_plugin_adultswim.py
new file mode 100644
index 00000000..2fba0fa2
--- /dev/null
+++ b/tests/test_plugin_adultswim.py
@@ -0,0 +1,16 @@
+import unittest
+
+from streamlink.plugins.adultswim import AdultSwim
+
+
+class TestPluginAdultSwim(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/toonami"))
+        self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/"))
+        self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/last-stream-on-the-left"))
+
+        # shouldn't match
+        self.assertFalse(AdultSwim.can_handle_url("http://www.adultswim.com/videos/specials/the-adult-swim-golf-classic-extended/"))
+        self.assertFalse(AdultSwim.can_handle_url("http://www.tvcatchup.com/"))
+        self.assertFalse(AdultSwim.can_handle_url("http://www.youtube.com/"))