Repository: yt-dlp/yt-dlp

Issue:
[mixch:archive] add support for /archive/ URLs (closes #2363)
## Please follow the guide below

- You will be asked some questions, please read them **carefully** and answer honestly
- Put an `x` into all the boxes [ ] relevant to your *pull request* (like that [x])
- Use *Preview* tab to see how your *pull request* will actually look like

---

### Before submitting a *pull request* make sure you have:
- [x] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [x] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests
- [x] Checked the code with [flake8](https://pypi.python.org/pypi/flake8)

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check one of the following options:
- [x] I am the original author of this code and I am willing to release it under [Unlicense](http://unlicense.org/)
- [ ] I am not the original author of this code but it is in public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence)

### What is the purpose of your *pull request*?
- [ ] Bug fix
- [ ] Improvement
- [x] New extractor
- [ ] New feature

---

### Description of your *pull request* and other information

Add support for /archive/ URLs for mixch.tv. Paid video.

Too few fields? I'm serious. (see below)
![image](https://user-images.githubusercontent.com/10355528/149869780-3a9489e7-20ea-4a8b-a73a-f745c6e00f30.png)

Closes #2363


Discussion:
~~Since it's just html5, why doesn't generic catch it?~~

Edit: nvm, I see it now

Resolving patch:
diff --git a/yt_dlp/extractor/mixch.py b/yt_dlp/extractor/mixch.py
--- a/yt_dlp/extractor/mixch.py
+++ b/yt_dlp/extractor/mixch.py
@@ -1,5 +1,7 @@
 from .common import InfoExtractor
-from ..utils import UserNotLive, traverse_obj
+from ..networking.exceptions import HTTPError
+from ..utils import ExtractorError, UserNotLive, url_or_none
+from ..utils.traversal import traverse_obj
 
 
 class MixchIE(InfoExtractor):
@@ -60,22 +62,38 @@ class MixchArchiveIE(InfoExtractor):
         'skip': 'paid video, no DRM. expires at Jan 23',
         'info_dict': {
             'id': '421',
+            'ext': 'mp4',
             'title': '96NEKO SHOW TIME',
         }
+    }, {
+        'url': 'https://mixch.tv/archive/1213',
+        'skip': 'paid video, no DRM. expires at Dec 31, 2023',
+        'info_dict': {
+            'id': '1213',
+            'ext': 'mp4',
+            'title': '【特別トーク番組アーカイブス】Merm4id×燐舞曲 2nd LIVE「VERSUS」',
+            'release_date': '20231201',
+            'thumbnail': str,
+        }
+    }, {
+        'url': 'https://mixch.tv/archive/1214',
+        'only_matching': True,
     }]
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
-        webpage = self._download_webpage(url, video_id)
 
-        html5_videos = self._parse_html5_media_entries(
-            url, webpage.replace('video-js', 'video'), video_id, 'hls')
-        if not html5_videos:
-            self.raise_login_required(method='cookies')
-        infodict = html5_videos[0]
-        infodict.update({
-            'id': video_id,
-            'title': self._html_search_regex(r'class="archive-title">(.+?)</', webpage, 'title')
-        })
+        try:
+            info_json = self._download_json(
+                f'https://mixch.tv/api-web/archive/{video_id}', video_id)['archive']
+        except ExtractorError as e:
+            if isinstance(e.cause, HTTPError) and e.cause.status == 401:
+                self.raise_login_required()
+            raise
 
-        return infodict
+        return {
+            'id': video_id,
+            'title': traverse_obj(info_json, ('title', {str})),
+            'formats': self._extract_m3u8_formats(info_json['archiveURL'], video_id),
+            'thumbnail': traverse_obj(info_json, ('thumbnailURL', {url_or_none})),
+        }


Test patch: