Repository: yt-dlp/yt-dlp

Issue:
Add YAPPY
### Checklist

- [X] I'm reporting a new site support request
- [X] I've verified that I'm running yt-dlp version **2022.04.08** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [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](https://github.com/ytdl-org/youtube-dl#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free) or contain any [DRM](https://en.wikipedia.org/wiki/Digital_rights_management) to the best of my knowledge
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and am willing to share it if required

### Region

Russian Federation

### Example URLs

- Single video: https://yappy.media/video/47fea6d8586f48d1a0cf96a7342aabd2
- Single video: https://yappy.media/video/ec14622a56f54990893215da3dc7e66e?utm_source=url&utm_medium=share&utm_campaign=video

### Description

`47fea6d8586f48d1a0cf96a7342aabd2` is the id from the URL.
To get the video (with watermark):

1. Create a `GET` request to `https://bl.ritm.media/route/47fea6d8586f48d1a0cf96a7342aabd2.m3u8?guids=fab9c340-09b8-4844-bc62-ae314715e374_368x640_1266859_avc1.42c01f_mp4a.40.5,9033cfae-52fd-440f-8275-95079c3eca54_480x848_1451802_avc1.4d401f_mp4a.40.5,2eb6cad8-a483-4363-a87d-1b2f1d6d95b9_720x1280_2794100_avc1.640029_mp4a.40.2,95c8e3c0-5b82-471c-8fb3-3bbac285973c_1072x1920_4041424_avc1.640029_mp4a.40.2&sign=UMwlS-iDXkF6RnNZuDKZYg&expire=1650724789` (I don't know where the GUIDs, the sign and the expire date are coming from)
2. Download desired video quality from the URL given in the response of the previous request. For 1072p, it's: `https://ant-cdn-1.uma.media/hls-vod/mYsJXS5sT-kr_I-Io3WgyA/1650652983/137/0x5000c500b6da1268/95c8e3c05b82471c8fb33bbac285973c.mp4/index.m3u8?i=1072x1920_4041`
3. Download the files listed in the request in the 2nd step. Example: `https://ant-cdn-1.uma.media/hls-vod/D-MBuA39IwZ94B6AYP6w9w/1650652982/87/0x5000c500b364a3fc/fab9c34009b84844bc62ae314715e374.mp4/[FILENAME].ts`
4. If needed, merge them.
5. Done!

_(Sorry for the bad explanation)_

### Verbose log

```shell
D:\Program Files (x86)\yt-dlp>yt-dlp -vU https://yappy.media/video/47fea6d8586f48d1a0cf96a7342aabd2
[debug] Command-line config: ['-vU', 'https://yappy.media/video/47fea6d8586f48d1a0cf96a7342aabd2']
[debug] Encodings: locale cp1251, fs utf-8, out utf-8, err utf-8, pref cp1251
[debug] yt-dlp version 2022.04.08 [7884ade] (win_exe)
[debug] Python version 3.8.10 (CPython 64bit) - Windows-10-10.0.19044-SP0
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg 4.4.1-full_build-www.gyan.dev (setts), ffprobe 4.4.1-full_build-www.gyan.dev
[debug] Optional libraries: brotli, certifi, Cryptodome, mutagen, sqlite, websockets
[debug] Proxy map: {}
Latest version: 2022.04.08, Current version: 2022.04.08
yt-dlp is up to date (2022.04.08)
[debug] [generic] Extracting URL: https://yappy.media/video/47fea6d8586f48d1a0cf96a7342aabd2
[generic] 47fea6d8586f48d1a0cf96a7342aabd2: Requesting header
WARNING: [generic] Falling back on generic information extractor.
[generic] 47fea6d8586f48d1a0cf96a7342aabd2: Downloading webpage
[generic] 47fea6d8586f48d1a0cf96a7342aabd2: Extracting information
[debug] Looking for video embeds
ERROR: Unsupported URL: https://yappy.media/video/47fea6d8586f48d1a0cf96a7342aabd2
Traceback (most recent call last):
  File "yt_dlp\YoutubeDL.py", line 1408, in wrapper
  File "yt_dlp\YoutubeDL.py", line 1478, in __extract_info
  File "yt_dlp\extractor\common.py", line 641, in extract
  File "yt_dlp\extractor\generic.py", line 4039, in _real_extract
yt_dlp.utils.UnsupportedError: Unsupported URL: https://yappy.media/video/47fea6d8586f48d1a0cf96a7342aabd2
```



Discussion:
>I don't know where the GUIDs, the sign and the expire date are coming from

This url can be obtained from the webpage.

Nice issue :)

By the way, there is a way to get the video without the watermark, but that probably requires setting up root on my phone. Will send the instructions for that though when I will somehow install Wireshark or something on my phone or an Android emulator.

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
@@ -2325,6 +2325,7 @@
     ZenYandexChannelIE,
 )
 from .yapfiles import YapFilesIE
+from .yappy import YappyIE
 from .yesjapan import YesJapanIE
 from .yinyuetai import YinYueTaiIE
 from .yle_areena import YleAreenaIE
diff --git a/yt_dlp/extractor/yappy.py b/yt_dlp/extractor/yappy.py
new file mode 100644
--- /dev/null
+++ b/yt_dlp/extractor/yappy.py
@@ -0,0 +1,99 @@
+from .common import InfoExtractor
+from ..utils import (
+    int_or_none,
+    traverse_obj,
+    unified_timestamp,
+    url_or_none
+)
+
+
+class YappyIE(InfoExtractor):
+    _VALID_URL = r'https?://yappy\.media/video/(?P<id>\w+)'
+    _TESTS = [{
+        'url': 'https://yappy.media/video/47fea6d8586f48d1a0cf96a7342aabd2',
+        'info_dict': {
+            'id': '47fea6d8586f48d1a0cf96a7342aabd2',
+            'ext': 'mp4',
+            'title': 'Куда нажимать? Как снимать? Смотри видос и погнали!🤘🏻',
+            'timestamp': 1661893200,
+            'description': 'Куда нажимать? Как снимать? Смотри видос и погнали!🤘🏻',
+            'thumbnail': 'https://cdn-st.ritm.media/static/pic/thumbnails/0c7c4d73388f47848acaf540d2e2bb8c-thumbnail.jpg',
+            'upload_date': '20220830',
+            'view_count': int,
+            'like_count': int,
+            'uploader_id': '59a0c8c485e5410b9c43474bf4c6a373',
+            'categories': ['Образование и наука', 'Лайфхак', 'Технологии', 'Арт/искусство'],
+            'repost_count': int,
+            'uploader': 'YAPPY',
+        }
+    }, {
+        'url': 'https://yappy.media/video/3862451954ad4bd58ae2ccefddb0bd33',
+        'info_dict': {
+            'id': '3862451954ad4bd58ae2ccefddb0bd33',
+            'ext': 'mp4',
+            'title': 'Опиши свой характер 3 словами🙃\n#психология #дружба #отношения',
+            'timestamp': 1674726985,
+            'like_count': int,
+            'description': 'Опиши свой характер 3 словами🙃\n#психология #дружба #отношения',
+            'uploader_id': '6793ee3581974a3586fc01e157de6c99',
+            'view_count': int,
+            'repost_count': int,
+            'uploader': 'LENA SHTURMAN',
+            'upload_date': '20230126',
+            'thumbnail': 'https://cdn-st.ritm.media/static/pic/user_thumbnails/6e76bb4bbad640b6/9ec84c115b2b1967/1674716171.jpg',
+        }
+    }]
+
+    def _real_extract(self, url):
+        video_id = self._match_id(url)
+        webpage = self._download_webpage(url, video_id)
+        json_ld = self._search_json_ld(webpage, video_id)
+        nextjs_data = self._search_nextjs_data(webpage, video_id)
+
+        media_data = (
+            traverse_obj(
+                nextjs_data, ('props', 'pageProps', ('data', 'OpenGraphParameters')), get_all=False)
+            or self._download_json(f'https://yappy.media/api/video/{video_id}', video_id))
+
+        media_url = traverse_obj(media_data, ('link', {url_or_none})) or ''
+        has_watermark = media_url.endswith('-wm.mp4')
+
+        formats = [{
+            'url': media_url,
+            'ext': 'mp4',
+            'format_note': 'Watermarked' if has_watermark else None,
+            'preference': -10 if has_watermark else None
+        }] if media_url else []
+
+        if has_watermark:
+            formats.append({
+                'url': media_url.replace('-wm.mp4', '.mp4'),
+                'ext': 'mp4'
+            })
+
+        audio_link = traverse_obj(media_data, ('audio', 'link'))
+        if audio_link:
+            formats.append({
+                'url': audio_link,
+                'ext': 'mp3',
+                'acodec': 'mp3',
+                'vcodec': 'none'
+            })
+
+        return {
+            'id': video_id,
+            'title': (json_ld.get('description') or self._html_search_meta(['og:title'], webpage)
+                      or self._html_extract_title(webpage)),
+            'formats': formats,
+            'thumbnail': (media_data.get('thumbnail')
+                          or self._html_search_meta(['og:image', 'og:image:secure_url'], webpage)),
+            'description': (media_data.get('description') or json_ld.get('description')
+                            or self._html_search_meta(['description', 'og:description'], webpage)),
+            'timestamp': unified_timestamp(media_data.get('publishedAt') or json_ld.get('timestamp')),
+            'view_count': int_or_none(media_data.get('viewsCount') or json_ld.get('view_count')),
+            'like_count': int_or_none(media_data.get('likesCount')),
+            'uploader': traverse_obj(media_data, ('creator', 'firstName')),
+            'uploader_id': traverse_obj(media_data, ('creator', ('uuid', 'nickname')), get_all=False),
+            'categories': traverse_obj(media_data, ('categories', ..., 'name')) or None,
+            'repost_count': int_or_none(media_data.get('sharingCount'))
+        }


Test patch: