Repository: streamlink/streamlink

Issue:
Cube.TV Plugin
## Plugin Request

- [x] This is a plugin request and I have read the contribution guidelines and plugin request requirements.


### Description

https://www.cubetv.sg/

**Cube.TV** is a chinese company live streaming plataform that focous on PC and Mobile Games. They are expanding their business worldwide (huge in Brazil right now) with great reward system for streamers and viewers. 
Like Twitch, streamers can interact with their viewers in a chat box, and also get donate by viewers using their currency called "diamonds".
Also, viewers can get prices with daily rewards system and many other features.

Cube.TV literally pays you by how many viewers you got. It depends on the fifth highest number of viewers you got montly.


### Example stream URLs


1. https://www.cubetv.sg/Tecnosh
2. https://www.cubetv.sg/letsgodroid
3. https://www.cubetv.sg/14939646
4. https://www.cubetv.sg/Luladopub


### Additional comments, screenshots, etc.

I'm sure adding cube.tv plugin to streamlink would increase its users.

Thank you for your time!


[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)


Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/cubetv.py b/src/streamlink/plugins/cubetv.py
new file mode 100644
--- /dev/null
+++ b/src/streamlink/plugins/cubetv.py
@@ -0,0 +1,59 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import validate
+from streamlink.stream import HLSStream
+from streamlink import NoStreamsError
+from streamlink.utils import parse_json
+
+class CubeTV(Plugin):
+
+    _url_re = re.compile(r"https?://(www\.)?cubetv\.sg/(?P<channel>[^/]{2,})")
+
+    _channel_info_api_url_base = "https://www.cubetv.sg/studio/info?cube_id={channel}"
+    _stream_data_api_url_base = "https://www.cubetv.sg/studioApi/getStudioSrcBySid?sid={gid}&videoType=1&https=1"
+
+    _channel_info_schema = validate.Schema({
+        u"code" : 1,
+        u"msg": u"success",
+        u"data": {
+            u"gid": validate.text,
+            u"cube_id": validate.text
+        }
+    })
+
+    _stream_data_schema = validate.Schema({
+        u"code" : 1,
+        u"msg": u"success",
+        u"data": {
+            u"video": u"hls",
+            u"video_src": validate.url()
+        }
+    })
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url) is not None
+
+    def _get_api_res(self, user_id):
+        try:
+            res = self.session.http.get(self._channel_info_api_url_base.format(channel=user_id))
+            return res
+        except Exception as e:
+            raise NoStreamsError(self.url)
+
+    def _get_streams(self):
+        user_id = self._url_re.match(self.url).group(2)
+        res = self._get_api_res(user_id)
+        user_gid = self.session.http.json(res, schema=self._channel_info_schema)['data']['gid']
+
+        try:
+            stream_data  = self.session.http.get(self._stream_data_api_url_base.format(gid=user_gid))
+            hls = self.session.http.json(stream_data, schema=self._stream_data_schema)['data']['video_src']
+        except Exception as e:
+            raise NoStreamsError(self.url)
+
+        return HLSStream.parse_variant_playlist(self.session, hls)
+
+
+__plugin__ = CubeTV


Test patch:
diff --git a/tests/plugins/test_cubetv.py b/tests/plugins/test_cubetv.py
new file mode 100644
--- /dev/null
+++ b/tests/plugins/test_cubetv.py
@@ -0,0 +1,15 @@
+from streamlink.plugins.cubetv import CubeTV
+import unittest
+
+class TestPluginCubeTV(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(CubeTV.can_handle_url("https://www.cubetv.sg/Tecnosh"))
+        self.assertTrue(CubeTV.can_handle_url("https://www.cubetv.sg/letsgodroid"))
+        self.assertTrue(CubeTV.can_handle_url("https://www.cubetv.sg/14939646"))
+        self.assertTrue(CubeTV.can_handle_url("https://www.cubetv.sg/Luladopub"))
+        # shouldn't match
+        self.assertFalse(CubeTV.can_handle_url("https://www.cubetv.sg/g/PUBG"))
+        self.assertFalse(CubeTV.can_handle_url("https://www.cubetv.sg/c"))
+        self.assertFalse(CubeTV.can_handle_url("http://www.twitch.tv/"))
+        self.assertFalse(CubeTV.can_handle_url("http://www.qoo10.sg/"))