logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

dangalplay.py (8600B)


  1. import hashlib
  2. import json
  3. import re
  4. import time
  5. from .common import InfoExtractor
  6. from ..networking.exceptions import HTTPError
  7. from ..utils import ExtractorError, int_or_none, join_nonempty, url_or_none
  8. from ..utils.traversal import traverse_obj
  9. class DangalPlayBaseIE(InfoExtractor):
  10. _NETRC_MACHINE = 'dangalplay'
  11. _OTV_USER_ID = None
  12. _LOGIN_HINT = 'Pass credentials as -u "token" -p "USER_ID" where USER_ID is the `otv_user_id` in browser local storage'
  13. _API_BASE = 'https://ottapi.dangalplay.com'
  14. _AUTH_TOKEN = 'jqeGWxRKK7FK5zEk3xCM' # from https://www.dangalplay.com/main.48ad19e24eb46acccef3.js
  15. _SECRET_KEY = 'f53d31a4377e4ef31fa0' # same as above
  16. def _perform_login(self, username, password):
  17. if self._OTV_USER_ID:
  18. return
  19. if username != 'token' or not re.fullmatch(r'[\da-f]{32}', password):
  20. raise ExtractorError(self._LOGIN_HINT, expected=True)
  21. self._OTV_USER_ID = password
  22. def _real_initialize(self):
  23. if not self._OTV_USER_ID:
  24. self.raise_login_required(f'Login required. {self._LOGIN_HINT}', method=None)
  25. def _extract_episode_info(self, metadata, episode_slug, series_slug):
  26. return {
  27. 'display_id': episode_slug,
  28. 'episode_number': int_or_none(self._search_regex(
  29. r'ep-(?:number-)?(\d+)', episode_slug, 'episode number', default=None)),
  30. 'season_number': int_or_none(self._search_regex(
  31. r'season-(\d+)', series_slug, 'season number', default='1')),
  32. 'series': series_slug,
  33. **traverse_obj(metadata, {
  34. 'id': ('content_id', {str}),
  35. 'title': ('display_title', {str}),
  36. 'episode': ('title', {str}),
  37. 'series': ('show_name', {str}, filter),
  38. 'series_id': ('catalog_id', {str}),
  39. 'duration': ('duration', {int_or_none}),
  40. 'release_timestamp': ('release_date_uts', {int_or_none}),
  41. }),
  42. }
  43. def _call_api(self, path, display_id, note='Downloading JSON metadata', fatal=True, query={}):
  44. return self._download_json(
  45. f'{self._API_BASE}/{path}', display_id, note, fatal=fatal,
  46. headers={'Accept': 'application/json'}, query={
  47. 'auth_token': self._AUTH_TOKEN,
  48. 'region': 'IN',
  49. **query,
  50. })
  51. class DangalPlayIE(DangalPlayBaseIE):
  52. IE_NAME = 'dangalplay'
  53. _VALID_URL = r'https?://(?:www\.)?dangalplay.com/shows/(?P<series>[^/?#]+)/(?P<id>(?!episodes)[^/?#]+)/?(?:$|[?#])'
  54. _TESTS = [{
  55. 'url': 'https://www.dangalplay.com/shows/kitani-mohabbat-hai-season-2/kitani-mohabbat-hai-season-2-ep-number-01',
  56. 'info_dict': {
  57. 'id': '647c61dc1e7171310dcd49b4',
  58. 'ext': 'mp4',
  59. 'release_timestamp': 1262304000,
  60. 'episode_number': 1,
  61. 'episode': 'EP 1 | KITANI MOHABBAT HAI SEASON 2',
  62. 'series': 'kitani-mohabbat-hai-season-2',
  63. 'season_number': 2,
  64. 'title': 'EP 1 | KITANI MOHABBAT HAI SEASON 2',
  65. 'release_date': '20100101',
  66. 'duration': 2325,
  67. 'season': 'Season 2',
  68. 'display_id': 'kitani-mohabbat-hai-season-2-ep-number-01',
  69. 'series_id': '645c9ea41e717158ca574966',
  70. },
  71. }, {
  72. 'url': 'https://www.dangalplay.com/shows/milke-bhi-hum-na-mile/milke-bhi-hum-na-mile-ep-number-01',
  73. 'info_dict': {
  74. 'id': '65d31d9ba73b9c3abd14a7f3',
  75. 'ext': 'mp4',
  76. 'episode': 'EP 1 | MILKE BHI HUM NA MILE',
  77. 'release_timestamp': 1708367411,
  78. 'episode_number': 1,
  79. 'season': 'Season 1',
  80. 'title': 'EP 1 | MILKE BHI HUM NA MILE',
  81. 'duration': 156048,
  82. 'release_date': '20240219',
  83. 'season_number': 1,
  84. 'series': 'MILKE BHI HUM NA MILE',
  85. 'series_id': '645c9ea41e717158ca574966',
  86. 'display_id': 'milke-bhi-hum-na-mile-ep-number-01',
  87. },
  88. }]
  89. def _generate_api_data(self, data):
  90. catalog_id = data['catalog_id']
  91. content_id = data['content_id']
  92. timestamp = str(int(time.time()))
  93. unhashed = ''.join((catalog_id, content_id, self._OTV_USER_ID, timestamp, self._SECRET_KEY))
  94. return json.dumps({
  95. 'catalog_id': catalog_id,
  96. 'content_id': content_id,
  97. 'category': '',
  98. 'region': 'IN',
  99. 'auth_token': self._AUTH_TOKEN,
  100. 'id': self._OTV_USER_ID,
  101. 'md5': hashlib.md5(unhashed.encode()).hexdigest(),
  102. 'ts': timestamp,
  103. }, separators=(',', ':')).encode()
  104. def _real_extract(self, url):
  105. series_slug, episode_slug = self._match_valid_url(url).group('series', 'id')
  106. metadata = self._call_api(
  107. f'catalogs/shows/{series_slug}/episodes/{episode_slug}.gzip',
  108. episode_slug, query={'item_language': ''})['data']
  109. try:
  110. details = self._download_json(
  111. f'{self._API_BASE}/v2/users/get_all_details.gzip', episode_slug,
  112. 'Downloading playback details JSON', headers={
  113. 'Accept': 'application/json',
  114. 'Content-Type': 'application/json',
  115. }, data=self._generate_api_data(metadata))['data']
  116. except ExtractorError as e:
  117. if isinstance(e.cause, HTTPError) and e.cause.status == 422:
  118. error_info = traverse_obj(e.cause.response.read().decode(), ({json.loads}, 'error', {dict})) or {}
  119. if error_info.get('code') == '1016':
  120. self.raise_login_required(
  121. f'Your token has expired or is invalid. {self._LOGIN_HINT}', method=None)
  122. elif msg := error_info.get('message'):
  123. raise ExtractorError(msg)
  124. raise
  125. m3u8_url = traverse_obj(details, (
  126. ('adaptive_url', ('adaptive_urls', 'hd', 'hls', ..., 'playback_url')), {url_or_none}, any))
  127. formats, subtitles = self._extract_m3u8_formats_and_subtitles(m3u8_url, episode_slug, 'mp4')
  128. return {
  129. 'formats': formats,
  130. 'subtitles': subtitles,
  131. **self._extract_episode_info(metadata, episode_slug, series_slug),
  132. }
  133. class DangalPlaySeasonIE(DangalPlayBaseIE):
  134. IE_NAME = 'dangalplay:season'
  135. _VALID_URL = r'https?://(?:www\.)?dangalplay.com/shows/(?P<id>[^/?#]+)(?:/(?P<sub>ep-[^/?#]+)/episodes)?/?(?:$|[?#])'
  136. _TESTS = [{
  137. 'url': 'https://www.dangalplay.com/shows/kitani-mohabbat-hai-season-1',
  138. 'playlist_mincount': 170,
  139. 'info_dict': {
  140. 'id': 'kitani-mohabbat-hai-season-1',
  141. },
  142. }, {
  143. 'url': 'https://www.dangalplay.com/shows/kitani-mohabbat-hai-season-1/ep-01-30-1/episodes',
  144. 'playlist_count': 30,
  145. 'info_dict': {
  146. 'id': 'kitani-mohabbat-hai-season-1-ep-01-30-1',
  147. },
  148. }, {
  149. # 1 season only, series page is season page
  150. 'url': 'https://www.dangalplay.com/shows/milke-bhi-hum-na-mile',
  151. 'playlist_mincount': 15,
  152. 'info_dict': {
  153. 'id': 'milke-bhi-hum-na-mile',
  154. },
  155. }]
  156. def _entries(self, subcategories, series_slug):
  157. for subcategory in subcategories:
  158. data = self._call_api(
  159. f'catalogs/shows/items/{series_slug}/subcategories/{subcategory}/episodes.gzip',
  160. series_slug, f'Downloading episodes JSON for {subcategory}', fatal=False, query={
  161. 'order_by': 'asc',
  162. 'status': 'published',
  163. })
  164. for ep in traverse_obj(data, ('data', 'items', lambda _, v: v['friendly_id'])):
  165. episode_slug = ep['friendly_id']
  166. yield self.url_result(
  167. f'https://www.dangalplay.com/shows/{series_slug}/{episode_slug}',
  168. DangalPlayIE, **self._extract_episode_info(ep, episode_slug, series_slug))
  169. def _real_extract(self, url):
  170. series_slug, subcategory = self._match_valid_url(url).group('id', 'sub')
  171. subcategories = [subcategory] if subcategory else traverse_obj(
  172. self._call_api(
  173. f'catalogs/shows/items/{series_slug}.gzip', series_slug,
  174. 'Downloading season info JSON', query={'item_language': ''}),
  175. ('data', 'subcategories', ..., 'friendly_id', {str}))
  176. return self.playlist_result(
  177. self._entries(subcategories, series_slug), join_nonempty(series_slug, subcategory))