Repository: yt-dlp/yt-dlp

Issue:
[Site Request] http://saitosan.net/
<!--

######################################################################
  WARNING!
  IGNORING THE FOLLOWING TEMPLATE WILL RESULT IN ISSUE CLOSED AS INCOMPLETE
######################################################################

-->


## Checklist

<!--
Carefully read and work through this check list in order to prevent the most common mistakes and misuse of yt-dlp:
- First of, make sure you are using the latest version of yt-dlp. Run `yt-dlp --version` and ensure your version is 2021.04.03. If it's not, see https://github.com/yt-dlp/yt-dlp on how to update. Issues with outdated version will be REJECTED.
- Make sure that all provided video/audio/playlist URLs (if any) are alive and playable in a browser.
- Make sure that site you are requesting is not dedicated to copyright infringement, see https://github.com/yt-dlp/yt-dlp. yt-dlp does not support such sites. In order for site support request to be accepted all provided example URLs should not violate any copyrights.
- Search the bugtracker for similar site support requests: https://github.com/yt-dlp/yt-dlp. DO NOT post duplicates.
- Finally, put x into all relevant boxes like this [x] (Dont forget to delete the empty space)
-->

- [x] I'm reporting a new site support request
- [x] I've verified that I'm running yt-dlp version **2021.04.03**
- [x] I've checked that all provided URLs are alive and playable in a browser
- [x] I've checked that none of provided URLs violate any copyrights
- [x] I've searched the bugtracker for similar site support requests including closed ones


## Example URLs

<!--
Provide all kinds of example URLs support for which should be included. Replace following example URLs by yours.
-->

- http://saitosan.net/broadcast.html


## Description

<!--
Provide any additional information.
If work on your issue requires account credentials please provide them or explain how one can obtain them.
-->

Japanese live website.



Discussion:
The video is transferred over websocket and would therefore be very hard to implement
The video isn't, only the initial session negotiation is to get the video metadata and URL, so we could probably request the `socket.io` endpoints through HTTPS.

Resolving patch:
diff --git a/yt_dlp/extractor/extractors.py b/yt_dlp/extractor/extractors.py
--- a/yt_dlp/extractor/extractors.py
+++ b/yt_dlp/extractor/extractors.py
@@ -1119,6 +1119,7 @@
     SafariApiIE,
     SafariCourseIE,
 )
+from .saitosan import SaitosanIE
 from .samplefocus import SampleFocusIE
 from .sapo import SapoIE
 from .savefrom import SaveFromIE
diff --git a/yt_dlp/extractor/saitosan.py b/yt_dlp/extractor/saitosan.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/saitosan.py
@@ -0,0 +1,78 @@
+# coding: utf-8
+
+from __future__ import unicode_literals
+
+from .common import InfoExtractor
+from ..utils import ExtractorError, try_get
+
+
+class SaitosanIE(InfoExtractor):
+    IE_NAME = 'Saitosan'
+    _VALID_URL = r'https?://(?:www\.)?saitosan\.net/bview.html\?id=(?P<id>[0-9]+)'
+    _TESTS = [{
+        'url': 'http://www.saitosan.net/bview.html?id=10031846',
+        'info_dict': {
+            'id': '10031846',
+            'ext': 'mp4',
+            'title': '井下原 和弥',
+            'uploader': '井下原 和弥',
+            'thumbnail': 'http://111.171.196.85:8088/921f916f-7f55-4c97-b92e-5d9d0fef8f5f/thumb',
+            'is_live': True,
+        },
+        'params': {
+            # m3u8 download
+            'skip_download': True,
+        },
+        'skip': 'Broadcasts are ephemeral',
+    },
+        {
+        'url': 'http://www.saitosan.net/bview.html?id=10031795',
+        'info_dict': {
+            'id': '10031795',
+            'ext': 'mp4',
+            'title': '橋本',
+            'uploader': '橋本',
+            'thumbnail': 'http://111.171.196.85:8088/1a3933e1-a01a-483b-8931-af15f37f8082/thumb',
+            'is_live': True,
+        },
+        'params': {
+            # m3u8 download
+            'skip_download': True,
+        },
+        'skip': 'Broadcasts are ephemeral',
+    }]
+
+    def _real_extract(self, url):
+        b_id = self._match_id(url)
+
+        base = 'http://hankachi.saitosan-api.net:8002/socket.io/?transport=polling&EIO=3'
+        sid = self._download_socket_json(base, b_id, note='Opening socket').get('sid')
+        base += '&sid=' + sid
+
+        self._download_webpage(base, b_id, note='Polling socket')
+        payload = '420["room_start_join",{"room_id":"%s"}]' % b_id
+        payload = '%s:%s' % (len(payload), payload)
+
+        self._download_webpage(base, b_id, data=payload, note='Polling socket with payload')
+        response = self._download_socket_json(base, b_id, note='Polling socket')
+        if not response.get('ok'):
+            err = response.get('error') or {}
+            raise ExtractorError(
+                '%s said: %s - %s' % (self.IE_NAME, err.get('code', '?'), err.get('msg', 'Unknown')) if err
+                else 'The socket reported that the broadcast could not be joined. Maybe it\'s offline or the URL is incorrect',
+                expected=True, video_id=b_id)
+
+        self._download_webpage(base, b_id, data='26:421["room_finish_join",{}]', note='Polling socket')
+        b_data = self._download_socket_json(base, b_id, note='Getting broadcast metadata from socket')
+        m3u8_url = b_data.get('url')
+
+        self._download_webpage(base, b_id, data='1:1', note='Closing socket', fatal=False)
+
+        return {
+            'id': b_id,
+            'title': b_data.get('name'),
+            'formats': self._extract_m3u8_formats(m3u8_url, b_id, 'mp4', live=True),
+            'thumbnail': m3u8_url.replace('av.m3u8', 'thumb'),
+            'uploader': try_get(b_data, lambda x: x['broadcast_user']['name']),  # same as title
+            'is_live': True
+        }


Test patch: