[nebula] Authentication via tokens from cookie jar (#537)

Closes #496
Co-authored-by: hheimbuerger, TpmKranz
This commit is contained in:
Henrik Heimbuerger 2021-07-21 14:42:43 +02:00 committed by GitHub
parent b35496d825
commit 145bd631c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,9 +2,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json import json
import time
from urllib.error import HTTPError
from .common import InfoExtractor from .common import InfoExtractor
from ..compat import compat_str from ..compat import compat_str, compat_urllib_parse_unquote, compat_urllib_parse_quote
from ..utils import ( from ..utils import (
ExtractorError, ExtractorError,
parse_iso8601, parse_iso8601,
@ -78,7 +80,9 @@ class NebulaIE(InfoExtractor):
] ]
_NETRC_MACHINE = 'watchnebula' _NETRC_MACHINE = 'watchnebula'
def _retrieve_nebula_auth(self, video_id): _nebula_token = None
def _retrieve_nebula_auth(self):
""" """
Log in to Nebula, and returns a Nebula API token Log in to Nebula, and returns a Nebula API token
""" """
@ -91,7 +95,7 @@ def _retrieve_nebula_auth(self, video_id):
data = json.dumps({'email': username, 'password': password}).encode('utf8') data = json.dumps({'email': username, 'password': password}).encode('utf8')
response = self._download_json( response = self._download_json(
'https://api.watchnebula.com/api/v1/auth/login/', 'https://api.watchnebula.com/api/v1/auth/login/',
data=data, fatal=False, video_id=video_id, data=data, fatal=False, video_id=None,
headers={ headers={
'content-type': 'application/json', 'content-type': 'application/json',
# Submitting the 'sessionid' cookie always causes a 403 on auth endpoint # Submitting the 'sessionid' cookie always causes a 403 on auth endpoint
@ -101,6 +105,19 @@ def _retrieve_nebula_auth(self, video_id):
errnote='Authentication failed or rejected') errnote='Authentication failed or rejected')
if not response or not response.get('key'): if not response or not response.get('key'):
self.raise_login_required() self.raise_login_required()
# save nebula token as cookie
self._set_cookie(
'nebula.app', 'nebula-auth',
compat_urllib_parse_quote(
json.dumps({
"apiToken": response["key"],
"isLoggingIn": False,
"isLoggingOut": False,
}, separators=(",", ":"))),
expire_time=int(time.time()) + 86400 * 365,
)
return response['key'] return response['key']
def _retrieve_zype_api_key(self, page_url, display_id): def _retrieve_zype_api_key(self, page_url, display_id):
@ -139,8 +156,17 @@ def _call_nebula_api(self, path, video_id, access_token, note):
'Authorization': 'Token {access_token}'.format(access_token=access_token) 'Authorization': 'Token {access_token}'.format(access_token=access_token)
}, note=note) }, note=note)
def _fetch_zype_access_token(self, video_id, nebula_token): def _fetch_zype_access_token(self, video_id):
user_object = self._call_nebula_api('/auth/user/', video_id, nebula_token, note='Retrieving Zype access token') try:
user_object = self._call_nebula_api('/auth/user/', video_id, self._nebula_token, note='Retrieving Zype access token')
except ExtractorError as exc:
# if 401, attempt credential auth and retry
if exc.cause and isinstance(exc.cause, HTTPError) and exc.cause.code == 401:
self._nebula_token = self._retrieve_nebula_auth()
user_object = self._call_nebula_api('/auth/user/', video_id, self._nebula_token, note='Retrieving Zype access token')
else:
raise
access_token = try_get(user_object, lambda x: x['zype_auth_info']['access_token'], compat_str) access_token = try_get(user_object, lambda x: x['zype_auth_info']['access_token'], compat_str)
if not access_token: if not access_token:
if try_get(user_object, lambda x: x['is_subscribed'], bool): if try_get(user_object, lambda x: x['is_subscribed'], bool):
@ -162,9 +188,21 @@ def _extract_channel_title(self, video_meta):
if category.get('value'): if category.get('value'):
return category['value'][0] return category['value'][0]
def _real_initialize(self):
# check cookie jar for valid token
nebula_cookies = self._get_cookies('https://nebula.app')
nebula_cookie = nebula_cookies.get('nebula-auth')
if nebula_cookie:
self.to_screen('Authenticating to Nebula with token from cookie jar')
nebula_cookie_value = compat_urllib_parse_unquote(nebula_cookie.value)
self._nebula_token = self._parse_json(nebula_cookie_value, None).get('apiToken')
# try to authenticate using credentials if no valid token has been found
if not self._nebula_token:
self._nebula_token = self._retrieve_nebula_auth()
def _real_extract(self, url): def _real_extract(self, url):
display_id = self._match_id(url) display_id = self._match_id(url)
nebula_token = self._retrieve_nebula_auth(display_id)
api_key = self._retrieve_zype_api_key(url, display_id) api_key = self._retrieve_zype_api_key(url, display_id)
response = self._call_zype_api('/videos', {'friendly_title': display_id}, response = self._call_zype_api('/videos', {'friendly_title': display_id},
@ -174,7 +212,7 @@ def _real_extract(self, url):
video_meta = response['response'][0] video_meta = response['response'][0]
video_id = video_meta['_id'] video_id = video_meta['_id']
zype_access_token = self._fetch_zype_access_token(display_id, nebula_token=nebula_token) zype_access_token = self._fetch_zype_access_token(display_id)
channel_title = self._extract_channel_title(video_meta) channel_title = self._extract_channel_title(video_meta)
@ -187,13 +225,12 @@ def _real_extract(self, url):
'title': video_meta.get('title'), 'title': video_meta.get('title'),
'description': video_meta.get('description'), 'description': video_meta.get('description'),
'timestamp': parse_iso8601(video_meta.get('published_at')), 'timestamp': parse_iso8601(video_meta.get('published_at')),
'thumbnails': [ 'thumbnails': [{
{ 'id': tn.get('name'), # this appears to be null
'id': tn.get('name'), # this appears to be null 'url': tn['url'],
'url': tn['url'], 'width': tn.get('width'),
'width': tn.get('width'), 'height': tn.get('height'),
'height': tn.get('height'), } for tn in video_meta.get('thumbnails', [])],
} for tn in video_meta.get('thumbnails', [])],
'duration': video_meta.get('duration'), 'duration': video_meta.get('duration'),
'channel': channel_title, 'channel': channel_title,
'uploader': channel_title, # we chose uploader = channel name 'uploader': channel_title, # we chose uploader = channel name