Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/blackboardcollaborate.py (Python)

Code before:
            'only_matching': True,
        },
        {
            'url': 'https://ca.bbcollab.com/collab/ui/session/playback/load/b6399dcb44df4f21b29ebe581e22479d',
            'only_matching': True,
        },
        {
            'url': 'https://eu.bbcollab.com/recording/51ed7b50810c4444a106e48cefb3e6b5',
            'only_matching': True,
        },
        {
            'url': 'https://au.bbcollab.com/collab/ui/session/playback/load/2bccf7165d7c419ab87afc1ec3f3bb15',
            'only_matching': True,
        },
    ]

    def _call_api(self, region, video_id, api_call='', token=None, note='Downloading JSON metadata', fatal=False):
        return self._download_json(f'https://{region}.bbcollab.com/collab/api/csa/recordings/{video_id}/{api_call}',
                                   video_id, note=note,
                                   headers={'Authorization': f'Bearer {token}'} if token else '', fatal=fatal)

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        region = mobj.group('region')
        video_id = mobj.group('id')
        token = mobj.group('token') or parse_qs(url).get('authToken')

        if video_info := self._call_api(region, video_id, 'data/secure', token, 'Trying auth token'):
            video_extra = self._call_api(region, video_id, token=token, note='Retrieving extra attributes')
        else:
            video_info = self._call_api(region, video_id, 'data', note='Trying fallback', fatal=True)
            video_extra = {}

        duration = int_or_none(video_info.get('duration'), 1000)
        title = video_info.get('name')
        upload_date = video_info.get('created')

        formats = traverse_obj(video_info, ('extStreams', ..., {
            'url': ('streamUrl', {url_or_none}),
            'container': ('contentType', {mimetype2ext}),
            'aspect_ratio': ('aspectRatio'),
        }))

        for cur_format in formats:
            cur_format['filesize'] = int_or_none(video_extra.get('storageSize'))

        subtitles = {}
        for current_subs in video_info.get('subtitles'):
            lang_code = current_subs.get('lang')
            subtitles.setdefault(lang_code, []).append({
                'name': str_or_none(current_subs.get('label')),

Review comment (security):
`parse_qs(url).get('authToken')` will return a list of string(s), not a string
```suggestion
        token = parse_qs(url).get('authToken', [None])[-1]
```

Code after:
            'only_matching': True,
        },
        {
            'url': 'https://eu.bbcollab.com/recording/51ed7b50810c4444a106e48cefb3e6b5',
            'only_matching': True,
        },
        {
            'url': 'https://au.bbcollab.com/collab/ui/session/playback/load/2bccf7165d7c419ab87afc1ec3f3bb15',
            'only_matching': True,
        },
    ]

    def _call_api(self, region, video_id, path=None, token=None, note=None, fatal=False):
        # Ref: https://github.com/blackboard/BBDN-Collab-Postman-REST
        return self._download_json(
            join_nonempty(f'https://{region}.bbcollab.com/collab/api/csa/recordings', video_id, path, delim='/'),
            video_id, note or 'Downloading JSON metadata', fatal=fatal,
            headers={'Authorization': f'Bearer {token}'} if token else None)

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        region = mobj.group('region')
        video_id = mobj.group('id')
        token = parse_qs(url).get('authToken', [None])[-1]

        video_info = self._call_api(region, video_id, path='data/secure', token=token, note='Trying auth token')
        if video_info:
            video_extra = self._call_api(region, video_id, token=token, note='Retrieving extra attributes')
        else:
            video_info = self._call_api(region, video_id, path='data', note='Trying fallback', fatal=True)
            video_extra = {}

        formats = traverse_obj(video_info, ('extStreams', lambda _, v: url_or_none(v['streamUrl']), {
            'url': 'streamUrl',
            'ext': ('contentType', {mimetype2ext}),
            'aspect_ratio': ('aspectRatio', {float_or_none}),
        }))

        if filesize := traverse_obj(video_extra, ('storageSize', {int_or_none})):
            for fmt in formats:
                fmt['filesize'] = filesize

        subtitles = {}
        for subs in traverse_obj(video_info, ('subtitles', lambda _, v: url_or_none(v['url']))):
            subtitles.setdefault(subs.get('lang') or 'und', []).append({
                'name': traverse_obj(subs, ('label', {str})),
                'url': subs['url'],
            })

        for live_chat_url in traverse_obj(video_info, ('chats', ..., 'url', {url_or_none})):
            subtitles.setdefault('live_chat', []).append({'url': live_chat_url})