Repository: streamlink/streamlink
Pull request #1371 (2017-12-11 15:26:20)

[qq] New Plugin for live.qq.com
Fixed https://github.com/streamlink/streamlink/issues/1190


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index fee4b3a5..7d4ba832 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -157,6 +157,7 @@ pluzz               - france.tv          Yes   Yes   Streams may be geo-restrict
                     - zouzous.fr
                     - france3-reg.. [8]_
 powerapp            powerapp.com.tr      Yes   No
+qq                  live.qq.com          Yes   No
 radionet            - radio.net          Yes   --
                     - radio.at
                     - radio.de
diff --git a/src/streamlink/plugins/qq.py b/src/streamlink/plugins/qq.py
new file mode 100644
index 00000000..ff291b84
--- /dev/null
+++ b/src/streamlink/plugins/qq.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+import re
+
+from streamlink.exceptions import NoStreamsError
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http
+from streamlink.plugin.api import validate
+from streamlink.plugin.api.utils import parse_json
+from streamlink.stream import HLSStream
+
+
+class QQ(Plugin):
+    """Streamlink Plugin for live.qq.com"""
+
+    _data_schema = validate.Schema(
+        {
+            "data": {
+                "hls_url": validate.text
+            }
+        },
+        validate.get("data", {}),
+        validate.get("hls_url")
+    )
+
+    api_url = "http://live.qq.com/api/h5/room?room_id={0}"
+
+    _data_re = re.compile(r"""(?P<data>{.+})""")
+    _url_re = re.compile(r"""https?://(m\.)?live\.qq\.com/(?P<room_id>\d+)""")
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url)
+
+    def _get_streams(self):
+        match = self._url_re.match(self.url)
+        if not match:
+            return
+
+        room_id = match.group("room_id")
+        res = http.get(self.api_url.format(room_id))
+
+        data = self._data_re.search(res.text)
+        if not data:
+            return
+
+        try:
+            hls_url = parse_json(data.group("data"), schema=self._data_schema)
+        except Exception as e:
+            raise NoStreamsError(self.url)
+
+        self.logger.debug("URL={0}".format(hls_url))
+        return {"live": HLSStream(self.session, hls_url)}
+
+
+__plugin__ = QQ


Test patch:
diff --git a/tests/test_plugin_qq.py b/tests/test_plugin_qq.py
new file mode 100644
index 00000000..261740a4
--- /dev/null
+++ b/tests/test_plugin_qq.py
@@ -0,0 +1,41 @@
+import unittest
+
+from streamlink.plugins.qq import QQ
+
+
+class TestPluginQQ(unittest.TestCase):
+    def test_can_handle_url(self):
+        should_match = [
+            "http://live.qq.com/10003715",
+            "http://live.qq.com/10007266",
+            "http://live.qq.com/10039165",
+            "http://m.live.qq.com/10003715",
+            "http://m.live.qq.com/10007266",
+            "http://m.live.qq.com/10039165"
+        ]
+        for url in should_match:
+            self.assertTrue(QQ.can_handle_url(url))
+
+        should_not_match = [
+            "http://live.qq.com/",
+            "http://qq.com/",
+            "http://www.qq.com/"
+        ]
+        for url in should_not_match:
+            self.assertFalse(QQ.can_handle_url(url))
+
+    def test_url_re(self):
+        regex_match_list = [
+            {
+                "data": "http://live.qq.com/10003715",
+                "result": "10003715"
+            },
+            {
+                "data": "http://m.live.qq.com/10039165",
+                "result": "10039165"
+            }
+        ]
+        for m_test in regex_match_list:
+            m = QQ._url_re.match(m_test.get("data"))
+            self.assertIsNotNone(m)
+            self.assertEqual(m_test.get("result"), m.group("room_id"))