Repository: yt-dlp/yt-dlp

Issue:
[Site Request] holodex.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.08.10. 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.08.10**
- [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] The provided URLs do not contain any DRM to the best of my knowledge
- [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.
-->

- Single video: https://holodex.net/watch/8ZdLXELdF9Q
- Single video (dev branch): https://staging.holodex.net/watch/MiuFIzr8bR0
- Playlist (dev branch): https://staging.holodex.net/watch/9kQ2GtvDV3s?playlist=239 
- Playlist: https://holodex.net/watch/9kQ2GtvDV3s?playlist=239 (The site doesn't currently have a page for playlists specifically, more info in description)


## Description

<!--
Provide any additional information.
If work on your issue requires account credentials please provide them or explain how one can obtain them.
-->

Holodex fetches VTuber-related content from Youtube and displaying the watch url contains the Youtube video id, so pretty much everything about the watch page should be the same as Youtube.
Holodex has 2 url's; "holodex.net" and "staging.holodex.com". The second one is just the development branch, so the both urls should use the same methods.

About Playlists: Holodex currently doesn't have a page for viewing a whole playlist, the url for playlists is just the watch url with `?playlist=playlist_id` at the end. 
You can get the video list for a playlist using the `https://holodex.net/api/v2/playlist/playlist_id` (`https://holodex.net/api/v2/playlist/239`) url.



Discussion:
Please dismiss this comment. Sorry for dumb message.

Unless it's deleted on YouTube, "Single video" URLs can be fed into YoutubeIE. Same applies to staging domain.
on holodex: https://holodex.net/watch/8ZdLXELdF9Q
corresponds to: https://www.youtube.com/watch?v=8ZdLXELdF9Q
does the website have any self-hosted content? or is everything youtube embeds?
> does the website have any self-hosted content? or is everything youtube embeds?

Yes, everything in this website is YouTube embed except playlists as far as I can see in the code. https://github.com/HolodexNet/Holodex
Hololive is a group of VTubers (Virtual youTubers), so it'll always be YouTube embed.

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
@@ -640,6 +640,7 @@
 from .historicfilms import HistoricFilmsIE
 from .hitbox import HitboxIE, HitboxLiveIE
 from .hitrecord import HitRecordIE
+from .holodex import HolodexIE
 from .hotnewhiphop import HotNewHipHopIE
 from .hotstar import (
     HotStarIE,
diff --git a/yt_dlp/extractor/holodex.py b/yt_dlp/extractor/holodex.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/holodex.py
@@ -0,0 +1,100 @@
+from .common import InfoExtractor
+from .youtube import YoutubeIE
+from ..utils import traverse_obj
+
+
+class HolodexIE(InfoExtractor):
+    _VALID_URL = r'''(?x)https?://(?:www\.|staging\.)?holodex\.net/(?:
+            api/v2/playlist/(?P<playlist>\d+)|
+            watch/(?P<id>\w+)(?:\?(?:[^#]+&)?playlist=(?P<playlist2>\d+))?
+        )'''
+    _TESTS = [{
+        'url': 'https://holodex.net/watch/9kQ2GtvDV3s',
+        'md5': 'be5ffce2f0feae8ba4c01553abc0f175',
+        'info_dict': {
+            'ext': 'mp4',
+            'id': '9kQ2GtvDV3s',
+            'title': '【おちゃめ機能】ホロライブが吹っ切れた【24人で歌ってみた】',
+            'channel_id': 'UCJFZiqLMntJufDCHc6bQixg',
+            'playable_in_embed': True,
+            'tags': 'count:43',
+            'age_limit': 0,
+            'live_status': 'not_live',
+            'description': 'md5:040e866c09dc4ab899b36479f4b7c7a2',
+            'channel_url': 'https://www.youtube.com/channel/UCJFZiqLMntJufDCHc6bQixg',
+            'upload_date': '20200406',
+            'uploader_url': 'http://www.youtube.com/channel/UCJFZiqLMntJufDCHc6bQixg',
+            'view_count': int,
+            'channel': 'hololive ホロライブ - VTuber Group',
+            'categories': ['Music'],
+            'uploader': 'hololive ホロライブ - VTuber Group',
+            'channel_follower_count': int,
+            'uploader_id': 'UCJFZiqLMntJufDCHc6bQixg',
+            'availability': 'public',
+            'thumbnail': 'https://i.ytimg.com/vi_webp/9kQ2GtvDV3s/maxresdefault.webp',
+            'duration': 263,
+            'like_count': int,
+        },
+    }, {
+        'url': 'https://holodex.net/api/v2/playlist/239',
+        'info_dict': {
+            'id': '239',
+            'title': 'Songs/Videos that made fall into the rabbit hole (from my google activity history)',
+        },
+        'playlist_count': 14,
+    }, {
+        'url': 'https://holodex.net/watch/_m2mQyaofjI?foo=bar&playlist=69',
+        'info_dict': {
+            'id': '69',
+            'title': '拿著金斧頭的藍髮大姊姊'
+        },
+        'playlist_count': 3,
+    }, {
+        'url': 'https://holodex.net/watch/_m2mQyaofjI?playlist=69',
+        'info_dict': {
+            'id': '_m2mQyaofjI',
+            'ext': 'mp4',
+            'playable_in_embed': True,
+            'like_count': int,
+            'uploader': 'Ernst / エンスト',
+            'duration': 11,
+            'uploader_url': 'http://www.youtube.com/channel/UCqSX4PPZY0cyetqKVY_wRVA',
+            'categories': ['Entertainment'],
+            'title': '【星街すいせい】星街向你獻上晚安',
+            'upload_date': '20210705',
+            'description': 'md5:8b8ffb157bae77f2d109021a0b577d4a',
+            'channel': 'Ernst / エンスト',
+            'channel_id': 'UCqSX4PPZY0cyetqKVY_wRVA',
+            'channel_follower_count': int,
+            'view_count': int,
+            'tags': [],
+            'live_status': 'not_live',
+            'channel_url': 'https://www.youtube.com/channel/UCqSX4PPZY0cyetqKVY_wRVA',
+            'availability': 'public',
+            'thumbnail': 'https://i.ytimg.com/vi_webp/_m2mQyaofjI/maxresdefault.webp',
+            'age_limit': 0,
+            'uploader_id': 'UCqSX4PPZY0cyetqKVY_wRVA',
+            'comment_count': int,
+        },
+        'params': {'noplaylist': True},
+    }, {
+        'url': 'https://staging.holodex.net/api/v2/playlist/125',
+        'only_matching': True,
+    }, {
+        'url': 'https://staging.holodex.net/watch/rJJTJA_T_b0?playlist=25',
+        'only_matching': True,
+    }, {
+        'url': 'https://staging.holodex.net/watch/s1ifBeukThg',
+        'only_matching': True,
+    }]
+
+    def _real_extract(self, url):
+        video_id, playlist_id, pl_id2 = self._match_valid_url(url).group('id', 'playlist', 'playlist2')
+        playlist_id = playlist_id or pl_id2
+
+        if not self._yes_playlist(playlist_id, video_id):
+            return self.url_result(f'https://www.youtube.com/watch?v={video_id}', YoutubeIE)
+
+        data = self._download_json(f'https://holodex.net/api/v2/playlist/{playlist_id}', playlist_id)
+        return self.playlist_from_matches(
+            traverse_obj(data, ('videos', ..., 'id')), playlist_id, data.get('name'), ie=YoutubeIE)


Test patch: