Repository: yt-dlp/yt-dlp

Issue:
9gag.com - NineGagIE - InfoExtractor - add Uploader info to the returned metadata
### Checklist

- [X] I'm requesting a site-specific feature
- [X] I've verified that I'm running yt-dlp version **2022.07.18** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [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 I'm willing to share it if required

### Region

_No response_

### Example URLs

https://9gag.com/gag/a119eY2 (Anonymous Uploader)
https://9gag.com/gag/ajgp66G (Non-Anonymous Uploader)

### Provide a description that is worded well enough to be understood

9gag recently added the uploader of a post on the website.

I would like to know if you could add uploader informations to the return metadata if the uploader isn't anonymous.

That could be done with the already extracted JSON stored by the variable `post`.
Then it's a matter of getting the `creator = post.get('creator')`, if it is not `null` we can get :

`uploader =  creator['fullName']`,
`uploader_id = creator['username']`,
`uploader_url = url_or_none(creator[('profileUrl'])`

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
No verbose it's just a request feature to add addiotional metadata to an already existing InfoExtractor.
```



Discussion:
> No verbose it's just a request feature to add addiotional metadata to an already existing InfoExtractor.

You realize the field is mandatory for a reason, right? I am sick of people evading the issue template 😩 

The point is to show us what you have tried to get the requested "feature" and so make sure it doesn't already exist. Too many times, we get requests for features that are already supported
> The point is to show us what you have tried to get the requested "feature" and so make sure it doesn't already exist.

I looked up at the NineGag InfoExtractor it's only 128 lines, it never return nor get this specific data. So no matter how hard you will try, how many verbose you print, it will never appear.

`./extractor/ninegag.py` lines 115-128
```python
  class NineGagIE(InfoExtractor):
    def _real_extract(self, url) :
        return {
            'id': post_id,
            'title': title,
            'timestamp': int_or_none(post.get('creationTs')),
            'duration': duration,
            'formats': formats,
            'thumbnails': thumbnails,
            'like_count': get_count('upVote'),
            'dislike_count': get_count('downVote'),
            'comment_count': get_count('comments'),
            'age_limit': 18 if post.get('nsfw') == 1 else None,
            'categories': [section] if section else None,
            'tags': tags,
        }
```
Sure, you were competent enough to provide sufficient information without needing verbose log. Hence why I did not mark the issue as `incomplete`. But the fact is that those who are not similarly competent also generally think that they are! Having the verbose makes it easier to look for user error at a glance without me actually having to check the code/trying to reproduce. When I am dealing with dozens of issues every day, it does make a difference. It often also reveals issues that even experienced users may overlook. I hope you get where I am coming from

---

That aside, since this is a pretty simple issue, and you seem comfortable modifying the code, would you like to make a PR?
Working on it !

Resolving patch:
diff --git a/yt_dlp/extractor/ninegag.py b/yt_dlp/extractor/ninegag.py
--- a/yt_dlp/extractor/ninegag.py
+++ b/yt_dlp/extractor/ninegag.py
@@ -3,7 +3,7 @@
     ExtractorError,
     determine_ext,
     int_or_none,
-    try_get,
+    traverse_obj,
     unescapeHTML,
     url_or_none,
 )
@@ -11,18 +11,20 @@
 
 class NineGagIE(InfoExtractor):
     IE_NAME = '9gag'
+    IE_DESC = '9GAG'
     _VALID_URL = r'https?://(?:www\.)?9gag\.com/gag/(?P<id>[^/?&#]+)'
 
     _TESTS = [{
         'url': 'https://9gag.com/gag/ae5Ag7B',
         'info_dict': {
             'id': 'ae5Ag7B',
-            'ext': 'mp4',
+            'ext': 'webm',
             'title': 'Capybara Agility Training',
             'upload_date': '20191108',
             'timestamp': 1573237208,
+            'thumbnail': 'https://img-9gag-fun.9cache.com/photo/ae5Ag7B_460s.jpg',
             'categories': ['Awesome'],
-            'tags': ['Weimaraner', 'American Pit Bull Terrier'],
+            'tags': ['Awesome'],
             'duration': 44,
             'like_count': int,
             'dislike_count': int,
@@ -32,6 +34,26 @@ class NineGagIE(InfoExtractor):
         # HTML escaped title
         'url': 'https://9gag.com/gag/av5nvyb',
         'only_matching': True,
+    }, {
+        # Non Anonymous Uploader
+        'url': 'https://9gag.com/gag/ajgp66G',
+        'info_dict': {
+            'id': 'ajgp66G',
+            'ext': 'webm',
+            'title': 'Master Shifu! Or Splinter! You decide:',
+            'upload_date': '20220806',
+            'timestamp': 1659803411,
+            'thumbnail': 'https://img-9gag-fun.9cache.com/photo/ajgp66G_460s.jpg',
+            'categories': ['Funny'],
+            'tags': ['Funny'],
+            'duration': 26,
+            'like_count': int,
+            'dislike_count': int,
+            'comment_count': int,
+            'uploader': 'Peter Klaus',
+            'uploader_id': 'peterklaus12',
+            'uploader_url': 'https://9gag.com/u/peterklaus12',
+        }
     }]
 
     def _real_extract(self, url):
@@ -46,8 +68,6 @@ def _real_extract(self, url):
                 'The given url does not contain a video',
                 expected=True)
 
-        title = unescapeHTML(post['title'])
-
         duration = None
         formats = []
         thumbnails = []
@@ -98,7 +118,7 @@ def _real_extract(self, url):
                 formats.append(common)
         self._sort_formats(formats)
 
-        section = try_get(post, lambda x: x['postSection']['name'])
+        section = traverse_obj(post, ('postSection', 'name'))
 
         tags = None
         post_tags = post.get('tags')
@@ -110,18 +130,19 @@ def _real_extract(self, url):
                     continue
                 tags.append(tag_key)
 
-        get_count = lambda x: int_or_none(post.get(x + 'Count'))
-
         return {
             'id': post_id,
-            'title': title,
+            'title': unescapeHTML(post.get('title')),
             'timestamp': int_or_none(post.get('creationTs')),
             'duration': duration,
+            'uploader': traverse_obj(post, ('creator', 'fullName')),
+            'uploader_id': traverse_obj(post, ('creator', 'username')),
+            'uploader_url': url_or_none(traverse_obj(post, ('creator', 'profileUrl'))),
             'formats': formats,
             'thumbnails': thumbnails,
-            'like_count': get_count('upVote'),
-            'dislike_count': get_count('downVote'),
-            'comment_count': get_count('comments'),
+            'like_count': int_or_none(post.get('upVoteCount')),
+            'dislike_count': int_or_none(post.get('downVoteCount')),
+            'comment_count': int_or_none(post.get('commentsCount')),
             'age_limit': 18 if post.get('nsfw') == 1 else None,
             'categories': [section] if section else None,
             'tags': tags,


Test patch: