mirror of
https://github.com/derrod/legendary.git
synced 2025-02-27 23:56:45 +00:00
[cli/utils] Add support for logging in via web view
Also adds pywebview as optional dependency to setup.py
This commit is contained in:
parent
26715695d8
commit
15591a1e2d
|
@ -142,21 +142,36 @@ class LegendaryCLI:
|
|||
|
||||
exchange_token = ''
|
||||
if not args.auth_code and not args.session_id:
|
||||
# unfortunately the captcha stuff makes a complete CLI login flow kinda impossible right now...
|
||||
print('Please login via the epic web login!')
|
||||
webbrowser.open(
|
||||
'https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Fid%2Fapi%2Fredirect'
|
||||
)
|
||||
print('If web page did not open automatically, please manually open the following URL: '
|
||||
'https://www.epicgames.com/id/login?redirectUrl=https://www.epicgames.com/id/api/redirect')
|
||||
sid = input('Please enter the "sid" value from the JSON response: ')
|
||||
sid = sid.strip()
|
||||
if sid[0] == '{':
|
||||
tmp = json.loads(sid)
|
||||
sid = tmp['sid']
|
||||
# only import here since pywebview import is slow
|
||||
from legendary.utils.webview_login import webview_available, do_webview_login
|
||||
|
||||
if not webview_available or args.no_webview:
|
||||
# unfortunately the captcha stuff makes a complete CLI login flow kinda impossible right now...
|
||||
print('Please login via the epic web login!')
|
||||
webbrowser.open(
|
||||
'https://www.epicgames.com/id/login?redirectUrl='
|
||||
'https%3A%2F%2Fwww.epicgames.com%2Fid%2Fapi%2Fredirect'
|
||||
)
|
||||
print('If web page did not open automatically, please manually open the following URL: '
|
||||
'https://www.epicgames.com/id/login?redirectUrl=https://www.epicgames.com/id/api/redirect')
|
||||
sid = input('Please enter the "sid" value from the JSON response: ')
|
||||
sid = sid.strip()
|
||||
if sid[0] == '{':
|
||||
tmp = json.loads(sid)
|
||||
sid = tmp['sid']
|
||||
else:
|
||||
sid = sid.strip('"')
|
||||
exchange_token = self.core.auth_sid(sid)
|
||||
else:
|
||||
sid = sid.strip('"')
|
||||
exchange_token = self.core.auth_sid(sid)
|
||||
def callback(web_sid):
|
||||
exchange_code = self.core.auth_sid(web_sid)
|
||||
return exchange_code and self.core.auth_code(exchange_code)
|
||||
|
||||
if do_webview_login(callback=callback):
|
||||
logger.info(f'Successfully logged in as "{self.core.lgd.userdata["displayName"]}" via WebView')
|
||||
else:
|
||||
logger.error('WebView login attempt failed, please see log for details.')
|
||||
return
|
||||
elif args.session_id:
|
||||
exchange_token = self.core.auth_sid(args.session_id)
|
||||
elif args.auth_code:
|
||||
|
@ -1646,6 +1661,8 @@ def main():
|
|||
help='Use specified session id instead of interactive authentication')
|
||||
auth_parser.add_argument('--delete', dest='auth_delete', action='store_true',
|
||||
help='Remove existing authentication (log out)')
|
||||
auth_parser.add_argument('--disable-webview', dest='no_webview', action='store_true',
|
||||
help='Do not use embedded browser for login')
|
||||
|
||||
install_parser.add_argument('--base-path', dest='base_path', action='store', metavar='<path>',
|
||||
help='Path for game installations (defaults to ~/legendary)')
|
||||
|
|
102
legendary/utils/webview_login.py
Normal file
102
legendary/utils/webview_login.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
import logging
|
||||
import json
|
||||
|
||||
logger = logging.getLogger('WebViewHelper')
|
||||
webview_available = True
|
||||
|
||||
try:
|
||||
import webview
|
||||
|
||||
# silence logger
|
||||
webview.logger.setLevel(logging.FATAL)
|
||||
webview.initialize()
|
||||
except Exception as e:
|
||||
logger.debug(f'Webview unavailable, disabling webview login (Exception: {e!r}).')
|
||||
webview_available = False
|
||||
|
||||
login_url = 'https://www.epicgames.com/id/login'
|
||||
sid_url = 'https://www.epicgames.com/id/api/redirect?'
|
||||
logout_url = 'https://www.epicgames.com/id/logout?productName=epic-games&redirectUrl=https://www.epicgames.com/site/'
|
||||
window_js = '''
|
||||
window.ue = {
|
||||
signinprompt: {
|
||||
requestexchangecodesignin: pywebview.api.complete_login,
|
||||
registersignincompletecallback: pywebview.api.trigger_logout
|
||||
},
|
||||
common: {
|
||||
launchexternalurl: function(url) {
|
||||
window.open(url, "blank");
|
||||
},
|
||||
// not required, just needs to be non-null
|
||||
auth: {
|
||||
completeLogin: pywebview.api.nop
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
class MockLauncher:
|
||||
def __init__(self, callback=None):
|
||||
self.callback = callback
|
||||
self.window = None
|
||||
self.exchange_code = None
|
||||
self.sid = None
|
||||
self.inject_js = True
|
||||
self.destroy_on_load = False
|
||||
self.callback_result = None
|
||||
|
||||
def on_loaded(self):
|
||||
url = self.window.get_current_url()
|
||||
logger.debug(f'Loaded url: {url.partition("?")[0]}')
|
||||
# make sure JS necessary window. stuff is available
|
||||
if self.inject_js:
|
||||
self.window.evaluate_js(window_js)
|
||||
|
||||
if 'id/api/redirect' in url:
|
||||
body = self.window.get_elements('body')[0]['textContent']
|
||||
try:
|
||||
j = json.loads(body)
|
||||
self.sid = j['sid']
|
||||
logger.debug(f'Got SID (stage 2)!')
|
||||
if self.callback:
|
||||
logger.debug(f'Calling login callback...')
|
||||
self.callback_result = self.callback(self.sid)
|
||||
except Exception as e:
|
||||
logger.error(f'Loading SID response failed with {e!r}')
|
||||
logger.debug('Starting browser logout...')
|
||||
self.window.load_url(logout_url)
|
||||
elif 'logout' in url:
|
||||
# prepare to close browser after logout redirect
|
||||
self.destroy_on_load = True
|
||||
elif self.destroy_on_load:
|
||||
# close browser after logout
|
||||
logger.info('Closing web view...')
|
||||
self.window.destroy()
|
||||
|
||||
def nop(self, *args, **kwargs):
|
||||
return
|
||||
|
||||
def trigger_logout(self, *args, **kwargs):
|
||||
self.inject_js = False
|
||||
# first obtain SID, then log out
|
||||
self.window.load_url(sid_url)
|
||||
|
||||
def complete_login(self, exchange_code):
|
||||
logger.debug('Got exchange code (stage 1)!')
|
||||
# we cannot use this exchange code as our login would be invalidated
|
||||
# after logging out on the website. Hence we do the dance of using
|
||||
# the SID to create *another* exchange code which will create a session
|
||||
# that remains valid after logging out.
|
||||
self.exchange_code = exchange_code
|
||||
|
||||
|
||||
def do_webview_login(callback=None):
|
||||
api = MockLauncher(callback=callback)
|
||||
logger.info('Opening web view with Epic Games Login...')
|
||||
window = webview.create_window('Epic Login', url=login_url, width=1024, height=1024, js_api=api)
|
||||
api.window = window
|
||||
window.loaded += api.on_loaded
|
||||
webview.start()
|
||||
|
||||
return api.callback_result
|
Loading…
Reference in a new issue