allstar.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import functools
  2. import json
  3. from .common import InfoExtractor
  4. from ..utils import (
  5. ExtractorError,
  6. OnDemandPagedList,
  7. int_or_none,
  8. join_nonempty,
  9. parse_qs,
  10. urljoin,
  11. )
  12. from ..utils.traversal import traverse_obj
  13. _FIELDS = '''
  14. _id
  15. clipImageSource
  16. clipImageThumb
  17. clipLink
  18. clipTitle
  19. createdDate
  20. shareId
  21. user { _id }
  22. username
  23. views'''
  24. _EXTRA_FIELDS = '''
  25. clipLength
  26. clipSizeBytes'''
  27. _QUERIES = {
  28. 'clip': '''query ($id: String!) {
  29. video: getClip(clipIdentifier: $id) {
  30. %s %s
  31. }
  32. }''' % (_FIELDS, _EXTRA_FIELDS), # noqa: UP031
  33. 'montage': '''query ($id: String!) {
  34. video: getMontage(clipIdentifier: $id) {
  35. %s
  36. }
  37. }''' % _FIELDS, # noqa: UP031
  38. 'Clips': '''query ($page: Int!, $user: String!, $game: Int) {
  39. videos: clips(search: createdDate, page: $page, user: $user, mobile: false, game: $game) {
  40. data { %s %s }
  41. }
  42. }''' % (_FIELDS, _EXTRA_FIELDS), # noqa: UP031
  43. 'Montages': '''query ($page: Int!, $user: String!) {
  44. videos: montages(search: createdDate, page: $page, user: $user) {
  45. data { %s }
  46. }
  47. }''' % _FIELDS, # noqa: UP031
  48. 'Mobile Clips': '''query ($page: Int!, $user: String!) {
  49. videos: clips(search: createdDate, page: $page, user: $user, mobile: true) {
  50. data { %s %s }
  51. }
  52. }''' % (_FIELDS, _EXTRA_FIELDS), # noqa: UP031
  53. }
  54. class AllstarBaseIE(InfoExtractor):
  55. @staticmethod
  56. def _parse_video_data(video_data):
  57. def media_url_or_none(path):
  58. return urljoin('https://media.allstar.gg/', path)
  59. info = traverse_obj(video_data, {
  60. 'id': ('_id', {str}),
  61. 'display_id': ('shareId', {str}),
  62. 'title': ('clipTitle', {str}),
  63. 'url': ('clipLink', {media_url_or_none}),
  64. 'thumbnails': (('clipImageThumb', 'clipImageSource'), {'url': {media_url_or_none}}),
  65. 'duration': ('clipLength', {int_or_none}),
  66. 'filesize': ('clipSizeBytes', {int_or_none}),
  67. 'timestamp': ('createdDate', {functools.partial(int_or_none, scale=1000)}),
  68. 'uploader': ('username', {str}),
  69. 'uploader_id': ('user', '_id', {str}),
  70. 'view_count': ('views', {int_or_none}),
  71. })
  72. if info.get('id') and info.get('url'):
  73. basename = 'clip' if '/clips/' in info['url'] else 'montage'
  74. info['webpage_url'] = f'https://allstar.gg/{basename}?{basename}={info["id"]}'
  75. info.update({
  76. 'extractor_key': AllstarIE.ie_key(),
  77. 'extractor': AllstarIE.IE_NAME,
  78. 'uploader_url': urljoin('https://allstar.gg/u/', info.get('uploader_id')),
  79. })
  80. return info
  81. def _call_api(self, query, variables, path, video_id=None, note=None):
  82. response = self._download_json(
  83. 'https://a1.allstar.gg/graphql', video_id, note=note,
  84. headers={'content-type': 'application/json'},
  85. data=json.dumps({'variables': variables, 'query': query}).encode())
  86. errors = traverse_obj(response, ('errors', ..., 'message', {str}))
  87. if errors:
  88. raise ExtractorError('; '.join(errors))
  89. return traverse_obj(response, path)
  90. class AllstarIE(AllstarBaseIE):
  91. _VALID_URL = r'https?://(?:www\.)?allstar\.gg/(?P<type>(?:clip|montage))\?(?P=type)=(?P<id>[^/?#&]+)'
  92. _TESTS = [{
  93. 'url': 'https://allstar.gg/clip?clip=64482c2da9eec30008a67d1b',
  94. 'info_dict': {
  95. 'id': '64482c2da9eec30008a67d1b',
  96. 'title': '4K on Inferno',
  97. 'url': 'md5:66befb5381eef0c9456026386c25fa55',
  98. 'thumbnail': r're:https://media\.allstar\.gg/.+\.(?:png|jpg)$',
  99. 'uploader': 'chrk.',
  100. 'ext': 'mp4',
  101. 'duration': 20,
  102. 'filesize': 21199257,
  103. 'timestamp': 1682451501,
  104. 'uploader_id': '62b8bdfc9021052f7905882d',
  105. 'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
  106. 'upload_date': '20230425',
  107. 'view_count': int,
  108. },
  109. }, {
  110. 'url': 'https://allstar.gg/clip?clip=8LJLY4JKB',
  111. 'info_dict': {
  112. 'id': '64a1ec6b887f4c0008dc50b8',
  113. 'display_id': '8LJLY4JKB',
  114. 'title': 'AK-47 3K on Mirage',
  115. 'url': 'md5:dde224fd12f035c0e2529a4ae34c4283',
  116. 'ext': 'mp4',
  117. 'thumbnail': r're:https://media\.allstar\.gg/.+\.(?:png|jpg)$',
  118. 'duration': 16,
  119. 'filesize': 30175859,
  120. 'timestamp': 1688333419,
  121. 'uploader': 'cherokee',
  122. 'uploader_id': '62b8bdfc9021052f7905882d',
  123. 'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
  124. 'upload_date': '20230702',
  125. 'view_count': int,
  126. },
  127. }, {
  128. 'url': 'https://allstar.gg/montage?montage=643e64089da7e9363e1fa66c',
  129. 'info_dict': {
  130. 'id': '643e64089da7e9363e1fa66c',
  131. 'display_id': 'APQLGM2IMXW',
  132. 'title': 'cherokee Rapid Fire Snipers Montage',
  133. 'url': 'md5:a3ee356022115db2b27c81321d195945',
  134. 'thumbnail': r're:https://media\.allstar\.gg/.+\.(?:png|jpg)$',
  135. 'ext': 'mp4',
  136. 'timestamp': 1681810448,
  137. 'uploader': 'cherokee',
  138. 'uploader_id': '62b8bdfc9021052f7905882d',
  139. 'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
  140. 'upload_date': '20230418',
  141. 'view_count': int,
  142. },
  143. }, {
  144. 'url': 'https://allstar.gg/montage?montage=RILJMH6QOS',
  145. 'info_dict': {
  146. 'id': '64a2697372ce3703de29e868',
  147. 'display_id': 'RILJMH6QOS',
  148. 'title': 'cherokee Rapid Fire Snipers Montage',
  149. 'url': 'md5:d5672e6f88579730c2310a80fdbc4030',
  150. 'thumbnail': r're:https://media\.allstar\.gg/.+\.(?:png|jpg)$',
  151. 'ext': 'mp4',
  152. 'timestamp': 1688365434,
  153. 'uploader': 'cherokee',
  154. 'uploader_id': '62b8bdfc9021052f7905882d',
  155. 'uploader_url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d',
  156. 'upload_date': '20230703',
  157. 'view_count': int,
  158. },
  159. }]
  160. def _real_extract(self, url):
  161. query_id, video_id = self._match_valid_url(url).group('type', 'id')
  162. return self._parse_video_data(
  163. self._call_api(
  164. _QUERIES.get(query_id), {'id': video_id}, ('data', 'video'), video_id))
  165. class AllstarProfileIE(AllstarBaseIE):
  166. _VALID_URL = r'https?://(?:www\.)?allstar\.gg/(?:profile\?user=|u/)(?P<id>[^/?#&]+)'
  167. _TESTS = [{
  168. 'url': 'https://allstar.gg/profile?user=62b8bdfc9021052f7905882d',
  169. 'info_dict': {
  170. 'id': '62b8bdfc9021052f7905882d-clips',
  171. 'title': 'cherokee - Clips',
  172. },
  173. 'playlist_mincount': 15,
  174. }, {
  175. 'url': 'https://allstar.gg/u/cherokee?game=730&view=Clips',
  176. 'info_dict': {
  177. 'id': '62b8bdfc9021052f7905882d-clips-730',
  178. 'title': 'cherokee - Clips - 730',
  179. },
  180. 'playlist_mincount': 15,
  181. }, {
  182. 'url': 'https://allstar.gg/u/62b8bdfc9021052f7905882d?view=Montages',
  183. 'info_dict': {
  184. 'id': '62b8bdfc9021052f7905882d-montages',
  185. 'title': 'cherokee - Montages',
  186. },
  187. 'playlist_mincount': 4,
  188. }, {
  189. 'url': 'https://allstar.gg/profile?user=cherokee&view=Mobile Clips',
  190. 'info_dict': {
  191. 'id': '62b8bdfc9021052f7905882d-mobile',
  192. 'title': 'cherokee - Mobile Clips',
  193. },
  194. 'playlist_mincount': 1,
  195. }]
  196. _PAGE_SIZE = 10
  197. def _get_page(self, user_id, display_id, game, query, page_num):
  198. page_num += 1
  199. for video_data in self._call_api(
  200. query, {
  201. 'user': user_id,
  202. 'page': page_num,
  203. 'game': game,
  204. }, ('data', 'videos', 'data'), display_id, f'Downloading page {page_num}'):
  205. yield self._parse_video_data(video_data)
  206. def _real_extract(self, url):
  207. display_id = self._match_id(url)
  208. profile_data = self._download_json(
  209. urljoin('https://api.allstar.gg/v1/users/profile/', display_id), display_id)
  210. user_id = traverse_obj(profile_data, ('data', ('_id'), {str}))
  211. if not user_id:
  212. raise ExtractorError('Unable to extract the user id')
  213. username = traverse_obj(profile_data, ('data', 'profile', ('username'), {str}))
  214. url_query = parse_qs(url)
  215. game = traverse_obj(url_query, ('game', 0, {int_or_none}))
  216. query_id = traverse_obj(url_query, ('view', 0), default='Clips')
  217. if query_id not in ('Clips', 'Montages', 'Mobile Clips'):
  218. raise ExtractorError(f'Unsupported playlist URL type {query_id!r}')
  219. return self.playlist_result(
  220. OnDemandPagedList(
  221. functools.partial(
  222. self._get_page, user_id, display_id, game, _QUERIES.get(query_id)), self._PAGE_SIZE),
  223. playlist_id=join_nonempty(user_id, query_id.lower().split()[0], game),
  224. playlist_title=join_nonempty((username or display_id), query_id, game, delim=' - '))