Repository: streamlink/streamlink

Issue:
MICO LIVE plugin request
<!--
Thanks for requesting a plugin!
USE THE TEMPLATE. Otherwise your plugin request may be rejected.

First, see the contribution guidelines and plugin request requirements:
https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink

Plugin requests which fall into the categories we will not implement will be closed immediately.

Also check the list of open and closed plugin requests:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+request%22

Please see the text preview to avoid unnecessary formatting errors.
-->


## Plugin Request

<!-- Replace [ ] with [x] in order to check the box -->
- [ x] This is a plugin request and I have read the contribution guidelines and plugin request requirements.


### Description

 This is a popular live stream app's website which is known as **MICO LIVE** similar to bigo live
<!-- Explain the plugin and site as clearly as you can. What is the site about? Who runs it? What content does it provide? What value does it bring to Streamlink? Etc. -->


### Example stream URLs

<!-- Example URLs for streams are required. Plugin requests which do not have example URLs will be closed. -->

1. https://www.micous.com/live/70314292
2. https://www.micous.com/live/68928340




Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/mico.py b/src/streamlink/plugins/mico.py
new file mode 100644
--- /dev/null
+++ b/src/streamlink/plugins/mico.py
@@ -0,0 +1,72 @@
+import logging
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import validate
+from streamlink.stream import HLSStream
+from streamlink.utils import parse_json
+from streamlink.utils.url import update_scheme
+
+log = logging.getLogger(__name__)
+
+
+class Mico(Plugin):
+    author = None
+    category = None
+    title = None
+
+    url_re = re.compile(r'https?://(?:www\.)?micous\.com/live/\d+')
+    json_data_re = re.compile(r'win._profile\s*=\s*({.*})')
+
+    _json_data_schema = validate.Schema(
+        validate.transform(json_data_re.search),
+        validate.any(None, validate.all(
+            validate.get(1),
+            validate.transform(parse_json),
+            validate.any(None, validate.all({
+                'mico_id': int,
+                'nickname': validate.text,
+                'h5_url': validate.all(
+                    validate.transform(lambda x: update_scheme('http:', x)),
+                    validate.url(),
+                ),
+                'is_live': bool,
+            })),
+        )),
+    )
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls.url_re.match(url) is not None
+
+    def get_author(self):
+        if self.author is not None:
+            return self.author
+
+    def get_category(self):
+        if self.category is not None:
+            return self.category
+
+    def get_title(self):
+        if self.title is not None:
+            return self.title
+
+    def _get_streams(self):
+        json_data = self.session.http.get(self.url, schema=self._json_data_schema)
+
+        if not json_data:
+            log.error('Failed to get JSON data')
+            return
+
+        if not json_data['is_live']:
+            log.info('This stream is no longer online')
+            return
+
+        self.author = json_data['mico_id']
+        self.category = 'Live'
+        self.title = json_data['nickname']
+
+        return HLSStream.parse_variant_playlist(self.session, json_data['h5_url'])
+
+
+__plugin__ = Mico


Test patch:
diff --git a/tests/plugins/test_mico.py b/tests/plugins/test_mico.py
new file mode 100644
--- /dev/null
+++ b/tests/plugins/test_mico.py
@@ -0,0 +1,23 @@
+import unittest
+
+from streamlink.plugins.mico import Mico
+
+
+class TestPluginMico(unittest.TestCase):
+    def test_can_handle_url(self):
+        should_match = [
+            'http://micous.com/live/73750760',
+            'http://www.micous.com/live/73750760',
+            'https://micous.com/live/73750760',
+            'https://www.micous.com/live/73750760',
+        ]
+        for url in should_match:
+            self.assertTrue(Mico.can_handle_url(url))
+
+    def test_can_handle_url_negative(self):
+        should_not_match = [
+            'http://www.micous.com/73750760',
+            'https://example.com/index.html',
+        ]
+        for url in should_not_match:
+            self.assertFalse(Mico.can_handle_url(url))