murrtube.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import functools
  2. import json
  3. from .common import InfoExtractor
  4. from ..utils import (
  5. ExtractorError,
  6. OnDemandPagedList,
  7. determine_ext,
  8. int_or_none,
  9. try_get,
  10. )
  11. class MurrtubeIE(InfoExtractor):
  12. _WORKING = False
  13. _VALID_URL = r'''(?x)
  14. (?:
  15. murrtube:|
  16. https?://murrtube\.net/videos/(?P<slug>[a-z0-9\-]+)\-
  17. )
  18. (?P<id>[a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{12})
  19. '''
  20. _TEST = {
  21. 'url': 'https://murrtube.net/videos/inferno-x-skyler-148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
  22. 'md5': '169f494812d9a90914b42978e73aa690',
  23. 'info_dict': {
  24. 'id': '148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
  25. 'ext': 'mp4',
  26. 'title': 'Inferno X Skyler',
  27. 'description': 'Humping a very good slutty sheppy (roomate)',
  28. 'thumbnail': r're:^https?://.*\.jpg$',
  29. 'duration': 284,
  30. 'uploader': 'Inferno Wolf',
  31. 'age_limit': 18,
  32. 'comment_count': int,
  33. 'view_count': int,
  34. 'like_count': int,
  35. 'tags': ['hump', 'breed', 'Fursuit', 'murrsuit', 'bareback'],
  36. },
  37. }
  38. def _download_gql(self, video_id, op, note=None, fatal=True):
  39. result = self._download_json(
  40. 'https://murrtube.net/graphql',
  41. video_id, note, data=json.dumps(op).encode(), fatal=fatal,
  42. headers={'Content-Type': 'application/json'})
  43. return result['data']
  44. def _real_extract(self, url):
  45. video_id = self._match_id(url)
  46. data = self._download_gql(video_id, {
  47. 'operationName': 'Medium',
  48. 'variables': {
  49. 'id': video_id,
  50. },
  51. 'query': '''\
  52. query Medium($id: ID!) {
  53. medium(id: $id) {
  54. title
  55. description
  56. key
  57. duration
  58. commentsCount
  59. likesCount
  60. viewsCount
  61. thumbnailKey
  62. tagList
  63. user {
  64. name
  65. __typename
  66. }
  67. __typename
  68. }
  69. }'''})
  70. meta = data['medium']
  71. storage_url = 'https://storage.murrtube.net/murrtube/'
  72. format_url = storage_url + meta.get('key', '')
  73. thumbnail = storage_url + meta.get('thumbnailKey', '')
  74. if determine_ext(format_url) == 'm3u8':
  75. formats = self._extract_m3u8_formats(
  76. format_url, video_id, 'mp4', entry_protocol='m3u8_native', fatal=False)
  77. else:
  78. formats = [{'url': format_url}]
  79. return {
  80. 'id': video_id,
  81. 'title': meta.get('title'),
  82. 'description': meta.get('description'),
  83. 'formats': formats,
  84. 'thumbnail': thumbnail,
  85. 'duration': int_or_none(meta.get('duration')),
  86. 'uploader': try_get(meta, lambda x: x['user']['name']),
  87. 'view_count': meta.get('viewsCount'),
  88. 'like_count': meta.get('likesCount'),
  89. 'comment_count': meta.get('commentsCount'),
  90. 'tags': meta.get('tagList'),
  91. 'age_limit': 18,
  92. }
  93. class MurrtubeUserIE(MurrtubeIE): # XXX: Do not subclass from concrete IE
  94. _WORKING = False
  95. IE_DESC = 'Murrtube user profile'
  96. _VALID_URL = r'https?://murrtube\.net/(?P<id>[^/]+)$'
  97. _TEST = {
  98. 'url': 'https://murrtube.net/stormy',
  99. 'info_dict': {
  100. 'id': 'stormy',
  101. },
  102. 'playlist_mincount': 27,
  103. }
  104. _PAGE_SIZE = 10
  105. def _fetch_page(self, username, user_id, page):
  106. data = self._download_gql(username, {
  107. 'operationName': 'Media',
  108. 'variables': {
  109. 'limit': self._PAGE_SIZE,
  110. 'offset': page * self._PAGE_SIZE,
  111. 'sort': 'latest',
  112. 'userId': user_id,
  113. },
  114. 'query': '''\
  115. query Media($q: String, $sort: String, $userId: ID, $offset: Int!, $limit: Int!) {
  116. media(q: $q, sort: $sort, userId: $userId, offset: $offset, limit: $limit) {
  117. id
  118. __typename
  119. }
  120. }'''},
  121. f'Downloading page {page + 1}')
  122. if data is None:
  123. raise ExtractorError(f'Failed to retrieve video list for page {page + 1}')
  124. media = data['media']
  125. for entry in media:
  126. yield self.url_result('murrtube:{}'.format(entry['id']), MurrtubeIE.ie_key())
  127. def _real_extract(self, url):
  128. username = self._match_id(url)
  129. data = self._download_gql(username, {
  130. 'operationName': 'User',
  131. 'variables': {
  132. 'id': username,
  133. },
  134. 'query': '''\
  135. query User($id: ID!) {
  136. user(id: $id) {
  137. id
  138. __typename
  139. }
  140. }'''},
  141. 'Downloading user info')
  142. if data is None:
  143. raise ExtractorError('Failed to fetch user info')
  144. user = data['user']
  145. entries = OnDemandPagedList(functools.partial(
  146. self._fetch_page, username, user.get('id')), self._PAGE_SIZE)
  147. return self.playlist_result(entries, username)