shahid.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import json
  2. import math
  3. import re
  4. from .aws import AWSIE
  5. from ..networking.exceptions import HTTPError
  6. from ..utils import (
  7. ExtractorError,
  8. InAdvancePagedList,
  9. clean_html,
  10. int_or_none,
  11. parse_iso8601,
  12. str_or_none,
  13. urlencode_postdata,
  14. )
  15. class ShahidBaseIE(AWSIE):
  16. _AWS_PROXY_HOST = 'api2.shahid.net'
  17. _AWS_API_KEY = '2RRtuMHx95aNI1Kvtn2rChEuwsCogUd4samGPjLh'
  18. _VALID_URL_BASE = r'https?://shahid\.mbc\.net/[a-z]{2}/'
  19. def _handle_error(self, e):
  20. fail_data = self._parse_json(
  21. e.cause.response.read().decode('utf-8'), None, fatal=False)
  22. if fail_data:
  23. faults = fail_data.get('faults', [])
  24. faults_message = ', '.join([clean_html(fault['userMessage']) for fault in faults if fault.get('userMessage')])
  25. if faults_message:
  26. raise ExtractorError(faults_message, expected=True)
  27. def _call_api(self, path, video_id, request=None):
  28. query = {}
  29. if request:
  30. query['request'] = json.dumps(request)
  31. try:
  32. return self._aws_execute_api({
  33. 'uri': '/proxy/v2/' + path,
  34. 'access_key': 'AKIAI6X4TYCIXM2B7MUQ',
  35. 'secret_key': '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',
  36. }, video_id, query)
  37. except ExtractorError as e:
  38. if isinstance(e.cause, HTTPError):
  39. self._handle_error(e)
  40. raise
  41. class ShahidIE(ShahidBaseIE):
  42. _NETRC_MACHINE = 'shahid'
  43. _VALID_URL = ShahidBaseIE._VALID_URL_BASE + r'(?:serie|show|movie)s/[^/]+/(?P<type>episode|clip|movie)-(?P<id>\d+)'
  44. _TESTS = [{
  45. 'url': 'https://shahid.mbc.net/ar/shows/%D9%85%D8%AA%D8%AD%D9%81-%D8%A7%D9%84%D8%AF%D8%AD%D9%8A%D8%AD-%D8%A7%D9%84%D9%85%D9%88%D8%B3%D9%85-1-%D9%83%D9%84%D9%8A%D8%A8-1/clip-816924',
  46. 'info_dict': {
  47. 'id': '816924',
  48. 'ext': 'mp4',
  49. 'title': 'متحف الدحيح الموسم 1 كليب 1',
  50. 'timestamp': 1602806400,
  51. 'upload_date': '20201016',
  52. 'description': 'برومو',
  53. 'duration': 22,
  54. 'categories': ['كوميديا'],
  55. },
  56. 'params': {
  57. # m3u8 download
  58. 'skip_download': True,
  59. },
  60. }, {
  61. 'url': 'https://shahid.mbc.net/ar/movies/%D8%A7%D9%84%D9%82%D9%86%D8%A7%D8%B5%D8%A9/movie-151746',
  62. 'only_matching': True,
  63. }, {
  64. # shahid plus subscriber only
  65. 'url': 'https://shahid.mbc.net/ar/series/%D9%85%D8%B1%D8%A7%D9%8A%D8%A7-2011-%D8%A7%D9%84%D9%85%D9%88%D8%B3%D9%85-1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/episode-90511',
  66. 'only_matching': True,
  67. }, {
  68. 'url': 'https://shahid.mbc.net/en/shows/Ramez-Fi-Al-Shallal-season-1-episode-1/episode-359319',
  69. 'only_matching': True,
  70. }]
  71. def _perform_login(self, username, password):
  72. try:
  73. user_data = self._download_json(
  74. 'https://shahid.mbc.net/wd/service/users/login',
  75. None, 'Logging in', data=json.dumps({
  76. 'email': username,
  77. 'password': password,
  78. 'basic': 'false',
  79. }).encode(), headers={
  80. 'Content-Type': 'application/json; charset=UTF-8',
  81. })['user']
  82. except ExtractorError as e:
  83. if isinstance(e.cause, HTTPError):
  84. self._handle_error(e)
  85. raise
  86. self._download_webpage(
  87. 'https://shahid.mbc.net/populateContext',
  88. None, 'Populate Context', data=urlencode_postdata({
  89. 'firstName': user_data['firstName'],
  90. 'lastName': user_data['lastName'],
  91. 'userName': user_data['email'],
  92. 'csg_user_name': user_data['email'],
  93. 'subscriberId': user_data['id'],
  94. 'sessionId': user_data['sessionId'],
  95. }))
  96. def _real_extract(self, url):
  97. page_type, video_id = self._match_valid_url(url).groups()
  98. if page_type == 'clip':
  99. page_type = 'episode'
  100. playout = self._call_api(
  101. 'playout/new/url/' + video_id, video_id)['playout']
  102. if not self.get_param('allow_unplayable_formats') and playout.get('drm'):
  103. self.report_drm(video_id)
  104. formats = self._extract_m3u8_formats(re.sub(
  105. # https://docs.aws.amazon.com/mediapackage/latest/ug/manifest-filtering.html
  106. r'aws\.manifestfilter=[\w:;,-]+&?',
  107. '', playout['url']), video_id, 'mp4')
  108. # video = self._call_api(
  109. # 'product/id', video_id, {
  110. # 'id': video_id,
  111. # 'productType': 'ASSET',
  112. # 'productSubType': page_type.upper()
  113. # })['productModel']
  114. response = self._download_json(
  115. f'http://api.shahid.net/api/v1_1/{page_type}/{video_id}',
  116. video_id, 'Downloading video JSON', query={
  117. 'apiKey': 'sh@hid0nlin3',
  118. 'hash': 'b2wMCTHpSmyxGqQjJFOycRmLSex+BpTK/ooxy6vHaqs=',
  119. })
  120. data = response.get('data', {})
  121. error = data.get('error')
  122. if error:
  123. raise ExtractorError(
  124. '{} returned error: {}'.format(self.IE_NAME, '\n'.join(error.values())),
  125. expected=True)
  126. video = data[page_type]
  127. title = video['title']
  128. categories = [
  129. category['name']
  130. for category in video.get('genres', []) if 'name' in category]
  131. return {
  132. 'id': video_id,
  133. 'title': title,
  134. 'description': video.get('description'),
  135. 'thumbnail': video.get('thumbnailUrl'),
  136. 'duration': int_or_none(video.get('duration')),
  137. 'timestamp': parse_iso8601(video.get('referenceDate')),
  138. 'categories': categories,
  139. 'series': video.get('showTitle') or video.get('showName'),
  140. 'season': video.get('seasonTitle'),
  141. 'season_number': int_or_none(video.get('seasonNumber')),
  142. 'season_id': str_or_none(video.get('seasonId')),
  143. 'episode_number': int_or_none(video.get('number')),
  144. 'episode_id': video_id,
  145. 'formats': formats,
  146. }
  147. class ShahidShowIE(ShahidBaseIE):
  148. _VALID_URL = ShahidBaseIE._VALID_URL_BASE + r'(?:show|serie)s/[^/]+/(?:show|series)-(?P<id>\d+)'
  149. _TESTS = [{
  150. 'url': 'https://shahid.mbc.net/ar/shows/%D8%B1%D8%A7%D9%85%D8%B2-%D9%82%D8%B1%D8%B4-%D8%A7%D9%84%D8%A8%D8%AD%D8%B1/show-79187',
  151. 'info_dict': {
  152. 'id': '79187',
  153. 'title': 'رامز قرش البحر',
  154. 'description': 'md5:c88fa7e0f02b0abd39d417aee0d046ff',
  155. },
  156. 'playlist_mincount': 32,
  157. }, {
  158. 'url': 'https://shahid.mbc.net/ar/series/How-to-live-Longer-(The-Big-Think)/series-291861',
  159. 'only_matching': True,
  160. }]
  161. _PAGE_SIZE = 30
  162. def _real_extract(self, url):
  163. show_id = self._match_id(url)
  164. product = self._call_api(
  165. 'playableAsset', show_id, {'showId': show_id})['productModel']
  166. playlist = product['playlist']
  167. playlist_id = playlist['id']
  168. show = product.get('show', {})
  169. def page_func(page_num):
  170. playlist = self._call_api(
  171. 'product/playlist', show_id, {
  172. 'playListId': playlist_id,
  173. 'pageNumber': page_num,
  174. 'pageSize': 30,
  175. 'sorts': [{
  176. 'order': 'DESC',
  177. 'type': 'SORTDATE',
  178. }],
  179. })
  180. for product in playlist.get('productList', {}).get('products', []):
  181. product_url = product.get('productUrl', []).get('url')
  182. if not product_url:
  183. continue
  184. yield self.url_result(
  185. product_url, 'Shahid',
  186. str_or_none(product.get('id')),
  187. product.get('title'))
  188. entries = InAdvancePagedList(
  189. page_func,
  190. math.ceil(playlist['count'] / self._PAGE_SIZE),
  191. self._PAGE_SIZE)
  192. return self.playlist_result(
  193. entries, show_id, show.get('title'), show.get('description'))