# coding: utf-8
from __future__ import unicode_literals
+import itertools
import json
from .common import InfoExtractor
from ..utils import (
ExtractorError,
+ format_field,
int_or_none,
str_or_none,
try_get,
_VALID_URL_BASE = r'https?://(?:www\.)?trovo\.live/'
_HEADERS = {'Origin': 'https://trovo.live'}
+ def _call_api(self, video_id, query=None, data=None):
+ return self._download_json(
+ 'https://gql.trovo.live/', video_id, query=query, data=data,
+ headers={'Accept': 'application/json'})
+
def _extract_streamer_info(self, data):
streamer_info = data.get('streamerInfo') or {}
username = streamer_info.get('userName')
return {
'uploader': streamer_info.get('nickName'),
'uploader_id': str_or_none(streamer_info.get('uid')),
- 'uploader_url': 'https://trovo.live/' + username if username else None,
+ 'uploader_url': format_field(username, template='https://trovo.live/%s'),
}
def _real_extract(self, url):
username = self._match_id(url)
- live_info = self._download_json(
- 'https://gql.trovo.live/', username, query={
- 'query': '''{
+ live_info = self._call_api(username, query={
+ 'query': '''{
getLiveInfo(params: {userName: "%s"}) {
isLive
programInfo {
}
}
}''' % username,
- })['data']['getLiveInfo']
+ })['data']['getLiveInfo']
if live_info.get('isLive') == 0:
raise ExtractorError('%s is offline' % username, expected=True)
program_info = live_info['programInfo']
program_id = program_info['id']
- title = self._live_title(program_info['title'])
+ title = program_info['title']
formats = []
for stream_info in (program_info.get('streamInfo') or []):
'comments': 'mincount:8',
'categories': ['Grand Theft Auto V'],
},
+ 'skip': '404'
}, {
'url': 'https://trovo.live/clip/lc-5285890810184026005',
'only_matching': True,
def _real_extract(self, url):
vid = self._match_id(url)
- resp = self._download_json(
- 'https://gql.trovo.live/', vid, data=json.dumps([{
- 'query': '''{
+ resp = self._call_api(vid, data=json.dumps([{
+ 'query': '''{
batchGetVodDetailInfo(params: {vids: ["%s"]}) {
VodDetailInfos
}
}''' % vid,
- }, {
- 'query': '''{
+ }, {
+ 'query': '''{
getCommentList(params: {appInfo: {postID: "%s"}, pageSize: 1000000000, preview: {}}) {
commentList {
author {
}
}
}''' % vid,
- }]).encode(), headers={
- 'Content-Type': 'application/json',
- })
+ }]).encode())
vod_detail_info = resp[0]['data']['batchGetVodDetailInfo']['VodDetailInfos'][vid]
vod_info = vod_detail_info['vodInfo']
title = vod_info['title']
}
info.update(self._extract_streamer_info(vod_detail_info))
return info
+
+
+class TrovoChannelBaseIE(TrovoBaseIE):
+ def _get_vod_json(self, page, uid):
+ raise NotImplementedError('This method must be implemented by subclasses')
+
+ def _entries(self, uid):
+ for page in itertools.count(1):
+ vod_json = self._get_vod_json(page, uid)
+ vods = vod_json.get('vodInfos', [])
+ for vod in vods:
+ yield self.url_result(
+ 'https://trovo.live/%s/%s' % (self._TYPE, vod.get('vid')),
+ ie=TrovoVodIE.ie_key())
+ has_more = vod_json['hasMore']
+ if not has_more:
+ break
+
+ def _real_extract(self, url):
+ id = self._match_id(url)
+ uid = str(self._call_api(id, query={
+ 'query': '{getLiveInfo(params:{userName:"%s"}){streamerInfo{uid}}}' % id
+ })['data']['getLiveInfo']['streamerInfo']['uid'])
+ return self.playlist_result(self._entries(uid), playlist_id=uid)
+
+
+class TrovoChannelVodIE(TrovoChannelBaseIE):
+ _VALID_URL = r'trovovod:(?P<id>[^\s]+)'
+ IE_DESC = 'All VODs of a trovo.live channel; "trovovod:" prefix'
+
+ _TESTS = [{
+ 'url': 'trovovod:OneTappedYou',
+ 'playlist_mincount': 24,
+ 'info_dict': {
+ 'id': '100719456',
+ },
+ }]
+
+ _QUERY = '{getChannelLtvVideoInfos(params:{pageSize:99,currPage:%d,channelID:%s}){hasMore,vodInfos{vid}}}'
+ _TYPE = 'video'
+
+ def _get_vod_json(self, page, uid):
+ return self._call_api(uid, query={
+ 'query': self._QUERY % (page, uid)
+ })['data']['getChannelLtvVideoInfos']
+
+
+class TrovoChannelClipIE(TrovoChannelBaseIE):
+ _VALID_URL = r'trovoclip:(?P<id>[^\s]+)'
+ IE_DESC = 'All Clips of a trovo.live channel; "trovoclip:" prefix'
+
+ _TESTS = [{
+ 'url': 'trovoclip:OneTappedYou',
+ 'playlist_mincount': 29,
+ 'info_dict': {
+ 'id': '100719456',
+ },
+ }]
+
+ _QUERY = '{getChannelClipVideoInfos(params:{pageSize:99,currPage:%d,channelID:%s,albumType:VOD_CLIP_ALBUM_TYPE_LATEST}){hasMore,vodInfos{vid}}}'
+ _TYPE = 'clip'
+
+ def _get_vod_json(self, page, uid):
+ return self._call_api(uid, query={
+ 'query': self._QUERY % (page, uid)
+ })['data']['getChannelClipVideoInfos']