You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

127 lines
4.1 KiB

  1. # encoding: utf-8
  2. from __future__ import unicode_literals
  3. import re
  4. from .common import InfoExtractor
  5. from ..utils import (
  6. ExtractorError,
  7. compat_html_parser,
  8. #compat_urllib_request,
  9. #compat_urllib_parse,
  10. )
  11. class PatreonHTMLParser(compat_html_parser.HTMLParser):
  12. _PREFIX = 'http://www.patreon.com'
  13. _ATTACH_TAGS = 5 * ['div']
  14. _ATTACH_CLASSES = [
  15. 'fancyboxhidden', 'box photo double', 'boxwrapper double',
  16. 'hiddendisplay shareinfo', 'attach'
  17. ]
  18. _INFO_TAGS = 4 * ['div']
  19. _INFO_CLASSES = [
  20. 'fancyboxhidden', 'box photo double', 'boxwrapper double',
  21. 'hiddendisplay shareinfo'
  22. ]
  23. def get_creation_info(self, html_data):
  24. self.tag_stack = []
  25. self.attrs_stack = []
  26. self.creation_info = {}
  27. self.feed(html_data)
  28. def handle_starttag(self, tag, attrs):
  29. self.tag_stack.append(tag.lower())
  30. self.attrs_stack.append(dict(attrs))
  31. def handle_endtag(self, tag):
  32. self.tag_stack.pop()
  33. self.attrs_stack.pop()
  34. def handle_data(self, data):
  35. # Check first if this is a creation attachment
  36. if self.tag_stack[-6:-1] == self._ATTACH_TAGS:
  37. attrs_classes = [
  38. x.get('class', '').lower() for x in self.attrs_stack[-6:-1]
  39. ]
  40. if attrs_classes == self._ATTACH_CLASSES:
  41. if self.tag_stack[-1] == 'a':
  42. url = self._PREFIX + self.attrs_stack[-1].get('href')
  43. self.creation_info['url'] = url
  44. if '.' in data:
  45. self.creation_info['ext'] = data.rsplit('.')[-1]
  46. # Next, check if this is within the div containing the creation info
  47. if self.tag_stack[-5:-1] == self._INFO_TAGS:
  48. attrs_classes = [
  49. x.get('class', '').lower() for x in self.attrs_stack[-5:-1]
  50. ]
  51. if attrs_classes == self._INFO_CLASSES:
  52. if self.attrs_stack[-1].get('class') == 'utitle':
  53. self.creation_info['title'] = data.strip()
  54. class PatreonIE(InfoExtractor):
  55. IE_NAME = 'patreon'
  56. _VALID_URL = r'https?://(?:www\.)?patreon\.com/creation\?hid=(.+)'
  57. _TESTS = [
  58. {
  59. 'url': 'http://www.patreon.com/creation?hid=743933',
  60. 'md5': 'e25505eec1053a6e6813b8ed369875cc',
  61. 'name': 'Patreon',
  62. 'info_dict': {
  63. 'id': '743933',
  64. 'ext': 'mp3',
  65. 'title': 'Episode 166: David Smalley of Dogma Debate',
  66. 'uploader': 'Cognitive Dissonance Podcast',
  67. },
  68. },
  69. ]
  70. # Currently Patreon exposes download URL via hidden CSS, so login is not
  71. # needed. Keeping this commented for when this inevitably changes.
  72. '''
  73. def _login(self):
  74. (username, password) = self._get_login_info()
  75. if username is None:
  76. return
  77. login_form = {
  78. 'redirectUrl': 'http://www.patreon.com/',
  79. 'email': username,
  80. 'password': password,
  81. }
  82. request = compat_urllib_request.Request(
  83. 'https://www.patreon.com/processLogin',
  84. compat_urllib_parse.urlencode(login_form).encode('utf-8')
  85. )
  86. login_page = self._download_webpage(request, None, note='Logging in as %s' % username)
  87. if re.search(r'onLoginFailed', login_page):
  88. raise ExtractorError('Unable to login, incorrect username and/or password', expected=True)
  89. def _real_initialize(self):
  90. self._login()
  91. '''
  92. def _real_extract(self, url):
  93. mobj = re.match(self._VALID_URL, url)
  94. video_id = mobj.group(1)
  95. info_page = self._download_webpage(url, video_id)
  96. ret = {'id': video_id}
  97. try:
  98. ret['uploader'] = re.search(
  99. r'<strong>(.+)</strong> is creating', info_page
  100. ).group(1)
  101. except AttributeError:
  102. pass
  103. parser = PatreonHTMLParser()
  104. parser.get_creation_info(info_page)
  105. if not parser.creation_info.get('url'):
  106. raise ExtractionError('Unable to retrieve creation URL')
  107. ret.update(parser.creation_info)
  108. return ret